├── .gitignore
├── LICENSE.md
├── README.md
├── RELEASE_NOTES.md
├── Src
├── .paket
│ └── Paket.Restore.targets
├── ES.Update.Backend
│ ├── ES.Update.Backend.fsproj
│ ├── Entities.fs
│ ├── UpdateManager.fs
│ ├── UpdateService.fs
│ ├── Utility.fs
│ ├── WebServer.fs
│ ├── WebServerLogger.fs
│ └── paket.references
├── ES.Update.Releaser
│ ├── ES.Update.Releaser.fsproj
│ ├── MetadataBuilder.fs
│ └── paket.references
├── ES.Update
│ ├── CryptoUtility.fs
│ ├── ES.Update.fsproj
│ ├── Entities.fs
│ ├── Installer.fs
│ ├── Updater.fs
│ ├── Utility.fs
│ └── paket.references
├── Examples
│ ├── Example1
│ │ ├── Client.cs
│ │ ├── Example1.csproj
│ │ ├── Helpers.cs
│ │ ├── Program.cs
│ │ ├── Server.cs
│ │ └── paket.references
│ ├── Example3
│ │ ├── App.config
│ │ ├── AuthenticatedWebServer.cs
│ │ ├── Client.cs
│ │ ├── Example3.csproj
│ │ ├── Program.cs
│ │ ├── Properties
│ │ │ └── AssemblyInfo.cs
│ │ └── paket.references
│ └── Example4
│ │ ├── App.config
│ │ ├── Client.cs
│ │ ├── Example4.csproj
│ │ ├── Program.cs
│ │ └── Properties
│ │ └── AssemblyInfo.cs
├── ProgramUpdaterSln.sln
├── Tests
│ └── UnitTests
│ │ ├── BackendTests.fs
│ │ ├── CryptoUtilityTests.fs
│ │ ├── Program.fs
│ │ └── UnitTests.fsproj
├── Tools
│ ├── KeysGenerator
│ │ ├── KeysGenerator.fsproj
│ │ ├── Program.fs
│ │ └── paket.references
│ └── VersionReleaser
│ │ ├── Program.fs
│ │ ├── Settings.fs
│ │ ├── VersionReleaser.fsproj
│ │ ├── configuration.json
│ │ └── paket.references
├── build.fsx
├── mergeInstaller.fsx
├── nuget.exe
├── paket.dependencies
├── paket.exe
└── paket.lock
└── build.bat
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | build/
24 | fake/
25 | release/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 | **/Properties/launchSettings.json
59 |
60 | # StyleCop
61 | StyleCopReport.xml
62 |
63 | # Files built by Visual Studio
64 | *_i.c
65 | *_p.c
66 | *_i.h
67 | *.ilk
68 | *.meta
69 | *.obj
70 | *.iobj
71 | *.pch
72 | *.pdb
73 | *.ipdb
74 | *.pgc
75 | *.pgd
76 | *.rsp
77 | *.sbr
78 | *.tlb
79 | *.tli
80 | *.tlh
81 | *.tmp
82 | *.tmp_proj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 |
259 | # Microsoft Fakes
260 | FakesAssemblies/
261 |
262 | # GhostDoc plugin setting file
263 | *.GhostDoc.xml
264 |
265 | # Node.js Tools for Visual Studio
266 | .ntvs_analysis.dat
267 | node_modules/
268 |
269 | # Visual Studio 6 build log
270 | *.plg
271 |
272 | # Visual Studio 6 workspace options file
273 | *.opt
274 |
275 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
276 | *.vbw
277 |
278 | # Visual Studio LightSwitch build output
279 | **/*.HTMLClient/GeneratedArtifacts
280 | **/*.DesktopClient/GeneratedArtifacts
281 | **/*.DesktopClient/ModelManifest.xml
282 | **/*.Server/GeneratedArtifacts
283 | **/*.Server/ModelManifest.xml
284 | _Pvt_Extensions
285 |
286 | # Paket dependency manager
287 | .paket/paket.exe
288 | paket-files/
289 |
290 | # FAKE - F# Make
291 | .fake/
292 |
293 | # JetBrains Rider
294 | .idea/
295 | *.sln.iml
296 |
297 | # CodeRush
298 | .cr/
299 |
300 | # Python Tools for Visual Studio (PTVS)
301 | __pycache__/
302 | *.pyc
303 |
304 | # Cake - Uncomment if you are using it
305 | # tools/**
306 | # !tools/packages.config
307 |
308 | # Tabs Studio
309 | *.tss
310 |
311 | # Telerik's JustMock configuration file
312 | *.jmconfig
313 |
314 | # BizTalk build output
315 | *.btp.cs
316 | *.btm.cs
317 | *.odx.cs
318 | *.xsd.cs
319 |
320 | # OpenCover UI analysis results
321 | OpenCover/
322 |
323 | # Azure Stream Analytics local run output
324 | ASALocalRun/
325 |
326 | # MSBuild Binary and Structured Log
327 | *.binlog
328 |
329 | # NVidia Nsight GPU debugger configuration file
330 | *.nvuser
331 |
332 | # MFractors (Xamarin productivity tool) working folder
333 | .mfractor/
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## creative commons
2 |
3 | # Attribution-NonCommercial 4.0 International
4 |
5 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
6 |
7 | ### Using Creative Commons Public Licenses
8 |
9 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
10 |
11 | * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).
12 |
13 | * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).
14 |
15 | ## Creative Commons Attribution-NonCommercial 4.0 International Public License
16 |
17 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
18 |
19 | ### Section 1 – Definitions.
20 |
21 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
22 |
23 | b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
24 |
25 | c. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
26 |
27 | d. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
28 |
29 | e. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
30 |
31 | f. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
32 |
33 | g. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
34 |
35 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.
36 |
37 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
38 |
39 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
40 |
41 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
42 |
43 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
44 |
45 | ### Section 2 – Scope.
46 |
47 | a. ___License grant.___
48 |
49 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
50 |
51 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
52 |
53 | B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
54 |
55 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
56 |
57 | 3. __Term.__ The term of this Public License is specified in Section 6(a).
58 |
59 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
60 |
61 | 5. __Downstream recipients.__
62 |
63 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
64 |
65 | B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
66 |
67 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
68 |
69 | b. ___Other rights.___
70 |
71 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
72 |
73 | 2. Patent and trademark rights are not licensed under this Public License.
74 |
75 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
76 |
77 | ### Section 3 – License Conditions.
78 |
79 | Your exercise of the Licensed Rights is expressly made subject to the following conditions.
80 |
81 | a. ___Attribution.___
82 |
83 | 1. If You Share the Licensed Material (including in modified form), You must:
84 |
85 | A. retain the following if it is supplied by the Licensor with the Licensed Material:
86 |
87 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
88 |
89 | ii. a copyright notice;
90 |
91 | iii. a notice that refers to this Public License;
92 |
93 | iv. a notice that refers to the disclaimer of warranties;
94 |
95 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
96 |
97 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
98 |
99 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
100 |
101 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
102 |
103 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
104 |
105 | 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
106 |
107 | ### Section 4 – Sui Generis Database Rights.
108 |
109 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
110 |
111 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
112 |
113 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
114 |
115 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
116 |
117 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
118 |
119 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability.
120 |
121 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__
122 |
123 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__
124 |
125 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
126 |
127 | ### Section 6 – Term and Termination.
128 |
129 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
130 |
131 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
132 |
133 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
134 |
135 | 2. upon express reinstatement by the Licensor.
136 |
137 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
138 |
139 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
140 |
141 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
142 |
143 | ### Section 7 – Other Terms and Conditions.
144 |
145 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
146 |
147 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
148 |
149 | ### Section 8 – Interpretation.
150 |
151 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
152 |
153 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
154 |
155 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
156 |
157 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
158 |
159 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
160 | >
161 | > Creative Commons may be contacted at creativecommons.org
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Program Updater Framework
2 | A framework to automatize the process of updating a program in an **efficent** and **secure way**. The updates are provided by a server component through and HTTP/HTTPS channel.
3 |
4 | It was created with the following intents:
5 |
6 | * to be very easy to use and to integrate
7 | * to provide an high secure update process even on a not encrypted channel like HTTP (the integrity of all files is checked before being used)
8 | * to be efficient, this means that if your new release just changed one file you don't need to download the full application but only the changed files
9 | * to be autoconsistent, you don't need any other external software (web server, database, ...) to create an update solution
10 |
11 | ## Download
12 |
13 | A pre-compiled file of the framework can be downloaded from the Release section.
14 |
15 | ## Resolving _Could not load file or assembly FSharp.Core_
16 |
17 | If your project uses a different **FSharp.Core** version you may encounter the following error on startup:
18 |
19 | ````
20 | System.IO.FileLoadException: 'Could not load file or assembly 'FSharp.Core, Version=4.6.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)'
21 | ````
22 |
23 | In order to solve it you have to modify your **App.config** configuration file in order to add a redirect. For example, if your are using **FSharp.Core version 4.5.0.0** you may want to add the following content to the **App.config** file:
24 |
25 | ````xml
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ````
38 |
39 | ## Core Concepts
40 |
41 | The framework can be used via the command line tools or by integrating it in your web application. In both cases the process to release a new update is composed of the following three steps:
42 |
43 | * Create the metadata related to the new update
44 | * Push the metadata to the update server (this step can be merged with the one above)
45 | * Run the update program from the client
46 |
47 | In order to setup an update process you need:
48 |
49 | * A folder where all the metadata are saved
50 | * To follow a naming convention for your update file (only zip file are supported for now). The convention is that the name must be something like: MyApplication.1.2.3.zip.
51 | * To generate and distribute the encryption key (this process is done automatically on first server execution)
52 |
53 | ## Configuration File
54 |
55 | All the command line options can also be specified in the given configuration file for each tools. The deafult name for the configuration file is **configuration.json** and it is in JSON format. If a command line value is specified it will take precedence over the value set in the configuration file.
56 |
57 | # Examples
58 |
59 | Below you can find some examples that should provide enough information to use the framework proficiently.
60 |
61 | ## Example 1
62 |
63 | The goal of this example is to provide a full update process by only using the commant line utilities. We will suppose that we have four versions of our software and we want to release a new version 5.0. We will use the _update_ directory in order to store the information related to the updates of our software.
64 |
65 | Details
66 |
67 |
Step 0 - Start up
68 |
69 | If you have never used the framework to provide updates to your clients, it is a good practice to follow the _Step 1_ for each release of your software, starting from the oldest to the newest.
70 |
71 | Step 1 - Metadata Creation
72 |
73 | The first step is to create the metadata, this is done with the **VersionReleaser.exe** tool. We run the following command:
74 |
75 | ````bash
76 | VersionReleaser.exe --working-dir updates Examples\Example1\MyApplication.v5.0.zip
77 | -=[ Version Releaser ]=-
78 | Copyright (c) 2019 Enkomio
79 |
80 | [INFO] 2019-08-09 19:26:45 - Analyze release file: MyApplication.v5.0.zip
81 | [INFO] 2019-08-09 19:26:45 - Saving release metadata
82 | [INFO] 2019-08-09 19:26:45 - Saving artifacts to update
83 | [INFO] 2019-08-09 19:26:45 - Adding new file 'folder\file8.txt' as 77C6EC70B75CE3254B910DC6073DB04A61E2EB5273191F73B0AB539F6CAD43C2
84 | [INFO] 2019-08-09 19:26:45 - Process completed
85 | ````
86 | Now the metadata are created and the new artifacts are saved. You can exclude some files from the update process, this is very important for configuration file or local database. You can configure the patterns of the files to exclude in the **configuration.json** file. The current list can be found here.
87 |
88 | Step 2 - Start the update server
89 |
90 | Now you have to start the update server. The framework provides a program named **UpdateServer.exe** that will run a web server in order to accept update requests. You can do this with the following command:
91 | ````bash
92 | UpdateServer.exe --working-dir updates
93 | -=[ Version Releaser ]=-
94 | Copyright (c) 2019 Enkomio
95 |
96 | [INFO] 2019-08-10 15:06:48 - Encryption keys not found. Generating them
97 | [INFO] 2019-08-10 15:06:48 - Encryption keys created and saved to files. The public key must be distributed togheter with the updater
98 | [INFO] 2019-08-10 15:06:48 - Public key: RUNTNUIAAAABQa5NN74/BqJW7Ial8xj2D/QB32Dj7ZuMOmtfIfo4PiHuXD3QiM6xvOvEZbJ1vQPdjUignHYE7BCLdslEMYbCj4AA8QeSc9v7jc1X5cqKCL1tHaJc+B/MWp8sRXlL6wYUJj4bfcC3p/xEJZXeO/RUsO8gKA4KT0UAXsq0bExWRQr6Ioc=
99 | [INFO] 2019-08-10 15:06:48 - Loaded project MyApplication version 1.0
100 | [INFO] 2019-08-10 15:06:48 - Loaded project MyApplication version 2.0
101 | [INFO] 2019-08-10 15:06:48 - Loaded project MyApplication version 3.0
102 | [INFO] 2019-08-10 15:06:48 - Loaded project MyApplication version 4.0
103 | [INFO] 2019-08-10 15:06:48 - Loaded project MyApplication version 5.0
104 | [17:06:48 INF] Smooth! Suave listener started in 86.698ms with binding 127.0.0.1:80
105 | ````
106 | The server recognizes that we defined five applications. It is also very important to take note of the *public key*. This value must be set in the client in order to ensure the integrity of the updates.
107 |
108 | Step 3 - Run the update client
109 |
110 | The final step of this example is to update the client code by connecting to the server. In order to do this, it is necessary to specify the following information:
111 |
112 | * The address of the update server
113 | * The public key of the server
114 | * The name of the project that must be updated
115 |
116 | The first two information can be retrieved from the output of the server in the previous step. We suppose that the update must be installed in the current directory (a very common case if you distribute the update program togheter with your binary), if this is not the case you can change this value with the _--directory_ argument. You can now run the following command:
117 | ````bash
118 | Updater.exe --project MyApplication --server-uri http://127.0.0.1 --server-key "RUNTNUIAAAABQa5NN74/BqJW7Ial8xj2D/QB32Dj7ZuMOmtfIfo4PiHuXD3QiM6xvOvEZbJ1vQPdjUignHYE7BCLdslEMYbCj4AA8QeSc9v7jc1X5cqKCL1tHaJc+B/MWp8sRXlL6wYUJj4bfcC3p/xEJZXeO/RUsO8gKA4KT0UAXsq0bExWRQr6Ioc="
119 | -=[ Program Updater ]=-
120 | Copyright (c) 2019 Enkomio
121 |
122 | [INFO] 2019-08-13 14:31:28 - Found a more recent version: 5.0. Start update
123 | [INFO] 2019-08-13 14:31:28 - Project 'MyApplication' was updated to version '5.0' in directory: .
124 | ````
125 | If you now take a look at the current directory you will see that new files were created due to the update process.
126 |
127 |
128 |
129 | ## Example 2
130 |
131 | The goal of this example is to show how to use the library in order to create a custom update. The result will be the same as the previous example. You can find the related files in the Example 2 folder.
132 |
133 | Details
134 |
135 |
136 | ### Step 1 - Metadata Creation
137 |
138 | The most common case when you have to generate the metada for a new release is to use the command line utility. If for some reason you want to use the library you must use the **MetadataBuilder** class and specify the working directory where the metadata will be saved.
139 |
140 | An example of usage is:
141 | ````csharp
142 | var metadataBuilder = new MetadataBuilder(workspaceDirectory);
143 | metadataBuilder.CreateReleaseMetadata(fileName);
144 | ````
145 | ### Step 2 - Start the update server
146 |
147 | The framework provides a **WebServer** class that can be used to run the update server. The web server is based on the Suave project. To run a web server you have to specify:
148 |
149 | * The binding base URI
150 | * The workspace directory where the metadata are stored
151 | * The private key
152 |
153 | To generate a new pair of public and private keys you can use the **CryptoUtility.GenerateKeys** method. Find below an example of code that starts a web server.
154 | ````csharp
155 | var (publicKey, privateKey) = CryptoUtility.GenerateKeys();
156 | var server = new WebServer(this.BindingUri, this.WorkspaceDirectory, privateKey);
157 | ````
158 | ### Step 3 - Implement the update client
159 |
160 | The last step is to integrate the update client in your solution. In this case you need the following information:
161 |
162 | * The server base URI
163 | * The server public key
164 | * The name of the project that you want to update
165 | * The current project version
166 | * The destination directory where the update must be installed
167 |
168 | All information should alredy know if you followed the Step 2. Now you can update your client with the following code:
169 | ````csharp
170 | var applicationVersion = new Version(3, 0);
171 | var updater = new Updater(serverBaseUri, applicationName, applicationVersion, destinationDirectory, serverPublicKey);
172 |
173 | var latestVersion = updater.GetLatestVersion();
174 | if (latestVersion > applicationVersion)
175 | {
176 | var updateResult = updater.Update(applicationVersion);
177 | if (updateResult.Success)
178 | {
179 | // Update ok
180 | }
181 | else
182 | {
183 | // Error
184 | }
185 | }
186 | ````
187 |
188 |
189 |
190 | ## Example 3
191 |
192 | The goal of this example is to show how to customize the web server. Often the update must be provided only to clients that have the needed authorization, in this example we will see how to authenticate the update requests. You can find the example files in the Example 3 folder.
193 |
194 | Details
195 |
196 |
197 | ### Step 0 - Installing dependency
198 |
199 | The framework uses Suave in order to implements the web server. In case of simple use of the ProgramUpdater framework, you don't have to worry about it but in this example it is necessary to reference it in order to use its classes. You can use Paket to reference it or add it via NuGet.
200 |
201 | ### Step 1 - Metadata Creation
202 |
203 | See *Example 1* Step 1
204 |
205 | ### Step 2 - Start the update server
206 |
207 | For this example we will create a sub-class of the **WebServer** framework class and we override the **Authenticate** method in order to verify the credentials that will be sent by the updater.
208 |
209 | Below you can find the relevant code that checks if the credentials are correct:
210 | ````csharp
211 | var formParameters = Encoding.UTF8.GetString(ctx.request.rawForm).Split('&');
212 | var username = String.Empty;
213 | var password = String.Empty;
214 |
215 | foreach(var parameter in formParameters)
216 | {
217 | var nameValue = parameter.Split('=');
218 | if (nameValue[0].Equals("Username", StringComparison.OrdinalIgnoreCase))
219 | {
220 | username = nameValue[1];
221 | }
222 | else if (nameValue[0].Equals("Password", StringComparison.OrdinalIgnoreCase))
223 | {
224 | password = nameValue[1];
225 | }
226 | }
227 |
228 | return
229 | username.Equals(AuthenticatedWebServer.Username, StringComparison.Ordinal)
230 | && password.Equals(AuthenticatedWebServer.Password, StringComparison.Ordinal);
231 | ````
232 | Here you can find the full source code of the **AuthenticatedWebServer** class.
233 |
234 | ### Step 3 - Implement the update client
235 |
236 | In this case the difference with the previous example is that we have to authenticate to the server. This is an easy step if we know the username and password. We just have to add these info to the update request. This is easily done with the following code:
237 | ````csharp
238 | // add username and password to the update request
239 | updater.AddParameter("username", AuthenticatedWebServer.Username);
240 | updater.AddParameter("password", AuthenticatedWebServer.Password);
241 | ````
242 | The specified parameters will be added to the update request and will be used to verify the authentication. At his time it is possible to specify only POST parameters.
243 |
244 | Here you can find the full source code of the **Client** class.
245 |
246 |
247 |
248 |
249 | ## Example 4
250 |
251 | The goal of this example is to provide a flexible update method by invoking an external program to do the update. Often the update method is not just a matter of copy the files to the destination directory but other, more complex, tasks must be done. The full source code of this example can be find in the Example 4 folder.
252 |
253 | Details
254 |
255 |
256 | In version 1.1 was released a new feature that allows to invoke an external program in order to do the installation. The framework provides an **Installer** program that copy the files to a destination directory. Using this approach is the suggested one, since it will avoid to have update problems when you have to update the current running program (you cannot write on a file associated to a running process). In order achieve this, when an external Installer is used, the update process is terminated in order to avoid conflict. If you want to be sure that the parent exited just wait on the mutex name composed from the arguments hashes, to know more take a look at the mutex name generation code.
257 |
258 | Of course you can use your own installer program, you have just to add it to the configuration (we will see how to do it). The only rules that must be respected are:
259 |
260 | * The name of the installer program must be **Installer.exe**
261 | * It accepts the following arguments:
262 | * **--source** that is the directory where the new files are stored
263 | * **--dest** that is the directory that must be updated with the new files
264 |
265 | ### Step 1 - Metadata Creation
266 |
267 | See *Example 1* Step 1.
268 |
269 | ### Step 2 - Start the update server
270 |
271 | This step is very similar to *Example 2* Step 2. The main difference is that you have to specify the directory where your installer is stored. All the files in this directory will be copied in the update package sent to the client. In the listing below you can see an example that use the **Installer.exe** provided by the framework:
272 |
273 | ````csharp
274 | // set the installer path
275 | var installerPath = Path.GetDirectoryName(typeof(Installer.Program).Assembly.Location);
276 | _server.WebServer.InstallerPath = installerPath;
277 | ````
278 | The **Installer** program from the framework by default will start again the main process with the specified arguments.
279 |
280 | For security reason the framework will add the integrity info about the installer inside the update package. These info will be checked by the client before to invoke the installer.
281 |
282 | ### Step 3 - Run the update client
283 |
284 | See Step 3 of previous examples.
285 |
286 |
287 |
288 |
289 | # Security
290 |
291 | The update process use **ECDSA** with **SHA-256** in order to ensure the integrity of the update. The *public* and *private* keys are automatically generated on first start and saved to local files (*public.txt* and *private.txt*).
292 |
293 | ## Exporting the private key
294 |
295 | In order to protect the private key from an attacker that is able to read arbitrary files from your filesystem, the key is AES encrypted with parameters that are related to the execution environment (MAC address, properties of the installed HDs). This means that you cannot just copy the private key file from one computer to another, since it will not work. If you want to obtain an exportable private key you have to export it by executing the following command:
296 |
297 | UpdateServer.exe --export-key clean-private-key.txt
298 | -=[ Version Releaser ]=-
299 | Copyright (c) 2019 Enkomio
300 |
301 | Enter password: ****
302 | Re-enter password: ****
303 | [INFO] 2019-08-27 17:02:33 - Private key exported to file: clean-private-key.txt
304 |
305 | The exported key is AES encrypted with the input password.
306 |
307 | ## Importing private key
308 |
309 | If you want to import a private key that was exported from another server you can do it by run the following command:
310 |
311 | UpdateServer.exe --import-key clean-private-key.txt
312 | -=[ Version Releaser ]=-
313 | Copyright (c) 2019 Enkomio
314 |
315 | Enter password: ****
316 | Re-enter password: ****
317 | [INFO] 2019-08-27 17:04:14 - Private key from file 'clean-private-key.txt' imported. Be sure to set the public key accordingly.
318 |
319 | This command will read and save the private key in an encrypted form with the new parameters of the new server. You have also to copy the public key to the server.
320 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | ### 1.3 - 20/09/2019
2 | * Added --skip-on-exist option to Updater.exe to specify patterns for files that must be copied only if not exist
3 | * Added SkipIntegrityCheck property to Updater class to ignore integrity check (useful during development)
4 | * Removed args and exec option from installer since considered not very useful
5 | * Minor improvements
6 |
7 | ### 1.2.0 - 29/08/2019
8 | * Added utility to generate public and private key from command line
9 | * Added timer to avoid to restart program when a new update is available
10 | * Moved to Bouncy Castle for encryption in order to be supported on Mono too
11 | * Fixed minor error
12 |
13 | ### 1.1.0 - 28/08/2019
14 | * Improved installer by invoking an external program to avoid conflict on copy.
15 | * Exporting and importing the private key now require a password to save the value in an encrypted form. This will ensure that the private key is never stored in clear text.
16 | * Minor bug fixing and code improvement.
17 |
18 | ### 1.0.0 - 19/08/2019
19 | * First Release.
20 |
--------------------------------------------------------------------------------
/Src/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
240 |
241 |
242 | %(PaketReferencesFileLinesInfo.PackageVersion)
243 | All
244 | runtime
245 | runtime
246 | true
247 | true
248 |
249 |
250 |
251 |
252 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
262 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
263 |
264 |
265 | %(PaketCliToolFileLinesInfo.PackageVersion)
266 |
267 |
268 |
269 |
273 |
274 |
275 |
276 |
277 |
278 | false
279 |
280 |
281 |
282 |
283 |
284 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
285 |
286 |
287 |
288 |
289 |
290 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
291 | true
292 | false
293 | true
294 | false
295 | true
296 | false
297 | true
298 | false
299 | true
300 | $(PaketIntermediateOutputPath)\$(Configuration)
301 | $(PaketIntermediateOutputPath)
302 |
303 |
304 |
305 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
363 |
364 |
407 |
408 |
450 |
451 |
492 |
493 |
494 |
495 |
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/ES.Update.Backend.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/Entities.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 |
5 | module Entities =
6 | let DefaultVersion = "0.0"
7 |
8 | type File = {
9 | Path: String
10 | ContentHash: String
11 | }
12 |
13 | type Application = {
14 | Version: Version
15 | Files: File array
16 | }
17 |
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/UpdateManager.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 | open System.Timers
5 | open System.IO
6 | open System.Text
7 | open ES.Update.Backend.Entities
8 |
9 | type UpdateManager(workingDirectory: String) =
10 | let _lock = new Object()
11 | let _timer = new Timer()
12 | let mutable _applications : Application array = Array.empty
13 |
14 | let populateKnowledgeBase() =
15 | _timer.Stop()
16 | lock _lock (fun () ->
17 | let versionDir = Path.Combine(workingDirectory, "Versions")
18 | if Directory.Exists(versionDir) then
19 | _applications <-
20 | Directory.GetFiles(versionDir)
21 | |> Array.map(fun fileName ->
22 | {
23 | Version = Version.Parse(Path.GetFileNameWithoutExtension(fileName))
24 | Files =
25 | File.ReadAllLines(fileName)
26 | |> Array.map(fun line -> line.Trim().Split(','))
27 | |> Array.map(fun items -> (items.[0], String.Join(",", items.[1..])))
28 | |> Array.map(fun (hashValue, path) -> {ContentHash = hashValue; Path = path})
29 | }
30 | )
31 | )
32 | _timer.Start()
33 |
34 | do
35 | if Directory.Exists(workingDirectory) then
36 | populateKnowledgeBase()
37 |
38 | // add directory watcher
39 | _timer.Interval <- TimeSpan.FromMinutes(1.).TotalMilliseconds |> float
40 | _timer.Elapsed.Add(fun _ -> populateKnowledgeBase())
41 |
42 | abstract GetAvailableVersions: unit -> Version array
43 | default this.GetAvailableVersions() =
44 | _applications
45 | |> Seq.toArray
46 | |> Array.map(fun application -> application.Version)
47 |
48 | abstract GetApplication: Version -> Application option
49 | default this.GetApplication(version: Version) =
50 | _applications |> Seq.tryFind(fun app -> app.Version = version)
51 |
52 | abstract GetLatestVersion: unit -> Application option
53 | default this.GetLatestVersion() =
54 | _applications
55 | |> Seq.sortByDescending(fun application -> application.Version)
56 | |> Seq.tryHead
57 |
58 | abstract ComputeCatalog: File array -> String
59 | default this.ComputeCatalog(files: File array) =
60 | let fileContent = new StringBuilder()
61 | files
62 | |> Array.iter(fun file ->
63 | fileContent.AppendFormat("{0},{1}\r\n", file.ContentHash, file.Path) |> ignore
64 | )
65 | fileContent.ToString()
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/UpdateService.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 | open System.Timers
5 | open System.IO
6 | open System.Collections.Concurrent
7 | open ES.Update
8 | open System.Text
9 |
10 | type UpdateService(workspaceDirectory: String, privateKey: Byte array) =
11 | let _lock = new Object()
12 | let _timer = new Timer()
13 | let _updateManagers = new ConcurrentDictionary()
14 | let _syncRoot = new Object()
15 | let mutable _allFiles = Array.empty
16 |
17 | let getUpdateManager(projectName: String) =
18 | _updateManagers.[projectName]
19 |
20 | let doUpdate() =
21 | _timer.Stop()
22 | lock _lock (fun _ ->
23 | // read all version
24 | Directory.GetDirectories(workspaceDirectory)
25 | |> Array.map(fun directory -> Path.GetFileName(directory))
26 | |> Array.iter(fun projectName ->
27 | let projectDirectory = Path.Combine(workspaceDirectory, projectName)
28 | Directory.CreateDirectory(projectDirectory) |> ignore
29 | _updateManagers.[projectName] <- new UpdateManager(projectDirectory)
30 | )
31 |
32 | // read all available files
33 | let fileBucketDirectory = Path.Combine(workspaceDirectory, "FileBucket")
34 | let allFiles = Directory.GetFiles(fileBucketDirectory, "*", SearchOption.AllDirectories)
35 | lock _syncRoot (fun () -> _allFiles <- allFiles)
36 | )
37 | _timer.Start()
38 |
39 | do
40 | _timer.Interval <- TimeSpan.FromMinutes(1.).TotalMilliseconds |> float
41 | _timer.Elapsed.Add(fun _ -> doUpdate())
42 | doUpdate()
43 |
44 | member this.GetLatestVersion(projectName: String) =
45 | lock _lock (fun _ ->
46 | if _updateManagers.ContainsKey(projectName) then
47 | match getUpdateManager(projectName).GetLatestVersion() with
48 | | Some application -> Some <| application.Version.ToString()
49 | | None -> None
50 | else
51 | None
52 | )
53 |
54 | member this.IsValidProject(projectName: String) =
55 | _updateManagers.ContainsKey(projectName)
56 |
57 | member this.GetCatalog(version: Version, projectName: String) =
58 | let updateManager = getUpdateManager(projectName)
59 | match updateManager.GetLatestVersion() with
60 | | Some application when application.Version > version ->
61 | let catalog = updateManager.ComputeCatalog(application.Files)
62 | let signature = CryptoUtility.hexSign(Encoding.UTF8.GetBytes(catalog), privateKey)
63 | let result = String.Format("{0}\r\n{1}", signature, catalog)
64 | result
65 | | _ -> String.Empty
66 |
67 | member this.GetAvailableVersions() =
68 | lock _lock (fun _ ->
69 | _updateManagers
70 | |> Seq.toArray
71 | |> Array.collect(fun kv ->
72 | kv.Value.GetAvailableVersions()
73 | |> Array.map(fun version -> (kv.Key, version))
74 | )
75 | )
76 |
77 | member this.GetFilePath(hash: String) =
78 | lock _syncRoot (fun () ->
79 | _allFiles
80 | |> Array.tryFind(fun file -> file.EndsWith(hash, StringComparison.OrdinalIgnoreCase))
81 | )
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/Utility.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 | open System.IO
5 | open System.IO.Compression
6 | open System.Text
7 | open Suave
8 | open Entities
9 | open System.Security.Cryptography
10 | open ES.Update
11 |
12 | []
13 | module Utility =
14 | let private readIntegrityInfo(zipFile: String) =
15 | use zipStream = File.OpenRead(zipFile)
16 | use zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read)
17 | let catalogEntry =
18 | zipArchive.Entries
19 | |> Seq.find(fun entry -> entry.FullName.Equals("catalog", StringComparison.OrdinalIgnoreCase))
20 |
21 | use zipStream = catalogEntry.Open()
22 | use memStream = new MemoryStream()
23 | zipStream.CopyTo(memStream)
24 | Encoding.UTF8.GetString(memStream.ToArray())
25 |
26 | let private addEntry(zipFile: String, name: String, content: Byte array) =
27 | use zipStream = File.Open(zipFile, FileMode.Open)
28 | use zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Update)
29 | let zipEntry = zipArchive.CreateEntry(name)
30 | use zipEntryStream = zipEntry.Open()
31 | zipEntryStream.Write(content, 0, content.Length)
32 |
33 | let addSignature(zipFile: String, privateKey: Byte array) =
34 | // compute signature and add it to the new file
35 | let integrityInfo = readIntegrityInfo(zipFile)
36 | let signature = CryptoUtility.sign(Encoding.UTF8.GetBytes(integrityInfo), privateKey)
37 | addEntry(zipFile, "signature", signature)
38 |
39 | let private getRelativePath(fileName: String, basePath: String) =
40 | fileName.Replace(basePath, String.Empty).TrimStart(Path.DirectorySeparatorChar)
41 |
42 | let addInstaller(zipFile: String, installerPath: String, privateKey: Byte array) =
43 | // compute the integrtiy info for the installer
44 | let integrityInfo = new StringBuilder()
45 | Directory.GetFiles(installerPath, "*.*", SearchOption.AllDirectories)
46 | |> Array.iter(fun fileName ->
47 | let relativePath = getRelativePath(fileName, installerPath)
48 | let hashValue = sha256(File.ReadAllBytes(fileName))
49 | integrityInfo.AppendFormat("{0},{1}", hashValue, relativePath).AppendLine() |> ignore
50 | )
51 |
52 | // sign the integrity info and add the catalog
53 | let catalog = Encoding.UTF8.GetBytes(integrityInfo.ToString())
54 | let signature = CryptoUtility.sign(catalog, privateKey)
55 | addEntry(zipFile, "installer-signature", signature)
56 | addEntry(zipFile, "installer-catalog", catalog)
57 |
58 | // add all files from installerPath to the zip file
59 | Directory.GetFiles(installerPath, "*.*", SearchOption.AllDirectories)
60 | |> Array.iter(fun fileName ->
61 | let relativePath = getRelativePath(fileName, installerPath)
62 | addEntry(zipFile, relativePath, File.ReadAllBytes(fileName))
63 | )
64 |
65 | let createZipFile(zipFile: String, files: (File * Byte array) list, integrityInfo: String) =
66 | use zipStream = File.OpenWrite(zipFile)
67 | use zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create)
68 |
69 | // add integrity info and files
70 | let files = files |> List.map(fun (f, c) -> (f.ContentHash, c))
71 | ("catalog", integrityInfo |> Encoding.UTF8.GetBytes)::files
72 | |> List.iter(fun (name, content) ->
73 | let zipEntry = zipArchive.CreateEntry(name)
74 | use zipEntryStream = zipEntry.Open()
75 | zipEntryStream.Write(content, 0, content.Length)
76 | )
77 |
78 | let private getPostParameters(ctx: HttpContext) =
79 | // this method is necessary since Suave seems to have some problems in parsing params
80 | let data = Encoding.UTF8.GetString(ctx.request.rawForm)
81 | data.Split([|'&'|])
82 | |> Array.map(fun v -> v.Split([|'='|]))
83 | |> Array.filter(fun items -> items.Length > 1)
84 | |> Array.map(fun items -> (items.[0], String.Join("=", items.[1..])))
85 |
86 | let tryGetPostParameters(parameters: String list, ctx: HttpContext) =
87 | getPostParameters(ctx)
88 | |> Array.filter(fun (name, _) -> parameters |> List.contains name)
89 | |> fun resultValues ->
90 | if resultValues.Length = parameters.Length
91 | then Some(dict resultValues)
92 | else None
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/WebServer.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 | open System.Net
5 | open System.Threading
6 | open Suave
7 | open Suave.Successful
8 | open Suave.Writers
9 | open Suave.Operators
10 | open Suave.RequestErrors
11 | open Suave.Filters
12 | open Suave.Files
13 | open ES.Fslog
14 |
15 | type WebServer(binding: Uri, workspaceDirectory: String, privateKey: Byte array, logProvider: ILogProvider) as this =
16 | let _shutdownToken = new CancellationTokenSource()
17 | let _updateService = new UpdateService(workspaceDirectory, privateKey)
18 | let _logger =
19 | let tmp = new WebServerLogger()
20 | logProvider.AddLogSourceToLoggers(tmp)
21 | tmp
22 |
23 | let getFileFullPath(fileName: String) (ctx: HttpContext) = async {
24 | match _updateService.GetFilePath(fileName) with
25 | | Some filePath ->
26 | let newState = ctx.userState.Add("file", filePath).Add("name", fileName)
27 | return Some {ctx with userState = newState}
28 | | None ->
29 | _logger.FileNotFound(fileName)
30 | return None
31 | }
32 |
33 | let preFilter (oldCtx: HttpContext) = async {
34 | let! ctx = addHeader "X-Xss-Protection" "1; mode=block" oldCtx
35 | let! ctx = addHeader "Content-Security-Policy" "script-src 'self' 'unsafe-inline' 'unsafe-eval'" ctx.Value
36 | let! ctx = addHeader "X-Frame-Options" "SAMEORIGIN" ctx.Value
37 | let! ctx = addHeader "X-Content-Type-Options" "nosniff" ctx.Value
38 | return Some ctx.Value
39 | }
40 |
41 | let postFilter (ctx: HttpContext) = async {
42 | _logger.LogRequest(ctx)
43 | return (Some ctx)
44 | }
45 |
46 | let index(ctx: HttpContext) =
47 | OK this.IndexPage ctx
48 |
49 | let latest(ctx: HttpContext) =
50 | match ctx.request.queryParam "project" with
51 | | Choice1Of2 projectName ->
52 | match _updateService.GetLatestVersion(projectName) with
53 | | Some version -> OK version ctx
54 | | None -> OK Entities.DefaultVersion ctx
55 | | _ ->
56 | OK Entities.DefaultVersion ctx
57 |
58 | let updates(ctx: HttpContext) =
59 | let inputVersion = ref(new Version())
60 | match tryGetPostParameters(["version"; "project"], ctx) with
61 | | Some values
62 | when
63 | Version.TryParse(values.["version"], inputVersion)
64 | && _updateService.IsValidProject(values.["project"])
65 | ->
66 |
67 | let projectName = values.["project"]
68 | let catalog = _updateService.GetCatalog(!inputVersion, projectName)
69 | OK catalog ctx
70 | | _ ->
71 | BAD_REQUEST "Missing parameters" ctx
72 |
73 | let downloadFile(ctx: HttpContext) =
74 | let file = ctx.userState.["file"] :?> String
75 | let name = ctx.userState.["name"] :?> String
76 | addHeader "Content-Type" "application/octet-stream"
77 | >=> addHeader "Content-Disposition" ("inline; filename=\"" + name + "\"")
78 | >=> sendFile file true
79 | <| ctx
80 |
81 | let authorize (webPart: WebPart) (ctx: HttpContext) =
82 | if this.Authenticate(ctx)
83 | then webPart ctx
84 | else FORBIDDEN "Forbidden" ctx
85 |
86 | let pathNotFound(p: String)(ctx: HttpContext) =
87 | NOT_FOUND "Path not valid" ctx
88 |
89 | let buildCfg(uri: Uri) =
90 | { defaultConfig with
91 | bindings = [HttpBinding.create HTTP (IPAddress.Parse uri.Host) (uint16 uri.Port)]
92 | listenTimeout = TimeSpan.FromMilliseconds (float 10000)
93 | cancellationToken = _shutdownToken.Token
94 | }
95 |
96 | do
97 | _logger.SettingInfo("Workspace Directory", workspaceDirectory)
98 | _logger.SettingInfo("Binding Uri", binding.ToString())
99 |
100 | new (binding: Uri, workspaceDirectory: String, privateKey: Byte array) = new WebServer(binding, workspaceDirectory, privateKey, LogProvider.GetDefault())
101 |
102 | member val IndexPage = "-=[ Enkomio Updater Server ]=-" with get, set
103 |
104 | /// This parameter can specify a uri path prefix to use when invoking endpoints
105 | member val PathPrefix = String.Empty with get, set
106 |
107 | /// The path where the installer program is stored. If this path exists an Installer will be pushed in the update package
108 | member val InstallerPath = String.Empty with get, set
109 |
110 | abstract GetRoutes: unit -> WebPart list
111 | default this.GetRoutes() = [
112 | GET >=> preFilter >=> choose [
113 | path (this.PathPrefix + "/") >=> index
114 | path (this.PathPrefix + "/latest") >=> latest
115 | pathScan "/%s" pathNotFound
116 | ] >=> postFilter
117 |
118 | POST >=> preFilter >=> choose [
119 | // get the catalog for the specified version
120 | path (this.PathPrefix + "/updates") >=> authorize updates
121 |
122 | // get the specified file
123 | pathScan (PrintfFormat<_, _, _, _, String>(this.PathPrefix + "/file/%s")) getFileFullPath >=> authorize downloadFile
124 | pathScan "/%s" pathNotFound
125 | ] >=> postFilter
126 | ]
127 |
128 | abstract Authenticate: HttpContext -> Boolean
129 | default this.Authenticate(ctx: HttpContext) =
130 | true
131 |
132 | member this.Start() =
133 | logProvider.AddLogSourceToLoggers(_logger)
134 |
135 | _updateService.GetAvailableVersions()
136 | |> Array.iter(fun (prj, ver) -> _logger.VersionInfo(prj, ver))
137 |
138 | // start web server
139 | let cfg = buildCfg(binding)
140 | let routes = this.GetRoutes() |> choose
141 | startWebServer cfg routes
142 |
143 | member this.Stop() =
144 | _shutdownToken.Cancel()
145 |
146 |
147 |
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/WebServerLogger.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Backend
2 |
3 | open System
4 | open System.Text
5 | open ES.Fslog
6 | open Suave
7 |
8 | type WebServerLogger() =
9 | inherit LogSource("WebServer")
10 |
11 | []
12 | member this.LogRequest(ctx: HttpContext, ?logData: Boolean) =
13 | let ip = ctx.clientIpTrustProxy.ToString()
14 | let httpMethod = ctx.request.``method``
15 | let path = ctx.request.url.PathAndQuery
16 |
17 | let data =
18 | let tmpData = " - Data: " + Encoding.Default.GetString(ctx.request.rawForm)
19 | match (logData, httpMethod) with
20 | | (None, HttpMethod.POST) -> tmpData
21 | | (Some printData, HttpMethod.POST) when printData -> tmpData
22 | | _ -> String.Empty
23 | base.WriteLog(1, ip, httpMethod, path, data, ctx.response.status.code)
24 |
25 | []
26 | member this.VersionInfo(projectName: String, version: Version) =
27 | base.WriteLog(2, projectName, version)
28 |
29 | []
30 | member this.FileNotFound(file: String) =
31 | base.WriteLog(3, file)
32 |
33 | []
34 | member this.SettingInfo(name: String, value: String) =
35 | base.WriteLog(4, name, value)
--------------------------------------------------------------------------------
/Src/ES.Update.Backend/paket.references:
--------------------------------------------------------------------------------
1 | Suave
2 | Newtonsoft.Json
3 | ES.Fslog.Core
--------------------------------------------------------------------------------
/Src/ES.Update.Releaser/ES.Update.Releaser.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Src/ES.Update.Releaser/MetadataBuilder.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update.Releaser
2 |
3 | open System
4 | open System.Collections.Generic
5 | open System.IO
6 | open System.Text
7 | open System.Text.RegularExpressions
8 | open System.IO.Compression
9 | open ES.Fslog
10 | open ES.Update
11 |
12 | type MetadataBuilder(workingDirectory: String, patternsToExclude: List, logProvider: ILogProvider) =
13 | let _logger =
14 | log "MetadataBuilder"
15 | |> info "AnalyzeReleaseFile" "Analyze release file: {0}"
16 | |> info "SaveMetadata" "Saving release metadata"
17 | |> info "SavingFiles" "Saving artifacts to update"
18 | |> info "SavingFile" "Adding new file '{0}' as {1}"
19 | |> info "Completed" "Process completed"
20 | |> info "SkipZipEntry" "Skipped entry '{0}' due to forbidden pattern"
21 | |> buildAndAdd logProvider
22 |
23 | let fileBucketDirectory = Path.Combine(workingDirectory, "FileBucket")
24 |
25 | let extractProjectName(releaseFile: String) =
26 | let m = Regex.Match(releaseFile |> Path.GetFileName, "(.+?)[0-9]+(\.[0-9]+)+")
27 | m.Groups.[1].Value.Trim('v').Trim('.')
28 |
29 | let extractVersion(releaseFile: String) =
30 | let m = Regex.Match(releaseFile |> Path.GetFileName, "[0-9]+(\.[0-9]+)+")
31 | m.Value |> Version.Parse
32 |
33 | let readZipEntryContent(name: String, entries: ZipArchiveEntry seq) =
34 | let zipEntry =
35 | entries
36 | |> Seq.find(fun entry -> entry.FullName.Equals(name, StringComparison.OrdinalIgnoreCase))
37 |
38 | use zipStream = zipEntry.Open()
39 | use memStream = new MemoryStream()
40 | zipStream.CopyTo(memStream)
41 | memStream.ToArray()
42 |
43 | let getVersionFilesSummary(releaseFile: String) =
44 | use fileHandle = File.OpenRead(releaseFile)
45 |
46 | // inspect zip
47 | (new System.IO.Compression.ZipArchive(fileHandle, ZipArchiveMode.Read)).Entries
48 | |> Seq.toArray
49 | |> Array.filter(fun zipEntry ->
50 | let skipZipEntry =
51 | patternsToExclude
52 | |> Seq.exists(fun pattern -> Regex.IsMatch(zipEntry.FullName, pattern))
53 |
54 | if skipZipEntry then _logger?SkipZipEntry(zipEntry.FullName)
55 | not skipZipEntry
56 | )
57 | |> Array.map(fun zipEntry ->
58 | // check if it is a directory
59 | if String.IsNullOrEmpty(zipEntry.Name) && (zipEntry.FullName.EndsWith("/") || zipEntry.FullName.EndsWith("\\")) then
60 | (zipEntry.FullName, String.Empty)
61 | else
62 | use zipStream = zipEntry.Open()
63 | use memStream = new MemoryStream()
64 | zipStream.CopyTo(memStream)
65 | (zipEntry.FullName, CryptoUtility.sha256(memStream.ToArray()))
66 | )
67 |
68 | let saveApplicationMetadata(workingDirectory: String, releaseFile: String, files: (String * String) seq) =
69 | let fileContent = new StringBuilder()
70 | files |> Seq.iter(fun (name, hashValue) ->
71 | fileContent.AppendFormat("{0},{1}", hashValue, name).AppendLine() |> ignore
72 | )
73 |
74 | // save the content
75 | let versionsDirectory = Path.Combine(workingDirectory, "Versions")
76 | Directory.CreateDirectory(versionsDirectory) |> ignore
77 | let filename = Path.Combine(versionsDirectory, String.Format("{0}.txt", extractVersion(releaseFile)))
78 | File.WriteAllText(filename, fileContent.ToString())
79 |
80 | let getAllHashPerVersion(workingDirectory: String) =
81 | Directory.GetFiles(Path.Combine(workingDirectory, "Versions"))
82 | |> Array.map(fun filename ->
83 | (
84 | Path.GetFileNameWithoutExtension(filename),
85 | (File.ReadAllLines(filename) |> Array.map(fun line -> line.Split([|','|]).[0]))
86 | )
87 | )
88 |
89 | let saveFilesContent(workingDirectory: String, releaseFile: String, files: (String * String) seq) =
90 | let releaseVersion = extractVersion(releaseFile).ToString()
91 | Directory.CreateDirectory(fileBucketDirectory) |> ignore
92 |
93 | // compute the new files that must be copied
94 | let allHashFiles =
95 | getAllHashPerVersion(workingDirectory)
96 | |> Array.filter(fun (version, _) -> version.Equals(releaseVersion, StringComparison.OrdinalIgnoreCase) |> not)
97 | |> Array.collect(snd)
98 | |> Set.ofArray
99 |
100 | let allVersionHash = files |> Seq.map(fun (_, h) -> h) |> Set.ofSeq
101 | let newHashFiles = Set.difference allVersionHash allHashFiles
102 |
103 | // open the zip file again
104 | use fileHandle = File.OpenRead(releaseFile)
105 | let entries = (new ZipArchive(fileHandle, ZipArchiveMode.Read)).Entries
106 |
107 | // save the new files
108 | files
109 | |> Seq.filter(fun (_, hashValue) -> newHashFiles.Contains(hashValue) )
110 | |> Seq.iter(fun (name, hashValue) ->
111 | let filename = Path.Combine(fileBucketDirectory, hashValue)
112 | if not(File.Exists(filename)) then
113 | _logger?SavingFile(name, hashValue)
114 | File.WriteAllBytes(filename, readZipEntryContent(name, entries))
115 | )
116 |
117 | new(workingDirectory: String) = new MetadataBuilder(workingDirectory, new List())
118 | new(workingDirectory: String, patternsToExclude: List) = new MetadataBuilder(workingDirectory, patternsToExclude, new LogProvider())
119 |
120 | member this.CreateReleaseMetadata(releaseFile: String) =
121 | _logger?AnalyzeReleaseFile(Path.GetFileName(releaseFile))
122 | let files = getVersionFilesSummary(releaseFile)
123 | let projectWorkspace = Path.Combine(workingDirectory, extractProjectName(releaseFile))
124 |
125 | _logger?SaveMetadata()
126 | saveApplicationMetadata(projectWorkspace, releaseFile, files)
127 |
128 | _logger?SavingFiles()
129 | saveFilesContent(projectWorkspace, releaseFile, files)
130 |
131 | _logger?Completed()
132 | ()
133 |
--------------------------------------------------------------------------------
/Src/ES.Update.Releaser/paket.references:
--------------------------------------------------------------------------------
1 | ES.Fslog.Core
--------------------------------------------------------------------------------
/Src/ES.Update/CryptoUtility.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update
2 |
3 | open System
4 | open System.Security.Cryptography
5 | open System.IO
6 | open Org.BouncyCastle.Crypto.Generators
7 | open Org.BouncyCastle.Crypto.Parameters
8 | open Org.BouncyCastle.Asn1.X9
9 | open Org.BouncyCastle.Security
10 | open Org.BouncyCastle.Pkcs
11 | open Org.BouncyCastle.X509
12 | open System.Net.NetworkInformation
13 | open System.Text
14 |
15 | []
16 | module CryptoUtility =
17 | []
18 | let generateKeys() =
19 | let ecSpec = ECNamedCurveTable.GetOid("prime256v1")
20 | let gen = new ECKeyPairGenerator("ECDSA")
21 | let keyGenParam = new ECKeyGenerationParameters(ecSpec, new SecureRandom())
22 | gen.Init(keyGenParam)
23 | let keyPair = gen.GenerateKeyPair()
24 |
25 | // public key
26 | let publicKey = keyPair.Public :?> ECPublicKeyParameters
27 | let publicKeyBytes = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey).GetDerEncoded()
28 |
29 | // private key
30 | let privatekey = keyPair.Private :?> ECPrivateKeyParameters
31 | let pkinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privatekey)
32 | let privatekeyBytes = pkinfo.GetDerEncoded()
33 |
34 | (publicKeyBytes, privatekeyBytes)
35 |
36 | let sign(data: Byte array, privateKey: Byte array) =
37 | let privateKey = PrivateKeyFactory.CreateKey(privateKey)
38 | let signer = SignerUtilities.GetSigner("SHA256withECDSA")
39 | signer.Init(true, privateKey)
40 | signer.BlockUpdate(data, 0, data.Length)
41 | signer.GenerateSignature()
42 |
43 | let hexSign(data: Byte array, privateKey: Byte array) =
44 | BitConverter.ToString(sign(data, privateKey)).Replace("-","")
45 |
46 | let verifyData(data: Byte array, signature: Byte array, publicKey: Byte array) =
47 | let bpubKey = PublicKeyFactory.CreateKey(publicKey) :?> ECPublicKeyParameters
48 | let signer = SignerUtilities.GetSigner("SHA256withECDSA")
49 | signer.Init (false, bpubKey)
50 | signer.BlockUpdate (data, 0, data.Length)
51 | signer.VerifySignature(signature)
52 |
53 | let verifyString(data: String, signature: String, publicKey: Byte array) =
54 | let byteSignature = [|
55 | for i in 0..2..(signature.Length-1) do
56 | let s = String.Format("{0}{1}", signature.[i], signature.[i+1])
57 | yield Convert.ToByte(s, 16)
58 | |]
59 |
60 | let byteData = Encoding.UTF8.GetBytes(data)
61 | verifyData(byteData, byteSignature, publicKey)
62 |
63 | let sha256Raw(content: Byte array) =
64 | use sha = new SHA256Managed()
65 | sha.ComputeHash(content)
66 |
67 | let sha256(content: Byte array) =
68 | BitConverter.ToString(sha256Raw(content)).Replace("-",String.Empty).ToUpperInvariant()
69 |
70 | let decrypt(data: Byte array, key: Byte array, iv: Byte array) =
71 | use aes = new AesManaged(Key = key, IV = iv, Padding = PaddingMode.ISO10126)
72 | use ms = new MemoryStream(data)
73 | use resultStream = new MemoryStream()
74 | use sr = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read)
75 | sr.CopyTo(resultStream)
76 | sr.Close()
77 | resultStream.ToArray()
78 |
79 | let encrypt(data: Byte array, key: Byte array, iv: Byte array) =
80 | use aes = new AesManaged(Key = key, IV = iv, Padding = PaddingMode.ISO10126)
81 | use ms = new MemoryStream()
82 | use sw = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)
83 | sw.Write(data, 0, data.Length)
84 | sw.Close()
85 | ms.ToArray()
86 |
87 | let getMacAddresses() =
88 | NetworkInterface.GetAllNetworkInterfaces()
89 | |> Array.filter(fun ni ->
90 | (ni.NetworkInterfaceType = NetworkInterfaceType.Wireless80211
91 | || ni.NetworkInterfaceType = NetworkInterfaceType.Ethernet)
92 | && ni.OperationalStatus = OperationalStatus.Up
93 | )
94 | |> Array.collect(fun ni -> ni.GetPhysicalAddress().GetAddressBytes())
95 |
96 | let getHardDiskSerials() =
97 | let bytes =
98 | DriveInfo.GetDrives()
99 | |> Array.filter(fun drive -> drive.IsReady)
100 | |> Array.collect(fun drive -> drive.RootDirectory.CreationTime.ToBinary() |> BitConverter.GetBytes)
101 | |> sha256Raw
102 | Array.sub bytes 0 16
103 |
104 | let encryptKey(data: Byte array) =
105 | let key = getMacAddresses() |> sha256Raw
106 | let iv = getHardDiskSerials()
107 | encrypt(data, key, iv)
108 |
109 | let private decryptKey(data: Byte array) =
110 | let key = getMacAddresses() |> sha256Raw
111 | let iv = getHardDiskSerials()
112 | decrypt(data, key, iv)
113 |
114 | let readPrivateKey(filename: String) =
115 | let encodedKey = Convert.FromBase64String(File.ReadAllText(filename))
116 | let effectiveKey = decryptKey(encodedKey)
117 | Convert.ToBase64String(effectiveKey)
118 |
119 | let encryptExportedKey(password: String, base64KeyData: String) =
120 | let keyDataBytes = Convert.FromBase64String(base64KeyData)
121 | let passwordBytes = Encoding.UTF8.GetBytes(password) |> sha256Raw
122 | let iv = Array.zeroCreate(16)
123 | use provider = new RNGCryptoServiceProvider()
124 | provider.GetBytes(iv)
125 | let encryptedKey = encrypt(keyDataBytes, passwordBytes, iv)
126 | let encryptedData = Array.concat [iv; encryptedKey]
127 | Convert.ToBase64String(encryptedData)
128 |
129 | let decryptImportedKey(password: String, base64KeyData: String) =
130 | let keyDataBytes = Convert.FromBase64String(base64KeyData)
131 | let passwordBytes = Encoding.UTF8.GetBytes(password) |> sha256Raw
132 | let iv = Array.sub keyDataBytes 0 16
133 | let encryptedKey = keyDataBytes.[16..]
134 | let decryptedKey = decrypt(encryptedKey, passwordBytes, iv)
135 | Convert.ToBase64String(decryptedKey)
--------------------------------------------------------------------------------
/Src/ES.Update/ES.Update.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Src/ES.Update/Entities.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update
2 |
3 | open System
4 |
5 | // create this class to easier the C# interoperability
6 | type Result(result: Boolean) =
7 | member val Success = result with get, set
8 | member val Error = String.Empty with get, set
9 |
10 | type ApplicationFile = {
11 | FilePath: String
12 | Hash: String
13 | }
--------------------------------------------------------------------------------
/Src/ES.Update/Installer.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update
2 |
3 | open System
4 | open System.IO
5 | open System.IO.Compression
6 | open System.Diagnostics
7 | open System.Text
8 | open System.Threading
9 | open System.Collections.Generic
10 | open System.Text.RegularExpressions
11 | open ES.Fslog
12 |
13 | module AbbandonedMutex =
14 | let mutable mutex: Mutex option = None
15 |
16 | type Installer(destinationDirectory: String, logProvider: ILogProvider) as this =
17 | let _logger =
18 | log "Installer"
19 | |> info "ZipExtracted" "Update zip extracted to: {0}"
20 | |> info "FilesCopied" "All update files were copied to: {0}"
21 | |> info "RunInstaller" "Run installer: {0} {1}"
22 | |> info "NoInstaller" "No installer found in directory '{0}'. Copy files to '{1}'"
23 | |> verbose "CopyFile" "Copy '{0}' to '{1}'"
24 | |> verbose "MoveFile" "Move existing file '{0}' to '{1}'"
25 | |> verbose "SkipFile" "Skip file due to forbidden pattern: {0}"
26 | |> verbose "SkipFileSameHash" "Skip file becasue same hash: {0}"
27 | |> verbose "InstallerNotFound" "Installer {0} not found"
28 | |> critical "InstallerIntegrityFail" "The integrity check of the installer failed"
29 | |> buildAndAdd logProvider
30 |
31 | let compareHash(hashValue: String, content: Byte array) =
32 | let computedHashValue = CryptoUtility.sha256(content)
33 | hashValue.Equals(computedHashValue, StringComparison.OrdinalIgnoreCase)
34 |
35 | let moveFile(destinationFile: String) =
36 | let oldFilesDir = Path.Combine(destinationDirectory, "OLD-files")
37 | Directory.CreateDirectory(oldFilesDir) |> ignore
38 | let copyFile = Path.Combine(oldFilesDir, Path.GetFileName(destinationFile))
39 | if not <| File.Exists(copyFile) then
40 | _logger?MoveFile(destinationFile, copyFile)
41 | File.Move(destinationFile, copyFile)
42 |
43 | let copyFile(filePath: String, contantHashValue: String, content: Byte array) =
44 | let destinationFile = Path.Combine(destinationDirectory, filePath)
45 | Directory.CreateDirectory(Path.GetDirectoryName(destinationFile)) |> ignore
46 | if File.Exists(destinationFile) then
47 | // check if same hash, if so, skip the file move
48 | if not <| sha256(File.ReadAllBytes(destinationFile)).Equals(contantHashValue) then
49 | moveFile(destinationFile)
50 | _logger?CopyFile(filePath, destinationFile)
51 | else
52 | File.WriteAllBytes(destinationFile, content)
53 | _logger?CopyFile(filePath, destinationFile)
54 |
55 | let getFiles(fileList: String) =
56 | fileList.Split([|'\r'; '\n'|], StringSplitOptions.RemoveEmptyEntries)
57 | |> Array.filter(String.IsNullOrWhiteSpace >> not)
58 | |> Array.map(fun line ->
59 | let items = line.Split([|','|])
60 | (items.[0], String.Join(",", items.[1..]))
61 | )
62 |
63 | let verifyIntegrity(directory: String, catalogFileName: String) =
64 | // read catalog
65 | let catalog = File.ReadAllText(Path.Combine(directory, catalogFileName))
66 | let files = getFiles(catalog)
67 |
68 | // check integrity
69 | let mutable integrityFailedOnFile = String.Empty
70 | files
71 | |> Array.filter(fun (hashValue, _) -> not(String.IsNullOrWhiteSpace(hashValue)))
72 | |> Array.forall(fun (hashValue, filePath) ->
73 | // try to read the file content via hash or file name (in case of installer)
74 | let fullFileName =
75 | let tmp = Path.Combine(directory, hashValue)
76 | if File.Exists(tmp) then tmp
77 | else Path.Combine(directory, filePath)
78 |
79 | let content = File.ReadAllBytes(fullFileName)
80 | if compareHash(hashValue, content) then
81 | true
82 | else
83 | integrityFailedOnFile <- filePath
84 | false
85 | )
86 | |> fun result ->
87 | if result then Ok ()
88 | else
89 | _logger?InstallerIntegrityFail()
90 | Error("Integrity check failed on file: " + integrityFailedOnFile)
91 |
92 | let extractZip(zipArchive: ZipArchive, destinationDirectory: String) =
93 | Directory.CreateDirectory(destinationDirectory) |> ignore
94 |
95 | zipArchive.Entries
96 | |> Seq.iter(fun entry ->
97 | let fileName = Path.Combine(destinationDirectory, entry.FullName)
98 | Directory.CreateDirectory(Path.GetDirectoryName(fileName)) |> ignore
99 | let content = Utility.readEntry(zipArchive, entry.FullName)
100 | File.WriteAllBytes(fileName, content)
101 | )
102 |
103 | destinationDirectory
104 |
105 | let isOkToCopy(patternsSkipOnExist: List) (hashValueFile: String, filePath: String) =
106 | if File.Exists(filePath) && patternsSkipOnExist.Count > 0 then
107 | // is forbidden pattern
108 | let skipFile =
109 | patternsSkipOnExist
110 | |> Seq.exists(fun pattern -> Regex.IsMatch(filePath, pattern))
111 |
112 | if skipFile then _logger?SkipFile(filePath)
113 | not skipFile
114 | else
115 | true
116 |
117 | let copyAllFiles(extractedDirectory: String, files: (String * String) array, patternsSkipOnExist: List) =
118 | files
119 | |> Array.filter(isOkToCopy patternsSkipOnExist)
120 | |> Array.iter(fun (hashValue, filePath) ->
121 | let sourceFilePath = Path.Combine(extractedDirectory, hashValue)
122 | if File.Exists(sourceFilePath) then
123 | let content = File.ReadAllBytes(sourceFilePath)
124 | copyFile(filePath, hashValue, content)
125 | )
126 |
127 | let createInstallerMutex(argumentString: String) =
128 | let mutexName =
129 | Regex.Replace(argumentString, "[^a-zA-Z]+", String.Empty)
130 | |> Encoding.UTF8.GetBytes
131 | |> sha256
132 |
133 | AbbandonedMutex.mutex <- Some <| new Mutex(true, mutexName)
134 |
135 | let runInstaller(processInfo: ProcessStartInfo) =
136 | try
137 | createInstallerMutex(processInfo.Arguments)
138 | _logger?RunInstaller(processInfo.FileName, processInfo.Arguments)
139 | Process.Start(processInfo) |> ignore
140 | Ok ()
141 | with e ->
142 | Error(e.ToString())
143 |
144 | let buildProcessObject(extractedDirectory: String) =
145 | let isVerbose =
146 | logProvider.GetLoggers()
147 | |> Seq.exists(fun logger -> logger.Level = LogLevel.Verbose)
148 |
149 | let baseArgumentString = new StringBuilder()
150 | baseArgumentString.AppendFormat("--source \"{0}\" --dest \"{1}\"", extractedDirectory, destinationDirectory) |> ignore
151 |
152 | if isVerbose then
153 | baseArgumentString.Append(" --verbose") |> ignore
154 |
155 | if not this.RemoveTempFile then
156 | baseArgumentString.Append(" --no-clean") |> ignore
157 |
158 | let dllInstaller = Path.Combine(extractedDirectory, "Installer.dll")
159 | if not <| File.Exists(dllInstaller) then
160 | _logger?InstallerNotFound(dllInstaller)
161 |
162 | let exeInstaller = Path.Combine(extractedDirectory, "Installer.exe")
163 | if not <| File.Exists(dllInstaller) then
164 | _logger?InstallerNotFound(exeInstaller)
165 |
166 | if File.Exists(dllInstaller) then
167 | Some("dotnet", String.Format("\"{0}\" {1}", dllInstaller, baseArgumentString.ToString()))
168 | elif File.Exists(exeInstaller) then
169 | Some(exeInstaller, baseArgumentString.ToString())
170 | else None
171 | |> function
172 | | Some (installer, argumentString) ->
173 | new ProcessStartInfo(
174 | FileName = installer,
175 | UseShellExecute = false,
176 | Arguments = argumentString
177 | )
178 | |> Some
179 | | None ->
180 | _logger?NoInstaller(extractedDirectory, destinationDirectory)
181 | None
182 |
183 | let runVerifiedInstaller(processInfo, extractedDirectory: String) =
184 | match verifyIntegrity(extractedDirectory, "installer-catalog") with
185 | | Ok _ -> runInstaller(processInfo)
186 | | Error e -> Error e
187 |
188 | member private this.DoInstall(extractedDirectory: String, fileList: String, patternsSkipOnExist: List) =
189 | match buildProcessObject(extractedDirectory) with
190 | | Some processInfo ->
191 | if this.SkipIntegrityCheck
192 | then runInstaller(processInfo)
193 | else runVerifiedInstaller(processInfo, extractedDirectory)
194 | | None ->
195 | let files = getFiles(fileList)
196 | copyAllFiles(extractedDirectory, files, patternsSkipOnExist)
197 | _logger?FilesCopied(destinationDirectory)
198 |
199 | // cleanup spurious files
200 | if this.RemoveTempFile then Directory.Delete(extractedDirectory, true)
201 | Ok ()
202 |
203 | member val PatternsSkipOnExist = new List() with get, set
204 | member val SkipIntegrityCheck = false with get, set
205 | member val RemoveTempFile = true with get, set
206 |
207 | member this.CopyUpdates(sourceDirectory: String) =
208 | let catalog = File.ReadAllText(Path.Combine(sourceDirectory, "catalog"))
209 | let files = getFiles(catalog)
210 | copyAllFiles(sourceDirectory, files, this.PatternsSkipOnExist)
211 |
212 | member internal this.InstallUpdate(directory: String, fileList: String) =
213 | match verifyIntegrity(directory, "catalog") with
214 | | Ok _ -> this.DoInstall(directory, fileList, this.PatternsSkipOnExist)
215 | | Error e -> Error e
216 |
217 | member internal this.InstallUpdate(zipArchive: ZipArchive, fileList: String) =
218 | let tempDir = Path.Combine(Path.GetTempPath(), fileList |> Encoding.UTF8.GetBytes |> sha256)
219 | let extractedDirectory = extractZip(zipArchive, tempDir)
220 | _logger?ZipExtracted(extractedDirectory)
221 | this.InstallUpdate(extractedDirectory, fileList)
--------------------------------------------------------------------------------
/Src/ES.Update/Updater.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update
2 |
3 | open System
4 | open System.Collections.Generic
5 | open System.Net
6 | open System.IO
7 | open System.IO.Compression
8 | open System.Text
9 | open ES.Fslog
10 |
11 | type Updater(serverUri: Uri, projectName: String, currentVersion: Version, destinationDirectory: String, publicKey: Byte array, logProvider: ILogProvider) as this =
12 | let _downloadingFileEvent = new Event()
13 | let _downloadedFileEvent = new Event()
14 | let mutable _additionalData: Dictionary option = None
15 |
16 | let _logger =
17 | log "Updater"
18 | |> critical "CatalogIntegrityFail" "The integrity check of the catalog failed"
19 | |> critical "DownloadError" "Download error: {0}"
20 | |> info "DownloadDone" "Updates downloaded to file: {0}"
21 | |> buildAndAdd logProvider
22 |
23 | let getContent(url: String) =
24 | try
25 | // configure request
26 | let webRequest = WebRequest.Create(new Uri(serverUri, url)) :?> HttpWebRequest
27 | webRequest.Method <- "POST"
28 | webRequest.Timeout <- 5 * 60 * 1000
29 | webRequest.ContentType <- "application/x-www-form-urlencoded"
30 |
31 | // compose data
32 | let data = new StringBuilder()
33 | data.AppendFormat("version={0}&project={1}", currentVersion, projectName) |> ignore
34 |
35 | _additionalData
36 | |> Option.iter(fun additionalData ->
37 | additionalData
38 | |> Seq.iter(fun kv ->
39 | data.AppendFormat("&{0}={1}", kv.Key, kv.Value) |> ignore
40 | )
41 | )
42 |
43 | // write data
44 | use streamWriter = new StreamWriter(webRequest.GetRequestStream())
45 | streamWriter.Write(data.ToString().Trim('&'))
46 | streamWriter.Close()
47 |
48 | // send the request and save the response to file
49 | use webResponse = webRequest.GetResponse() :?> HttpWebResponse
50 | use responseStream = webResponse.GetResponseStream()
51 | use memoryStream = new MemoryStream()
52 | responseStream.CopyTo(memoryStream)
53 | memoryStream.ToArray()
54 | with e ->
55 | _logger?DownloadError(e.Message)
56 | Array.empty
57 |
58 | let getString(url: String) =
59 | Encoding.UTF8.GetString(getContent(url))
60 |
61 | let parseCatalog(catalog: String) =
62 | catalog.Split("\r\n")
63 | |> Array.filter(fun line -> String.IsNullOrWhiteSpace(line) |> not)
64 | |> Array.map(fun line ->
65 | // hash, file path
66 | let items = line.Split(",")
67 | {Hash = items.[0]; FilePath = items.[1]}
68 | )
69 |
70 | let tryGetCatalog() =
71 | let catalog = getString("updates")
72 | let items = catalog.Split("\r\n")
73 | if items.Length >= 2 then
74 | let signature = items.[0].Trim()
75 | let catalog = String.Join("\r\n", items.[1..])
76 | if this.SkipIntegrityCheck || CryptoUtility.verifyString(catalog, signature, publicKey) then
77 | (String.Empty, Some(parseCatalog(catalog)))
78 | else
79 | ("Wrong catalog signature", None)
80 | else
81 | ("Wrong catalog format", None)
82 |
83 | let isFileAlreadyDownloaded(hash: String, filePath: String) =
84 | if File.Exists(filePath) then
85 | let receivedHash = CryptoUtility.sha256(File.ReadAllBytes(filePath))
86 | receivedHash.Equals(hash, StringComparison.OrdinalIgnoreCase)
87 | else
88 | false
89 |
90 | let downloadFile(file: ApplicationFile) =
91 | _downloadingFileEvent.Trigger(file.Hash)
92 | let storagePath = Path.Combine(destinationDirectory, file.FilePath)
93 | let fileContent = getContent(String.Format("file/{0}", file.Hash))
94 |
95 | if fileContent |> Array.isEmpty then
96 | new Result(false, Error = String.Format("Error downloading file: {0}", file.FilePath))
97 | else
98 | let receivedHash = CryptoUtility.sha256(fileContent)
99 | if receivedHash.Equals(file.Hash, StringComparison.OrdinalIgnoreCase) then
100 | Directory.CreateDirectory(Path.GetDirectoryName(storagePath)) |> ignore
101 | File.WriteAllBytes(storagePath, fileContent)
102 | _downloadedFileEvent.Trigger(file.Hash)
103 | new Result(true)
104 | else
105 | new Result(false, Error = String.Format("Downloaded file '{0}' has a wrong hash", file.FilePath))
106 |
107 | let verifyCatalogContentIntegrity(catalog: Byte array, installerCatalog: (Byte array) option, signature: Byte array, installerSignature: (Byte array) option) =
108 | let catalogOk = CryptoUtility.verifyData(catalog, signature, publicKey)
109 | let installerCatalogOk =
110 | match (installerCatalog, installerSignature) with
111 | | (Some installerCatalog, Some installerSignature) ->
112 | CryptoUtility.verifyData(installerCatalog, installerSignature, publicKey)
113 | | (Some _, None) -> false
114 | | (None, Some _) -> false
115 | | _ -> true
116 | catalogOk && installerCatalogOk
117 |
118 | let verifyCatalogIntegrity(zipArchive: ZipArchive) =
119 | let catalog = Utility.readEntry(zipArchive, "catalog")
120 | let signature = Utility.readEntry(zipArchive, "signature")
121 | let installerCatalog = Utility.tryReadEntry(zipArchive, "installer-catalog")
122 | let installerSignature = Utility.tryReadEntry(zipArchive, "installer-signature")
123 | verifyCatalogContentIntegrity(catalog, installerCatalog, signature, installerSignature)
124 |
125 | let downloadFiles(files: ApplicationFile array) =
126 | files
127 | |> Array.map(downloadFile)
128 | |> Array.tryFind(fun res -> not res.Success)
129 |
130 | let getNewFiles() =
131 | match tryGetCatalog() with
132 | | (_, Some files) ->
133 | let newFiles =
134 | files
135 | |> Array.filter(fun fi ->
136 | let storagePath = Path.Combine(destinationDirectory, fi.FilePath)
137 | isFileAlreadyDownloaded(fi.Hash, storagePath) |> not
138 | )
139 | (String.Empty, Some newFiles)
140 | | r -> r
141 |
142 | new (serverUri: Uri, projectName: String, currentVersion: Version, destinationDirectory: String, publicKey: Byte array) = new Updater(serverUri, projectName, currentVersion, destinationDirectory, publicKey, LogProvider.GetDefault())
143 |
144 | member val PatternsSkipOnExist = new List() with get, set
145 | member val SkipIntegrityCheck = false with get, set
146 | member val RemoveTempFile = true with get, set
147 |
148 | []
149 | member val DownloadingFile = _downloadingFileEvent.Publish
150 |
151 | []
152 | member val DownloadedFile = _downloadedFileEvent.Publish
153 |
154 | member this.AddParameter(name: String, value: String) =
155 | let dataStorage =
156 | match _additionalData with
157 | | None ->
158 | _additionalData <- new Dictionary() |> Some
159 | _additionalData.Value
160 | | Some d -> d
161 | dataStorage.[name] <- value
162 |
163 | member internal this.InstallUpdatesFromFile(file: String, installer: Installer) =
164 | use zipStream = File.OpenRead(file)
165 | use zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read)
166 | if this.SkipIntegrityCheck || verifyCatalogIntegrity(zipArchive) then
167 | let fileList = Utility.readEntry(zipArchive, "catalog")
168 | installer.InstallUpdate(zipArchive, Encoding.UTF8.GetString(fileList))
169 | else
170 | _logger?CatalogIntegrityFail()
171 | Error "Integrity check failed"
172 |
173 | member internal this.InstallUpdatesFromDirectory(directory: String, installer: Installer) =
174 | let fileList = Path.Combine(directory, "catalog") |> File.ReadAllText
175 | let mutable integrityCheckOk = true
176 |
177 | // check integrity
178 | if not this.SkipIntegrityCheck then
179 | let catalog = Path.Combine(directory, "catalog") |> File.ReadAllBytes
180 | let signature = Path.Combine(directory, "signature") |> File.ReadAllBytes
181 | let installerCatalogFile = Path.Combine(directory, "installer-catalog")
182 | let installerSignatureFile = Path.Combine(directory, "installer-signature")
183 |
184 | if File.Exists(installerCatalogFile) && File.Exists(installerSignatureFile) then
185 | let installerCatalog = File.ReadAllBytes(installerCatalogFile)
186 | let installerSignature = File.ReadAllBytes(installerSignatureFile)
187 | integrityCheckOk <- verifyCatalogContentIntegrity(catalog, Some installerCatalog, signature, Some installerSignature)
188 | elif not(File.Exists(installerCatalogFile)) && not(File.Exists(installerSignatureFile)) then
189 | integrityCheckOk <- verifyCatalogContentIntegrity(catalog, None, signature, None)
190 | else
191 | // do I have an installer and not associated signature or the opposite
192 | integrityCheckOk <- false
193 |
194 | if integrityCheckOk then
195 | installer.InstallUpdate(directory, fileList)
196 | else
197 | _logger?CatalogIntegrityFail()
198 | Error "Integrity check failed"
199 |
200 | member this.InstallUpdates(fileOrDirectory: String) =
201 | let installer =
202 | new Installer(
203 | destinationDirectory,
204 | logProvider,
205 | PatternsSkipOnExist = this.PatternsSkipOnExist,
206 | SkipIntegrityCheck = this.SkipIntegrityCheck
207 | )
208 |
209 | if Directory.Exists(fileOrDirectory) then
210 | this.InstallUpdatesFromDirectory(fileOrDirectory, installer)
211 | elif File.Exists(fileOrDirectory) then
212 | this.InstallUpdatesFromFile(fileOrDirectory, installer)
213 | else
214 | Error(String.Format("{0} is not a valid update path", fileOrDirectory))
215 |
216 | member this.GetLatestVersion() =
217 | use webClient = new WebClient()
218 | let latestVersionUri = new Uri(serverUri, String.Format("latest?project={0}", projectName))
219 | webClient.DownloadString(latestVersionUri) |> Version.Parse
220 |
221 | member this.GetNewFiles() =
222 | match getNewFiles() with
223 | | (_, Some files) -> files
224 | | (error, _) -> Array.empty
225 |
226 | member this.Update() =
227 | // prepare update file
228 | let resultDirectory = Path.Combine(Path.GetTempPath(), projectName)
229 | Directory.CreateDirectory(resultDirectory) |> ignore
230 |
231 | // download catalog
232 | match getNewFiles() with
233 | | (_, Some files) ->
234 | match downloadFiles(files) with
235 | | Some error -> error
236 | | None -> new Result(true)
237 | | (error, _) ->
238 | new Result(false, Error = error)
--------------------------------------------------------------------------------
/Src/ES.Update/Utility.fs:
--------------------------------------------------------------------------------
1 | namespace ES.Update
2 |
3 | open System
4 | open System.IO.Compression
5 | open System.IO
6 |
7 | module Utility =
8 | let tryReadEntry(zipArchive: ZipArchive, name: String) =
9 | let entry =
10 | zipArchive.Entries
11 | |> Seq.tryFind(fun entry -> entry.FullName.Equals(name, StringComparison.OrdinalIgnoreCase))
12 |
13 | match entry with
14 | | Some entry ->
15 | use zipStream = entry.Open()
16 | use memStream = new MemoryStream()
17 | zipStream.CopyTo(memStream)
18 | Some <| memStream.ToArray()
19 | | None ->
20 | None
21 |
22 | let readEntry(zipArchive: ZipArchive, name: String) =
23 | tryReadEntry(zipArchive, name).Value
--------------------------------------------------------------------------------
/Src/ES.Update/paket.references:
--------------------------------------------------------------------------------
1 | ES.Fslog.Core
2 | BouncyCastle
--------------------------------------------------------------------------------
/Src/Examples/Example1/Client.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using ES.Update;
4 |
5 | namespace Example1
6 | {
7 | public class Client
8 | {
9 | public void Run(Server server, String destinationDirectory)
10 | {
11 | var myVersion = Helpers.GetCurrentVersion();
12 | var updater = new Updater(server.BindingUri, "MyApplication", myVersion, destinationDirectory, server.PublicKey);
13 |
14 | var latestVersion = updater.GetLatestVersion();
15 | Console.WriteLine("My version: {0}. Latest version: {1}", myVersion, latestVersion);
16 |
17 | if (latestVersion > myVersion)
18 | {
19 | // start update
20 | updater.DownloadingFile += (args, file) => { Console.WriteLine("[-] Downloading: {0}", file); };
21 | updater.DownloadedFile += (args, file) => { Console.WriteLine("[+] Downloaded: {0}", file); };
22 |
23 | var updateResult = updater.Update();
24 | if (updateResult.Success)
25 | {
26 | var fileContent = File.ReadAllText(Path.Combine(destinationDirectory, "folder", "file7.txt"));
27 | Console.WriteLine("Update installed correctly! {0}", fileContent);
28 | Helpers.SaveVersion(latestVersion);
29 | }
30 | else
31 | {
32 | Console.WriteLine("Error during installing updates: {0}", updateResult.Error);
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Src/Examples/Example1/Example1.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Src/Examples/Example1/Helpers.cs:
--------------------------------------------------------------------------------
1 | using ES.Update.Releaser;
2 | using System;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace Example1
11 | {
12 | public static class Helpers
13 | {
14 | private static String GetVersionFileName()
15 | {
16 | var curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
17 | var versionFileName = Path.Combine(curDirectory, Assembly.GetEntryAssembly().ManifestModule.Name + ".version");
18 | return versionFileName;
19 | }
20 |
21 | public static Version GetCurrentVersion()
22 | {
23 | if (File.Exists(GetVersionFileName()))
24 | {
25 | return Version.Parse(File.ReadAllText(GetVersionFileName()));
26 | }
27 | else
28 | {
29 | return new Version(3, 0);
30 | }
31 | }
32 |
33 | public static void SaveVersion(Version version)
34 | {
35 | File.WriteAllText(GetVersionFileName(), version.ToString());
36 | }
37 |
38 | private static void CreateFakeReleaseFile(String fileName, Int32 numberOfItems)
39 | {
40 | using (var fileHandle = File.OpenWrite(fileName))
41 | using (var zipArchive = new ZipArchive(fileHandle, ZipArchiveMode.Create))
42 | {
43 | for (var i = 0; i < numberOfItems; i++)
44 | {
45 | var entryName =
46 | (i > numberOfItems / 2) ?
47 | Path.Combine("folder", String.Format("file{0}.txt", i)):
48 | String.Format("file{0}.txt", i);
49 |
50 | var entryContent = Encoding.UTF8.GetBytes(String.Format("Content of file: {0}", entryName));
51 | var entry = zipArchive.CreateEntry(entryName);
52 | using (var entryStream = entry.Open())
53 | {
54 | entryStream.Write(entryContent, 0, entryContent.Length);
55 | }
56 | }
57 | }
58 | }
59 |
60 | public static (String, String) CreateEnvironment(Int32 numOfFiles = 5)
61 | {
62 | if (File.Exists(GetVersionFileName()))
63 | {
64 | var deleteFile = GetVersionFileName() + ".DELETE";
65 | if (File.Exists(deleteFile))
66 | {
67 | // second run
68 | File.Delete(GetVersionFileName());
69 | File.Delete(deleteFile);
70 | }
71 | else
72 | {
73 | // first run, create delete file for next run
74 | File.WriteAllText(deleteFile, "DELETE ME");
75 | }
76 | }
77 |
78 | // create dirs
79 | var workspaceDirectory = Path.Combine(Path.GetTempPath(), "TEST_ProgramUpdater_Examples");
80 | if (Directory.Exists(workspaceDirectory))
81 | {
82 | Directory.Delete(workspaceDirectory, true);
83 | }
84 | Directory.CreateDirectory(workspaceDirectory);
85 |
86 | var destinationDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "MyApplication");
87 | if (Directory.Exists(destinationDirectory))
88 | {
89 | Directory.Delete(destinationDirectory, true);
90 | }
91 | Directory.CreateDirectory(destinationDirectory);
92 |
93 | // create some fake zip File and build metadata
94 | var metadataBuilder = new MetadataBuilder(workspaceDirectory);
95 | var currentDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
96 | for (var i = 0; i < numOfFiles; i++)
97 | {
98 | var fileName = Path.Combine(currentDir, String.Format("MyApplication.v{0}.0.zip", i + 1));
99 | Helpers.CreateFakeReleaseFile(fileName, 5 + i);
100 | metadataBuilder.CreateReleaseMetadata(fileName);
101 | File.Delete(fileName);
102 | }
103 |
104 | return (workspaceDirectory, destinationDirectory);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Src/Examples/Example1/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Example1
7 | {
8 | public static class Program
9 | {
10 | private static Server _server = null;
11 |
12 | private static void RunServer(String workspaceDirectory)
13 | {
14 | _server = new Server(workspaceDirectory);
15 |
16 | Task.Factory.StartNew(() =>
17 | {
18 | _server.Start();
19 | });
20 |
21 | var req = (HttpWebRequest)WebRequest.Create(_server.BindingUri);
22 | HttpWebResponse resp = null;
23 |
24 | do
25 | {
26 | try
27 | {
28 | resp = (HttpWebResponse)req.GetResponse();
29 | }
30 | catch { }
31 | } while (resp != null && resp.StatusCode != HttpStatusCode.OK);
32 |
33 | }
34 |
35 | private static void RunClient(String destinationDirectory)
36 | {
37 | var client = new Client();
38 | client.Run(_server, destinationDirectory);
39 | }
40 |
41 | static void Main(string[] args)
42 | {
43 | var (workspaceDirectory, destinationDirectory) = Helpers.CreateEnvironment(4);
44 | RunServer(workspaceDirectory);
45 | RunClient(destinationDirectory);
46 | _server.Stop();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Src/Examples/Example1/Server.cs:
--------------------------------------------------------------------------------
1 | using ES.Fslog;
2 | using ES.Fslog.Loggers;
3 | using ES.Fslog.TextFormatters;
4 | using ES.Update;
5 | using ES.Update.Backend;
6 | using System;
7 | using System.IO;
8 | using System.Reflection;
9 |
10 | namespace Example1
11 | {
12 | public class Server
13 | {
14 | public Uri BindingUri { get; set; }
15 | public Byte[] PublicKey { get; set; }
16 | public String WorkspaceDirectory { get; set; }
17 |
18 | public Server(String workspaceDirectory)
19 | {
20 | var logProvider = new LogProvider();
21 | logProvider.AddLogger(new ConsoleLogger(LogLevel.Verbose, new ConsoleLogFormatter()));
22 |
23 | this.WorkspaceDirectory = workspaceDirectory;
24 | var (publicKey, privateKey) = CryptoUtility.GenerateKeys();
25 | this.PublicKey = publicKey;
26 |
27 | var rnd = new Random();
28 | this.BindingUri = new Uri(String.Format("http://127.0.0.1:{0}", rnd.Next(1025, 65534)));
29 | this.WebServer = new WebServer(this.BindingUri, this.WorkspaceDirectory, privateKey, logProvider);
30 | }
31 |
32 | public WebServer WebServer { get; set; }
33 |
34 | public void Start()
35 | {
36 | this.WebServer.Start();
37 | }
38 |
39 | public void Stop()
40 | {
41 | this.WebServer.Stop();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Src/Examples/Example1/paket.references:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/AuthenticatedWebServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using ES.Update;
7 | using ES.Update.Backend;
8 | using static Suave.Http;
9 |
10 | namespace Example3
11 | {
12 | public class AuthenticatedWebServer : WebServer
13 | {
14 | public static Byte[] PrivateKey = null;
15 | public static Byte[] PublicKey = null;
16 | public static Uri BindingUri = null;
17 | public static String Username = "admin";
18 | public static String Password = "password";
19 |
20 | static AuthenticatedWebServer()
21 | {
22 | var (publicKey, privateKey) = CryptoUtility.GenerateKeys();
23 | PrivateKey = privateKey;
24 | PublicKey = publicKey;
25 |
26 | var rnd = new Random();
27 | BindingUri = new Uri(String.Format("http://127.0.0.1:{0}", rnd.Next(1025, 65534)));
28 | }
29 |
30 | public AuthenticatedWebServer(String workspaceDirectory) :
31 | base(BindingUri, workspaceDirectory, PrivateKey)
32 | {
33 | // specify a prefix to use for the uri composition
34 | this.PathPrefix = "/myupdate";
35 | }
36 |
37 | public override Boolean Authenticate(HttpContext ctx)
38 | {
39 | if (ctx.request.method.IsPOST)
40 | {
41 | var formParameters = Encoding.UTF8.GetString(ctx.request.rawForm).Split('&');
42 | var username = String.Empty;
43 | var password = String.Empty;
44 |
45 | foreach(var parameter in formParameters)
46 | {
47 | var nameValue = parameter.Split('=');
48 | if (nameValue[0].Equals("Username", StringComparison.OrdinalIgnoreCase))
49 | {
50 | username = nameValue[1];
51 | }
52 | else if (nameValue[0].Equals("Password", StringComparison.OrdinalIgnoreCase))
53 | {
54 | password = nameValue[1];
55 | }
56 | }
57 |
58 | return
59 | username.Equals(AuthenticatedWebServer.Username, StringComparison.Ordinal)
60 | && password.Equals(AuthenticatedWebServer.Password, StringComparison.Ordinal);
61 | }
62 | else
63 | {
64 | return true;
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/Client.cs:
--------------------------------------------------------------------------------
1 | using ES.Update;
2 | using Example2;
3 | using System;
4 | using System.IO;
5 |
6 | namespace Example3
7 | {
8 | public class Client
9 | {
10 | public void Run(String destinationDirectory)
11 | {
12 | var myVersion = Helpers.GetCurrentVersion();
13 | var serverUri = new Uri(AuthenticatedWebServer.BindingUri, "myupdate/");
14 | var updater = new Updater(serverUri, "MyApplication", myVersion, destinationDirectory, AuthenticatedWebServer.PublicKey);
15 |
16 | // add username and password to the update request
17 | updater.AddParameter("username", AuthenticatedWebServer.Username);
18 | updater.AddParameter("password", AuthenticatedWebServer.Password);
19 |
20 | var latestVersion = updater.GetLatestVersion();
21 | Console.WriteLine("My version: {0}. Latest version: {1}", myVersion, latestVersion);
22 |
23 | if (latestVersion > myVersion)
24 | {
25 | // start update
26 | var updateResult = updater.Update(myVersion);
27 | if (updateResult.Success)
28 | {
29 | var fileContent = File.ReadAllText(Path.Combine(destinationDirectory, "folder", "file8.txt"));
30 | Console.WriteLine("Update installed correctly! {0}", fileContent);
31 | Helpers.SaveVersion(latestVersion);
32 | }
33 | else
34 | {
35 | Console.WriteLine("Error during installing updates: {0}", updateResult.Error);
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/Example3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}
8 | Exe
9 | Example3
10 | Example3
11 | v4.7
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {6176038c-5a8c-4117-81cd-5a936b0679ea}
58 | Example2
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ..\..\packages\FSharp.Core\lib\net45\FSharp.Core.dll
67 | True
68 | True
69 |
70 |
71 |
72 |
73 |
74 |
75 | ..\..\packages\FSharp.Core\lib\netstandard2.0\FSharp.Core.dll
76 | True
77 | True
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | ..\..\packages\Suave\lib\net461\Suave.dll
87 | True
88 | True
89 |
90 |
91 |
92 |
93 |
94 |
95 | ..\..\packages\Suave\lib\netstandard2.0\Suave.dll
96 | True
97 | True
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/Program.cs:
--------------------------------------------------------------------------------
1 | using Example2;
2 | using System;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Example3
7 | {
8 | class Program
9 | {
10 | private static AuthenticatedWebServer _server = null;
11 |
12 | private static void RunServer(String workspaceDirectory)
13 | {
14 | var wait = new ManualResetEventSlim();
15 | Task.Factory.StartNew(() =>
16 | {
17 | _server = new AuthenticatedWebServer(workspaceDirectory);
18 | wait.Set();
19 | _server.Start();
20 | });
21 | wait.Wait();
22 | Thread.Sleep(2000);
23 | }
24 |
25 | private static void RunClient(String destinationDirectory)
26 | {
27 | var client = new Client();
28 | client.Run(destinationDirectory);
29 | }
30 |
31 | static void Main(string[] args)
32 | {
33 | var (workspaceDirectory, destinationDirectory) = Helpers.CreateEnvironment(5);
34 | RunServer(workspaceDirectory);
35 | RunClient(destinationDirectory);
36 | _server.Stop();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Example3")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Example3")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("3cb4c2c6-2e3d-4dda-95bf-67cb19dc0f50")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Src/Examples/Example3/paket.references:
--------------------------------------------------------------------------------
1 | Suave
--------------------------------------------------------------------------------
/Src/Examples/Example4/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Src/Examples/Example4/Client.cs:
--------------------------------------------------------------------------------
1 | using ES.Update;
2 | using Example2;
3 | using System;
4 |
5 | namespace Example4
6 | {
7 | public class Client
8 | {
9 | public void Run(Server server, String destinationDirectory)
10 | {
11 | var myVersion = Helpers.GetCurrentVersion();
12 | var updater = new Updater(server.BindingUri, "MyApplication", myVersion, destinationDirectory, server.PublicKey);
13 |
14 | var latestVersion = updater.GetLatestVersion();
15 | Console.WriteLine("My version: {0}. Latest version: {1}", myVersion, latestVersion);
16 |
17 | if (latestVersion > myVersion)
18 | {
19 | // start update
20 | var updateResult = updater.Update(myVersion);
21 | if (updateResult.Success)
22 | {
23 | Console.WriteLine("Everything is fine the installer program is now running. After completation you should see in this directory a file named 'file8.txt' in directory 'folder'");
24 | Helpers.SaveVersion(latestVersion);
25 | }
26 | else
27 | {
28 | Console.WriteLine("Error during installing updates: {0}", updateResult.Error);
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Src/Examples/Example4/Example4.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}
8 | Exe
9 | Example4
10 | Example4
11 | v4.7
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {6176038c-5a8c-4117-81cd-5a936b0679ea}
56 | Example2
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Src/Examples/Example4/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Example2;
8 |
9 | namespace Example4
10 | {
11 | public static class Program
12 | {
13 | private static Server _server = null;
14 |
15 | private static void RunServer(String workspaceDirectory)
16 | {
17 | var wait = new ManualResetEventSlim();
18 | Task.Factory.StartNew(() =>
19 | {
20 | _server = new Server(workspaceDirectory);
21 |
22 | // set the installer path
23 | var installerPath = Path.GetDirectoryName(typeof(Installer.Program).Assembly.Location);
24 | _server.WebServer.InstallerPath = installerPath;
25 |
26 | wait.Set();
27 | _server.Start();
28 | });
29 | wait.Wait();
30 | Thread.Sleep(2000);
31 | }
32 |
33 | private static void RunClient(String destinationDirectory)
34 | {
35 | var client = new Client();
36 | client.Run(_server, destinationDirectory);
37 | }
38 |
39 | static void Main(string[] args)
40 | {
41 | var (workspaceDirectory, _) = Helpers.CreateEnvironment(6);
42 | RunServer(workspaceDirectory);
43 | RunClient(Directory.GetCurrentDirectory());
44 | _server.Stop();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Src/Examples/Example4/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Example4")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Example4")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("85ec4f92-a7cd-49f2-ab2d-57b9cbd1adbf")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Src/ProgramUpdaterSln.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F1C186E9-2666-448C-B634-02D208C8C68A}"
7 | ProjectSection(SolutionItems) = preProject
8 | build.fsx = build.fsx
9 | paket.dependencies = paket.dependencies
10 | EndProjectSection
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B20D108B-7063-4FC3-8227-4085E163DA78}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{C0083A5C-9720-4D88-B766-D466A6D33B2A}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example3", "Examples\Example3\Example3.csproj", "{3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example4", "Examples\Example4\Example4.csproj", "{85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{2231F5C6-089D-4C3A-AEB0-B9FDDA092EE4}"
21 | EndProject
22 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ES.Update", "ES.Update\ES.Update.fsproj", "{D08E89F2-7F4E-42DE-A775-95447764FC4A}"
23 | EndProject
24 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ES.Update.Backend", "ES.Update.Backend\ES.Update.Backend.fsproj", "{52FB72CD-AA4A-47F4-9B47-A8DDB2580426}"
25 | EndProject
26 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ES.Update.Releaser", "ES.Update.Releaser\ES.Update.Releaser.fsproj", "{1EA88AB4-F0F9-4568-B9D7-F929BE54282E}"
27 | EndProject
28 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "UnitTests", "Tests\UnitTests\UnitTests.fsproj", "{3D4E04B2-A735-413C-AE11-1607D9DDA51A}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example1", "Examples\Example1\Example1.csproj", "{FB2F41AA-8607-4197-9319-277CCEF553BF}"
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{298CDF37-C12D-4B00-B313-53E665F89BF4}"
33 | EndProject
34 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "KeysGenerator", "Tools\KeysGenerator\KeysGenerator.fsproj", "{C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4}"
35 | EndProject
36 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "VersionReleaser", "Tools\VersionReleaser\VersionReleaser.fsproj", "{67BE6366-B644-49DF-BB9F-D9DEE52097C4}"
37 | EndProject
38 | Global
39 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
40 | Debug|Any CPU = Debug|Any CPU
41 | Release|Any CPU = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
44 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {D08E89F2-7F4E-42DE-A775-95447764FC4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {D08E89F2-7F4E-42DE-A775-95447764FC4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {D08E89F2-7F4E-42DE-A775-95447764FC4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {D08E89F2-7F4E-42DE-A775-95447764FC4A}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {52FB72CD-AA4A-47F4-9B47-A8DDB2580426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {52FB72CD-AA4A-47F4-9B47-A8DDB2580426}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {52FB72CD-AA4A-47F4-9B47-A8DDB2580426}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {52FB72CD-AA4A-47F4-9B47-A8DDB2580426}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {1EA88AB4-F0F9-4568-B9D7-F929BE54282E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {1EA88AB4-F0F9-4568-B9D7-F929BE54282E}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {1EA88AB4-F0F9-4568-B9D7-F929BE54282E}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {1EA88AB4-F0F9-4568-B9D7-F929BE54282E}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {3D4E04B2-A735-413C-AE11-1607D9DDA51A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {3D4E04B2-A735-413C-AE11-1607D9DDA51A}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {3D4E04B2-A735-413C-AE11-1607D9DDA51A}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {3D4E04B2-A735-413C-AE11-1607D9DDA51A}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {FB2F41AA-8607-4197-9319-277CCEF553BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {FB2F41AA-8607-4197-9319-277CCEF553BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {FB2F41AA-8607-4197-9319-277CCEF553BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {FB2F41AA-8607-4197-9319-277CCEF553BF}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73 | {C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
74 | {C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4}.Release|Any CPU.Build.0 = Release|Any CPU
76 | {67BE6366-B644-49DF-BB9F-D9DEE52097C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
77 | {67BE6366-B644-49DF-BB9F-D9DEE52097C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
78 | {67BE6366-B644-49DF-BB9F-D9DEE52097C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
79 | {67BE6366-B644-49DF-BB9F-D9DEE52097C4}.Release|Any CPU.Build.0 = Release|Any CPU
80 | EndGlobalSection
81 | GlobalSection(SolutionProperties) = preSolution
82 | HideSolutionNode = FALSE
83 | EndGlobalSection
84 | GlobalSection(NestedProjects) = preSolution
85 | {3CB4C2C6-2E3D-4DDA-95BF-67CB19DC0F50} = {C0083A5C-9720-4D88-B766-D466A6D33B2A}
86 | {85EC4F92-A7CD-49F2-AB2D-57B9CBD1ADBF} = {C0083A5C-9720-4D88-B766-D466A6D33B2A}
87 | {D08E89F2-7F4E-42DE-A775-95447764FC4A} = {2231F5C6-089D-4C3A-AEB0-B9FDDA092EE4}
88 | {52FB72CD-AA4A-47F4-9B47-A8DDB2580426} = {2231F5C6-089D-4C3A-AEB0-B9FDDA092EE4}
89 | {1EA88AB4-F0F9-4568-B9D7-F929BE54282E} = {2231F5C6-089D-4C3A-AEB0-B9FDDA092EE4}
90 | {3D4E04B2-A735-413C-AE11-1607D9DDA51A} = {B20D108B-7063-4FC3-8227-4085E163DA78}
91 | {FB2F41AA-8607-4197-9319-277CCEF553BF} = {C0083A5C-9720-4D88-B766-D466A6D33B2A}
92 | {C72BCAF0-9DF8-4CAC-A912-01355BB4DDE4} = {298CDF37-C12D-4B00-B313-53E665F89BF4}
93 | {67BE6366-B644-49DF-BB9F-D9DEE52097C4} = {298CDF37-C12D-4B00-B313-53E665F89BF4}
94 | EndGlobalSection
95 | GlobalSection(ExtensibilityGlobals) = postSolution
96 | SolutionGuid = {A497458A-92F6-47AB-9C90-3103B990C6F6}
97 | EndGlobalSection
98 | EndGlobal
99 |
--------------------------------------------------------------------------------
/Src/Tests/UnitTests/BackendTests.fs:
--------------------------------------------------------------------------------
1 | namespace UnitTests
2 |
3 | open System
4 | open System.Reflection
5 | open System.Text
6 | open ES.Update
7 |
8 | module BackendTests =
9 | let private ``Encrypt and Decrypt exported key``() =
10 | let password = "testPassword"
11 | let privateKey =
12 | "This private key value will be used to test the export and import feature"
13 | |> Encoding.UTF8.GetBytes
14 | |> Convert.ToBase64String
15 |
16 | // export key
17 | let exportedKey = encryptExportedKey(password, privateKey)
18 |
19 | // import key
20 | let importedKey = decryptImportedKey(password, exportedKey)
21 |
22 | // verify
23 | let testResult = importedKey.Equals(privateKey, StringComparison.Ordinal)
24 | assert testResult
25 |
26 | let runAll() =
27 | ``Encrypt and Decrypt exported key``()
28 |
--------------------------------------------------------------------------------
/Src/Tests/UnitTests/CryptoUtilityTests.fs:
--------------------------------------------------------------------------------
1 | namespace UnitTests
2 |
3 | open System
4 | open System.Reflection
5 | open System.Text
6 | open ES.Update
7 | open System.Net.NetworkInformation
8 | open System.IO
9 |
10 | module CryptoUtilityTests =
11 | let private ``Sign data and verify``() =
12 | // generate signature
13 | let data = Encoding.UTF8.GetBytes("This buffer contains data that must be signed")
14 | let (publicKey, privateKey) = CryptoUtility.generateKeys()
15 | let signature = CryptoUtility.sign(data, privateKey)
16 |
17 | // verify signature
18 | let testResult = CryptoUtility.verifyData(data, signature, publicKey)
19 | assert testResult
20 |
21 | let private ``Sign data and verify a corrupted data``() =
22 | // generate signature
23 | let data = Encoding.UTF8.GetBytes("This buffer contains data that must be signed")
24 | let (publicKey, privateKey) = CryptoUtility.generateKeys()
25 | let signature = CryptoUtility.sign(data, privateKey)
26 |
27 | // verify signature
28 | data.[2] <- data.[2] ^^^ 0xA1uy
29 | let testResult = CryptoUtility.verifyData(data, signature, publicKey)
30 | assert(not testResult)
31 |
32 | let private ``Sign data and verify a corrupted signature``() =
33 | // generate signature
34 | let data = Encoding.UTF8.GetBytes("This buffer contains data that must be signed")
35 | let (publicKey, privateKey) = CryptoUtility.generateKeys()
36 | let signature = CryptoUtility.sign(data, privateKey)
37 |
38 | // verify signature
39 | signature.[2] <- signature.[2] ^^^ 0xA1uy
40 | let testResult = CryptoUtility.verifyData(data, signature, publicKey)
41 | assert(not testResult)
42 |
43 | let private ``Encrypt and decrypt data``() =
44 | let text = "This is the text that must be used in order to verify if the encryption and decryption work correctly"
45 | let key = getMacAddresses() |> CryptoUtility.sha256Raw
46 | let iv = getHardDiskSerials()
47 |
48 | // encrypt and decrypt
49 | let textBytes = Encoding.UTF8.GetBytes(text)
50 | let encryptedText = CryptoUtility.encrypt(textBytes, key, iv)
51 | let decryptedText = CryptoUtility.decrypt(encryptedText, key, iv) |> Encoding.UTF8.GetString
52 |
53 | // check
54 | let testResult = decryptedText.Equals(text, StringComparison.Ordinal)
55 | assert testResult
56 |
57 | let runAll() =
58 | ``Sign data and verify``()
59 | ``Sign data and verify a corrupted data``()
60 | ``Sign data and verify a corrupted signature``()
61 | ``Encrypt and decrypt data``()
62 |
--------------------------------------------------------------------------------
/Src/Tests/UnitTests/Program.fs:
--------------------------------------------------------------------------------
1 | namespace UnitTests
2 |
3 | module Program =
4 |
5 | []
6 | let main argv =
7 | CryptoUtilityTests.runAll()
8 | BackendTests.runAll()
9 | 0
10 |
--------------------------------------------------------------------------------
/Src/Tests/UnitTests/UnitTests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Src/Tools/KeysGenerator/KeysGenerator.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Src/Tools/KeysGenerator/Program.fs:
--------------------------------------------------------------------------------
1 | namespace Installer
2 |
3 | open System
4 | open Argu
5 | open ES.Update
6 | open System.Diagnostics
7 | open System.Threading
8 | open System.Text
9 | open System.Text.RegularExpressions
10 | open ES.Fslog
11 |
12 | module Program =
13 | open System.IO
14 |
15 | type CLIArguments =
16 | | Public of path:String
17 | | Private of path:String
18 | with
19 | interface IArgParserTemplate with
20 | member s.Usage =
21 | match s with
22 | | Public _ -> "if specified, the file where to save the public key."
23 | | Private _ -> "if specified, the file where to save the private key."
24 |
25 | let printColor(msg: String, color: ConsoleColor) =
26 | Console.ForegroundColor <- color
27 | Console.WriteLine(msg)
28 | Console.ResetColor()
29 |
30 | let printError(errorMsg: String) =
31 | printColor(errorMsg, ConsoleColor.Red)
32 |
33 | let printBanner() =
34 | Console.ForegroundColor <- ConsoleColor.Cyan
35 | let banner = "-=[ Keys Generator ]=-"
36 | let year = if DateTime.Now.Year = 2019 then "2019" else String.Format("2019-{0}", DateTime.Now.Year)
37 | let copy = String.Format("Copyright (c) {0} Enkomio {1}", year, Environment.NewLine)
38 | Console.WriteLine(banner)
39 | Console.WriteLine(copy)
40 | Console.ResetColor()
41 |
42 | let printUsage(body: String) =
43 | Console.WriteLine(body)
44 |
45 | []
46 | let main argv =
47 | printBanner()
48 |
49 | let parser = ArgumentParser.Create()
50 | try
51 | let results = parser.Parse(argv)
52 |
53 | if results.IsUsageRequested then
54 | printUsage(parser.PrintUsage())
55 | 0
56 | else
57 | let (publicKeyBytes, privateKeyBytes) = CryptoUtility.generateKeys()
58 | let (publicKey, privateKey) = (publicKeyBytes |> Convert.ToBase64String, privateKeyBytes |> Convert.ToBase64String)
59 |
60 | Console.WriteLine("Public key: " + publicKey)
61 | Console.WriteLine()
62 | Console.WriteLine("Private key: " + privateKey)
63 | Console.WriteLine()
64 |
65 | results.TryGetResult(<@ Public @>)
66 | |> Option.iter(fun file ->
67 | File.WriteAllText(file, publicKey)
68 | Console.WriteLine("Public key saved to: " + file)
69 | )
70 |
71 | results.TryGetResult(<@ Public @>)
72 | |> Option.iter(fun file ->
73 | File.WriteAllText(file, privateKey)
74 | Console.WriteLine("Private key saved to: " + file)
75 | )
76 | 0
77 | with
78 | | :? ArguParseException ->
79 | printUsage(parser.PrintUsage())
80 | 1
81 | | e ->
82 | printError(e.ToString())
83 | 1
84 |
--------------------------------------------------------------------------------
/Src/Tools/KeysGenerator/paket.references:
--------------------------------------------------------------------------------
1 | Argu
2 | ES.Fslog.Core
--------------------------------------------------------------------------------
/Src/Tools/VersionReleaser/Program.fs:
--------------------------------------------------------------------------------
1 | namespace VersionReleaser
2 |
3 | open System
4 | open System.IO
5 | open System.Reflection
6 | open Argu
7 | open ES.Fslog
8 | open ES.Fslog.Loggers
9 | open ES.Fslog.TextFormatters
10 | open ES.Update.Releaser
11 |
12 | module Program =
13 | type CLIArguments =
14 | | [] File of file:String
15 | | Working_Dir of path:String
16 | with
17 | interface IArgParserTemplate with
18 | member s.Usage =
19 | match s with
20 | | File _ -> "the release file to analyze in order to generate the metadata."
21 | | Working_Dir _ -> "the directory where the update artifacts will be saved."
22 |
23 | let printColor(msg: String, color: ConsoleColor) =
24 | Console.ForegroundColor <- color
25 | Console.WriteLine(msg)
26 | Console.ResetColor()
27 |
28 | let printError(errorMsg: String) =
29 | printColor(errorMsg, ConsoleColor.Red)
30 |
31 | let printBanner() =
32 | Console.ForegroundColor <- ConsoleColor.Cyan
33 | let banner = "-=[ Version Releaser ]=-"
34 | let year = if DateTime.Now.Year = 2019 then "2019" else String.Format("2019-{0}", DateTime.Now.Year)
35 | let copy = String.Format("Copyright (c) {0} Enkomio {1}", year, Environment.NewLine)
36 | Console.WriteLine(banner)
37 | Console.WriteLine(copy)
38 | Console.ResetColor()
39 |
40 | let createLogProvider() =
41 | let logProvider = new LogProvider()
42 | let logFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "version-releaser.log")
43 | logProvider.AddLogger(new ConsoleLogger(LogLevel.Informational, new ConsoleLogFormatter()))
44 | logProvider.AddLogger(new FileLogger(LogLevel.Informational, logFile))
45 | logProvider
46 |
47 | let printUsage(body: String) =
48 | Console.WriteLine(body)
49 |
50 | []
51 | let main argv =
52 | printBanner()
53 |
54 | let parser = ArgumentParser.Create()
55 | try
56 | let results = parser.Parse(argv)
57 |
58 | if results.IsUsageRequested then
59 | printUsage(parser.PrintUsage())
60 | 0
61 | else
62 | let workingDir = results.GetResult(<@ Working_Dir @>, Settings.Read().WorkspaceDirectory)
63 | Directory.CreateDirectory(workingDir) |> ignore
64 | let filename = results.GetResult(<@ File @>)
65 | let metadataBuilder = new MetadataBuilder(workingDir, Settings.Read().PatternToExclude, createLogProvider())
66 | metadataBuilder.CreateReleaseMetadata(filename)
67 | 0
68 | with
69 | | :? ArguParseException ->
70 | printUsage(parser.PrintUsage())
71 | 1
72 | | e ->
73 | printError(e.ToString())
74 | 1
75 |
--------------------------------------------------------------------------------
/Src/Tools/VersionReleaser/Settings.fs:
--------------------------------------------------------------------------------
1 | namespace VersionReleaser
2 |
3 | open System
4 | open System.Collections.Generic
5 | open System.IO
6 | open Newtonsoft.Json
7 | open System.Reflection
8 |
9 | type Settings() =
10 | member val WorkspaceDirectory = String.Empty with get, set
11 | member val PatternToExclude = new List() with get, set
12 |
13 | static member Read() =
14 | let configFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "configuration.json")
15 | if File.Exists(configFile) then
16 | (File.ReadAllText >> JsonConvert.DeserializeObject)(configFile)
17 | else
18 | new Settings()
--------------------------------------------------------------------------------
/Src/Tools/VersionReleaser/VersionReleaser.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 | Always
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Src/Tools/VersionReleaser/configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "WorkspaceDirectory": "updates",
3 | "PatternToExclude": [
4 | "\\.lic$",
5 | "\\.log$"
6 | ]
7 | }
--------------------------------------------------------------------------------
/Src/Tools/VersionReleaser/paket.references:
--------------------------------------------------------------------------------
1 | ES.Fslog.Core
2 | Argu
3 | Newtonsoft.Json
--------------------------------------------------------------------------------
/Src/build.fsx:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------
2 | // FAKE build script
3 | // --------------------------------------------------------------------------------------
4 | #r "paket: groupref FakeBuild //"
5 | #load ".fake/build.fsx/intellisense.fsx"
6 |
7 | #r @"System.IO.Compression"
8 | #r @"System.IO.Compression.FileSystem"
9 |
10 | open System
11 | open System.Diagnostics
12 | open System.IO
13 | open Fake
14 | open Fake.Core.TargetOperators
15 | open Fake.DotNet
16 | open Fake.Core
17 | open Fake.IO
18 |
19 | // the project file name
20 | let projectFileName = "ProgramUpdater"
21 |
22 | // The name of the project
23 | let project = "Program Updater Framework"
24 |
25 | // Short summary of the project
26 | let summary = "A framework to automatize the process of updating a program in an efficent and secure way."
27 |
28 | // List of author names
29 | let authors = "Enkomio"
30 |
31 | // Build dir
32 | let buildDir = "build"
33 |
34 | // Release dir
35 | let releaseDir = "release"
36 |
37 | // Extension to not include in release
38 | let forbiddenExtensions = [".pdb"]
39 |
40 | // Projecy Guid
41 | let projectGuid = "0F026EA5-501A-4947-B8E2-5860D3520E99"
42 |
43 | // F# project names
44 | let fsharpProjects = [
45 | "ES.Update"
46 | "ES.Update.Backend"
47 | "ES.Update.Releaser"
48 | "Installer"
49 | "Updater"
50 | "UpdateServer"
51 | "VersionReleaser"
52 | ]
53 |
54 | // C# project names
55 | let csharpProjects = [
56 | ]
57 |
58 | //////////////////////////////////////////////////////////
59 | // All code below should be generic enought //
60 | // to not be modified in order to build the solution //
61 | //////////////////////////////////////////////////////////
62 |
63 | // set the script dir as current
64 | Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__)
65 |
66 | // Read additional information from the release notes document
67 | let releaseNotesData =
68 | let changelogFile = Path.Combine("..", "RELEASE_NOTES.md")
69 | File.ReadAllLines(changelogFile)
70 | |> ReleaseNotes.parse
71 |
72 | let releaseNoteVersion = Version.Parse(releaseNotesData.AssemblyVersion)
73 | let now = DateTime.UtcNow
74 | let timeSpan = now.Subtract(new DateTime(1980,2,1,0,0,0))
75 | let months = timeSpan.TotalDays / 30. |> int32
76 | let remaining = int32 timeSpan.TotalDays - months * 30
77 | let releaseVersion = string <| new Version(releaseNoteVersion.Major, releaseNoteVersion.Minor, months, remaining)
78 | Trace.trace("Build Version: " + releaseVersion)
79 |
80 | // Targets
81 | Core.Target.create "Clean" (fun _ ->
82 | Fake.IO.Shell.cleanDirs [buildDir; releaseDir]
83 | )
84 |
85 | Core.Target.create "SetAssemblyInfo" (fun _ ->
86 | fsharpProjects
87 | |> List.iter(fun projName ->
88 | let fileName = Path.Combine(projName, "AssemblyInfo.fs")
89 | AssemblyInfoFile.createFSharp fileName [
90 | DotNet.AssemblyInfo.Title project
91 | DotNet.AssemblyInfo.Product project
92 | DotNet.AssemblyInfo.Guid projectGuid
93 | DotNet.AssemblyInfo.Company authors
94 | DotNet.AssemblyInfo.Description summary
95 | DotNet.AssemblyInfo.Version releaseVersion
96 | DotNet.AssemblyInfo.FileVersion releaseVersion
97 | DotNet.AssemblyInfo.InformationalVersion releaseVersion
98 | DotNet.AssemblyInfo.Metadata("BuildDate", DateTime.UtcNow.ToString("yyyy-MM-dd"))
99 | ]
100 | )
101 |
102 | csharpProjects
103 | |> List.iter(fun projName ->
104 | let fileName = Path.Combine(projName, "AssemblyInfo.cs")
105 | AssemblyInfoFile.createCSharp fileName [
106 | DotNet.AssemblyInfo.Title project
107 | DotNet.AssemblyInfo.Product project
108 | DotNet.AssemblyInfo.Guid projectGuid
109 | DotNet.AssemblyInfo.Company authors
110 | DotNet.AssemblyInfo.Description summary
111 | DotNet.AssemblyInfo.Version releaseVersion
112 | DotNet.AssemblyInfo.FileVersion releaseVersion
113 | DotNet.AssemblyInfo.InformationalVersion releaseVersion
114 | DotNet.AssemblyInfo.Metadata("BuildDate", DateTime.UtcNow.ToString("yyyy-MM-dd"))
115 | ]
116 | )
117 | )
118 |
119 | Core.Target.create "Compile" (fun _ ->
120 | fsharpProjects
121 | |> List.iter(fun projectName ->
122 | let project = Path.Combine(projectName, projectName + ".fsproj")
123 | let buildAppDir = Path.Combine(buildDir, projectName)
124 | Fake.IO.Directory.ensure buildAppDir
125 |
126 | // compile
127 | DotNet.MSBuild.runRelease id buildAppDir "Build" [project]
128 | |> Trace.logItems "Build Output: "
129 | )
130 |
131 | csharpProjects
132 | |> List.iter(fun projectName ->
133 | let project = Path.Combine(projectName, projectName + ".csproj")
134 | let buildAppDir = Path.Combine(buildDir, projectName)
135 | Fake.IO.Directory.ensure buildAppDir
136 |
137 | // compile
138 | DotNet.MSBuild.runRelease id buildAppDir "Build" [project]
139 | |> Trace.logItems "Build Output: "
140 | )
141 | )
142 |
143 | Core.Target.create "CleanBuild" (fun _ ->
144 | Directory.GetFiles(buildDir, "*.*", SearchOption.AllDirectories)
145 | |> Array.filter(fun file ->
146 | forbiddenExtensions
147 | |> List.contains (Path.GetExtension(file).ToLowerInvariant())
148 | )
149 | |> Array.iter(File.Delete)
150 | )
151 |
152 | Core.Target.create "MergeInstaller" (fun _ ->
153 | let ilMerge = Path.Combine("Src", "packages", "ilmerge", "tools", "net452", "ILMerge.exe")
154 |
155 | [("Installer", "Installer.exe")]
156 | |> List.iter(fun (projectName, mainExecutable) ->
157 | let projectDir = Path.Combine(buildDir, projectName)
158 | let mainExe = Path.Combine(projectDir, mainExecutable)
159 | let allFiles = Directory.GetFiles(projectDir, "*.dll", SearchOption.AllDirectories) |> List.ofArray
160 | let arguments = String.Join(" ", mainExe::allFiles)
161 | Process.Start(ilMerge, arguments).WaitForExit()
162 | )
163 | )
164 |
165 | Core.Target.create "Release" (fun _ ->
166 | let releaseDirectory = Path.Combine(releaseDir, String.Format("{0}.v{1}", projectFileName, releaseVersion))
167 | Directory.CreateDirectory(releaseDirectory) |> ignore
168 |
169 | // copy all binaries in Bin directory
170 | fsharpProjects@csharpProjects
171 | |> List.iter(fun projName ->
172 | let buildProjectDir = Path.Combine(buildDir, projName)
173 | Shell.copyDir releaseDirectory buildProjectDir (fun _ -> true)
174 | )
175 |
176 | // create zip file
177 | let releaseFilename = releaseDirectory + ".zip"
178 | Directory.GetFiles(releaseDirectory, "*.*", SearchOption.AllDirectories)
179 | |> Fake.IO.Zip.zip releaseDirectory releaseFilename
180 | )
181 |
182 | "Clean"
183 | ==> "SetAssemblyInfo"
184 | ==> "Compile"
185 | ==> "CleanBuild"
186 | ==> "MergeInstaller"
187 | ==> "Release"
188 |
189 | // Start build
190 | Core.Target.runOrDefault "Release"
--------------------------------------------------------------------------------
/Src/mergeInstaller.fsx:
--------------------------------------------------------------------------------
1 | open System
2 | open System.IO
3 | open System.Diagnostics
4 |
5 | let ilMerge = Path.Combine("packages", "ilmerge", "tools", "net452", "ILMerge.exe")
6 |
7 | [
8 | (Path.Combine("Installer", "bin", "Release"), "Installer.exe")
9 | (Path.Combine("Installer.Core", "bin", "Release", "netcoreapp3.1"), "Installer.exe")
10 | ]
11 | |> List.iter(fun (projectDir, mainExecutable) ->
12 | let mainExe = Path.Combine(projectDir, mainExecutable)
13 | let allFiles =
14 | Directory.GetFiles(projectDir, "*.dll", SearchOption.AllDirectories)
15 | |> List.ofArray
16 | |> List.map(Path.GetFullPath)
17 |
18 | let arguments = String.Join(" ", mainExe::allFiles)
19 | Console.WriteLine("Cmd: {0} {1}", ilMerge, arguments)
20 | Process.Start(ilMerge, arguments).WaitForExit()
21 | )
--------------------------------------------------------------------------------
/Src/nuget.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enkomio/ProgramUpdater/fdec3aa7f71e96a1e0ac1468c39a27d291058e93/Src/nuget.exe
--------------------------------------------------------------------------------
/Src/paket.dependencies:
--------------------------------------------------------------------------------
1 | version 5.206.0
2 | source https://nuget.org/api/v2
3 |
4 | nuget FSharp.Core
5 | nuget Suave
6 | nuget Newtonsoft.Json
7 | nuget Argu
8 | nuget BouncyCastle
9 | nuget ES.Fslog.Core
10 | nuget ilmerge ~> 3.0.29
11 |
12 | group FakeBuild
13 | source https://api.nuget.org/v3/index.json
14 | nuget Fake.Core.Target
15 | nuget Fake.IO.FileSystem
16 | nuget Fake.DotNet.MSBuild
17 | nuget Fake.Core.ReleaseNotes
18 | nuget Fake.DotNet.AssemblyInfoFile
19 | nuget Fake.Core.Trace
20 | nuget Fake.IO.Zip
21 | nuget Fake.Core.Process
--------------------------------------------------------------------------------
/Src/paket.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enkomio/ProgramUpdater/fdec3aa7f71e96a1e0ac1468c39a27d291058e93/Src/paket.exe
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cls
3 |
4 | @rem Move to src directory
5 | cd Src
6 |
7 | @rem install fake
8 | dotnet tool install fake-cli --tool-path fake
9 |
10 | @rem paket.exe install
11 | paket.exe install
12 | if errorlevel 1 (
13 | exit /b %errorlevel%
14 | )
15 |
16 | ".\fake\fake.exe" run build.fsx %*
17 |
18 | cd ..
--------------------------------------------------------------------------------