Compare commits
	
		
			228 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d11b45c409 | |||
| 36abf769be | |||
| c93ecf307b | |||
| 3ea7d3e97b | |||
| fff5192fb4 | |||
| fceeb8c72c | |||
| a7526ffad8 | |||
| d324179cb1 | |||
| 18b36d7d23 | |||
| 7a2de022fa | |||
| e478d27628 | |||
|  | 6f2c1ec5ba | ||
| 894a01fa4e | |||
| df1259cd9d | |||
| 3f8a54a988 | |||
| 26adaf2037 | |||
| 2a9b463964 | |||
| bc4a5b44ae | |||
|  | 378b9310aa | ||
|  | 405e98620c | ||
| 3f437c6ebb | |||
| 00e9d96f8b | |||
| 31ba1186be | |||
| af7c75a0f7 | |||
| 8d464d6810 | |||
| de051dd3a2 | |||
| d14163d153 | |||
| 6df0dc2f76 | |||
| 0dd3641dc2 | |||
| 9ab7844ea8 | |||
| afac2b01a6 | |||
| 0dcefa77d8 | |||
| c9e62ccb05 | |||
| 8732029eb1 | |||
| f3bae8580b | |||
|  | ada2b9ccfe | ||
| 4d400a9828 | |||
| 0f34e81d8a | |||
|  | f539de2611 | ||
| 8f369226cf | |||
| 44ff0ac5df | |||
| fbe7549604 | |||
| 6cdbe36e6c | |||
| 778416d49b | |||
| c73ad565a3 | |||
| a49ccb7398 | |||
|  | 8ba4b88e00 | ||
| 78fcebd567 | |||
| 1843f03d87 | |||
| 81a63b365d | |||
| c187bd0fb7 | |||
| bcc1c16ec2 | |||
| fec660bfeb | |||
| 1b42d866fb | |||
| 579709ae40 | |||
| c7dfec515d | |||
| 0d2d571458 | |||
| b71311b1bc | |||
| f6cc67ff53 | |||
| feeafc5c97 | |||
| 3634965e70 | |||
| 74b3419507 | |||
| a6f4de6c78 | |||
| 63b5182b38 | |||
| 98ca31256b | |||
| e83f92166d | |||
| 9ae6a768a7 | |||
| ffbcc433b3 | |||
| f088d0326d | |||
| 8c5e5435fc | |||
| cda9bfa418 | |||
| 16e6a766a6 | |||
| d589cc3a11 | |||
| 12058026ad | |||
| d4365bf9d0 | |||
| f722d9917c | |||
| f9d3ec5f76 | |||
| 7bcd7a2bf7 | |||
| 779ab234ac | |||
| b17805d1fb | |||
| cb1c2ce438 | |||
| 6b359a7cff | |||
| 05e01ce947 | |||
| df9a27ec20 | |||
| 8f6aa1cc8c | |||
| 7a12ffaef0 | |||
| da89714865 | |||
| 0476905a5b | |||
| d566cfc9f1 | |||
| efd7693dcd | |||
| ca2e5cdb99 | |||
| bc4da32f15 | |||
| 8c25386767 | |||
| 39d9ae4f31 | |||
| cfbaab0a27 | |||
| 8c0d6c718d | |||
| 4823a4d464 | |||
| b6bdca552b | |||
| 0c23c522a3 | |||
| af0b98ac14 | |||
| a7f1dea40b | |||
| 872962a8dd | |||
| b4d72bbd13 | |||
| 1dae4248c3 | |||
| 38de3e9d1c | |||
| 6f960b1a2a | |||
| bfaccdf725 | |||
| 0285f9b40f | |||
| aac54d63f2 | |||
| 11e0db1f20 | |||
| 8eaf269143 | |||
| f2dfbfac14 | |||
| 63e2d20f1c | |||
| 60917e611e | |||
| 61bd56f94d | |||
| 261eff2700 | |||
| 648ed4e4d5 | |||
| e785d9199d | |||
| a48c10e082 | |||
| c3d496531b | |||
| 8c8dde0a7d | |||
| 901c8659cf | |||
| 20de40cd1d | |||
| a06095f1e0 | |||
| 973be987da | |||
| bd5efaab3b | |||
| bbf8813355 | |||
| bf77d309af | |||
| 4c4ca98157 | |||
| 26a3631779 | |||
| def4f6fcb9 | |||
| 694caf3e72 | |||
| 8995edd620 | |||
| 0f195aed95 | |||
| 2494bca6cc | |||
|  | 16e1158705 | ||
|  | eb996e0cf4 | ||
|  | 0ee94d94ec | ||
|  | 4744f4050e | ||
|  | 6953068a7b | ||
| 4e9db5781a | |||
| caf804cc41 | |||
| ef78971ba5 | |||
| b5c47c8d7c | |||
| 3ea55fca5b | |||
| e976a410bf | |||
| 2d688f8551 | |||
| 0b2637b8e7 | |||
| d5e0abf0f8 | |||
| ff58ff6eb6 | |||
| a36cdd83c5 | |||
| 462f0bfc2c | |||
| 4498342005 | |||
| 32e57aa9cb | |||
| 238e262f95 | |||
| 8d89454c0a | |||
| a20f91661f | |||
|  | 2dab010be3 | ||
|  | d61955e6b6 | ||
|  | 53321d219a | ||
| 6d2d02b1d6 | |||
| 6c5813d86d | |||
| e192c4af11 | |||
| 48d990a6e8 | |||
| 57806ef06f | |||
| 27085a7f64 | |||
| 8cd7648df6 | |||
|  | 5eab460ec2 | ||
|  | 7f340412d7 | ||
|  | d9c6a77bfc | ||
|  | 89e0ddaecf | ||
|  | 3b27aa6806 | ||
| 98f51392f4 | |||
|  | 4d8b3b6859 | ||
|  | 377b9efb30 | ||
| 9f65da895f | |||
|  | 2ba8ba85a5 | ||
|  | 88c9ab9357 | ||
|  | ab6635cd4d | ||
| 24bbc24d90 | |||
| 2ffc76e242 | |||
| 40321b1c59 | |||
| 061ca7cb0a | |||
| 3a1d6ce9ae | |||
| a0bf0596ed | |||
| f23b5fa2eb | |||
| 85c8fdc0d9 | |||
|  | 26da6c84cc | ||
|  | 6eae454bed | ||
|  | d2aa9394aa | ||
|  | ab21671481 | ||
| 7786acc7a3 | |||
| 520a5676b5 | |||
| dfabdd15dd | |||
| b530696ed2 | |||
| 329ab128fd | |||
| 820ab2f2c5 | |||
| 769fe3008c | |||
| eaf0748871 | |||
| ec53f781e8 | |||
| 269b7c596f | |||
| 27ff2ef53f | |||
| 596ae53dbb | |||
| 456e47a9ac | |||
| 4b38afe581 | |||
| 47d1c85c0f | |||
| 59043f8ebd | |||
| 87cfc84dfa | |||
| 736863448d | |||
| 7f18123af8 | |||
| 52545f1530 | |||
| c50a03ea35 | |||
| 1cd04d9f64 | |||
| 06c9ec31b9 | |||
| 2aef5f838d | |||
| 7247211cdd | |||
|  | a9c4944dee | ||
|  | 4ea9115647 | ||
|  | bd05ba77b6 | ||
|  | 5fe75b8484 | ||
|  | 6abffa0620 | ||
|  | c8adbf331b | ||
|  | 5047d9aa96 | ||
|  | 745d635881 | ||
|  | 25c5def46e | ||
|  | 19cf4536b4 | ||
|  | a3de9c6cf7 | ||
|  | 901d3b4a1a | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -2,6 +2,7 @@ | |||||||
| logs | logs | ||||||
| *.log | *.log | ||||||
| npm-debug.log* | npm-debug.log* | ||||||
|  | .vscode | ||||||
| 
 | 
 | ||||||
| # Runtime data | # Runtime data | ||||||
| pids | pids | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | { | ||||||
|  |   "bracketSpacing": true, | ||||||
|  |   "printWidth": 120, | ||||||
|  |   "tabWidth": 2, | ||||||
|  |   "trailingComma": "none", | ||||||
|  |   "useTabs": true | ||||||
|  | } | ||||||
							
								
								
									
										388
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										388
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,21 +1,375 @@ | |||||||
| MIT License | Copyright 2015-2019 AJ ONeal | ||||||
| 
 | 
 | ||||||
| Copyright (c) 2016 Daplie, Inc | Mozilla Public License Version 2.0 | ||||||
|  | ================================== | ||||||
| 
 | 
 | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | 1. Definitions | ||||||
| of this software and associated documentation files (the "Software"), to deal | -------------- | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
| 
 | 
 | ||||||
| The above copyright notice and this permission notice shall be included in all | 1.1. "Contributor" | ||||||
| copies or substantial portions of the Software. |     means each individual or legal entity that creates, contributes to | ||||||
|  |     the creation of, or owns Covered Software. | ||||||
| 
 | 
 | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 1.2. "Contributor Version" | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |     means the combination of the Contributions of others (if any) used | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |     by a Contributor and that particular Contributor's Contribution. | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
 | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 1.3. "Contribution" | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |     means Covered Software of a particular Contributor. | ||||||
| SOFTWARE. | 
 | ||||||
|  | 1.4. "Covered Software" | ||||||
|  |     means Source Code Form to which the initial Contributor has attached | ||||||
|  |     the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|  |     Form, and Modifications of such Source Code Form, in each case | ||||||
|  |     including portions thereof. | ||||||
|  | 
 | ||||||
|  | 1.5. "Incompatible With Secondary Licenses" | ||||||
|  |     means | ||||||
|  | 
 | ||||||
|  |     (a) that the initial Contributor has attached the notice described | ||||||
|  |         in Exhibit B to the Covered Software; or | ||||||
|  | 
 | ||||||
|  |     (b) that the Covered Software was made available under the terms of | ||||||
|  |         version 1.1 or earlier of the License, but not also under the | ||||||
|  |         terms of a Secondary License. | ||||||
|  | 
 | ||||||
|  | 1.6. "Executable Form" | ||||||
|  |     means any form of the work other than Source Code Form. | ||||||
|  | 
 | ||||||
|  | 1.7. "Larger Work" | ||||||
|  |     means a work that combines Covered Software with other material, in | ||||||
|  |     a separate file or files, that is not Covered Software. | ||||||
|  | 
 | ||||||
|  | 1.8. "License" | ||||||
|  |     means this document. | ||||||
|  | 
 | ||||||
|  | 1.9. "Licensable" | ||||||
|  |     means having the right to grant, to the maximum extent possible, | ||||||
|  |     whether at the time of the initial grant or subsequently, any and | ||||||
|  |     all of the rights conveyed by this License. | ||||||
|  | 
 | ||||||
|  | 1.10. "Modifications" | ||||||
|  |     means any of the following: | ||||||
|  | 
 | ||||||
|  |     (a) any file in Source Code Form that results from an addition to, | ||||||
|  |         deletion from, or modification of the contents of Covered | ||||||
|  |         Software; or | ||||||
|  | 
 | ||||||
|  |     (b) any new file in Source Code Form that contains any Covered | ||||||
|  |         Software. | ||||||
|  | 
 | ||||||
|  | 1.11. "Patent Claims" of a Contributor | ||||||
|  |     means any patent claim(s), including without limitation, method, | ||||||
|  |     process, and apparatus claims, in any patent Licensable by such | ||||||
|  |     Contributor that would be infringed, but for the grant of the | ||||||
|  |     License, by the making, using, selling, offering for sale, having | ||||||
|  |     made, import, or transfer of either its Contributions or its | ||||||
|  |     Contributor Version. | ||||||
|  | 
 | ||||||
|  | 1.12. "Secondary License" | ||||||
|  |     means either the GNU General Public License, Version 2.0, the GNU | ||||||
|  |     Lesser General Public License, Version 2.1, the GNU Affero General | ||||||
|  |     Public License, Version 3.0, or any later versions of those | ||||||
|  |     licenses. | ||||||
|  | 
 | ||||||
|  | 1.13. "Source Code Form" | ||||||
|  |     means the form of the work preferred for making modifications. | ||||||
|  | 
 | ||||||
|  | 1.14. "You" (or "Your") | ||||||
|  |     means an individual or a legal entity exercising rights under this | ||||||
|  |     License. For legal entities, "You" includes any entity that | ||||||
|  |     controls, is controlled by, or is under common control with You. For | ||||||
|  |     purposes of this definition, "control" means (a) the power, direct | ||||||
|  |     or indirect, to cause the direction or management of such entity, | ||||||
|  |     whether by contract or otherwise, or (b) ownership of more than | ||||||
|  |     fifty percent (50%) of the outstanding shares or beneficial | ||||||
|  |     ownership of such entity. | ||||||
|  | 
 | ||||||
|  | 2. License Grants and Conditions | ||||||
|  | -------------------------------- | ||||||
|  | 
 | ||||||
|  | 2.1. Grants | ||||||
|  | 
 | ||||||
|  | Each Contributor hereby grants You a world-wide, royalty-free, | ||||||
|  | non-exclusive license: | ||||||
|  | 
 | ||||||
|  | (a) under intellectual property rights (other than patent or trademark) | ||||||
|  |     Licensable by such Contributor to use, reproduce, make available, | ||||||
|  |     modify, display, perform, distribute, and otherwise exploit its | ||||||
|  |     Contributions, either on an unmodified basis, with Modifications, or | ||||||
|  |     as part of a Larger Work; and | ||||||
|  | 
 | ||||||
