Compare commits
	
		
			281 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 | ||
| 
						 | 
					dd6d354b1f | ||
| 
						 | 
					d4bc899f6d | ||
| 
						 | 
					70468acae3 | ||
| 
						 | 
					0acfd48f65 | ||
| 
						 | 
					a193a47570 | ||
| 
						 | 
					5ebb22e165 | ||
| 
						 | 
					73b11f65e1 | ||
| 
						 | 
					8ba3e3f044 | ||
| 
						 | 
					bacaad9092 | ||
| 
						 | 
					9d326d55f7 | ||
| 
						 | 
					03f46861f6 | ||
| 
						 | 
					395d417e89 | ||
| 
						 | 
					7983ddaa6b | ||
| 
						 | 
					696bea720d | ||
| 
						 | 
					7245a4f3fa | ||
| 
						 | 
					04c4004903 | ||
| 
						 | 
					cdf62958af | ||
| 
						 | 
					2deed41ebb | ||
| 
						 | 
					d7f5c03fa2 | ||
| 
						 | 
					7aae27a0f5 | ||
| 
						 | 
					fa2f2c2940 | ||
| 
						 | 
					d69918b386 | ||
| 
						 | 
					d031ad10a1 | ||
| 
						 | 
					2bd05345d2 | ||
| 
						 | 
					c466e2b7c4 | ||
| 
						 | 
					5c7efcd3de | ||
| 
						 | 
					92a41cb6d5 | ||
| 
						 | 
					6243eca6a6 | ||
| 
						 | 
					fd8c025457 | ||
| 
						 | 
					5aaf3d9fd3 | ||
| 
						 | 
					c65539bf6c | ||
| 
						 | 
					287d0b6606 | ||
| 
						 | 
					685ef09951 | ||
| 
						 | 
					22db053e6c | ||
| 
						 | 
					412b0a6bf7 | ||
| 
						 | 
					505d4b02ce | ||
| 
						 | 
					103e4ae419 | ||
| 
						 | 
					c14077c099 | ||
| 
						 | 
					d8f41a51c4 | ||
| 
						 | 
					18778663d3 | ||
| 
						 | 
					c27ca9309c | ||
| 
						 | 
					db1afc8210 | ||
| 
						 | 
					4b03b35e3d | ||
| 
						 | 
					4f8f243624 | ||
| 
						 | 
					b921bf72bb | ||
| 
						 | 
					95777ebe89 | ||
| 
						 | 
					8a689f7b8d | ||
| 
						 | 
					86d0f7c4b8 | ||
| 
						 | 
					26eb38fb25 | ||
| 
						 | 
					fe039dc137 | ||
| 
						 | 
					a0ab3f9c85 | ||
| 
						 | 
					8e2848b204 | ||
| 
						 | 
					c68f748001 | 
							
								
								
									
										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.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										502
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										502
									
								
								README.md
									
									
									
									
									
								
							@ -1,202 +1,376 @@
 | 
				
			|||||||
letsencrypt-cluster
 | 
					# New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md)
 | 
				
			||||||
===================
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Use automatic letsencrypt with node on multiple cores or even multiple machines.
 | 
					Greenlock v3 just came out of private beta **today** (Nov 1st, 2019).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Take advantage of multi-core computing
 | 
					The code is complete and we're working on great documentation.
 | 
				
			||||||
* Process certificates in master
 | 
					 | 
				
			||||||
* Serve https from multiple workers
 | 
					 | 
				
			||||||