|  | (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||||
|  |     for sale, have made, import, and otherwise transfer either its | ||||||
|  |     Contributions or its Contributor Version. | ||||||
|  | 
 | ||||||
|  | 2.2. Effective Date | ||||||
|  | 
 | ||||||
|  | The licenses granted in Section 2.1 with respect to any Contribution | ||||||
|  | become effective for each Contribution on the date the Contributor first | ||||||
|  | distributes such Contribution. | ||||||
|  | 
 | ||||||
|  | 2.3. Limitations on Grant Scope | ||||||
|  | 
 | ||||||
|  | The licenses granted in this Section 2 are the only rights granted under | ||||||
|  | this License. No additional rights or licenses will be implied from the | ||||||
|  | distribution or licensing of Covered Software under this License. | ||||||
|  | Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||||
|  | Contributor: | ||||||
|  | 
 | ||||||
|  | (a) for any code that a Contributor has removed from Covered Software; | ||||||
|  |     or | ||||||
|  | 
 | ||||||
|  | (b) for infringements caused by: (i) Your and any other third party's | ||||||
|  |     modifications of Covered Software, or (ii) the combination of its | ||||||
|  |     Contributions with other software (except as part of its Contributor | ||||||
|  |     Version); or | ||||||
|  | 
 | ||||||
|  | (c) under Patent Claims infringed by Covered Software in the absence of | ||||||
|  |     its Contributions. | ||||||
|  | 
 | ||||||
|  | This License does not grant any rights in the trademarks, service marks, | ||||||
|  | or logos of any Contributor (except as may be necessary to comply with | ||||||
|  | the notice requirements in Section 3.4). | ||||||
|  | 
 | ||||||
|  | 2.4. Subsequent Licenses | ||||||
|  | 
 | ||||||
|  | No Contributor makes additional grants as a result of Your choice to | ||||||
|  | distribute the Covered Software under a subsequent version of this | ||||||
|  | License (see Section 10.2) or under the terms of a Secondary License (if | ||||||
|  | permitted under the terms of Section 3.3). | ||||||
|  | 
 | ||||||
|  | 2.5. Representation | ||||||
|  | 
 | ||||||
|  | Each Contributor represents that the Contributor believes its | ||||||
|  | Contributions are its original creation(s) or it has sufficient rights | ||||||
|  | to grant the rights to its Contributions conveyed by this License. | ||||||
|  | 
 | ||||||
|  | 2.6. Fair Use | ||||||
|  | 
 | ||||||
|  | This License is not intended to limit any rights You have under | ||||||
|  | applicable copyright doctrines of fair use, fair dealing, or other | ||||||
|  | equivalents. | ||||||
|  | 
 | ||||||
|  | 2.7. Conditions | ||||||
|  | 
 | ||||||
|  | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||||
|  | in Section 2.1. | ||||||
|  | 
 | ||||||
|  | 3. Responsibilities | ||||||
|  | ------------------- | ||||||
|  | 
 | ||||||
|  | 3.1. Distribution of Source Form | ||||||
|  | 
 | ||||||
|  | All distribution of Covered Software in Source Code Form, including any | ||||||
|  | Modifications that You create or to which You contribute, must be under | ||||||
|  | the terms of this License. You must inform recipients that the Source | ||||||
|  | Code Form of the Covered Software is governed by the terms of this | ||||||
|  | License, and how they can obtain a copy of this License. You may not | ||||||
|  | attempt to alter or restrict the recipients' rights in the Source Code | ||||||
|  | Form. | ||||||
|  | 
 | ||||||
|  | 3.2. Distribution of Executable Form | ||||||
|  | 
 | ||||||
|  | If You distribute Covered Software in Executable Form then: | ||||||
|  | 
 | ||||||
|  | (a) such Covered Software must also be made available in Source Code | ||||||
|  |     Form, as described in Section 3.1, and You must inform recipients of | ||||||
|  |     the Executable Form how they can obtain a copy of such Source Code | ||||||
|  |     Form by reasonable means in a timely manner, at a charge no more | ||||||
|  |     than the cost of distribution to the recipient; and | ||||||
|  | 
 | ||||||
|  | (b) You may distribute such Executable Form under the terms of this | ||||||
|  |     License, or sublicense it under different terms, provided that the | ||||||
|  |     license for the Executable Form does not attempt to limit or alter | ||||||
|  |     the recipients' rights in the Source Code Form under this License. | ||||||
|  | 
 | ||||||
|  | 3.3. Distribution of a Larger Work | ||||||
|  | 
 | ||||||
|  | You may create and distribute a Larger Work under terms of Your choice, | ||||||
|  | provided that You also comply with the requirements of this License for | ||||||
|  | the Covered Software. If the Larger Work is a combination of Covered | ||||||
|  | Software with a work governed by one or more Secondary Licenses, and the | ||||||
|  | Covered Software is not Incompatible With Secondary Licenses, this | ||||||
|  | License permits You to additionally distribute such Covered Software | ||||||
|  | under the terms of such Secondary License(s), so that the recipient of | ||||||
|  | the Larger Work may, at their option, further distribute the Covered | ||||||
|  | Software under the terms of either this License or such Secondary | ||||||
|  | License(s). | ||||||
|  | 
 | ||||||
|  | 3.4. Notices | ||||||
|  | 
 | ||||||
|  | You may not remove or alter the substance of any license notices | ||||||
|  | (including copyright notices, patent notices, disclaimers of warranty, | ||||||
|  | or limitations of liability) contained within the Source Code Form of | ||||||
|  | the Covered Software, except that You may alter any license notices to | ||||||
|  | the extent required to remedy known factual inaccuracies. | ||||||
|  | 
 | ||||||
|  | 3.5. Application of Additional Terms | ||||||
|  | 
 | ||||||
|  | You may choose to offer, and to charge a fee for, warranty, support, | ||||||
|  | indemnity or liability obligations to one or more recipients of Covered | ||||||
|  | Software. However, You may do so only on Your own behalf, and not on | ||||||
|  | behalf of any Contributor. You must make it absolutely clear that any | ||||||
|  | such warranty, support, indemnity, or liability obligation is offered by | ||||||
|  | You alone, and You hereby agree to indemnify every Contributor for any | ||||||
|  | liability incurred by such Contributor as a result of warranty, support, | ||||||
|  | indemnity or liability terms You offer. You may include additional | ||||||
|  | disclaimers of warranty and limitations of liability specific to any | ||||||
|  | jurisdiction. | ||||||
|  | 
 | ||||||
|  | 4. Inability to Comply Due to Statute or Regulation | ||||||
|  | --------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | If it is impossible for You to comply with any of the terms of this | ||||||
|  | License with respect to some or all of the Covered Software due to | ||||||
|  | statute, judicial order, or regulation then You must: (a) comply with | ||||||
|  | the terms of this License to the maximum extent possible; and (b) | ||||||
|  | describe the limitations and the code they affect. Such description must | ||||||
|  | be placed in a text file included with all distributions of the Covered | ||||||
|  | Software under this License. Except to the extent prohibited by statute | ||||||
|  | or regulation, such description must be sufficiently detailed for a | ||||||
|  | recipient of ordinary skill to be able to understand it. | ||||||
|  | 
 | ||||||
|  | 5. Termination | ||||||
|  | -------------- | ||||||
|  | 
 | ||||||
|  | 5.1. The rights granted under this License will terminate automatically | ||||||
|  | if You fail to comply with any of its terms. However, if You become | ||||||
|  | compliant, then the rights granted under this License from a particular | ||||||
|  | Contributor are reinstated (a) provisionally, unless and until such | ||||||
|  | Contributor explicitly and finally terminates Your grants, and (b) on an | ||||||
|  | ongoing basis, if such Contributor fails to notify You of the | ||||||
|  | non-compliance by some reasonable means prior to 60 days after You have | ||||||
|  | come back into compliance. Moreover, Your grants from a particular | ||||||
|  | Contributor are reinstated on an ongoing basis if such Contributor | ||||||
|  | notifies You of the non-compliance by some reasonable means, this is the | ||||||
|  | first time You have received notice of non-compliance with this License | ||||||
|  | from such Contributor, and You become compliant prior to 30 days after | ||||||
|  | Your receipt of the notice. | ||||||
|  | 
 | ||||||
|  | 5.2. If You initiate litigation against any entity by asserting a patent | ||||||
|  | infringement claim (excluding declaratory judgment actions, | ||||||
|  | counter-claims, and cross-claims) alleging that a Contributor Version | ||||||
|  | directly or indirectly infringes any patent, then the rights granted to | ||||||
|  | You by any and all Contributors for the Covered Software under Section | ||||||
|  | 2.1 of this License shall terminate. | ||||||
|  | 
 | ||||||
|  | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||||
|  | end user license agreements (excluding distributors and resellers) which | ||||||
|  | have been validly granted by You or Your distributors under this License | ||||||
|  | prior to termination shall survive termination. | ||||||
|  | 
 | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  6. Disclaimer of Warranty                                           * | ||||||
|  | *  -------------------------                                           * | ||||||
|  | *                                                                      * | ||||||
|  | *  Covered Software is provided under this License on an "as is"       * | ||||||
|  | *  basis, without warranty of any kind, either expressed, implied, or  * | ||||||
|  | *  statutory, including, without limitation, warranties that the       * | ||||||
|  | *  Covered Software is free of defects, merchantable, fit for a        * | ||||||
|  | *  particular purpose or non-infringing. The entire risk as to the     * | ||||||
|  | *  quality and performance of the Covered Software is with You.        * | ||||||
|  | *  Should any Covered Software prove defective in any respect, You     * | ||||||
|  | *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||||
|  | *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||||
|  | *  essential part of this License. No use of any Covered Software is   * | ||||||
|  | *  authorized under this License except under this disclaimer.         * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  | 
 | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  7. Limitation of Liability                                          * | ||||||
|  | *  --------------------------                                          * | ||||||
|  | *                                                                      * | ||||||
|  | *  Under no circumstances and under no legal theory, whether tort      * | ||||||
|  | *  (including negligence), contract, or otherwise, shall any           * | ||||||
|  | *  Contributor, or anyone who distributes Covered Software as          * | ||||||
|  | *  permitted above, be liable to You for any direct, indirect,         * | ||||||
|  | *  special, incidental, or consequential damages of any character      * | ||||||
|  | *  including, without limitation, damages for lost profits, loss of    * | ||||||
|  | *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||||
|  | *  and all other commercial damages or losses, even if such party      * | ||||||
|  | *  shall have been informed of the possibility of such damages. This   * | ||||||
|  | *  limitation of liability shall not apply to liability for death or   * | ||||||
|  | *  personal injury resulting from such party's negligence to the       * | ||||||
|  | *  extent applicable law prohibits such limitation. Some               * | ||||||
|  | *  jurisdictions do not allow the exclusion or limitation of           * | ||||||
|  | *  incidental or consequential damages, so this exclusion and          * | ||||||
|  | *  limitation may not apply to You.                                    * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  | 
 | ||||||
|  | 8. Litigation | ||||||
|  | ------------- | ||||||
|  | 
 | ||||||
|  | Any litigation relating to this License may be brought only in the | ||||||
|  | courts of a jurisdiction where the defendant maintains its principal | ||||||
|  | place of business and such litigation shall be governed by laws of that | ||||||
|  | jurisdiction, without reference to its conflict-of-law provisions. | ||||||
|  | Nothing in this Section shall prevent a party's ability to bring | ||||||
|  | cross-claims or counter-claims. | ||||||
|  | 
 | ||||||
|  | 9. Miscellaneous | ||||||
|  | ---------------- | ||||||
|  | 
 | ||||||
|  | This License represents the complete agreement concerning the subject | ||||||
|  | matter hereof. If any provision of this License is held to be | ||||||
|  | unenforceable, such provision shall be reformed only to the extent | ||||||
|  | necessary to make it enforceable. Any law or regulation which provides | ||||||
|  | that the language of a contract shall be construed against the drafter | ||||||
|  | shall not be used to construe this License against a Contributor. | ||||||
|  | 
 | ||||||
|  | 10. Versions of the License | ||||||
|  | --------------------------- | ||||||
|  | 
 | ||||||
|  | 10.1. New Versions | ||||||
|  | 
 | ||||||
|  | Mozilla Foundation is the license steward. Except as provided in Section | ||||||
|  | 10.3, no one other than the license steward has the right to modify or | ||||||
|  | publish new versions of this License. Each version will be given a | ||||||
|  | distinguishing version number. | ||||||
|  | 
 | ||||||
|  | 10.2. Effect of New Versions | ||||||
|  | 
 | ||||||
|  | You may distribute the Covered Software under the terms of the version | ||||||
|  | of the License under which You originally received the Covered Software, | ||||||
|  | or under the terms of any subsequent version published by the license | ||||||
|  | steward. | ||||||
|  | 
 | ||||||
|  | 10.3. Modified Versions | ||||||
|  | 
 | ||||||
|  | If you create software not governed by this License, and you want to | ||||||
|  | create a new license for such software, you may create and use a | ||||||
|  | modified version of this License if you rename the license and remove | ||||||
|  | any references to the name of the license steward (except to note that | ||||||
|  | such modified license differs from this License). | ||||||
|  | 
 | ||||||
|  | 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||||
|  | Licenses | ||||||
|  | 
 | ||||||
|  | If You choose to distribute Source Code Form that is Incompatible With | ||||||
|  | Secondary Licenses under the terms of this version of the License, the | ||||||
|  | notice described in Exhibit B of this License must be attached. | ||||||
|  | 
 | ||||||
|  | Exhibit A - Source Code Form License Notice | ||||||
|  | ------------------------------------------- | ||||||
|  | 
 | ||||||
|  |   This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |   file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  | 
 | ||||||
|  | If it is not possible or desirable to put the notice in a particular | ||||||
|  | file, then You may include the notice in a location (such as a LICENSE | ||||||
|  | file in a relevant directory) where a recipient would be likely to look | ||||||
|  | for such a notice. | ||||||
|  | 
 | ||||||
|  | You may add additional accurate notices of copyright ownership. | ||||||
|  | 
 | ||||||
|  | Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||||
|  | --------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  |   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||||
|  |   defined by the Mozilla Public License, v. 2.0. | ||||||
|  | |||||||
							
								
								
									
										504
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										504
									
								
								README.md
									
									
									
									
									
								
							| @ -1,208 +1,376 @@ | |||||||
| <!-- BANNER_TPL_BEGIN --> | # New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md) | ||||||
| 
 | 
 | ||||||
| About Daplie: We're taking back the Internet! | Greenlock v3 just came out of private beta **today** (Nov 1st, 2019). | ||||||
| -------------- |  | ||||||
| 
 | 
 | ||||||
| Down with Google, Apple, and Facebook! | The code is complete and we're working on great documentation. | ||||||
| 
 | 
 | ||||||
| We're re-decentralizing the web and making it read-write again - one home cloud system at a time. | Many **examples** and **full API** documentation are still coming. | ||||||
| 
 | 
 | ||||||
| Tired of serving the Empire? Come join the Rebel Alliance: | # [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) is Let's Encrypt for Node | ||||||
| 
 | 
 | ||||||
| <a href="mailto:jobs@daplie.com">jobs@daplie.com</a> | [Invest in Daplie on Wefunder](https://daplie.com/invest/) | [Pre-order Cloud](https://daplie.com/preorder/), The World's First Home Server for Everyone |  | ||||||
| 
 | 
 | ||||||
| <!-- BANNER_TPL_END --> | | Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub/) | ||||||
| 
 | 
 | ||||||
| greenlock-express (letsencrypt-express) | Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc. | ||||||
| ================= |  | ||||||
| 
 | 
 | ||||||
| [](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ### Let's Encrypt for Node, Express, etc | ||||||
| 
 | 
 | ||||||
| | [greenlock (lib)](https://git.daplie.com/Daplie/node-greenlock) | Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals. | ||||||
| | [greenlock-cli](https://git.daplie.com/Daplie/greenlock-cli) |  | ||||||
| | **greenlock-express** |  | ||||||
| | [greenlock-cluster](https://git.daplie.com/Daplie/greenlock-cluster) |  | ||||||
| | [greenlock-koa](https://git.daplie.com/Daplie/greenlock-koa) |  | ||||||
| | [greenlock-hapi](https://git.daplie.com/Daplie/greenlock-hapi) |  | ||||||
| | |  | ||||||
| 
 | 
 | ||||||
| Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems. | ```js | ||||||
|  | "use strict"; | ||||||
| 
 | 
 | ||||||
| * Automatic Registration via SNI (`httpsOptions.SNICallback`) | function httpsWorker(glx) { | ||||||
|   * **registrations** require an **approval callback** in *production* | 	// Serves on 80 and 443 | ||||||
| * Automatic Renewal (around 80 days) | 	// Get's SSL certificates magically! | ||||||
|   * **renewals** are *fully automatic* and happen in the *background*, with **no downtime** |  | ||||||
| * Automatic vhost / virtual hosting |  | ||||||
| 
 | 
 | ||||||
| All you have to do is start the webserver and then visit it at its domain name. | 	glx.serveApp(function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| Install | var pkg = require("./package.json"); | ||||||
| ======= | require("greenlock-express") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config | ||||||
| 
 | 
 | ||||||
| ```bash | 		return { | ||||||
| npm install --save greenlock-express@2.x | 			package: { name: pkg.name, version: pkg.version }, | ||||||
| ``` | 			maintainerEmail: pkg.author, | ||||||
| 
 | 			cluster: false | ||||||
| **Important**: Use node v4.5+ or v6.x, node <= v4.4 has a [known bug](https://github.com/nodejs/node/issues/8053) in the `Buffer` implementation. | 		}; | ||||||
| 
 |  | ||||||
| QuickStart |  | ||||||
| ========== |  | ||||||
| 
 |  | ||||||
| Here's a completely working example that will get you started: |  | ||||||
| 
 |  | ||||||
| `app.js`: |  | ||||||
| ```javascript |  | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| require('greenlock-express').create({ |  | ||||||
| 
 |  | ||||||
|   server: 'staging' |  | ||||||
| 
 |  | ||||||
| , email: 'john.doe@example.com' |  | ||||||
| 
 |  | ||||||
| , agreeTos: true |  | ||||||
| 
 |  | ||||||
| , approveDomains: [ 'example.com' ] |  | ||||||
| 
 |  | ||||||
| , app: require('express')().use('/', function (req, res) { |  | ||||||
|     res.end('Hello, World!'); |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 	.serve(httpsWorker); | ||||||
| }).listen(80, 443); |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Certificates will be stored in `~/letsencrypt`. | Manage via API or the config file: | ||||||
| 
 | 
 | ||||||
| **Important**: | `~/.config/greenlock/manage.json`: (default filesystem config) | ||||||
| 
 | 
 | ||||||
| You must set `server` to `https://acme-v01.api.letsencrypt.org/directory` **after** | ```json | ||||||
| you have tested that your setup works. | { | ||||||
| 
 | 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||||
| Why You Must Use 'staging' First | 	"agreeToTerms": true, | ||||||
| -------------------------------- | 	"sites": { | ||||||
| 
 | 		"example.com": { | ||||||
| There are a number of common problems related to system configuration - | 			"subject": "example.com", | ||||||
| firewalls, ports, permissions, etc - that you are likely to run up against | 			"altnames": ["example.com", "www.example.com"] | ||||||
| when using greenlock for your first time. |  | ||||||
| 
 |  | ||||||
| In order to avoid being blocked by hitting rate limits with bad requests, |  | ||||||
| you should always test against the `'staging'` server |  | ||||||
| (`https://acme-staging.api.letsencrypt.org/directory`) first. |  | ||||||
| 
 |  | ||||||
| Migrating from v1.x |  | ||||||
| =================== |  | ||||||
| 
 |  | ||||||
| Whereas v1.x had a few hundred lines of code, v2.x is a single small file of about 50 lines. |  | ||||||
| 
 |  | ||||||
| A few important things to note: |  | ||||||
| 
 |  | ||||||
| * Delete your v1.x `~/letsencrypt` directory, otherwise you get this: |  | ||||||
|   * `{ type: 'urn:acme:error:malformed', detail: 'Parse error reading JWS', status: 400 }` |  | ||||||
| * `approveRegistration` has been replaced by `approveDomains` |  | ||||||
| * All of the behavior has moved to the various plugins, which each have their own options |  | ||||||
| * Use https and http directly, don't rely on the silly `.listen()` helper. It's just there for looks. |  | ||||||
| * `lex.createAcmeResponder()` is now `lex.middleware(require('redirect-https')())` or `lex.middleware(app)` |  | ||||||
| 
 |  | ||||||
| Usage |  | ||||||
| ===== |  | ||||||
| 
 |  | ||||||
| The oversimplified example was the bait |  | ||||||
| (because everyone seems to want an example that fits in 3 lines, even if it's terribly bad practices), |  | ||||||
| now here's the switch: |  | ||||||
| 
 |  | ||||||
| `serve.js`: |  | ||||||
| ```javascript |  | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| // returns an instance of node-greenlock with additional helper methods |  | ||||||
| var lex = require('greenlock-express').create({ |  | ||||||
|   // set to https://acme-v01.api.letsencrypt.org/directory in production |  | ||||||
|   server: 'staging' |  | ||||||
| 
 |  | ||||||
| // If you wish to replace the default plugins, you may do so here |  | ||||||
| // |  | ||||||
| , challenges: { 'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }) } |  | ||||||
| , store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' }) |  | ||||||
| 
 |  | ||||||
| // You probably wouldn't need to replace the default sni handler |  | ||||||
| // See https://git.daplie.com/Daplie/le-sni-auto if you think you do |  | ||||||
| //, sni: require('le-sni-auto').create({}) |  | ||||||
| 
 |  | ||||||
| , approveDomains: approveDomains |  | ||||||
| }); |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ```javascript |  | ||||||
| function approveDomains(opts, certs, cb) { |  | ||||||
|   // This is where you check your database and associated |  | ||||||
|   // email addresses with domains and agreements and such |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   // The domains being approved for the first time are listed in opts.domains |  | ||||||
|   // Certs being renewed are listed in certs.altnames |  | ||||||
|   if (certs) { |  | ||||||
|     opts.domains = certs.altnames; |  | ||||||
| 		} | 		} | ||||||
|   else { |  | ||||||
|     opts.email = 'john.doe@example.com'; |  | ||||||
|     opts.agreeTos = true; |  | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
|   // NOTE: you can also change other options such as `challengeType` and `challenge` |  | ||||||
|   // opts.challengeType = 'http-01'; |  | ||||||
|   // opts.challenge = require('le-challenge-fs').create({}); |  | ||||||
| 
 |  | ||||||
|   cb(null, { options: opts, certs: certs }); |  | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | # Let's Encrypt for... | ||||||
| 
 | 
 | ||||||
| ```javascript | - IoT | ||||||
| // handles acme-challenge and redirects to https | - Enterprise On-Prem | ||||||
| require('http').createServer(lex.middleware(require('redirect-https')())).listen(80, function () { | - Local Development | ||||||
|   console.log("Listening for ACME http-01 challenges on", this.address()); | - Home Servers | ||||||
| }); | - Quitting Heroku | ||||||
| 
 | 
 | ||||||
|  | # Features | ||||||
| 
 | 
 | ||||||
|  | - [x] Let's Encrypt v2 (November 2019) | ||||||
|  |   - [x] ACME Protocol (RFC 8555) | ||||||
|  |   - [x] HTTP Validation (HTTP-01) | ||||||
|  |   - [x] DNS Validation (DNS-01) | ||||||
|  |   - [ ] ALPN Validation (TLS-ALPN-01) | ||||||
|  |     - Need ALPN validation? [contact us](mailto:greenlock-support@therootcompany.com) | ||||||
|  | - [x] Automated HTTPS | ||||||
|  |   - [x] Fully Automatic Renewals every 45 days | ||||||
|  |   - [x] Free SSL | ||||||
|  |   - [x] **Wildcard** SSL | ||||||
|  |   - [x] **Localhost** certificates | ||||||
|  |   - [x] HTTPS-enabled Secure **WebSockets** (`wss://`) | ||||||
|  | - [x] Fully customizable | ||||||
|  |   - [x] **Reasonable defaults** | ||||||
|  |   - [x] Domain Management | ||||||
|  |   - [x] Key and Certificate Management | ||||||
|  |   - [x] ACME Challenge Plugins | ||||||
| 
 | 
 | ||||||
| var app = require('express')(); | # QuickStart Guide | ||||||
| app.use('/', function (req, res) { |  | ||||||
|   res.end('Hello, World!'); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| // handles your app | Easy as 1, 2, 3... 4 | ||||||
| require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443, function () { | 
 | ||||||
|   console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); | <details> | ||||||
| }); | <summary>1. Create a node project</summary> | ||||||
|  | 
 | ||||||
|  | ## 1. Create a node project | ||||||
|  | 
 | ||||||
|  | Create an empty node project. | ||||||
|  | 
 | ||||||
|  | Be sure to fill out the package name, version, and an author email. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | mkdir ~/my-project | ||||||
|  | pushd ~/my-project | ||||||
|  | npm init | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **Security Warning**: | </details> | ||||||
| 
 | 
 | ||||||
| If you don't do proper checks in `approveDomains(opts, certs, cb)` | <details> | ||||||
| an attacker will spoof SNI packets with bad hostnames and that will | <summary>2. Create an http app (i.e. express)</summary> | ||||||
| cause you to be rate-limited and or blocked from the ACME server. |  | ||||||
| 
 | 
 | ||||||
|  | ## 2. Create an http app (i.e. express) | ||||||
| 
 | 
 | ||||||
| API | This example is shown with Express, but any node app will do. Greenlock | ||||||
| === | works with everything. | ||||||
|  | (or any node-style http app) | ||||||
| 
 | 
 | ||||||
| This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). | `my-express-app.js`: | ||||||
| 
 | 
 | ||||||
| The API is actually located at [node-greenlock options](https://git.daplie.com/Daplie/node-greenlock) | ```js | ||||||
| (because all options are simply passed through to `node-greenlock` proper without modification). | "use strict"; | ||||||
| 
 | 
 | ||||||
| The only "API" consists of two options, the rest is just a wrapper around `node-greenlock` to take LOC from 15 to 5: | // A plain, node-style app | ||||||
| 
 | 
 | ||||||
| * `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). | function myPlainNodeHttpApp(req, res) { | ||||||
| * `lex.listen(plainPort, tlsPort)` Accepts port numbers (or arrays of port numbers) to listen on. | 	res.end("Hello, Encrypted World!"); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| Brief overview of some simple options for `node-greenlock`: | // Wrap that plain app in express, | ||||||
|  | // because that's what you're used to | ||||||
| 
 | 
 | ||||||
| * `opts.server` set to https://acme-v01.api.letsencrypt.org/directory in production | var express = require("express"); | ||||||
| * `opts.email` The default email to use to accept agreements. | var app = express(); | ||||||
| * `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first. | app.get("/", myPlainNodeHttpApp); | ||||||
| * `opts.approveDomains` can be either of: | 
 | ||||||
|   * An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]` | // export the app normally | ||||||
|   * A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above) | // do not .listen() | ||||||
| * `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate. | 
 | ||||||
| * `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate. | module.exports = app; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>3. Serve with Greenlock Express</summary> | ||||||
|  | 
 | ||||||
|  | ## 3. Serve with Greenlock Express | ||||||
|  | 
 | ||||||
|  | Greenlock Express is designed with these goals in mind: | ||||||
|  | 
 | ||||||
|  | - Simplicity and ease-of-use | ||||||
|  | - Performance and scalability | ||||||
|  | - Configurability and control | ||||||
|  | 
 | ||||||
|  | You can start with **near-zero configuration** and | ||||||
|  | slowly add options for greater performance and customization | ||||||
|  | later, if you need them. | ||||||
|  | 
 | ||||||
|  | `server.js`: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | require("greenlock-express") | ||||||
|  | 	.init(getConfig) | ||||||
|  | 	.serve(worker); | ||||||
|  | 
 | ||||||
|  | function getConfig() { | ||||||
|  | 	return { | ||||||
|  | 		// uses name and version as part of the ACME client user-agent | ||||||
|  | 		// uses author as the contact for support notices | ||||||
|  | 		package: require("./package.json") | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function worker(server) { | ||||||
|  | 	// Works with any Node app (Express, etc) | ||||||
|  | 	var app = require("my-express-app.js"); | ||||||
|  | 	server.serveApp(app); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And start your server: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS) | ||||||
|  | sudo setcap 'cap_net_bind_service=+ep' $(which node) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # `npm start` will call `node ./server.js` by default | ||||||
|  | npm start | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | Greenlock v3.0.0 | ||||||
|  | Greenlock Manager Config File: ~/.config/greenlock/manager.json | ||||||
|  | Greenlock Storage Directory: ~/.config/greenlock/ | ||||||
|  | 
 | ||||||
|  | Listening on 0.0.0.0:80 for ACME challenges and HTTPS redirects | ||||||
|  | Listening on 0.0.0.0:443 for secure traffic | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>4. Manage SSL Certificates and Domains</summary> | ||||||
|  | 
 | ||||||
|  | ## 4. Manage domains | ||||||
|  | 
 | ||||||
|  | The management API is built to work with Databases, S3, etc. | ||||||
|  | 
 | ||||||
|  | HOWEVER, by default it starts with a simple config file. | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | This will update the config file (assuming the default fs-based management plugin): | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | `~/.config/greenlock/manager.json`: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  | 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||||
|  | 	"agreeToTerms": true, | ||||||
|  | 	"sites": { | ||||||
|  | 		"example.com": { | ||||||
|  | 			"subject": "example.com", | ||||||
|  | 			"altnames": ["example.com", "www.example.com"] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | COMING SOON | ||||||
|  | 
 | ||||||
|  | Management can be done via the **CLI** or the JavaScript [**API**](https://git.rootprojects.org/root/greenlock.js/). | ||||||
|  | Since this is the QuickStart, we'll demo the **CLI**: | ||||||
|  | 
 | ||||||
|  | You need to create a Let's Encrypt _subscriber account_, which can be done globally, or per-site. | ||||||
|  | All individuals, and most businesses, should set this globally: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # COMING SOON | ||||||
|  | # (this command should be here by Nov 5th) | ||||||
|  | # (edit the config by hand for now) | ||||||
|  | # | ||||||
|  | # Set a global subscriber account | ||||||
|  | npx greenlock config --subscriber-email 'mycompany@example.com' --agree-to-terms true | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | <!-- todo print where the key was saved --> | ||||||
|  | 
 | ||||||
|  | A Let's Encrypt SSL certificate has a "Subject" (Primary Domain) and up to 100 "Alternative Names" | ||||||
|  | (of which the first _must_ be the subject). | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # COMING SOON | ||||||
|  | # (this command should be here by Nov 5th) | ||||||
|  | # (edit the config by hand for now) | ||||||
|  | # | ||||||
|  | # Add a certificate with specific domains | ||||||
|  | npx greenlock add --subject example.com --altnames example.com,www.example.com | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | <!-- todo print where the cert was saved --> | ||||||
|  | 
 | ||||||
|  | Note: **Localhost**, **Wildcard**, and Certificates for Private Networks require | ||||||
|  | [**DNS validation**](https://git.rootprojects.org/root/greenlock-exp). | ||||||
|  | 
 | ||||||
|  | - DNS Validation | ||||||
|  |   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) | ||||||
|  |   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) | ||||||
|  |   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) | ||||||
|  | 
 | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
|  | # Plenty of Examples | ||||||
|  | 
 | ||||||
|  | **These are in-progress** Check back tomorrow (Nov 2nd, 2019). | ||||||
|  | 
 | ||||||
|  | - [greenlock-express.js/examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) | ||||||
|  |   - [Express](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/express/) | ||||||
|  |   - [Node's **http2**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http2/) | ||||||
|  |   - [Node's https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/https/) | ||||||
|  |   - [**WebSockets**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/websockets/) | ||||||
|  |   - [Socket.IO](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/socket-io/) | ||||||
|  |   - [Cluster](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/cluster/) | ||||||
|  |   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) | ||||||
|  |   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) | ||||||
|  |   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) | ||||||
|  |   - [HTTP Proxy](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http-proxy/) | ||||||
|  | 
 | ||||||
|  | # Easy to Customize | ||||||
|  | 
 | ||||||
|  | <!-- greenlock-manager-test => greenlock-manager-custom --> | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | - [greenlock.js/examples/](https://git.rootprojects.org/root/greenlock.js/src/branch/master/examples) | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | - [Custom Domain Management](https://git.rootprojects.org/root/greenlock-manager-test.js) | ||||||
|  | - [Custom Key & Cert Storage](https://git.rootprojects.org/root/greenlock-store-test.js) | ||||||
|  | - [Custom ACME HTTP-01 Challenges](https://git.rootprojects.org/root/acme-http-01-test.js) | ||||||
|  | - [Custom ACME DNS-01 Challenges](https://git.rootprojects.org/root/acme-dns-01-test.js) | ||||||
|  | 
 | ||||||
|  | # Ready-made Integrations | ||||||
|  | 
 | ||||||
|  | Greenlock Express integrates between Let's Encrypt's ACME Challenges and many popular services. | ||||||
|  | 
 | ||||||
|  | | Type        | Service                                                                             | Plugin                   | | ||||||
|  | | ----------- | ----------------------------------------------------------------------------------- | ------------------------ | | ||||||
|  | | dns-01      | CloudFlare                                                                          | acme-dns-01-cloudflare   | | ||||||
|  | | dns-01      | [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)      | acme-dns-01-digitalocean | | ||||||
|  | | dns-01      | [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)               | acme-dns-01-dnsimple     | | ||||||
|  | | dns-01      | [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)                 | acme-dns-01-duckdns      | | ||||||
|  | | http-01     | File System / [Web Root](https://git.rootprojects.org/root/acme-http-01-webroot.js) | acme-http-01-webroot     | | ||||||
|  | | dns-01      | [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)                 | acme-dns-01-godaddy      | | ||||||
|  | | dns-01      | [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)                     | acme-dns-01-gandi        | | ||||||
|  | | dns-01      | [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)             | acme-dns-01-namecheap    | | ||||||
|  | | dns-01      | [Name.com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)         | acme-dns-01-namedotcom   | | ||||||
|  | | dns-01      | Route53 (AWS)                                                                       | acme-dns-01-route53      | | ||||||
|  | | http-01     | S3 (AWS, Digital Ocean, Scaleway)                                                   | acme-http-01-s3          | | ||||||
|  | | dns-01      | [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)                     | acme-dns-01-vultr        | | ||||||
|  | | dns-01      | [Build your own](https://git.rootprojects.org/root/acme-dns-01-test.js)             | acme-dns-01-test         | | ||||||
|  | | http-01     | [Build your own](https://git.rootprojects.org/root/acme-http-01-test.js)            | acme-http-01-test        | | ||||||
|  | | tls-alpn-01 | [Contact us](mailto:support@therootcompany.com)                                     | -                        | | ||||||
|  | 
 | ||||||
|  | Search `acme-http-01-` or `acme-dns-01-` on npm to find more. | ||||||
|  | 
 | ||||||
|  | # Full Documentation | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | - Greenlock CLI | ||||||
|  | - Greenlock JavaScript API | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | Most of the documentation is done by use-case examples, as shown up at the top of the README. | ||||||
|  | 
 | ||||||
|  | We're working on more comprehensive documentation for this newly released version. | ||||||
|  | **Please open an issue** with questions in the meantime. | ||||||
|  | 
 | ||||||
|  | # Commercial Support | ||||||
|  | 
 | ||||||
|  | Do you need... | ||||||
|  | 
 | ||||||
|  | - training? | ||||||
|  | - specific features? | ||||||
|  | - different integrations? | ||||||
|  | - bugfixes, on _your_ timeline? | ||||||
|  | - custom code, built by experts? | ||||||
|  | - commercial support and licensing? | ||||||
|  | 
 | ||||||
|  | You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem, | ||||||
|  | Enterprise, and Internal installations, integrations, and deployments. | ||||||
|  | 
 | ||||||
|  | We have both commercial support and commercial licensing available. | ||||||
|  | 
 | ||||||
|  | We also offer consulting for all-things-ACME and Let's Encrypt. | ||||||
|  | 
 | ||||||
|  | # Legal & Rules of the Road | ||||||
|  | 
 | ||||||
|  | Greenlock™ is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal | ||||||
|  | 
 | ||||||
|  | The rule of thumb is "attribute, but don't confuse". For example: | ||||||
|  | 
 | ||||||
|  | > Built with [Greenlock Express](https://git.rootprojects.org/root/greenlock.js) (a [Root](https://rootprojects.org) project). | ||||||
|  | 
 | ||||||
|  | Please [contact us](mailto:aj@therootcompany.com) if you have any questions in regards to our trademark, | ||||||
|  | attribution, and/or visible source policies. We want to build great software and a great community. | ||||||
|  | 
 | ||||||
|  | [Greenlock™](https://git.rootprojects.org/root/greenlock.js) | | ||||||
|  | MPL-2.0 | | ||||||
|  | [Terms of Use](https://therootcompany.com/legal/#terms) | | ||||||
|  | [Privacy Policy](https://therootcompany.com/legal/#privacy) | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var path = require("path"); | ||||||
|  | module.exports = { | ||||||
|  | 	email: "jon.doe@example.com", | ||||||
|  | 	configDir: path.join(__dirname, "acme"), | ||||||
|  | 	srv: "/srv/www/", | ||||||
|  | 	api: "/srv/api/", | ||||||
|  | 	proxy: { | ||||||
|  | 		"example.com": "http://localhost:4080", | ||||||
|  | 		"*.example.com": "http://localhost:4080" | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	// DNS-01 challenges only
 | ||||||
|  | 	challenges: { | ||||||
|  | 		"*.example.com": require("acme-dns-01-YOUR_DNS_HOST").create({ | ||||||
|  | 			token: "xxxx" | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | }; | ||||||
							
								
								
									
										35
									
								
								demo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								demo.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | require("./") | ||||||
|  | 	.init(initialize) | ||||||
|  | 	.serve(worker) | ||||||
|  | 	.master(function() { | ||||||
|  | 		console.log("Hello from master"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | function initialize() { | ||||||
|  | 	var pkg = require("./package.json"); | ||||||
|  | 	var config = { | ||||||
|  | 		package: { | ||||||
|  | 			name: "Greenlock_Express_Demo", | ||||||
|  | 			version: pkg.version, | ||||||
|  | 			author: pkg.author | ||||||
|  | 		}, | ||||||
|  | 		staging: true, | ||||||
|  | 		cluster: true, | ||||||
|  | 
 | ||||||
|  | 		notify: function(ev, params) { | ||||||
|  | 			console.info(ev, params); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	return config; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function worker(glx) { | ||||||
|  | 	console.info(); | ||||||
|  | 	console.info("Hello from worker #" + glx.id()); | ||||||
|  | 
 | ||||||
|  | 	glx.serveApp(function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								dist/etc/systemd/system/greenlock-express.service
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								dist/etc/systemd/system/greenlock-express.service
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | # sudo systemctl daemon-reload | ||||||
|  | # sudo systemctl restart greenlock-express | ||||||
|  | # sudo journalctl -xefu greenlock-express | ||||||
|  | [Unit] | ||||||
|  | Description=Greenlock Static Server | ||||||
|  | Documentation=https://git.coolaj86.com/coolaj86/greenlock-express.js/ | ||||||
|  | After=network.target | ||||||
|  | Wants=network.target systemd-networkd-wait-online.service | ||||||
|  | 
 | ||||||
|  | [Service] | ||||||
|  | # Restart on crash (bad signal), 'clean' failure (error exit code), everything | ||||||
|  | # Allow up to 3 restarts within 10 seconds | ||||||
|  | # (it's unlikely that a user or properly-running script will do this) | ||||||
|  | Restart=always | ||||||
|  | StartLimitInterval=10 | ||||||
|  | StartLimitBurst=3 | ||||||
|  | 
 | ||||||
|  | # User and group the process will run as | ||||||
|  | # (git is the de facto standard on most systems) | ||||||
|  | User=ubuntu | ||||||
|  | Group=ubuntu | ||||||
|  | 
 | ||||||
|  | WorkingDirectory=/srv/www | ||||||
|  | # custom directory cannot be set and will be the place where gitea exists, not the working directory | ||||||
|  | ExecStart=/opt/node/bin/node /opt/greenlock-express.js/server.js /opt/greenlock-express.js/config.js | ||||||
|  | 
 | ||||||
|  | # Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. | ||||||
|  | # greenlock is not expected to use more than this. | ||||||
|  | LimitNOFILE=1048576 | ||||||
|  | LimitNPROC=64 | ||||||
|  | 
 | ||||||
|  | # Use private /tmp and /var/tmp, which are discarded after gitea stops. | ||||||
|  | PrivateTmp=true | ||||||
|  | # Use a minimal /dev | ||||||
|  | PrivateDevices=true | ||||||
|  | # Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. | ||||||
|  | ProtectHome=true | ||||||
|  | # Make /usr, /boot, /etc and possibly some more folders read-only. | ||||||
|  | ProtectSystem=full | ||||||
|  | # ... except /opt/greenlock-express.js/acme because we want a place for the database | ||||||
|  | # and /opt/greenlock-express.js/var because we want a place where logs can go. | ||||||
|  | # This merely retains r/w access rights, it does not add any new. | ||||||
|  | # Must still be writable on the host! | ||||||
|  | ReadWriteDirectories=/srv/www /opt/greenlock-express.js | ||||||
|  | 
 | ||||||
|  | # Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories | ||||||
|  | ; ReadWritePaths=/opt/gitea /var/log/gitea | ||||||
|  | 
 | ||||||
|  | # The following additional security directives only work with systemd v229 or later. | ||||||
|  | # They further retrict privileges that can be gained by gitea. | ||||||
|  | # Note that you may have to add capabilities required by any plugins in use. | ||||||
|  | CapabilityBoundingSet=CAP_NET_BIND_SERVICE | ||||||
|  | AmbientCapabilities=CAP_NET_BIND_SERVICE | ||||||
							
								
								
									
										39
									
								
								examples/cluster/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								examples/cluster/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "websocket-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 
 | ||||||
|  | 			// When you're ready to go full cloud scale, you just change this to true:
 | ||||||
|  | 			// Note: in cluster you CANNOT use in-memory state (see below)
 | ||||||
|  | 			cluster: true, | ||||||
|  | 
 | ||||||
|  |       // This will default to the number of workers being equal to
 | ||||||
|  |       // n-1 cpus, with a minimum of 2
 | ||||||
|  |       workers: 4 | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	// WRONG
 | ||||||
|  | 	// This won't work like you
 | ||||||
|  | 	// think because EACH worker
 | ||||||
|  | 	// has ITS OWN `count`.
 | ||||||
|  | 	var count = 0; | ||||||
|  | 
 | ||||||
|  | 	var app = function(req, res) { | ||||||
|  | 		res.end("Hello... how many times now? Oh, " + count + " times"); | ||||||
|  | 		count += 1; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	// Serves on 80 and 443... for each worker
 | ||||||
|  | 	// Get's SSL certificates magically!
 | ||||||
|  | 	glx.serveApp(app); | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								examples/express/my-express-app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/express/my-express-app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var express = require("express"); | ||||||
|  | var app = express(); | ||||||
|  | 
 | ||||||
|  | app.use("/", function(req, res) { | ||||||
|  | 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 	res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // DO NOT DO app.listen() unless we're testing this directly
 | ||||||
|  | if (require.main === module) { | ||||||
|  | 	app.listen(3000); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Instead do export the app:
 | ||||||
|  | module.exports = app; | ||||||
							
								
								
									
										27
									
								
								examples/express/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								examples/express/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	var app = require("./my-express-app.js"); | ||||||
|  | 
 | ||||||
|  | 	app.get("/hello", function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Serves on 80 and 443
 | ||||||
|  | 	// Get's SSL certificates magically!
 | ||||||
|  | 	glx.serveApp(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "http2-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
| @ -1,22 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| //require('greenlock-express')
 |  | ||||||
| require('../').create({ |  | ||||||
| 
 |  | ||||||
|   server: 'staging' |  | ||||||
| 
 |  | ||||||
| , email: 'john.doe@example.com' |  | ||||||
| 
 |  | ||||||
| , agreeTos: true |  | ||||||
| 
 |  | ||||||
| , approvedDomains: [ 'example.com', 'www.example.com' ] |  | ||||||
| 
 |  | ||||||
| , app: require('express')().use('/', function (req, res) { |  | ||||||
|     res.end('Hello, World!'); |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
| , renewWithin: (91 * 24 * 60 * 60 * 1000) |  | ||||||
| , renewBy: (90 * 24 * 60 * 60 * 1000) |  | ||||||
| 
 |  | ||||||
| , debug: true |  | ||||||
| }).listen(80, 443); |  | ||||||
							
								
								
									
										44
									
								
								examples/http-proxy/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								examples/http-proxy/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	// we need the raw https server
 | ||||||
|  | 	var server = glx.httpsServer(); | ||||||
|  | 	var proxy = require("http-proxy").createProxyServer({ xfwd: true }); | ||||||
|  | 
 | ||||||
|  | 	// catches error events during proxying
 | ||||||
|  | 	proxy.on("error", function(err, req, res) { | ||||||
|  | 		console.error(err); | ||||||
|  | 		res.statusCode = 500; | ||||||
|  | 		res.end(); | ||||||
|  | 		return; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// We'll proxy websockets too
 | ||||||
|  | 	server.on("upgrade", function(req, socket, head) { | ||||||
|  | 		proxy.ws(req, socket, head, { | ||||||
|  | 			ws: true, | ||||||
|  | 			target: "ws://localhost:3000" | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// servers a node app that proxies requests to a localhost
 | ||||||
|  | 	glx.serveApp(function(req, res) { | ||||||
|  | 		proxy.web(req, res, { | ||||||
|  | 			target: "http://localhost:3000" | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "http-proxy-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										42
									
								
								examples/http/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								examples/http/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | 
 | ||||||
|  | // The WRONG way:
 | ||||||
|  | //var http = require('http');
 | ||||||
|  | //var httpServer = https.createSecureServer(redirectToHttps);
 | ||||||
|  | //
 | ||||||
|  | // Why is that wrong?
 | ||||||
|  | // Greenlock needs to change some low-level http and https options.
 | ||||||
|  | // Use glx.httpServer(redirectToHttps) instead.
 | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	//
 | ||||||
|  | 	// HTTP can only be used for ACME HTTP-01 Challenges
 | ||||||
|  | 	// (and it is not required for DNS-01 challenges)
 | ||||||
|  | 	//
 | ||||||
|  | 
 | ||||||
|  | 	// Get the raw http server:
 | ||||||
|  | 	var httpServer = glx.httpServer(function(req, res) { | ||||||
|  | 		res.statusCode = 301; | ||||||
|  | 		res.setHeader("Location", "https://" + req.headers.host + req.path); | ||||||
|  | 		res.end("Insecure connections are not allowed. Redirecting..."); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	httpServer.listen(80, "0.0.0.0", function() { | ||||||
|  | 		console.info("Listening on ", httpServer.address()); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "plain-http-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										48
									
								
								examples/http2/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								examples/http2/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | 
 | ||||||
|  | // The WRONG way:
 | ||||||
|  | //var http2 = require('http2');
 | ||||||
|  | //var http2Server = https.createSecureServer(tlsOptions, app);
 | ||||||
|  | //
 | ||||||
|  | // Why is that wrong?
 | ||||||
|  | // Greenlock needs to change some low-level http and https options.
 | ||||||
|  | // Use glx.httpsServer(tlsOptions, app) instead.
 | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	//
 | ||||||
|  | 	// HTTP2 is the default httpsServer for node v12+
 | ||||||
|  | 	// (HTTPS/1.1 is used for node <= v11)
 | ||||||
|  | 	//
 | ||||||
|  | 
 | ||||||
|  | 	// Get the raw http2 server:
 | ||||||
|  | 	var http2Server = glx.httpsServer(function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	http2Server.listen(443, "0.0.0.0", function() { | ||||||
|  | 		console.info("Listening on ", http2Server.address()); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Note:
 | ||||||
|  | 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 | ||||||
|  | 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 | ||||||
|  | 	var httpServer = glx.httpServer(); | ||||||
|  | 	httpServer.listen(80, "0.0.0.0", function() { | ||||||
|  | 		console.info("Listening on ", httpServer.address()); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "http2-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										49
									
								
								examples/https/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/https/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | 
 | ||||||
|  | // The WRONG way:
 | ||||||
|  | //var https = require('https');
 | ||||||
|  | //var httpsServer = https.createServer(tlsOptions, app);
 | ||||||
|  | //
 | ||||||
|  | // Why is that wrong?
 | ||||||
|  | // Greenlock needs to change some low-level http and https options.
 | ||||||
|  | // Use glx.httpsServer(tlsOptions, app) instead.
 | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	//
 | ||||||
|  | 	// HTTPS/1.1 is only used for node v11 or lower
 | ||||||
|  | 	// (HTTP2 is used for node v12+)
 | ||||||
|  | 	//
 | ||||||
|  | 	// Why not just require('https')?
 | ||||||
|  | 
 | ||||||
|  | 	// Get the raw https server:
 | ||||||
|  | 	var httpsServer = glx.httpsServer(null, function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	httpsServer.listen(443, "0.0.0.0", function() { | ||||||
|  | 		console.info("Listening on ", httpsServer.address()); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// Note:
 | ||||||
|  | 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 | ||||||
|  | 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 | ||||||
|  | 	var httpServer = glx.httpServer(); | ||||||
|  | 	httpServer.listen(80, "0.0.0.0", function() { | ||||||
|  | 		console.info("Listening on ", httpServer.address()); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "https1-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										22
									
								
								examples/quickstart/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								examples/quickstart/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | # Quick Start for Let's Encrypt with Node.js | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | npm install --save greenlock-express | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Manage via API or the config file: | ||||||
|  | 
 | ||||||
|  | `~/.config/greenlock/manage.json`: (default filesystem config) | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  | 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||||
|  | 	"agreeToTerms": true, | ||||||
|  | 	"sites": { | ||||||
|  | 		"example.com": { | ||||||
|  | 			"subject": "example.com", | ||||||
|  | 			"altnames": ["example.com", "www.example.com"] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										32
									
								
								examples/quickstart/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								examples/quickstart/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	// This can be a node http app (shown),
 | ||||||
|  | 	// an Express app, or Hapi, Koa, Rill, etc
 | ||||||
|  | 	var app = function(req, res) { | ||||||
|  | 		res.end("Hello, Encrypted World!"); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	// Serves on 80 and 443
 | ||||||
|  | 	// Get's SSL certificates magically!
 | ||||||
|  | 	glx.serveApp(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			// Package name+version is used for ACME client user agent
 | ||||||
|  | 			package: { name: "websocket-example", version: pkg.version }, | ||||||
|  | 
 | ||||||
|  | 			// Maintainer email is the contact for critical bug and security notices
 | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 
 | ||||||
|  | 			// Change to true when you're ready to make your app cloud-scale
 | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
| @ -1,20 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| //require('greenlock-express')
 |  | ||||||
| require('../').create({ |  | ||||||
| 
 |  | ||||||
|   server: 'staging' |  | ||||||
| 
 |  | ||||||
| , email: 'john.doe@example.com' |  | ||||||
| 
 |  | ||||||
| , agreeTos: true |  | ||||||
| 
 |  | ||||||
| , approvedDomains: [ 'example.com', 'www.example.com' ] |  | ||||||
| 
 |  | ||||||
| , app: require('express')().use('/', function (req, res) { |  | ||||||
|     res.end('Hello, World!'); |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
| , debug: true |  | ||||||
| 
 |  | ||||||
| }).listen(80, 443); |  | ||||||
							
								
								
									
										49
									
								
								examples/socket.io/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/socket.io/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | // First and foremost:
 | ||||||
|  | // I'm not a fan of `socket.io` because it's huge and complex.
 | ||||||
|  | // I much prefer `ws` because it's very simple and easy.
 | ||||||
|  | // That said, it's popular.......
 | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // Note: You DO NOT NEED socket.io
 | ||||||
|  | //       You can just use WebSockets
 | ||||||
|  | //       (see the websocket example)
 | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	var socketio = require("socket.io"); | ||||||
|  | 	var io; | ||||||
|  | 
 | ||||||
|  | 	// we need the raw https server
 | ||||||
|  | 	var server = glx.httpsServer(); | ||||||
|  | 
 | ||||||
|  | 	io = socketio(server); | ||||||
|  | 
 | ||||||
|  | 	// Then you do your socket.io stuff
 | ||||||
|  | 	io.on("connection", function(socket) { | ||||||
|  | 		console.log("a user connected"); | ||||||
|  | 		socket.emit("Welcome"); | ||||||
|  | 
 | ||||||
|  | 		socket.on("chat message", function(msg) { | ||||||
|  | 			socket.broadcast.emit("chat message", msg); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// servers a node app that proxies requests to a localhost
 | ||||||
|  | 	glx.serveApp(function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "socket-io-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										3
									
								
								examples/spdy/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/spdy/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | // SPDY is dead. It was replaced by HTTP2, which is a native node module
 | ||||||
|  | //
 | ||||||
|  | // Greenlock uses HTTP2 as the default https server in node v12+
 | ||||||
							
								
								
									
										42
									
								
								examples/websockets/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								examples/websockets/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | function httpsWorker(glx) { | ||||||
|  | 	// we need the raw https server
 | ||||||
|  | 	var server = glx.httpsServer(); | ||||||
|  | 	var WebSocket = require("ws"); | ||||||
|  | 	var ws = new WebSocket.Server({ server: server }); | ||||||
|  | 	ws.on("connection", function(ws, req) { | ||||||
|  | 		// inspect req.headers.authorization (or cookies) for session info
 | ||||||
|  | 		ws.send( | ||||||
|  | 			"[Secure Echo Server] Hello!\nAuth: '" + | ||||||
|  | 				(req.headers.authorization || "none") + | ||||||
|  | 				"'\n" + | ||||||
|  | 				"Cookie: '" + | ||||||
|  | 				(req.headers.cookie || "none") + | ||||||
|  | 				"'\n" | ||||||
|  | 		); | ||||||
|  | 		ws.on("message", function(data) { | ||||||
|  | 			ws.send(data); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// servers a node app that proxies requests to a localhost
 | ||||||
|  | 	glx.serveApp(function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var pkg = require("../../package.json"); | ||||||
|  | //require("greenlock-express")
 | ||||||
|  | require("../../") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config
 | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			package: { name: "websocket-example", version: pkg.version }, | ||||||
|  | 			maintainerEmail: "jon@example.com", | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
							
								
								
									
										44
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | require("./lib/compat"); | ||||||
|  | var cluster = require("cluster"); | ||||||
|  | 
 | ||||||
|  | // Greenlock Express
 | ||||||
|  | var GLE = module.exports; | ||||||
|  | 
 | ||||||
|  | // Node's cluster is awesome, because it encourages writing scalable services.
 | ||||||
|  | //
 | ||||||
|  | // The point of this provide an API that is consistent between single-process
 | ||||||
|  | // and multi-process services so that beginners can more easily take advantage
 | ||||||
|  | // of what cluster has to offer.
 | ||||||
|  | //
 | ||||||
|  | // This API provides just enough abstraction to make it easy, but leaves just
 | ||||||
|  | // enough hoopla so that there's not a large gap in understanding what happens
 | ||||||
|  | // under the hood. That's the hope, anyway.
 | ||||||
|  | 
 | ||||||
|  | GLE.init = function(fn) { | ||||||
|  | 	if (cluster.isWorker) { | ||||||
|  | 		// ignore the init function and launch the worker
 | ||||||
|  | 		return require("./worker.js").create(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var opts = fn(); | ||||||
|  | 	if (!opts || "object" !== typeof opts) { | ||||||
|  | 		throw new Error( | ||||||
|  | 			"the `Greenlock.init(fn)` function should return an object `{ maintainerEmail, packageAgent, notify }`" | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// just for ironic humor
 | ||||||
|  | 	["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) { | ||||||
|  | 		if (opts[k]) { | ||||||
|  | 			opts.cluster = true; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (opts.cluster) { | ||||||
|  | 		return require("./master.js").create(opts); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return require("./single.js").create(opts); | ||||||
|  | }; | ||||||
							
								
								
									
										119
									
								
								greenlock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								greenlock.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | module.exports.create = function(opts) { | ||||||
|  | 	opts = parsePackage(opts); | ||||||
|  | 	opts.packageAgent = addGreenlockAgent(opts); | ||||||
|  | 
 | ||||||
|  | 	var Greenlock = require("@root/greenlock"); | ||||||
|  | 	var greenlock = Greenlock.create(opts); | ||||||
|  | 
 | ||||||
|  | 	// TODO move to greenlock proper
 | ||||||
|  | 	greenlock.getAcmeHttp01ChallengeResponse = function(opts) { | ||||||
|  | 		// TODO some sort of caching to prevent database hits?
 | ||||||
|  | 		return greenlock | ||||||
|  | 			._config({ servername: opts.servername }) | ||||||
|  | 			.then(function(site) { | ||||||
|  | 				if (!site) { | ||||||
|  | 					return null; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// Hmm... this _should_ be impossible
 | ||||||
|  | 				if (!site.challenges || !site.challenges["http-01"]) { | ||||||
|  | 					return null; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				return Greenlock._loadChallenge(site.challenges, "http-01"); | ||||||
|  | 			}) | ||||||
|  | 			.then(function(plugin) { | ||||||
|  | 				return plugin | ||||||
|  | 					.get({ | ||||||
|  | 						challenge: { | ||||||
|  | 							type: opts.type, | ||||||
|  | 							//hostname: opts.servername,
 | ||||||
|  | 							altname: opts.servername, | ||||||
|  | 							identifier: { value: opts.servername }, | ||||||
|  | 							token: opts.token | ||||||
|  | 						} | ||||||
|  | 					}) | ||||||
|  | 					.then(function(result) { | ||||||
|  | 						var keyAuth; | ||||||
|  | 						if (result) { | ||||||
|  | 							// backwards compat that shouldn't be dropped
 | ||||||
|  | 							// because new v3 modules had to do this to be
 | ||||||
|  | 							// backwards compatible with Greenlock v2.7 at
 | ||||||
|  | 							// the time.
 | ||||||
|  | 							if (result.challenge) { | ||||||
|  | 								result = challenge; | ||||||
|  | 							} | ||||||
|  | 							keyAuth = result.keyAuthorization; | ||||||
|  | 						} | ||||||
|  | 						return { | ||||||
|  | 							keyAuthorization: keyAuth | ||||||
|  | 						}; | ||||||
|  | 					}); | ||||||
|  | 			}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	return greenlock; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function addGreenlockAgent(opts) { | ||||||
|  | 	// Add greenlock as part of Agent, unless this is greenlock
 | ||||||
|  | 	var packageAgent = opts.packageAgent || ""; | ||||||
|  | 	if (!/greenlock(-express|-pro)?/i.test(packageAgent)) { | ||||||
|  | 		var pkg = require("./package.json"); | ||||||
|  | 		packageAgent += " Greenlock_Express/" + pkg.version; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return packageAgent.trim(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ex: "John Doe <john@example.com> (https://john.doe)"
 | ||||||
|  | // ex: "John Doe <john@example.com>"
 | ||||||
|  | // ex: "<john@example.com>"
 | ||||||
|  | // ex: "john@example.com"
 | ||||||
|  | var looseEmailRe = /(^|[\s<])([^'" <>:;`]+@[^'" <>:;`]+\.[^'" <>:;`]+)/; | ||||||
|  | function parsePackage(opts) { | ||||||
|  | 	// 'package' is sometimes a reserved word
 | ||||||
|  | 	var pkg = opts.package || opts.pkg; | ||||||
|  | 	if (!pkg) { | ||||||
|  | 		opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); | ||||||
|  | 		return opts; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!opts.packageAgent) { | ||||||
|  | 		var err = "missing `package.THING`, which is used for the ACME client user agent string"; | ||||||
|  | 		if (!pkg.name) { | ||||||
|  | 			throw new Error(err.replace("THING", "name")); | ||||||
|  | 		} | ||||||
|  | 		if (!pkg.version) { | ||||||
|  | 			throw new Error(err.replace("THING", "version")); | ||||||
|  | 		} | ||||||
|  | 		opts.packageAgent = pkg.name + "/" + pkg.version; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!opts.maintainerEmail) { | ||||||
|  | 		try { | ||||||
|  | 			opts.maintainerEmail = pkg.author.email || pkg.author.match(looseEmailRe)[2]; | ||||||
|  | 		} catch (e) {} | ||||||
|  | 	} | ||||||
|  | 	if (!opts.maintainerEmail) { | ||||||
|  | 		throw new Error("missing or malformed `package.author`, which is used as the contact for support notices"); | ||||||
|  | 	} | ||||||
|  | 	opts.package = undefined; | ||||||
|  | 	opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); | ||||||
|  | 
 | ||||||
|  | 	return opts; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function parseMaintainer(maintainerEmail) { | ||||||
|  | 	try { | ||||||
|  | 		maintainerEmail = maintainerEmail.match(looseEmailRe)[2]; | ||||||
|  | 	} catch (e) { | ||||||
|  | 		maintainerEmail = null; | ||||||
|  | 	} | ||||||
|  | 	if (!maintainerEmail) { | ||||||
|  | 		throw new Error("missing or malformed `maintainerEmail`, which is used as the contact for support notices"); | ||||||
|  | 	} | ||||||
|  | 	return maintainerEmail; | ||||||
|  | } | ||||||
							
								
								
									
										106
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var HttpMiddleware = module.exports; | ||||||
|  | var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||||
|  | var challengePrefix = "/.well-known/acme-challenge/"; | ||||||
|  | 
 | ||||||
|  | HttpMiddleware.create = function(gl, defaultApp) { | ||||||
|  | 	if (defaultApp && "function" !== typeof defaultApp) { | ||||||
|  | 		throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return function(req, res, next) { | ||||||
|  | 		var hostname = HttpMiddleware.sanitizeHostname(req); | ||||||
|  | 
 | ||||||
|  | 		req.on("error", function(err) { | ||||||
|  | 			explainError(gl, err, "http_01_middleware_socket", hostname); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		if (skipIfNeedBe(req, res, next, defaultApp, hostname)) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var token = req.url.slice(challengePrefix.length); | ||||||
|  | 
 | ||||||
|  | 		gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token }) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); | ||||||
|  | 				return { __done: true }; | ||||||
|  | 			}) | ||||||
|  | 			.then(function(result) { | ||||||
|  | 				if (result && result.__done) { | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 				return respondWithGrace(res, result, hostname, token); | ||||||
|  | 			}); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function skipIfNeedBe(req, res, next, defaultApp, hostname) { | ||||||
|  | 	if (!hostname || 0 !== req.url.indexOf(challengePrefix)) { | ||||||
|  | 		if ("function" === typeof defaultApp) { | ||||||
|  | 			defaultApp(req, res, next); | ||||||
|  | 		} else if ("function" === typeof next) { | ||||||
|  | 			next(); | ||||||
|  | 		} else { | ||||||
|  | 			res.statusCode = 500; | ||||||
|  | 			res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function respondWithGrace(res, result, hostname, token) { | ||||||
|  | 	var keyAuth = result && result.keyAuthorization; | ||||||
|  | 	if (keyAuth && "string" === typeof keyAuth) { | ||||||
|  | 		res.setHeader("Content-Type", "text/plain; charset=utf-8"); | ||||||
|  | 		res.end(keyAuth); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	res.statusCode = 404; | ||||||
|  | 	res.setHeader("Content-Type", "application/json; charset=utf-8"); | ||||||
|  | 	res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } })); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function explainError(gl, err, ctx, hostname) { | ||||||
|  | 	if (!err.servername) { | ||||||
|  | 		err.servername = hostname; | ||||||
|  | 	} | ||||||
|  | 	if (!err.context) { | ||||||
|  | 		err.context = ctx; | ||||||
|  | 	} | ||||||
|  | 	(gl.notify || gl._notify)("error", err); | ||||||
|  | 	return err; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function respondToError(gl, res, err, ctx, hostname) { | ||||||
|  | 	err = explainError(gl, err, ctx, hostname); | ||||||
|  | 	res.statusCode = 500; | ||||||
|  | 	res.end("Internal Server Error: See logs for details."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | HttpMiddleware.getHostname = function(req) { | ||||||
|  | 	return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || ""); | ||||||
|  | }; | ||||||
|  | HttpMiddleware.sanitizeHostname = function(req) { | ||||||
|  | 	// we can trust XFH because spoofing causes no ham in this limited use-case scenario
 | ||||||
|  | 	// (and only telebit would be legitimately setting XFH)
 | ||||||
|  | 	var servername = HttpMiddleware.getHostname(req) | ||||||
|  | 		.toLowerCase() | ||||||
|  | 		.replace(/:.*/, ""); | ||||||
|  | 	try { | ||||||
|  | 		req.hostname = servername; | ||||||
|  | 	} catch (e) { | ||||||
|  | 		// read-only express property
 | ||||||
|  | 	} | ||||||
|  | 	if (req.headers["x-forwarded-host"]) { | ||||||
|  | 		req.headers["x-forwarded-host"] = servername; | ||||||
|  | 	} | ||||||
|  | 	try { | ||||||
|  | 		req.headers.host = servername; | ||||||
|  | 	} catch (e) { | ||||||
|  | 		// TODO is this a possible error?
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || ""; | ||||||
|  | }; | ||||||
							
								
								
									
										139
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var SanitizeHost = module.exports; | ||||||
|  | var HttpMiddleware = require("./http-middleware.js"); | ||||||
|  | 
 | ||||||
|  | SanitizeHost.create = function(gl, app) { | ||||||
|  | 	return function(req, res, next) { | ||||||
|  | 		function realNext() { | ||||||
|  | 			if ("function" === typeof app) { | ||||||
|  | 				app(req, res); | ||||||
|  | 			} else if ("function" === typeof next) { | ||||||
|  | 				next(); | ||||||
|  | 			} else { | ||||||
|  | 				res.statusCode = 500; | ||||||
|  | 				res.end("Error: no middleware assigned"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var hostname = HttpMiddleware.getHostname(req); | ||||||
|  | 		// Replace the hostname, and get the safe version
 | ||||||
|  | 		var safehost = HttpMiddleware.sanitizeHostname(req); | ||||||
|  | 
 | ||||||
|  | 		// if no hostname, move along
 | ||||||
|  | 		if (!hostname) { | ||||||
|  | 			realNext(); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// if there were unallowed characters, complain
 | ||||||
|  | 		if (safehost.length !== hostname.length) { | ||||||
|  | 			res.statusCode = 400; | ||||||
|  | 			res.end("Malformed HTTP Header: 'Host: " + hostname + "'"); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 | ||||||
|  | 		if (req.socket.encrypted) { | ||||||
|  | 			if (req.socket && "string" === typeof req.socket.servername) { | ||||||
|  | 				// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||||
|  | 				if (!SanitizeHost._checkServername(safehost, req.socket)) { | ||||||
|  | 					res.statusCode = 400; | ||||||
|  | 					res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 					res.end( | ||||||
|  | 						"<h1>Domain Fronting Error</h1>" + | ||||||
|  | 							"<p>This connection was secured using TLS/SSL for '" + | ||||||
|  | 							(req.socket.servername || "").toLowerCase() + | ||||||
|  | 							"'</p>" + | ||||||
|  | 							"<p>The HTTP request specified 'Host: " + | ||||||
|  | 							safehost + | ||||||
|  | 							"', which is (obviously) different.</p>" + | ||||||
|  | 							"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>" | ||||||
|  | 					); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			/* | ||||||
|  |       else if (safehost && !gl._skip_fronting_check) { | ||||||
|  | 
 | ||||||
|  | 				// We used to print a log message here, but it turns out that it's
 | ||||||
|  | 				// really common for IoT devices to not use SNI (as well as many bots
 | ||||||
|  | 				// and such).
 | ||||||
|  | 				// It was common for the log message to pop up as the first request
 | ||||||
|  | 				// to the server, and that was confusing. So instead now we do nothing.
 | ||||||
|  | 
 | ||||||
|  | 				//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
 | ||||||
|  | 				//gl._skip_fronting_check = true;
 | ||||||
|  | 			} | ||||||
|  |       */ | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// carry on
 | ||||||
|  | 		realNext(); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var warnDomainFronting = true; | ||||||
|  | var warnUnexpectedError = true; | ||||||
|  | SanitizeHost._checkServername = function(safeHost, tlsSocket) { | ||||||
|  | 	var servername = (tlsSocket.servername || "").toLowerCase(); | ||||||
|  | 
 | ||||||
|  | 	// acceptable: older IoT devices may lack SNI support
 | ||||||
|  | 	if (!servername) { | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	// acceptable: odd... but acceptable
 | ||||||
|  | 	if (!safeHost) { | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	if (safeHost === servername) { | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ("function" !== typeof tlsSocket.getCertificate) { | ||||||
|  | 		// domain fronting attacks allowed
 | ||||||
|  | 		if (warnDomainFronting) { | ||||||
|  | 			// https://github.com/nodejs/node/issues/24095
 | ||||||
|  | 			console.warn( | ||||||
|  | 				"Warning: node " + | ||||||
|  | 					process.version + | ||||||
|  | 					" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater." | ||||||
|  | 			); | ||||||
|  | 			warnDomainFronting = false; | ||||||
|  | 		} | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// connection established with servername and session is re-used for allowed name
 | ||||||
|  | 	// See https://github.com/nodejs/node/issues/24095
 | ||||||
|  | 	var cert = tlsSocket.getCertificate(); | ||||||
|  | 	try { | ||||||
|  | 		// TODO optimize / cache?
 | ||||||
|  | 		// *should* always have a string, right?
 | ||||||
|  | 		// *should* always be lowercase already, right?
 | ||||||
|  | 		//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
 | ||||||
|  | 		var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost; | ||||||
|  | 		if (isSubject) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var dnsnames = (cert.subjectaltname || "").split(/,\s+/); | ||||||
|  | 		var inSanList = dnsnames.some(function(name) { | ||||||
|  | 			// always prefixed with "DNS:"
 | ||||||
|  | 			return safeHost === name.slice(4).toLowerCase(); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		if (inSanList) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	} catch (e) { | ||||||
|  | 		// not sure what else to do in this situation...
 | ||||||
|  | 		if (warnUnexpectedError) { | ||||||
|  | 			console.warn("Warning: encoutered error while performing domain fronting check: " + e.message); | ||||||
|  | 			warnUnexpectedError = false; | ||||||
|  | 		} | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false; | ||||||
|  | }; | ||||||
							
								
								
									
										14
									
								
								install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								install.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | # This is just an example (but it works) | ||||||
|  | export NODE_PATH=$NPM_CONFIG_PREFIX/lib/node_modules | ||||||
|  | export NPM_CONFIG_PREFIX=/opt/node | ||||||
|  | curl -fsSL https://bit.ly/node-installer | bash | ||||||
|  | 
 | ||||||
|  | /opt/node/bin/node /opt/node/bin/npm config set scripts-prepend-node-path true | ||||||
|  | /opt/node/bin/node /opt/node/bin/npm ci | ||||||
|  | sudo setcap 'cap_net_bind_service=+ep' /opt/node/bin/node | ||||||
|  | /opt/node/bin/node /opt/node/bin/npm start | ||||||
|  | 
 | ||||||
|  | sudo rsync -av dist/etc/systemd/system/greenlock-express.service /etc/systemd/system/ | ||||||
|  | sudo systemctl daemon-reload | ||||||
|  | 
 | ||||||
|  | sudo systemctl restart greenlock-express | ||||||
							
								
								
									
										66
									
								
								lex.js
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								lex.js
									
									
									
									
									
								
							| @ -1,66 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| // opts.approveDomains(options, certs, cb)
 |  | ||||||
| module.exports.create = function (opts) { |  | ||||||
|   // accept all defaults for le.challenges, le.store, le.middleware
 |  | ||||||
|   var le = require('greenlock').create(opts); |  | ||||||
| 
 |  | ||||||
|   opts.app = opts.app || function (req, res) { |  | ||||||
|     res.end("Hello, World!\nWith Love,\nLet's Encrypt Express"); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   opts.listen = function (plainPort, port) { |  | ||||||
|     var PromiseA; |  | ||||||
|     try { |  | ||||||
|       PromiseA = require('bluebird'); |  | ||||||
|     } catch(e) { |  | ||||||
|       console.warn("Package 'bluebird' not installed. Using global.Promise instead"); |  | ||||||
|       console.warn("(want bluebird instead? npm install --save bluebird)"); |  | ||||||
|       PromiseA = global.Promise; |  | ||||||
|     } |  | ||||||
|     var promises = []; |  | ||||||
|     var plainPorts = plainPort; |  | ||||||
|     var ports = port; |  | ||||||
|     var servers = []; |  | ||||||
| 
 |  | ||||||
|     if (!plainPorts) { |  | ||||||
|       plainPorts = 80; |  | ||||||
|     } |  | ||||||
|     if (!ports) { |  | ||||||
|       ports = 443; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!Array.isArray(plainPorts)) { |  | ||||||
|       plainPorts = [ plainPorts ]; |  | ||||||
|       ports = [ ports ]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     plainPorts.forEach(function (p) { |  | ||||||
|       promises.push(new PromiseA(function (resolve, reject) { |  | ||||||
|         require('http').createServer(le.middleware(require('redirect-https')())).listen(p, function () { |  | ||||||
|           console.log("Handling ACME challenges and redirecting to https on plain port " + p); |  | ||||||
|           resolve(); |  | ||||||
|         }).on('error', reject); |  | ||||||
|       })); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     ports.forEach(function (p) { |  | ||||||
|       promises.push(new PromiseA(function (resolve, reject) { |  | ||||||
|         var server = require('https').createServer(le.httpsOptions, le.middleware(le.app)).listen(p, function () { |  | ||||||
|           console.log("Handling ACME challenges and serving https " + p); |  | ||||||
|           resolve(); |  | ||||||
|         }).on('error', reject); |  | ||||||
|         servers.push(server); |  | ||||||
|       })); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     if (!Array.isArray(port)) { |  | ||||||
|       servers = servers[0]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return servers; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   return le; |  | ||||||
| }; |  | ||||||
							
								
								
									
										37
									
								
								lib/compat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/compat.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | function requireBluebird() { | ||||||
|  | 	try { | ||||||
|  | 		return require("bluebird"); | ||||||
|  | 	} catch (e) { | ||||||
|  | 		console.error(""); | ||||||
|  | 		console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); | ||||||
|  | 		console.error("EASY FIX: `npm install --save bluebird`"); | ||||||
|  | 		console.error(""); | ||||||
|  | 		throw e; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if ("undefined" === typeof Promise) { | ||||||
|  | 	global.Promise = requireBluebird(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if ("function" !== typeof require("util").promisify) { | ||||||
|  | 	require("util").promisify = requireBluebird().promisify; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (!console.debug) { | ||||||
|  | 	console.debug = console.log; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var fs = require("fs"); | ||||||
|  | var fsAsync = {}; | ||||||
|  | Object.keys(fs).forEach(function(key) { | ||||||
|  | 	var fn = fs[key]; | ||||||
|  | 	if ("function" !== typeof fn || !/[a-z]/.test(key[0])) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	fsAsync[key] = require("util").promisify(fn); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | exports.fsAsync = fsAsync; | ||||||
							
								
								
									
										36
									
								
								main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // this is the stuff that should run in the main foreground process,
 | ||||||
|  | // whether it's single or master
 | ||||||
|  | 
 | ||||||
|  | var major = process.versions.node.split(".")[0]; | ||||||
|  | var minor = process.versions.node.split(".")[1]; | ||||||
|  | var _hasSetSecureContext = false; | ||||||
|  | var shouldUpgrade = false; | ||||||
|  | 
 | ||||||
|  | // TODO can we trust earlier versions as well?
 | ||||||
|  | if (major >= 12) { | ||||||
|  | 	_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext; | ||||||
|  | } else { | ||||||
|  | 	_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO document in issues
 | ||||||
|  | if (!_hasSetSecureContext) { | ||||||
|  | 	// TODO this isn't necessary if greenlock options are set with options.cert
 | ||||||
|  | 	console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext()."); | ||||||
|  | 	console.warn("         The default certificate may not be set."); | ||||||
|  | 	shouldUpgrade = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (major < 11 || (11 === major && minor < 2)) { | ||||||
|  | 	// https://github.com/nodejs/node/issues/24095
 | ||||||
|  | 	console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate()."); | ||||||
|  | 	console.warn("         This is necessary to guard against domain fronting attacks."); | ||||||
|  | 	shouldUpgrade = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (shouldUpgrade) { | ||||||
|  | 	console.warn("Warning: Please upgrade to node v11.2.0 or greater."); | ||||||
|  | 	console.warn(); | ||||||
|  | } | ||||||
							
								
								
									
										160
									
								
								master.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								master.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | require("./main.js"); | ||||||
|  | 
 | ||||||
|  | var Master = module.exports; | ||||||
|  | 
 | ||||||
|  | var cluster = require("cluster"); | ||||||
|  | var os = require("os"); | ||||||
|  | var msgPrefix = "greenlock:"; | ||||||
|  | 
 | ||||||
|  | Master.create = function(opts) { | ||||||
|  | 	var resolveCb; | ||||||
|  | 	var _readyCb; | ||||||
|  | 	var _kicked = false; | ||||||
|  | 
 | ||||||
|  | 	var greenlock = require("./greenlock.js").create(opts); | ||||||
|  | 
 | ||||||
|  | 	var ready = new Promise(function(resolve) { | ||||||
|  | 		resolveCb = resolve; | ||||||
|  | 	}).then(function(fn) { | ||||||
|  | 		_readyCb = fn; | ||||||
|  | 		return fn; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	function kickoff() { | ||||||
|  | 		if (_kicked) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		_kicked = true; | ||||||
|  | 
 | ||||||
|  | 		Master._spawnWorkers(opts, greenlock); | ||||||
|  | 
 | ||||||
|  | 		ready.then(function(fn) { | ||||||
|  | 			// not sure what this API should be yet
 | ||||||
|  | 			fn(); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var master = { | ||||||
|  | 		serve: function() { | ||||||
|  | 			kickoff(); | ||||||
|  | 			return master; | ||||||
|  | 		}, | ||||||
|  | 		master: function(fn) { | ||||||
|  | 			if (_readyCb) { | ||||||
|  | 				throw new Error("can't call master twice"); | ||||||
|  | 			} | ||||||
|  | 			kickoff(); | ||||||
|  | 			resolveCb(fn); | ||||||
|  | 			return master; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	return master; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function range(n) { | ||||||
|  | 	n = parseInt(n, 10); | ||||||
|  | 	if (!n) { | ||||||
|  | 		return []; | ||||||
|  | 	} | ||||||
|  | 	return new Array(n).join(",").split(","); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Master._spawnWorkers = function(opts, greenlock) { | ||||||
|  | 	var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length; | ||||||
|  | 
 | ||||||
|  | 	// process rpc messages
 | ||||||
|  | 	// start when dead
 | ||||||
|  | 	var numWorkers = parseInt(opts.workers || opts.numWorkers, 10); | ||||||
|  | 	if (!numWorkers) { | ||||||
|  | 		if (numCpus <= 2) { | ||||||
|  | 			numWorkers = 2; | ||||||
|  | 		} else { | ||||||
|  | 			numWorkers = numCpus - 1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cluster.once("exit", function() { | ||||||
|  | 		setTimeout(function() { | ||||||
|  | 			process.exit(3); | ||||||
|  | 		}, 100); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	var workers = range(numWorkers); | ||||||
|  | 	function next() { | ||||||
|  | 		if (!workers.length) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		workers.pop(); | ||||||
|  | 
 | ||||||
|  | 		// for a nice aesthetic
 | ||||||
|  | 		setTimeout(function() { | ||||||
|  | 			Master._spawnWorker(opts, greenlock); | ||||||
|  | 			next(); | ||||||
|  | 		}, 250); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	next(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Master._spawnWorker = function(opts, greenlock) { | ||||||
|  | 	var w = cluster.fork(); | ||||||
|  | 	// automatically added to master's `cluster.workers`
 | ||||||
|  | 	w.once("exit", function(code, signal) { | ||||||
|  | 		// TODO handle failures
 | ||||||
|  | 		// Should test if the first starts successfully
 | ||||||
|  | 		// Should exit if failures happen too quickly
 | ||||||
|  | 
 | ||||||
|  | 		// For now just kill all when any die
 | ||||||
|  | 		if (signal) { | ||||||
|  | 			console.error("worker was killed by signal:", signal); | ||||||
|  | 		} else if (code !== 0) { | ||||||
|  | 			console.error("worker exited with error code:", code); | ||||||
|  | 		} else { | ||||||
|  | 			console.error("worker unexpectedly quit without exit code or signal"); | ||||||
|  | 		} | ||||||
|  | 		process.exit(2); | ||||||
|  | 
 | ||||||
|  | 		//addWorker();
 | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	function handleMessage(msg) { | ||||||
|  | 		if (0 !== (msg._id || "").indexOf(msgPrefix)) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		if ("string" !== typeof msg._funcname) { | ||||||
|  | 			// TODO developer error
 | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		function rpc() { | ||||||
|  | 			return greenlock[msg._funcname](msg._input) | ||||||
|  | 				.then(function(result) { | ||||||
|  | 					w.send({ | ||||||
|  | 						_id: msg._id, | ||||||
|  | 						_result: result | ||||||
|  | 					}); | ||||||
|  | 				}) | ||||||
|  | 				.catch(function(e) { | ||||||
|  | 					var error = new Error(e.message); | ||||||
|  | 					Object.getOwnPropertyNames(e).forEach(function(k) { | ||||||
|  | 						error[k] = e[k]; | ||||||
|  | 					}); | ||||||
|  | 					w.send({ | ||||||
|  | 						_id: msg._id, | ||||||
|  | 						_error: error | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		try { | ||||||
|  | 			rpc(); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:"); | ||||||
|  | 			console.error(e); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	w.on("message", handleMessage); | ||||||
|  | }; | ||||||
							
								
								
									
										140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | { | ||||||
|  | 	"name": "@root/greenlock-express", | ||||||
|  | 	"version": "3.0.7", | ||||||
|  | 	"lockfileVersion": 1, | ||||||
|  | 	"requires": true, | ||||||
|  | 	"dependencies": { | ||||||
|  | 		"@root/acme": { | ||||||
|  | 			"version": "3.0.8", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.8.tgz", | ||||||
|  | 			"integrity": "sha512-VmBvLvWdCDkolkanI9Dzm1ouSWPaAa2eCCwcDZcVQbWoNiUIOqbbd57fcMA/gZxLyuJPStD2WXFuEuSMPDxcww==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/encoding": "^1.0.1", | ||||||
|  | 				"@root/keypairs": "^0.9.0", | ||||||
|  | 				"@root/pem": "^1.0.4", | ||||||
|  | 				"@root/request": "^1.3.11", | ||||||
|  | 				"@root/x509": "^0.7.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"@root/asn1": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/encoding": "^1.0.1" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"@root/csr": { | ||||||
|  | 			"version": "0.8.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz", | ||||||
|  | 			"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/asn1": "^1.0.0", | ||||||
|  | 				"@root/pem": "^1.0.4", | ||||||
|  | 				"@root/x509": "^0.7.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"@root/encoding": { | ||||||
|  | 			"version": "1.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", | ||||||
|  | 			"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" | ||||||
|  | 		}, | ||||||
|  | 		"@root/greenlock": { | ||||||
|  | 			"version": "3.0.17", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.17.tgz", | ||||||
|  | 			"integrity": "sha512-1XKhcLFEx1WFdn1Bc2rkAE/SL1ZUJYYMZdbnehTrfhCr5Y+9U1gdkNZnR/jInhoUvcicF/PXuZkGVucU50RNUg==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/acme": "^3.0.8", | ||||||
|  | 				"@root/csr": "^0.8.1", | ||||||
|  | 				"@root/keypairs": "^0.9.0", | ||||||
|  | 				"@root/mkdirp": "^1.0.0", | ||||||
|  | 				"@root/request": "^1.3.10", | ||||||
|  | 				"acme-http-01-standalone": "^3.0.5", | ||||||
|  | 				"cert-info": "^1.5.1", | ||||||
|  | 				"greenlock-manager-fs": "^3.0.1", | ||||||
|  | 				"greenlock-store-fs": "^3.2.0", | ||||||
|  | 				"safe-replace": "^1.1.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"@root/keypairs": { | ||||||
|  | 			"version": "0.9.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz", | ||||||
|  | 			"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/encoding": "^1.0.1", | ||||||
|  | 				"@root/pem": "^1.0.4", | ||||||
|  | 				"@root/x509": "^0.7.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"@root/mkdirp": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||||
|  | 		}, | ||||||
|  | 		"@root/pem": { | ||||||
|  | 			"version": "1.0.4", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz", | ||||||
|  | 			"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" | ||||||
|  | 		}, | ||||||
|  | 		"@root/request": { | ||||||
|  | 			"version": "1.4.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", | ||||||
|  | 			"integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" | ||||||
|  | 		}, | ||||||
|  | 		"@root/x509": { | ||||||
|  | 			"version": "0.7.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz", | ||||||
|  | 			"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/asn1": "^1.0.0", | ||||||
|  | 				"@root/encoding": "^1.0.1" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"acme-http-01-standalone": { | ||||||
|  | 			"version": "3.0.5", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz", | ||||||
|  | 			"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg==" | ||||||
|  | 		}, | ||||||
|  | 		"cert-info": { | ||||||
|  | 			"version": "1.5.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", | ||||||
|  | 			"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" | ||||||
|  | 		}, | ||||||
|  | 		"escape-html": { | ||||||
|  | 			"version": "1.0.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | ||||||
|  | 			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" | ||||||
|  | 		}, | ||||||
|  | 		"greenlock-manager-fs": { | ||||||
|  | 			"version": "3.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.1.tgz", | ||||||
|  | 			"integrity": "sha512-vZfGFq1TTKxaAqdGDUwNservrNzXx0xCwT/ovG/N378GrhS+U5S8B8LUlNtQU7Fdw6RToMiBcm22OOxSrvZ2zw==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/mkdirp": "^1.0.0", | ||||||
|  | 				"safe-replace": "^1.1.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"greenlock-store-fs": { | ||||||
|  | 			"version": "3.2.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.0.tgz", | ||||||
|  | 			"integrity": "sha512-zqcPnF+173oYq5qU7FoGtuqeG8dmmvAiSnz98kEHAHyvgRF9pE1T0MM0AuqDdj45I3kXlCj2gZBwutnRi37J3g==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/mkdirp": "^1.0.0", | ||||||
|  | 				"safe-replace": "^1.1.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"redirect-https": { | ||||||
|  | 			"version": "1.3.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", | ||||||
|  | 			"integrity": "sha512-9GzwI/+Cqw3jlSg0CW6TgBQbhiVhkHSDvW8wjgRQ9IK34wtxS71YJiQeazSCSEqbvowHCJuQZgmQFl1xUHKEgg==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"escape-html": "^1.0.3" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"safe-replace": { | ||||||
|  | 			"version": "1.1.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||||
|  | 			"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								package.json
									
									
									
									
									
								
							| @ -1,47 +1,51 @@ | |||||||
| { | { | ||||||
|   "name": "greenlock-express", | 	"name": "@root/greenlock-express", | ||||||
|   "version": "2.0.7", | 	"version": "3.0.10", | ||||||
| 	"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", | 	"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", | ||||||
|   "main": "lex.js", | 	"main": "greenlock-express.js", | ||||||
|  | 	"homepage": "https://greenlock.domains", | ||||||
|  | 	"files": [ | ||||||
|  | 		"*.js", | ||||||
|  | 		"lib", | ||||||
|  | 		"scripts" | ||||||
|  | 	], | ||||||
|  | 	"scripts": { | ||||||
|  | 		"start": "node_todo server.js ./config.js", | ||||||
|  | 		"test": "node_todo test/greenlock.js" | ||||||
|  | 	}, | ||||||
| 	"directories": { | 	"directories": { | ||||||
| 		"example": "examples" | 		"example": "examples" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
|     "le-challenge-fs": "^2.0.4", | 		"@root/greenlock": "^3.0.17", | ||||||
|     "le-sni-auto": "^2.0.1", | 		"redirect-https": "^1.1.5" | ||||||
|     "le-store-certbot": "^2.0.3", | 	}, | ||||||
|     "greenlock": "^2.1.9", | 	"trulyOptionalDependencies": { | ||||||
|     "localhost.daplie.com-certificates": "^1.2.3", | 		"http-proxy": "^1.17.0", | ||||||
|     "redirect-https": "^1.1.0" | 		"express": "^4.16.3", | ||||||
|  | 		"express-basic-auth": "^1.2.0", | ||||||
|  | 		"finalhandler": "^1.1.1", | ||||||
|  | 		"serve-index": "^1.9.1", | ||||||
|  | 		"serve-static": "^1.13.2", | ||||||
|  | 		"ws": "^5.2.1" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": {}, | 	"devDependencies": {}, | ||||||
|   "scripts": { |  | ||||||
|     "test": "node examples/serve.js" |  | ||||||
|   }, |  | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
|     "url": "git+https://git.daplie.com/Daplie/greenlock-cluster.git" | 		"url": "https://git.rootprojects.org/root/greenlock-express.js.git" | ||||||
| 	}, | 	}, | ||||||
| 	"keywords": [ | 	"keywords": [ | ||||||
|     "acme", | 		"Let's Encrypt", | ||||||
|     "cloud", | 		"ACME", | ||||||
|     "cluster", |  | ||||||
|     "free", |  | ||||||
| 		"greenlock", | 		"greenlock", | ||||||
|  | 		"Free SSL", | ||||||
|  | 		"Automated HTTPS", | ||||||
| 		"https", | 		"https", | ||||||
|     "le", |  | ||||||
|     "letsencrypt", |  | ||||||
|     "multi-core", |  | ||||||
|     "node", |  | ||||||
|     "node.js", |  | ||||||
|     "scale", |  | ||||||
|     "ssl", |  | ||||||
| 		"tls" | 		"tls" | ||||||
| 	], | 	], | ||||||
|   "author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)", | 	"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)", | ||||||
|   "license": "(MIT OR Apache-2.0)", | 	"license": "MPL-2.0", | ||||||
| 	"bugs": { | 	"bugs": { | ||||||
|     "url": "https://git.daplie.com/Daplie/greenlock-cluster/issues" | 		"url": "https://git.rootprojects.org/root/greenlock-express.js/issues" | ||||||
|   }, | 	} | ||||||
|   "homepage": "https://git.daplie.com/Daplie/greenlock-cluster#readme" |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										77
									
								
								scripts/postinstall
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										77
									
								
								scripts/postinstall
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | #!/usr/bin/env node | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // BG WH \u001b[47m | ||||||
|  | // BOLD  \u001b[1m | ||||||
|  | // RED   \u001b[31m | ||||||
|  | // GREEN \u001b[32m | ||||||
|  | // RESET \u001b[0m | ||||||
|  | 
 | ||||||
|  | var grabbers = [ | ||||||
|  | 	[ | ||||||
|  | 		"", | ||||||
|  | 		"================================================================================", | ||||||
|  | 		"", | ||||||
|  | 		" 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥", | ||||||
|  | 		"🔥                            🔥", | ||||||
|  | 		"🔥  Do you rely on Greenlock? 🔥", | ||||||
|  | 		"🔥                            🔥", | ||||||
|  | 		" 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥" | ||||||
|  | 	], | ||||||
|  | 
 | ||||||
|  | 	[ | ||||||
|  | 		"", | ||||||
|  | 		"================================================================================", | ||||||
|  | 		"", | ||||||
|  | 		" 🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒", | ||||||
|  | 		"🍒                              🍒", | ||||||
|  | 		"🍒  Do you rely on Greenlock?   🍒", | ||||||
|  | 		"🍒                              🍒", | ||||||
|  | 		" 🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒🍒" | ||||||
|  | 	], | ||||||
|  | 
 | ||||||
|  | 	[ | ||||||
|  | 		"", | ||||||
|  | 		"================================================================================", | ||||||
|  | 		"", | ||||||
|  | 		" 👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇", | ||||||
|  | 		"👉                             👈", | ||||||
|  | 		"👉  Do you rely on Greenlock?  👈", | ||||||
|  | 		"👉                             👈", | ||||||
|  | 		" 👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆 " | ||||||
|  | 	], | ||||||
|  | 
 | ||||||
|  | 	[ | ||||||
|  | 		"", | ||||||
|  | 		"================================================================================", | ||||||
|  | 		"", | ||||||
|  | 		" 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 ", | ||||||
|  | 		"👀                              👀", | ||||||
|  | 		"👀  Do you rely on Greenlock?   👀", | ||||||
|  | 		"👀                              👀", | ||||||
|  | 		" 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 👀 ", | ||||||
|  | 	] | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | setTimeout(function() { | ||||||
|  | 	grabbers[Math.floor(Math.random() * grabbers.length)].concat([ | ||||||
|  | 		"", | ||||||
|  | 		"Hey! Let's Encrypt will \u001b[31mSTOP WORKING\u001b[0m with Greenlock v2 at the end of October,", | ||||||
|  | 		"and \u001b[31mWITHOUT YOUR HELP\u001b[0m we won't get the next release out in time.", | ||||||
|  | 		"", | ||||||
|  | 		"If Greenlock has saved you time and money, and taken stress out of your life,", | ||||||
|  | 		"or you just love it, please reach out to return the favor today:", | ||||||
|  | 		"", | ||||||
|  | 		"\u001b[31mSAVE GREENLOCK:\u001b[0m", | ||||||
|  | 		"https://indiegogo.com/at/greenlock", | ||||||
|  | 		"", | ||||||
|  | 		"================================================================================", | ||||||
|  | 		"" | ||||||
|  | 	]).forEach(function(line) { | ||||||
|  | 		console.info(line); | ||||||
|  | 	}); | ||||||
|  | }, 300); | ||||||
|  | 
 | ||||||
|  | setTimeout(function() { | ||||||
|  | 	// give time to read | ||||||
|  | }, 1500); | ||||||
							
								
								
									
										157
									
								
								servers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								servers.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var Servers = module.exports; | ||||||
|  | 
 | ||||||
|  | var http = require("http"); | ||||||
|  | var HttpMiddleware = require("./http-middleware.js"); | ||||||
|  | var HttpsMiddleware = require("./https-middleware.js"); | ||||||
|  | var sni = require("./sni.js"); | ||||||
|  | var cluster = require("cluster"); | ||||||
|  | 
 | ||||||
|  | Servers.create = function(greenlock) { | ||||||
|  | 	var servers = {}; | ||||||
|  | 	var _httpServer; | ||||||
|  | 	var _httpsServer; | ||||||
|  | 
 | ||||||
|  | 	function startError(e) { | ||||||
|  | 		explainError(e); | ||||||
|  | 		process.exit(1); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	servers.httpServer = function(defaultApp) { | ||||||
|  | 		if (_httpServer) { | ||||||
|  | 			return _httpServer; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		_httpServer = http.createServer(HttpMiddleware.create(greenlock, defaultApp)); | ||||||
|  | 		_httpServer.once("error", startError); | ||||||
|  | 
 | ||||||
|  | 		return _httpServer; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	var _middlewareApp; | ||||||
|  | 
 | ||||||
|  | 	servers.httpsServer = function(secureOpts, defaultApp) { | ||||||
|  | 		if (defaultApp) { | ||||||
|  | 			// TODO guard against being set twice?
 | ||||||
|  | 			_middlewareApp = defaultApp; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (_httpsServer) { | ||||||
|  | 			if (secureOpts && Object.keys(secureOpts).length) { | ||||||
|  | 				throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)"); | ||||||
|  | 			} | ||||||
|  | 			return _httpsServer; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!secureOpts) { | ||||||
|  | 			secureOpts = {}; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		_httpsServer = createSecureServer( | ||||||
|  | 			wrapDefaultSniCallback(greenlock, secureOpts), | ||||||
|  | 			HttpsMiddleware.create(greenlock, function(req, res) { | ||||||
|  | 				if (!_middlewareApp) { | ||||||
|  | 					throw new Error("Set app with `glx.serveApp(app)` or `glx.httpsServer(tlsOptions, app)`"); | ||||||
|  | 				} | ||||||
|  | 				_middlewareApp(req, res); | ||||||
|  | 			}) | ||||||
|  | 		); | ||||||
|  | 		_httpsServer.once("error", startError); | ||||||
|  | 
 | ||||||
|  | 		return _httpsServer; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	servers.id = function() { | ||||||
|  | 		return (cluster.isWorker && cluster.worker.id) || "0"; | ||||||
|  | 	}; | ||||||
|  | 	servers.serveApp = function(app) { | ||||||
|  | 		return new Promise(function(resolve, reject) { | ||||||
|  | 			if ("function" !== typeof app) { | ||||||
|  | 				reject(new Error("glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`")); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var id = cluster.isWorker && cluster.worker.id; | ||||||
|  | 			var idstr = (id && "#" + id + " ") || ""; | ||||||
|  | 			var plainServer = servers.httpServer(require("redirect-https")()); | ||||||
|  | 			var plainAddr = "0.0.0.0"; | ||||||
|  | 			var plainPort = 80; | ||||||
|  | 			plainServer.listen(plainPort, plainAddr, function() { | ||||||
|  | 				console.info( | ||||||
|  | 					idstr + "Listening on", | ||||||
|  | 					plainAddr + ":" + plainPort, | ||||||
|  | 					"for ACME challenges, and redirecting to HTTPS" | ||||||
|  | 				); | ||||||
|  | 
 | ||||||
|  | 				// TODO fetch greenlock.servername
 | ||||||
|  | 				_middlewareApp = app || _middlewareApp; | ||||||
|  | 				var secureServer = servers.httpsServer(null, app); | ||||||
|  | 				var secureAddr = "0.0.0.0"; | ||||||
|  | 				var securePort = 443; | ||||||
|  | 				secureServer.listen(securePort, secureAddr, function() { | ||||||
|  | 					console.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic"); | ||||||
|  | 
 | ||||||
|  | 					plainServer.removeListener("error", startError); | ||||||
|  | 					secureServer.removeListener("error", startError); | ||||||
|  | 					resolve(); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	return servers; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function explainError(e) { | ||||||
|  | 	console.error(); | ||||||
|  | 	console.error("Error: " + e.message); | ||||||
|  | 	if ("EACCES" === e.errno) { | ||||||
|  | 		console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); | ||||||
|  | 		console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"'); | ||||||
|  | 	} else if ("EADDRINUSE" === e.errno) { | ||||||
|  | 		console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); | ||||||
|  | 		console.error("You probably need to stop that program or restart your computer."); | ||||||
|  | 	} else { | ||||||
|  | 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); | ||||||
|  | 	} | ||||||
|  | 	console.error(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function wrapDefaultSniCallback(greenlock, secureOpts) { | ||||||
|  | 	// I'm not sure yet if the original SNICallback
 | ||||||
|  | 	// should be called before or after, so I'm just
 | ||||||
|  | 	// going to delay making that choice until I have the use case
 | ||||||
|  | 	/* | ||||||
|  | 		if (!secureOpts.SNICallback) { | ||||||
|  | 			secureOpts.SNICallback = function(servername, cb) { | ||||||
|  | 				cb(null, null); | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |   */ | ||||||
|  | 	if (secureOpts.SNICallback) { | ||||||
|  | 		console.warn(); | ||||||
|  | 		console.warn("[warning] Ignoring the given tlsOptions.SNICallback function."); | ||||||
|  | 		console.warn(); | ||||||
|  | 		console.warn("          We're very open to implementing support for this,"); | ||||||
|  | 		console.warn("          we just don't understand the use case yet."); | ||||||
|  | 		console.warn("          Please open an issue to discuss. We'd love to help."); | ||||||
|  | 		console.warn(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO greenlock.servername for workers
 | ||||||
|  | 	secureOpts.SNICallback = sni.create(greenlock, secureOpts); | ||||||
|  | 	return secureOpts; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function createSecureServer(secureOpts, fn) { | ||||||
|  | 	var major = process.versions.node.split(".")[0]; | ||||||
|  | 
 | ||||||
|  | 	// TODO can we trust earlier versions as well?
 | ||||||
|  | 	if (major >= 12) { | ||||||
|  | 		secureOpts.allowHTTP1 = true; | ||||||
|  | 		return require("http2").createSecureServer(secureOpts, fn); | ||||||
|  | 	} else { | ||||||
|  | 		return require("https").createServer(secureOpts, fn); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								single.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								single.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | require("./main.js"); | ||||||
|  | 
 | ||||||
|  | var Single = module.exports; | ||||||
|  | var Servers = require("./servers.js"); | ||||||
|  | 
 | ||||||
|  | Single.create = function(opts) { | ||||||
|  | 	var greenlock = require("./greenlock.js").create(opts); | ||||||
|  | 
 | ||||||
|  | 	var servers = Servers.create(greenlock); | ||||||
|  | 
 | ||||||
|  | 	var single = { | ||||||
|  | 		serve: function(fn) { | ||||||
|  | 			fn(servers); | ||||||
|  | 			return single; | ||||||
|  | 		}, | ||||||
|  | 		master: function(/*fn*/) { | ||||||
|  | 			// ignore
 | ||||||
|  | 			//fn(master);
 | ||||||
|  | 			return single; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	return single; | ||||||
|  | }; | ||||||
							
								
								
									
										194
									
								
								sni.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								sni.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var sni = module.exports; | ||||||
|  | var tls = require("tls"); | ||||||
|  | var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||||
|  | 
 | ||||||
|  | // a nice, round, irrational number - about every 6¼ hours
 | ||||||
|  | var refreshOffset = Math.round(Math.PI * 2 * (60 * 60 * 1000)); | ||||||
|  | // and another, about 15 minutes
 | ||||||
|  | var refreshStagger = Math.round(Math.PI * 5 * (60 * 1000)); | ||||||
|  | // and another, about 30 seconds
 | ||||||
|  | var smallStagger = Math.round(Math.PI * (30 * 1000)); | ||||||
|  | 
 | ||||||
|  | //secureOpts.SNICallback = sni.create(greenlock, secureOpts);
 | ||||||
|  | sni.create = function(greenlock, secureOpts) { | ||||||
|  | 	var _cache = {}; | ||||||
|  | 	var defaultServername = greenlock.servername || ""; | ||||||
|  | 
 | ||||||
|  | 	if (secureOpts.cert) { | ||||||
|  | 		// Note: it's fine if greenlock.servername is undefined,
 | ||||||
|  | 		// but if the caller wants this to auto-renew, they should define it
 | ||||||
|  | 		_cache[defaultServername] = { | ||||||
|  | 			refreshAt: 0, | ||||||
|  | 			secureContext: tls.createSecureContext(secureOpts) | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return getSecureContext; | ||||||
|  | 
 | ||||||
|  | 	function notify(ev, args) { | ||||||
|  | 		try { | ||||||
|  | 			// TODO _notify() or notify()?
 | ||||||
|  | 			(greenlock.notify || greenlock._notify)(ev, args); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.error(e); | ||||||
|  | 			console.error(ev, args); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getSecureContext(servername, cb) { | ||||||
|  | 		//console.log("debug sni", servername);
 | ||||||
|  | 		if ("string" !== typeof servername) { | ||||||
|  | 			// this will never happen... right? but stranger things have...
 | ||||||
|  | 			console.error("[sanity fail] non-string servername:", servername); | ||||||
|  | 			cb(new Error("invalid servername"), null); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var secureContext = getCachedContext(servername); | ||||||
|  | 		if (secureContext) { | ||||||
|  | 			//console.log("debug sni got cached context", servername, getCachedMeta(servername));
 | ||||||
|  | 			cb(null, secureContext); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		getFreshContext(servername) | ||||||
|  | 			.then(function(secureContext) { | ||||||
|  | 				if (secureContext) { | ||||||
|  | 					//console.log("debug sni got fresh context", servername, getCachedMeta(servername));
 | ||||||
|  | 					cb(null, secureContext); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 				// Note: this does not replace tlsSocket.setSecureContext()
 | ||||||
|  | 				// as it only works when SNI has been sent
 | ||||||
|  | 				//console.log("debug sni got default context", servername, getCachedMeta(servername));
 | ||||||
|  | 				cb(null, getDefaultContext()); | ||||||
|  | 			}) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				if (!err.context) { | ||||||
|  | 					err.context = "sni_callback"; | ||||||
|  | 				} | ||||||
|  | 				notify("error", err); | ||||||
|  | 				//console.log("debug sni error", servername, err);
 | ||||||
|  | 				cb(err); | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getCachedMeta(servername) { | ||||||
|  | 		var meta = _cache[servername]; | ||||||
|  | 		if (!meta) { | ||||||
|  | 			if (!_cache[wildname(servername)]) { | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return meta; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getCachedContext(servername) { | ||||||
|  | 		var meta = getCachedMeta(servername); | ||||||
|  | 		if (!meta) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// always renew in background
 | ||||||
|  | 		if (!meta.refreshAt || Date.now() >= meta.refreshAt) { | ||||||
|  | 			getFreshContext(servername).catch(function(e) { | ||||||
|  | 				if (!e.context) { | ||||||
|  | 					e.context = "sni_background_refresh"; | ||||||
|  | 				} | ||||||
|  | 				notify("error", e); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// under normal circumstances this would never be expired
 | ||||||
|  | 		// and, if it is expired, something is so wrong it's probably
 | ||||||
|  | 		// not worth wating for the renewal - it has probably failed
 | ||||||
|  | 		return meta.secureContext; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getFreshContext(servername) { | ||||||
|  | 		var meta = getCachedMeta(servername); | ||||||
|  | 		if (!meta && !validServername(servername)) { | ||||||
|  | 			return Promise.resolve(null); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (meta) { | ||||||
|  | 			// prevent stampedes
 | ||||||
|  | 			meta.refreshAt = Date.now() + randomRefreshOffset(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// TODO don't get unknown certs at all, rely on auto-updates from greenlock
 | ||||||
|  | 		// Note: greenlock.get() will return an existing fresh cert or issue a new one
 | ||||||
|  | 		return greenlock.get({ servername: servername }).then(function(result) { | ||||||
|  | 			var meta = getCachedMeta(servername); | ||||||
|  | 			if (!meta) { | ||||||
|  | 				meta = _cache[servername] = { secureContext: { _valid: false } }; | ||||||
|  | 			} | ||||||
|  | 			// prevent from being punked by bot trolls
 | ||||||
|  | 			meta.refreshAt = Date.now() + smallStagger; | ||||||
|  | 
 | ||||||
|  | 			// nothing to do
 | ||||||
|  | 			if (!result) { | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// we only care about the first one
 | ||||||
|  | 			var pems = result.pems; | ||||||
|  | 			var site = result.site; | ||||||
|  | 			if (!pems || !pems.cert) { | ||||||
|  | 				// nothing to do
 | ||||||
|  | 				// (and the error should have been reported already)
 | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			meta = { | ||||||
|  | 				refreshAt: Date.now() + randomRefreshOffset(), | ||||||
|  | 				secureContext: tls.createSecureContext({ | ||||||
|  | 					// TODO support passphrase-protected privkeys
 | ||||||
|  | 					key: pems.privkey, | ||||||
|  | 					cert: pems.cert + "\n" + pems.chain + "\n" | ||||||
|  | 				}) | ||||||
|  | 			}; | ||||||
|  | 			meta.secureContext._valid = true; | ||||||
|  | 
 | ||||||
|  | 			// copy this same object into every place
 | ||||||
|  | 			(result.altnames || site.altnames || [result.subject || site.subject]).forEach(function(altname) { | ||||||
|  | 				_cache[altname] = meta; | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			return meta.secureContext; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getDefaultContext() { | ||||||
|  | 		return getCachedContext(defaultServername); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // whenever we need to know when to refresh next
 | ||||||
|  | function randomRefreshOffset() { | ||||||
|  | 	var stagger = Math.round(refreshStagger / 2) - Math.round(Math.random() * refreshStagger); | ||||||
|  | 	return refreshOffset + stagger; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function validServername(servername) { | ||||||
|  | 	// format and (lightly) sanitize sni so that users can be naive
 | ||||||
|  | 	// and not have to worry about SQL injection or fs discovery
 | ||||||
|  | 
 | ||||||
|  | 	servername = (servername || "").toLowerCase(); | ||||||
|  | 	// hostname labels allow a-z, 0-9, -, and are separated by dots
 | ||||||
|  | 	// _ is sometimes allowed, but not as a "hostname", and not by Let's Encrypt ACME
 | ||||||
|  | 	// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
 | ||||||
|  | 	return servernameRe.test(servername) && -1 === servername.indexOf(".."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function wildname(servername) { | ||||||
|  | 	return ( | ||||||
|  | 		"*." + | ||||||
|  | 		servername | ||||||
|  | 			.split(".") | ||||||
|  | 			.slice(1) | ||||||
|  | 			.join(".") | ||||||
|  | 	); | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								test/greenlock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								test/greenlock.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | var Greenlock = require("../"); | ||||||
|  | var greenlock = Greenlock.create({ | ||||||
|  | 	version: "draft-11", | ||||||
|  | 	server: "https://acme-staging-v02.api.letsencrypt.org/directory", | ||||||
|  | 	agreeTos: true, | ||||||
|  | 	approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 	configDir: require("path").join(require("os").tmpdir(), "acme"), | ||||||
|  | 
 | ||||||
|  | 	app: require("express")().use("/", function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | 	}) | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server1 = greenlock.listen(5080, 5443); | ||||||
|  | server1.on("listening", function() { | ||||||
|  | 	console.log("### THREE 3333 - All is well server1", this.address()); | ||||||
|  | 	setTimeout(function() { | ||||||
|  | 		// so that the address() object doesn't disappear
 | ||||||
|  | 		server1.close(); | ||||||
|  | 		server1.unencrypted.close(); | ||||||
|  | 	}, 10); | ||||||
|  | }); | ||||||
|  | setTimeout(function() { | ||||||
|  | 	var server2 = greenlock.listen(6080, 6443, function() { | ||||||
|  | 		console.log("### FIVE 55555 - Started server 2!"); | ||||||
|  | 		setTimeout(function() { | ||||||
|  | 			server2.close(); | ||||||
|  | 			server2.unencrypted.close(); | ||||||
|  | 			server6.close(); | ||||||
|  | 			server6.unencrypted.close(); | ||||||
|  | 			server7.close(); | ||||||
|  | 			server7.unencrypted.close(); | ||||||
|  | 			setTimeout(function() { | ||||||
|  | 				// TODO greenlock needs a close event (and to listen to its server's close event)
 | ||||||
|  | 				process.exit(0); | ||||||
|  | 			}, 1000); | ||||||
|  | 		}, 1000); | ||||||
|  | 	}); | ||||||
|  | 	server2.on("listening", function() { | ||||||
|  | 		console.log("### FOUR 44444 - All is well server2", server2.address()); | ||||||
|  | 	}); | ||||||
|  | }, 1000); | ||||||
|  | 
 | ||||||
|  | var server3 = greenlock.listen( | ||||||
|  | 	22, | ||||||
|  | 	22, | ||||||
|  | 	function() { | ||||||
|  | 		console.error("Error: expected to get an error when launching plain server on port 22"); | ||||||
|  | 	}, | ||||||
|  | 	function() { | ||||||
|  | 		console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); | ||||||
|  | 	} | ||||||
|  | ); | ||||||
|  | server3.unencrypted.on("error", function() { | ||||||
|  | 	console.log("Success: caught expected (plain) error"); | ||||||
|  | }); | ||||||
|  | server3.on("error", function() { | ||||||
|  | 	console.log("Success: caught expected " + server3.type + " error"); | ||||||
|  | 	//server3.close();
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server4 = greenlock.listen( | ||||||
|  | 	7080, | ||||||
|  | 	7443, | ||||||
|  | 	function() { | ||||||
|  | 		console.log("Success: server4: plain"); | ||||||
|  | 		server4.unencrypted.close(); | ||||||
|  | 	}, | ||||||
|  | 	function() { | ||||||
|  | 		console.log("Success: server4: " + server4.type); | ||||||
|  | 		server4.close(); | ||||||
|  | 	} | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | var server5 = greenlock.listen(10080, 10443, function() { | ||||||
|  | 	console.log("Server 5 with one fn", this.address()); | ||||||
|  | 	server5.close(); | ||||||
|  | 	server5.unencrypted.close(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server6 = greenlock.listen("[::]:11080", "[::1]:11443"); | ||||||
|  | 
 | ||||||
|  | var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock"); | ||||||
							
								
								
									
										62
									
								
								worker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								worker.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var Worker = module.exports; | ||||||
|  | // *very* generous, but well below the http norm of 120
 | ||||||
|  | var messageTimeout = 30 * 1000; | ||||||
|  | var msgPrefix = "greenlock:"; | ||||||
|  | 
 | ||||||
|  | Worker.create = function() { | ||||||
|  | 	var greenlock = {}; | ||||||
|  | 	["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) { | ||||||
|  | 		greenlock[k] = function(args) { | ||||||
|  | 			return rpc(k, args); | ||||||
|  | 		}; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	var worker = { | ||||||
|  | 		serve: function(fn) { | ||||||
|  | 			var servers = require("./servers.js").create(greenlock); | ||||||
|  | 			fn(servers); | ||||||
|  | 			return worker; | ||||||
|  | 		}, | ||||||
|  | 		master: function() { | ||||||
|  | 			// ignore
 | ||||||
|  | 			return worker; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	return worker; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function rpc(funcname, msg) { | ||||||
|  | 	return new Promise(function(resolve, reject) { | ||||||
|  | 		var rnd = Math.random() | ||||||
|  | 			.toString() | ||||||
|  | 			.slice(2) | ||||||
|  | 			.toString(16); | ||||||
|  | 		var id = msgPrefix + rnd; | ||||||
|  | 		var timeout; | ||||||
|  | 
 | ||||||
|  | 		function getResponse(msg) { | ||||||
|  | 			if (msg._id !== id) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			process.removeListener("message", getResponse); | ||||||
|  | 			clearTimeout(timeout); | ||||||
|  | 			resolve(msg._result); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// TODO keep a single listener than just responds
 | ||||||
|  | 		// via a collection of callbacks? or leave as is?
 | ||||||
|  | 		process.on("message", getResponse); | ||||||
|  | 		process.send({ | ||||||
|  | 			_id: id, | ||||||
|  | 			_funcname: funcname, | ||||||
|  | 			_input: msg | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		timeout = setTimeout(function() { | ||||||
|  | 			process.removeListener("message", getResponse); | ||||||
|  | 			reject(new Error("worker rpc request timeout")); | ||||||
|  | 		}, messageTimeout); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user