* Can work with any clustering strategy [#1](https://github.com/Daplie/letsencrypt-cluster/issues/1)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install
 | 
					Many **examples** and **full API** documentation are still coming.
 | 
				
			||||||
=======
 | 
					
 | 
				
			||||||
 | 
					# [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) is Let's Encrypt for Node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Let's Encrypt for Node, Express, etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function httpsWorker(glx) {
 | 
				
			||||||
 | 
						// Serves on 80 and 443
 | 
				
			||||||
 | 
						// Get's SSL certificates magically!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						glx.serveApp(function(req, res) {
 | 
				
			||||||
 | 
							res.end("Hello, Encrypted World!");
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var pkg = require("./package.json");
 | 
				
			||||||
 | 
					require("greenlock-express")
 | 
				
			||||||
 | 
						.init(function getConfig() {
 | 
				
			||||||
 | 
							// Greenlock Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								package: { name: pkg.name, version: pkg.version },
 | 
				
			||||||
 | 
								maintainerEmail: pkg.author,
 | 
				
			||||||
 | 
								cluster: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						.serve(httpsWorker);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Let's Encrypt for...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- IoT
 | 
				
			||||||
 | 
					- Enterprise On-Prem
 | 
				
			||||||
 | 
					- Local Development
 | 
				
			||||||
 | 
					- 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# QuickStart Guide
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Easy as 1, 2, 3... 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<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
 | 
					```bash
 | 
				
			||||||
npm install --save letsencrypt-cluster@2.x
 | 
					mkdir ~/my-project
 | 
				
			||||||
 | 
					pushd ~/my-project
 | 
				
			||||||
 | 
					npm init
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Usage
 | 
					</details>
 | 
				
			||||||
=====
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
In a cluster environment you have some main file that boots your app
 | 
					<details>
 | 
				
			||||||
and then conditionally loads certain code based on whether that fork
 | 
					<summary>2. Create an http app (i.e. express)</summary>
 | 
				
			||||||
is the master or just a worker.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
In such a file you might want to define some of the options that need
 | 
					## 2. Create an http app (i.e. express)
 | 
				
			||||||
to be shared between both the master and the worker, like this:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
`boot.js`:
 | 
					This example is shown with Express, but any node app will do. Greenlock
 | 
				
			||||||
```javascript
 | 
					works with everything.
 | 
				
			||||||
'use strict';
 | 
					(or any node-style http app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var cluster = require('cluster');
 | 
					`my-express-app.js`:
 | 
				
			||||||
var path = require('path');
 | 
					 | 
				
			||||||
var os = require('os');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
var main;
 | 
					```js
 | 
				
			||||||
var sharedOptions = {
 | 
					"use strict";
 | 
				
			||||||
  webrootPath: path.join(os.tmpdir(), 'acme-challenge')			// /tmp/acme-challenge
 | 
					 | 
				
			||||||
                                                            // used by le-challenge-fs, the default plugin
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
, renewWithin: 10 * 24 * 60 * 60 * 1000 										// 10 days before expiration
 | 
					// A plain, node-style app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
, debug: true
 | 
					function myPlainNodeHttpApp(req, res) {
 | 
				
			||||||
};
 | 
						res.end("Hello, Encrypted World!");
 | 
				
			||||||
 | 
					 | 
				
			||||||
if (cluster.isMaster) {
 | 
					 | 
				
			||||||
  main = require('./master');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
else {
 | 
					 | 
				
			||||||
  main = require('./worker');
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
main.init(sharedOptions);
 | 
					// Wrap that plain app in express,
 | 
				
			||||||
 | 
					// because that's what you're used to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var express = require("express");
 | 
				
			||||||
 | 
					var app = express();
 | 
				
			||||||
 | 
					app.get("/", myPlainNodeHttpApp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export the app normally
 | 
				
			||||||
 | 
					// do not .listen()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = app;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Master
 | 
					</details>
 | 
				
			||||||
------
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
We think it makes the most sense to load letsencrypt in master.
 | 
					<details>
 | 
				
			||||||
This can prevent race conditions (see [node-letsencrypt#45](https://github.com/Daplie/node-letsencrypt/issues/45))
 | 
					<summary>3. Serve with Greenlock Express</summary>
 | 
				
			||||||
as only one process is writing the to file system or database at a time.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
The main implementation detail here is `approveDomains(options, certs, cb)` for new domain certificates
 | 
					## 3. Serve with Greenlock Express
 | 
				
			||||||
and potentially `agreeToTerms(opts, cb)` for new accounts.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
The master takes **the same arguments** as `node-letsencrypt` (`challenge`, `store`, etc),
 | 
					Greenlock Express is designed with these goals in mind:
 | 
				
			||||||
plus a few extra (`approveDomains`... okay, just one extra):
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
`master.js`:
 | 
					- Simplicity and ease-of-use
 | 
				
			||||||
```javascript
 | 
					- Performance and scalability
 | 
				
			||||||
'use strict';
 | 
					- Configurability and control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var cluster = require('cluster');
 | 
					You can start with **near-zero configuration** and
 | 
				
			||||||
 | 
					slowly add options for greater performance and customization
 | 
				
			||||||
 | 
					later, if you need them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.init = function (sharedOpts) {
 | 
					`server.js`:
 | 
				
			||||||
  var cores = require('os').cpus();
 | 
					 | 
				
			||||||
  var leMaster = require('letsencrypt-cluster/master').create({
 | 
					 | 
				
			||||||
    debug: sharedOpts.debug
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , server: 'staging'                                                       // CHANGE TO PRODUCTION
 | 
					```js
 | 
				
			||||||
 | 
					require("greenlock-express")
 | 
				
			||||||
 | 
						.init(getConfig)
 | 
				
			||||||
 | 
						.serve(worker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , renewWithin: sharedOpts.renewWithin
 | 
					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")
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , webrootPath: sharedOpts.webrootPath
 | 
					function worker(server) {
 | 
				
			||||||
 | 
						// Works with any Node app (Express, etc)
 | 
				
			||||||
  , approveDomains: function (masterOptions, certs, cb) {
 | 
						var app = require("my-express-app.js");
 | 
				
			||||||
      // Do any work that must be done by master to approve this domain
 | 
						server.serveApp(app);
 | 
				
			||||||
      // (in this example, it's assumed to be done by the worker)
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
      var results = { domain: masterOptions.domain                          // required
 | 
					 | 
				
			||||||
                    , options: masterOptions                                // domains, email, agreeTos
 | 
					 | 
				
			||||||
                    , certs: certs };                                       // altnames, privkey, cert
 | 
					 | 
				
			||||||
      cb(null, results);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  cores.forEach(function () {
 | 
					 | 
				
			||||||
    var worker = cluster.fork();
 | 
					 | 
				
			||||||
    leMaster.addWorker(worker);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### API
 | 
					And start your server:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All options are passed directly to `node-letsencrypt`
 | 
					```bash
 | 
				
			||||||
(in other works, `leMaster` is a `letsencrypt` instance),
 | 
					# Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS)
 | 
				
			||||||
but a few are only actually used by `letsencrypt-cluster`.
 | 
					sudo setcap 'cap_net_bind_service=+ep' $(which node)
 | 
				
			||||||
 | 
					 | 
				
			||||||
* `leOptions.approveDomains(options, certs, cb)` is special for `letsencrypt-cluster`, but will probably be included in `node-letsencrypt` in the future (no API change).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `leMaster.addWorker(worker)` is added by `letsencrypt-cluster` and **must be called** for each new worker.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Worker
 | 
					 | 
				
			||||||
------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The worker takes *similar* arguments to `node-letsencrypt`,
 | 
					 | 
				
			||||||
but only ones that are useful for determining certificate
 | 
					 | 
				
			||||||
renewal and for `le.challenge.get`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If you want to  a non-default `le.challenge`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`worker.js`:
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.init = function (sharedOpts) {
 | 
					 | 
				
			||||||
  var leWorker = require('letsencrypt-cluster/worker').create({
 | 
					 | 
				
			||||||
    debug: sharedOpts.debug
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , renewWithin: sharedOpts.renewWithin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , webrootPath: sharedOpts.webrootPath
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // , challenge: require('le-challenge-fs').create({ webrootPath: '...', ... })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , approveDomains: function (workerOptions, certs, cb) {
 | 
					 | 
				
			||||||
      // opts = { domains, email, agreeTos, tosUrl }
 | 
					 | 
				
			||||||
      // certs = { subject, altnames, expiresAt, issuedAt }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      var results = {
 | 
					 | 
				
			||||||
        domain: workerOptions.domains[0]
 | 
					 | 
				
			||||||
      , options: {
 | 
					 | 
				
			||||||
          domains: workerOptions.domains
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      , certs: certs
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (certs) {
 | 
					 | 
				
			||||||
        // modify opts.domains to match the original request
 | 
					 | 
				
			||||||
        // email is not necessary, because the account already exists
 | 
					 | 
				
			||||||
        // this will only fail if the account has become corrupt
 | 
					 | 
				
			||||||
        results.options.domains = certs.altnames;
 | 
					 | 
				
			||||||
        cb(null, results);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // This is where one would check one's application-specific database:
 | 
					 | 
				
			||||||
      //   1. Lookup the domain to see which email it belongs to
 | 
					 | 
				
			||||||
      //   2. Assign a default email if it isn't in the system
 | 
					 | 
				
			||||||
      //   3. If the email has no le account, `agreeToTerms` will fire unless `agreeTos` is preset
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      results.options.email = 'john.doe@example.com'
 | 
					 | 
				
			||||||
      results.options.agreeTos = true                                 // causes agreeToTerms to be skipped
 | 
					 | 
				
			||||||
      cb(null, results);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function app(req, res) {
 | 
					 | 
				
			||||||
    res.end("Hello, World!");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var redirectHttps = require('redirect-https')();
 | 
					 | 
				
			||||||
  var plainServer = require('http').createServer(leWorker.middleware(redirectHttps));
 | 
					 | 
				
			||||||
  plainServer.listen(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var server = require('https').createServer(leWorker.httpsOptions, leWorker.middleware(app));
 | 
					 | 
				
			||||||
  server.listen(443);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### API
 | 
					```bash
 | 
				
			||||||
 | 
					# `npm start` will call `node ./server.js` by default
 | 
				
			||||||
 | 
					npm start
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`node-letsencrypt` is **not used** directly by the worker,
 | 
					```txt
 | 
				
			||||||
but certain options are shared because certain logic is duplicated.
 | 
					Greenlock v3.0.0
 | 
				
			||||||
 | 
					Greenlock Manager Config File: ~/.config/greenlock/manager.json
 | 
				
			||||||
 | 
					Greenlock Storage Directory: ~/.config/greenlock/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `leOptions.renewWithin` is shared so that the worker knows how earlier to request a new cert
 | 
					Listening on 0.0.0.0:80 for ACME challenges and HTTPS redirects
 | 
				
			||||||
* `leOptions.renewBy` is passed to `le-sni-auto` so that it staggers renewals between `renewWithin` (latest) and `renewBy` (earlier)
 | 
					Listening on 0.0.0.0:443 for secure traffic
 | 
				
			||||||
* `leWorker.middleware(nextApp)` uses `letsencrypt/middleware` for GET-ing `http-01`, hence `sharedOptions.webrootPath`
 | 
					```
 | 
				
			||||||
* `leWorker.httpsOptions` has a default localhost certificate and the `SNICallback`.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
There are a few options that aren't shown in these examples, so if you need to change something
 | 
					</details>
 | 
				
			||||||
that isn't shown here, look at the code (it's not that much) or open an issue.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Message Passing
 | 
					<details>
 | 
				
			||||||
---------------
 | 
					<summary>4. Manage SSL Certificates and Domains</summary>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The master and workers will communicate through `process.on('message', fn)`, `process.send({})`,
 | 
					## 4. Manage domains
 | 
				
			||||||
`worker.on('message', fn)`and `worker.send({})`.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
All messages have a `type` property which is a string and begins with `LE_`.
 | 
					The management API is built to work with Databases, S3, etc.
 | 
				
			||||||
All other messages are ignored.
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
@ -1,12 +0,0 @@
 | 
				
			|||||||
letsencrypt cluster examples
 | 
					 | 
				
			||||||
-------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
First you need to change the email address in `examples/worker.js`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Then you can run the example like so:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
node examples/serve.js
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
That will put certificates in `~/letsencrypt.test` by default.
 | 
					 | 
				
			||||||
							
								
								
									
										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);
 | 
				
			||||||
							
								
								
									
										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);
 | 
				
			||||||
@ -1,35 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var cluster = require('cluster');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.init = function (sharedOpts) {
 | 
					 | 
				
			||||||
  var numCores = 2; // // Math.max(2, require('os').cpus().length)
 | 
					 | 
				
			||||||
  var i;
 | 
					 | 
				
			||||||
  var master = require('../master').create({
 | 
					 | 
				
			||||||
    debug: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , server: 'staging'
 | 
					 | 
				
			||||||
  , webrootPath: sharedOpts.webrootPath
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , approveDomains: function (masterOptions, certs, cb) {
 | 
					 | 
				
			||||||
      // Depending on your setup it may be more efficient
 | 
					 | 
				
			||||||
      // for you to implement the approveDomains function
 | 
					 | 
				
			||||||
      // in your master or in your workers.
 | 
					 | 
				
			||||||
      //
 | 
					 | 
				
			||||||
      // Since we implement it in the worker (below) in this example
 | 
					 | 
				
			||||||
      // we'll give it an immediate approval here in the master
 | 
					 | 
				
			||||||
      var results = { domain: masterOptions.domain, options: masterOptions, certs: certs };
 | 
					 | 
				
			||||||
      cb(null, results);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (i = 0; i < numCores; i += 1) {
 | 
					 | 
				
			||||||
    master.addWorker(cluster.fork());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										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,33 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var cluster = require('cluster');
 | 
					 | 
				
			||||||
var main;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// You'll often see examples where people use cluster
 | 
					 | 
				
			||||||
// master and worker all in the same file, which is fine,
 | 
					 | 
				
			||||||
// but in order to conserve memory and especially to be
 | 
					 | 
				
			||||||
// less confusing, I'm splitting the code into two files
 | 
					 | 
				
			||||||
if (cluster.isMaster) {
 | 
					 | 
				
			||||||
  main = require('./master');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
else {
 | 
					 | 
				
			||||||
  main = require('./worker');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// this is nothing letsencrypt-cluster specific
 | 
					 | 
				
			||||||
// I'm just arbitrarily choosing to share some configuration
 | 
					 | 
				
			||||||
// that I know I'm going to use in both places
 | 
					 | 
				
			||||||
main.init({
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Depending on the strategy, the whole le-challenge-<<strategy>>
 | 
					 | 
				
			||||||
  // could be shared between worker and server, but since I'm just
 | 
					 | 
				
			||||||
  // using using le-challenge-fs (as you'll see), I'm only sharing the webrootPath
 | 
					 | 
				
			||||||
  webrootPath: require('os').tmpdir() + require('path').sep + 'acme-challenge'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // this is used both by node-letsencrypt (master) and le-sni-auto (worker)
 | 
					 | 
				
			||||||
, renewWithin: 15 * 24 * 60 * 60 * 1000
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										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);
 | 
				
			||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.init = function (sharedOpts) {
 | 
					 | 
				
			||||||
  var worker = require('../worker').create({
 | 
					 | 
				
			||||||
    debug: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // We want both to renew well before the expiration date
 | 
					 | 
				
			||||||
    // and also to stagger the renewals, just a touch
 | 
					 | 
				
			||||||
    // here we specify to renew between 10 and 15 days
 | 
					 | 
				
			||||||
  , renewWithin: sharedOpts.renewWithin
 | 
					 | 
				
			||||||
  , renewBy: 10 * 24 * 60 * 60 * 1000 // optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  , webrootPath: sharedOpts.webrootPath
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    challenge: {
 | 
					 | 
				
			||||||
      get: function (ignored, domain, token, cb) {
 | 
					 | 
				
			||||||
        cb(null, keyAuthorization);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  , getChallenge: function (domain, token, cb) {
 | 
					 | 
				
			||||||
      // the default behavior is to use le-challenge-fs
 | 
					 | 
				
			||||||
      // TODO maybe provide a built-in option to pass a message to master to use its
 | 
					 | 
				
			||||||
      // but you could overwrite that with a function to pass a message to master or,
 | 
					 | 
				
			||||||
      // but if needed for performance, that can be overwritten here
 | 
					 | 
				
			||||||
      cb(null, );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // There are two approval processes:
 | 
					 | 
				
			||||||
    // 1. emails are tied to private keys (accounts) which must agree to the tos url
 | 
					 | 
				
			||||||
    // 2. domains are tied to accounts (and should be verifiable via loopback)
 | 
					 | 
				
			||||||
  , approveDomains: function (workerOptions, certs, cb) {
 | 
					 | 
				
			||||||
      // opts = { domains, email, agreeTos, tosUrl }
 | 
					 | 
				
			||||||
      // certs = { subject, altnames, expiresAt, issuedAt }
 | 
					 | 
				
			||||||
      var results = {
 | 
					 | 
				
			||||||
        domain: workerOptions.domains[0]
 | 
					 | 
				
			||||||
      , options: {
 | 
					 | 
				
			||||||
          domains: certs && certs.altnames || workerOptions.domains
 | 
					 | 
				
			||||||
        , email: 'john.doe@example.com'
 | 
					 | 
				
			||||||
        , agreeTos: true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      , certs: certs
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // We might want to do a check to make sure that all of the domains
 | 
					 | 
				
			||||||
      // specified in altnames are still approved to be renewed and have
 | 
					 | 
				
			||||||
      // the correct dns entries, but generally speaking it's probably okay
 | 
					 | 
				
			||||||
      // for renewals to be automatic
 | 
					 | 
				
			||||||
      if (certs) {
 | 
					 | 
				
			||||||
        // modify opts.domains to overwrite certs.altnames in renewal
 | 
					 | 
				
			||||||
        cb(null, results);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // This is where we would check our database to make sure that
 | 
					 | 
				
			||||||
      // this user (specified by email address) has agreed to the terms
 | 
					 | 
				
			||||||
      // and do some check that they have access to this domain
 | 
					 | 
				
			||||||
      cb(null, results);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function app(req, res) {
 | 
					 | 
				
			||||||
    res.end("Hello, World!");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // worker.handleAcmeOrRedirectToHttps()
 | 
					 | 
				
			||||||
  // worker.handleAcmeOrUse(app)
 | 
					 | 
				
			||||||
  var redirectHttps = require('redirect-https')();
 | 
					 | 
				
			||||||
  var plainServer = require('http').createServer(worker.middleware(redirectHttps));
 | 
					 | 
				
			||||||
  var server = require('https').createServer(worker.httpsOptions, worker.middleware(app));
 | 
					 | 
				
			||||||
  plainServer.listen(80);
 | 
					 | 
				
			||||||
  server.listen(443);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										12
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								index.js
									
									
									
									
									
								
							@ -1,12 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.error("");
 | 
					 | 
				
			||||||
console.error("One does not simply require('letsencrypt-cluster');");
 | 
					 | 
				
			||||||
console.error("");
 | 
					 | 
				
			||||||
console.error("Usage:");
 | 
					 | 
				
			||||||
console.error("\trequire('letsencrypt-cluster/master').create({ ... });");
 | 
					 | 
				
			||||||
console.error("\trequire('letsencrypt-cluster/worker').create({ ... });");
 | 
					 | 
				
			||||||
console.error("");
 | 
					 | 
				
			||||||
console.error("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
process.exit(1);
 | 
					 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										229
									
								
								master.js
									
									
									
									
									
								
							
							
						
						
									
										229
									
								
								master.js
									
									
									
									
									
								
							@ -1,91 +1,160 @@
 | 
				
			|||||||
'use strict';
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// opts.addWorker(worker)
 | 
					require("./main.js");
 | 
				
			||||||
// opts.approveDomains(options, certs, cb)
 | 
					 | 
				
			||||||
module.exports.create = function (opts) {
 | 
					 | 
				
			||||||
  opts = opts || { };
 | 
					 | 
				
			||||||
  opts._workers = [];
 | 
					 | 
				
			||||||
  opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge';
 | 
					 | 
				
			||||||
  if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create(opts); }
 | 
					 | 
				
			||||||
  if ('function' !== typeof opts.approveDomains) {
 | 
					 | 
				
			||||||
    throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function log(debug) {
 | 
					var Master = module.exports;
 | 
				
			||||||
    if (!debug) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var args = Array.prototype.slice.call(arguments);
 | 
					var cluster = require("cluster");
 | 
				
			||||||
    args.shift();
 | 
					var os = require("os");
 | 
				
			||||||
    args.unshift("[le/lib/core.js]");
 | 
					var msgPrefix = "greenlock:";
 | 
				
			||||||
    console.log.apply(console, args);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  opts.addWorker = function (worker) {
 | 
					Master.create = function(opts) {
 | 
				
			||||||
    opts._workers.push(worker);
 | 
						var resolveCb;
 | 
				
			||||||
 | 
						var _readyCb;
 | 
				
			||||||
 | 
						var _kicked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    worker.on('online', function () {
 | 
						var greenlock = require("./greenlock.js").create(opts);
 | 
				
			||||||
      log(opts.debug, 'worker is up');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    worker.on('message', function (msg) {
 | 
						var ready = new Promise(function(resolve) {
 | 
				
			||||||
      log(opts.debug, 'Message from worker ' + worker.id);
 | 
							resolveCb = resolve;
 | 
				
			||||||
      if ('LE_REQUEST' !== (msg && msg.type)) {
 | 
						}).then(function(fn) {
 | 
				
			||||||
        log(opts.debug, 'Ignoring irrelevant message');
 | 
							_readyCb = fn;
 | 
				
			||||||
        log(opts.debug, msg);
 | 
							return fn;
 | 
				
			||||||
        return;
 | 
						});
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      log(opts.debug, 'about to approveDomains');
 | 
						function kickoff() {
 | 
				
			||||||
      opts.approveDomains(msg.options, msg.certs, function (err, results) {
 | 
							if (_kicked) {
 | 
				
			||||||
        if (err) {
 | 
								return;
 | 
				
			||||||
          log(opts.debug, 'Approval got ERROR', err.stack || err);
 | 
							}
 | 
				
			||||||
          worker.send({
 | 
							_kicked = true;
 | 
				
			||||||
            type: 'LE_RESPONSE'
 | 
					 | 
				
			||||||
          , domain: msg.domain
 | 
					 | 
				
			||||||
          , error: { message: err.message, code: err.code, stack: err.stack }
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var promise;
 | 
							Master._spawnWorkers(opts, greenlock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //
 | 
							ready.then(function(fn) {
 | 
				
			||||||
        /*
 | 
								// not sure what this API should be yet
 | 
				
			||||||
        var certs = require('localhost.daplie.com-certificates').merge({
 | 
								fn();
 | 
				
			||||||
          subject: msg.domain
 | 
							});
 | 
				
			||||||
        , altnames: [ msg.domain ]
 | 
						}
 | 
				
			||||||
        , issuedAt: Date.now()
 | 
					 | 
				
			||||||
        , expiresAt: Date.now() + (90 * 24 * 60 * 60 * 1000)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        certs.privkey = certs.key.toString('ascii');
 | 
					 | 
				
			||||||
        certs.cert = certs.cert.toString('ascii');
 | 
					 | 
				
			||||||
        certs.chain = '';
 | 
					 | 
				
			||||||
        worker.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs });
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
        // */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (results.certs) {
 | 
						var master = {
 | 
				
			||||||
          promise = opts.letsencrypt.renew(results.options, results.certs);
 | 
							serve: function() {
 | 
				
			||||||
        }
 | 
								kickoff();
 | 
				
			||||||
        else {
 | 
								return master;
 | 
				
			||||||
          promise = opts.letsencrypt.register(results.options);
 | 
							},
 | 
				
			||||||
        }
 | 
							master: function(fn) {
 | 
				
			||||||
 | 
								if (_readyCb) {
 | 
				
			||||||
        promise.then(function (certs) {
 | 
									throw new Error("can't call master twice");
 | 
				
			||||||
          log(opts.debug, 'Approval got certs', certs);
 | 
								}
 | 
				
			||||||
          // certs = { subject, domains, issuedAt, expiresAt, privkey, cert, chain };
 | 
								kickoff();
 | 
				
			||||||
          opts._workers.forEach(function (w) {
 | 
								resolveCb(fn);
 | 
				
			||||||
            w.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs });
 | 
								return master;
 | 
				
			||||||
          });
 | 
							}
 | 
				
			||||||
        }, function (err) {
 | 
						};
 | 
				
			||||||
          log(opts.debug, 'Approval got ERROR', err.stack || err);
 | 
						return master;
 | 
				
			||||||
          worker.send({ type: 'LE_RESPONSE', domain: msg.domain, error: err });
 | 
					};
 | 
				
			||||||
        });
 | 
					
 | 
				
			||||||
      });
 | 
					function range(n) {
 | 
				
			||||||
    });
 | 
						n = parseInt(n, 10);
 | 
				
			||||||
  };
 | 
						if (!n) {
 | 
				
			||||||
 | 
							return [];
 | 
				
			||||||
  return opts;
 | 
						}
 | 
				
			||||||
 | 
						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=="
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										89
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								package.json
									
									
									
									
									
								
							@ -1,42 +1,51 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "letsencrypt-cluster",
 | 
						"name": "@root/greenlock-express",
 | 
				
			||||||
  "version": "2.0.0",
 | 
						"version": "3.0.10",
 | 
				
			||||||
  "description": "Use automatic letsencrypt (free ssl certs) on multiple cores or even multiple machines",
 | 
						"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.",
 | 
				
			||||||
  "main": "index.js",
 | 
						"main": "greenlock-express.js",
 | 
				
			||||||
  "directories": {
 | 
						"homepage": "https://greenlock.domains",
 | 
				
			||||||
    "example": "examples"
 | 
						"files": [
 | 
				
			||||||
  },
 | 
							"*.js",
 | 
				
			||||||
  "dependencies": {
 | 
							"lib",
 | 
				
			||||||
    "le-sni-auto": "^2.0.1",
 | 
							"scripts"
 | 
				
			||||||
    "letsencrypt": "^2.0.4",
 | 
						],
 | 
				
			||||||
    "localhost.daplie.com-certificates": "^1.2.3",
 | 
						"scripts": {
 | 
				
			||||||
    "redirect-https": "^1.1.0"
 | 
							"start": "node_todo server.js ./config.js",
 | 
				
			||||||
  },
 | 
							"test": "node_todo test/greenlock.js"
 | 
				
			||||||
  "devDependencies": {},
 | 
						},
 | 
				
			||||||
  "scripts": {
 | 
						"directories": {
 | 
				
			||||||
    "test": "node examples/serve.js"
 | 
							"example": "examples"
 | 
				
			||||||
  },
 | 
						},
 | 
				
			||||||
  "repository": {
 | 
						"dependencies": {
 | 
				
			||||||
    "type": "git",
 | 
							"@root/greenlock": "^3.0.17",
 | 
				
			||||||
    "url": "git+https://github.com/Daplie/letsencrypt-cluster.git"
 | 
							"redirect-https": "^1.1.5"
 | 
				
			||||||
  },
 | 
						},
 | 
				
			||||||
  "keywords": [
 | 
						"trulyOptionalDependencies": {
 | 
				
			||||||
    "cluster",
 | 
							"http-proxy": "^1.17.0",
 | 
				
			||||||
    "multi-core",
 | 
							"express": "^4.16.3",
 | 
				
			||||||
    "cloud",
 | 
							"express-basic-auth": "^1.2.0",
 | 
				
			||||||
    "scale",
 | 
							"finalhandler": "^1.1.1",
 | 
				
			||||||
    "free",
 | 
							"serve-index": "^1.9.1",
 | 
				
			||||||
    "ssl",
 | 
							"serve-static": "^1.13.2",
 | 
				
			||||||
    "https",
 | 
							"ws": "^5.2.1"
 | 
				
			||||||
    "tls",
 | 
						},
 | 
				
			||||||
    "letsencrypt",
 | 
						"devDependencies": {},
 | 
				
			||||||
    "node",
 | 
						"repository": {
 | 
				
			||||||
    "node.js"
 | 
							"type": "git",
 | 
				
			||||||
  ],
 | 
							"url": "https://git.rootprojects.org/root/greenlock-express.js.git"
 | 
				
			||||||
  "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
						},
 | 
				
			||||||
  "license": "(MIT OR Apache-2.0)",
 | 
						"keywords": [
 | 
				
			||||||
  "bugs": {
 | 
							"Let's Encrypt",
 | 
				
			||||||
    "url": "https://github.com/Daplie/letsencrypt-cluster/issues"
 | 
							"ACME",
 | 
				
			||||||
  },
 | 
							"greenlock",
 | 
				
			||||||
  "homepage": "https://github.com/Daplie/letsencrypt-cluster#readme"
 | 
							"Free SSL",
 | 
				
			||||||
 | 
							"Automated HTTPS",
 | 
				
			||||||
 | 
							"https",
 | 
				
			||||||
 | 
							"tls"
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)",
 | 
				
			||||||
 | 
						"license": "MPL-2.0",
 | 
				
			||||||
 | 
						"bugs": {
 | 
				
			||||||
 | 
							"url": "https://git.rootprojects.org/root/greenlock-express.js/issues"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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");
 | 
				
			||||||
							
								
								
									
										141
									
								
								worker.js
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								worker.js
									
									
									
									
									
								
							@ -1,87 +1,62 @@
 | 
				
			|||||||
'use strict';
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function log(debug) {
 | 
					var Worker = module.exports;
 | 
				
			||||||
	if (!debug) {
 | 
					// *very* generous, but well below the http norm of 120
 | 
				
			||||||
		return;
 | 
					var messageTimeout = 30 * 1000;
 | 
				
			||||||
	}
 | 
					var msgPrefix = "greenlock:";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var args = Array.prototype.slice.call(arguments);
 | 
					Worker.create = function() {
 | 
				
			||||||
	args.shift();
 | 
						var greenlock = {};
 | 
				
			||||||
	args.unshift("[le/lib/core.js]");
 | 
						["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) {
 | 
				
			||||||
	console.log.apply(console, args);
 | 
							greenlock[k] = function(args) {
 | 
				
			||||||
}
 | 
								return rpc(k, args);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var worker = {
 | 
				
			||||||
 | 
							serve: function(fn) {
 | 
				
			||||||
module.exports.create = function (opts) {
 | 
								var servers = require("./servers.js").create(greenlock);
 | 
				
			||||||
 | 
								fn(servers);
 | 
				
			||||||
  // if another worker updates the certs,
 | 
								return worker;
 | 
				
			||||||
  // receive a copy from master here as well
 | 
							},
 | 
				
			||||||
  // and update the sni cache manually
 | 
							master: function() {
 | 
				
			||||||
  process.on('message', function (msg) {
 | 
								// ignore
 | 
				
			||||||
    if ('LE_RESPONSE' === msg.type && msg.certs) {
 | 
								return worker;
 | 
				
			||||||
      opts.sni.cacheCerts(msg.certs);
 | 
							}
 | 
				
			||||||
    }
 | 
						};
 | 
				
			||||||
  });
 | 
						return worker;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  opts.sni = require('le-sni-auto').create({
 | 
					 | 
				
			||||||
    renewWithin: opts.renewWithin || (10 * 24 * 60 * 60 * 1000)
 | 
					 | 
				
			||||||
  , renewBy: opts.renewBy || (5 * 24 * 60 * 60 * 1000)
 | 
					 | 
				
			||||||
  , getCertificates: function (domain, certs, cb) {
 | 
					 | 
				
			||||||
      var workerOptions = { domains: [ domain ] };
 | 
					 | 
				
			||||||
      opts.approveDomains(workerOptions, certs, function (_err, results) {
 | 
					 | 
				
			||||||
        if (_err) {
 | 
					 | 
				
			||||||
          cb(_err);
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var err = new Error("___MESSAGE___");
 | 
					 | 
				
			||||||
        process.send({ type: 'LE_REQUEST', domain: domain, options: results.options, certs: results.certs });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        process.on('message', function (msg) {
 | 
					 | 
				
			||||||
          log(opts.debug, 'Message from master');
 | 
					 | 
				
			||||||
          log(opts.debug, msg);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (msg.domain !== domain) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (msg.error) {
 | 
					 | 
				
			||||||
            err.message = msg.error.message || "unknown error sent from cluster master to worker";
 | 
					 | 
				
			||||||
            err.stack.replace("___MESSAGE___", err.message);
 | 
					 | 
				
			||||||
            err = {
 | 
					 | 
				
			||||||
              message: err.message
 | 
					 | 
				
			||||||
            , stack: err.stack
 | 
					 | 
				
			||||||
            , data: { options: workerOptions, certs: certs }
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            err = null;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          cb(err, msg.certs);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  opts.httpsOptions = require('localhost.daplie.com-certificates').merge({ SNICallback: opts.sni.sniCallback });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  opts.challenge = {
 | 
					 | 
				
			||||||
    get: opts.getChallenge
 | 
					 | 
				
			||||||
      || (opts.challenge && opts.challenge.get)
 | 
					 | 
				
			||||||
      || require('le-challenge-fs').create({ webrootPath: opts.webrootPath }).get
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // opts.challenge.get, opts.acmeChallengePrefix
 | 
					 | 
				
			||||||
  opts.middleware = require('letsencrypt/lib/middleware').create(opts);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return opts;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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