forked from coolaj86/goldilocks.js
		
	rebrand to goldilocks
This commit is contained in:
		
						commit
						8df415c91d
					
				
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -30,6 +30,7 @@ build/Release | |||||||
| # Dependency directories | # Dependency directories | ||||||
| node_modules | node_modules | ||||||
| jspm_packages | jspm_packages | ||||||
|  | bower_components | ||||||
| 
 | 
 | ||||||
| # Optional npm cache directory | # Optional npm cache directory | ||||||
| .npm | .npm | ||||||
| @ -42,3 +43,7 @@ jspm_packages | |||||||
| 
 | 
 | ||||||
| # Output of 'npm pack' | # Output of 'npm pack' | ||||||
| *.tgz | *.tgz | ||||||
|  | 
 | ||||||
|  | # Dependency directory | ||||||
|  | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git | ||||||
|  | node_modules | ||||||
|  | |||||||
							
								
								
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | |||||||
|  |                                  Apache License | ||||||
|  |                            Version 2.0, January 2004 | ||||||
|  |                         http://www.apache.org/licenses/ | ||||||
|  | 
 | ||||||
|  |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||||
|  | 
 | ||||||
|  |    1. Definitions. | ||||||
|  | 
 | ||||||
|  |       "License" shall mean the terms and conditions for use, reproduction, | ||||||
|  |       and distribution as defined by Sections 1 through 9 of this document. | ||||||
|  | 
 | ||||||
|  |       "Licensor" shall mean the copyright owner or entity authorized by | ||||||
|  |       the copyright owner that is granting the License. | ||||||
|  | 
 | ||||||
|  |       "Legal Entity" shall mean the union of the acting entity and all | ||||||
|  |       other entities that control, are controlled by, or are under common | ||||||
|  |       control with that entity. For the purposes of this definition, | ||||||
|  |       "control" means (i) the power, direct or indirect, to cause the | ||||||
|  |       direction or management of such entity, whether by contract or | ||||||
|  |       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||||
|  |       outstanding shares, or (iii) beneficial ownership of such entity. | ||||||
|  | 
 | ||||||
|  |       "You" (or "Your") shall mean an individual or Legal Entity | ||||||
|  |       exercising permissions granted by this License. | ||||||
|  | 
 | ||||||
|  |       "Source" form shall mean the preferred form for making modifications, | ||||||
|  |       including but not limited to software source code, documentation | ||||||
|  |       source, and configuration files. | ||||||
|  | 
 | ||||||
|  |       "Object" form shall mean any form resulting from mechanical | ||||||
|  |       transformation or translation of a Source form, including but | ||||||
|  |       not limited to compiled object code, generated documentation, | ||||||
|  |       and conversions to other media types. | ||||||
|  | 
 | ||||||
|  |       "Work" shall mean the work of authorship, whether in Source or | ||||||
|  |       Object form, made available under the License, as indicated by a | ||||||
|  |       copyright notice that is included in or attached to the work | ||||||
|  |       (an example is provided in the Appendix below). | ||||||
|  | 
 | ||||||
|  |       "Derivative Works" shall mean any work, whether in Source or Object | ||||||
|  |       form, that is based on (or derived from) the Work and for which the | ||||||
|  |       editorial revisions, annotations, elaborations, or other modifications | ||||||
|  |       represent, as a whole, an original work of authorship. For the purposes | ||||||
|  |       of this License, Derivative Works shall not include works that remain | ||||||
|  |       separable from, or merely link (or bind by name) to the interfaces of, | ||||||
|  |       the Work and Derivative Works thereof. | ||||||
|  | 
 | ||||||
|  |       "Contribution" shall mean any work of authorship, including | ||||||
|  |       the original version of the Work and any modifications or additions | ||||||
|  |       to that Work or Derivative Works thereof, that is intentionally | ||||||
|  |       submitted to Licensor for inclusion in the Work by the copyright owner | ||||||
|  |       or by an individual or Legal Entity authorized to submit on behalf of | ||||||
|  |       the copyright owner. For the purposes of this definition, "submitted" | ||||||
|  |       means any form of electronic, verbal, or written communication sent | ||||||
|  |       to the Licensor or its representatives, including but not limited to | ||||||
|  |       communication on electronic mailing lists, source code control systems, | ||||||
|  |       and issue tracking systems that are managed by, or on behalf of, the | ||||||
|  |       Licensor for the purpose of discussing and improving the Work, but | ||||||
|  |       excluding communication that is conspicuously marked or otherwise | ||||||
|  |       designated in writing by the copyright owner as "Not a Contribution." | ||||||
|  | 
 | ||||||
|  |       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||||
|  |       on behalf of whom a Contribution has been received by Licensor and | ||||||
|  |       subsequently incorporated within the Work. | ||||||
|  | 
 | ||||||
|  |    2. Grant of Copyright License. Subject to the terms and conditions of | ||||||
|  |       this License, each Contributor hereby grants to You a perpetual, | ||||||
|  |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |       copyright license to reproduce, prepare Derivative Works of, | ||||||
|  |       publicly display, publicly perform, sublicense, and distribute the | ||||||
|  |       Work and such Derivative Works in Source or Object form. | ||||||
|  | 
 | ||||||
|  |    3. Grant of Patent License. Subject to the terms and conditions of | ||||||
|  |       this License, each Contributor hereby grants to You a perpetual, | ||||||
|  |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |       (except as stated in this section) patent license to make, have made, | ||||||
|  |       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||||
|  |       where such license applies only to those patent claims licensable | ||||||
|  |       by such Contributor that are necessarily infringed by their | ||||||
|  |       Contribution(s) alone or by combination of their Contribution(s) | ||||||
|  |       with the Work to which such Contribution(s) was submitted. If You | ||||||
|  |       institute patent litigation against any entity (including a | ||||||
|  |       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||||
|  |       or a Contribution incorporated within the Work constitutes direct | ||||||
|  |       or contributory patent infringement, then any patent licenses | ||||||
|  |       granted to You under this License for that Work shall terminate | ||||||
|  |       as of the date such litigation is filed. | ||||||
|  | 
 | ||||||
|  |    4. Redistribution. You may reproduce and distribute copies of the | ||||||
|  |       Work or Derivative Works thereof in any medium, with or without | ||||||
|  |       modifications, and in Source or Object form, provided that You | ||||||
|  |       meet the following conditions: | ||||||
|  | 
 | ||||||
|  |       (a) You must give any other recipients of the Work or | ||||||
|  |           Derivative Works a copy of this License; and | ||||||
|  | 
 | ||||||
|  |       (b) You must cause any modified files to carry prominent notices | ||||||
|  |           stating that You changed the files; and | ||||||
|  | 
 | ||||||
|  |       (c) You must retain, in the Source form of any Derivative Works | ||||||
|  |           that You distribute, all copyright, patent, trademark, and | ||||||
|  |           attribution notices from the Source form of the Work, | ||||||
|  |           excluding those notices that do not pertain to any part of | ||||||
|  |           the Derivative Works; and | ||||||
|  | 
 | ||||||
|  |       (d) If the Work includes a "NOTICE" text file as part of its | ||||||
|  |           distribution, then any Derivative Works that You distribute must | ||||||
|  |           include a readable copy of the attribution notices contained | ||||||
|  |           within such NOTICE file, excluding those notices that do not | ||||||
|  |           pertain to any part of the Derivative Works, in at least one | ||||||
|  |           of the following places: within a NOTICE text file distributed | ||||||
|  |           as part of the Derivative Works; within the Source form or | ||||||
|  |           documentation, if provided along with the Derivative Works; or, | ||||||
|  |           within a display generated by the Derivative Works, if and | ||||||
|  |           wherever such third-party notices normally appear. The contents | ||||||
|  |           of the NOTICE file are for informational purposes only and | ||||||
|  |           do not modify the License. You may add Your own attribution | ||||||
|  |           notices within Derivative Works that You distribute, alongside | ||||||
|  |           or as an addendum to the NOTICE text from the Work, provided | ||||||
|  |           that such additional attribution notices cannot be construed | ||||||
|  |           as modifying the License. | ||||||
|  | 
 | ||||||
|  |       You may add Your own copyright statement to Your modifications and | ||||||
|  |       may provide additional or different license terms and conditions | ||||||
|  |       for use, reproduction, or distribution of Your modifications, or | ||||||
|  |       for any such Derivative Works as a whole, provided Your use, | ||||||
|  |       reproduction, and distribution of the Work otherwise complies with | ||||||
|  |       the conditions stated in this License. | ||||||
|  | 
 | ||||||
|  |    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||||
|  |       any Contribution intentionally submitted for inclusion in the Work | ||||||
|  |       by You to the Licensor shall be under the terms and conditions of | ||||||
|  |       this License, without any additional terms or conditions. | ||||||
|  |       Notwithstanding the above, nothing herein shall supersede or modify | ||||||
|  |       the terms of any separate license agreement you may have executed | ||||||
|  |       with Licensor regarding such Contributions. | ||||||
|  | 
 | ||||||
|  |    6. Trademarks. This License does not grant permission to use the trade | ||||||
|  |       names, trademarks, service marks, or product names of the Licensor, | ||||||
|  |       except as required for reasonable and customary use in describing the | ||||||
|  |       origin of the Work and reproducing the content of the NOTICE file. | ||||||
|  | 
 | ||||||
|  |    7. Disclaimer of Warranty. Unless required by applicable law or | ||||||
|  |       agreed to in writing, Licensor provides the Work (and each | ||||||
|  |       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||||
|  |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||||
|  |       implied, including, without limitation, any warranties or conditions | ||||||
|  |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||||
|  |       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||||
|  |       appropriateness of using or redistributing the Work and assume any | ||||||
|  |       risks associated with Your exercise of permissions under this License. | ||||||
|  | 
 | ||||||
|  |    8. Limitation of Liability. In no event and under no legal theory, | ||||||
|  |       whether in tort (including negligence), contract, or otherwise, | ||||||
|  |       unless required by applicable law (such as deliberate and grossly | ||||||
|  |       negligent acts) or agreed to in writing, shall any Contributor be | ||||||
|  |       liable to You for damages, including any direct, indirect, special, | ||||||
|  |       incidental, or consequential damages of any character arising as a | ||||||
|  |       result of this License or out of the use or inability to use the | ||||||
|  |       Work (including but not limited to damages for loss of goodwill, | ||||||
|  |       work stoppage, computer failure or malfunction, or any and all | ||||||
|  |       other commercial damages or losses), even if such Contributor | ||||||
|  |       has been advised of the possibility of such damages. | ||||||
|  | 
 | ||||||
|  |    9. Accepting Warranty or Additional Liability. While redistributing | ||||||
|  |       the Work or Derivative Works thereof, You may choose to offer, | ||||||
|  |       and charge a fee for, acceptance of support, warranty, indemnity, | ||||||
|  |       or other liability obligations and/or rights consistent with this | ||||||
|  |       License. However, in accepting such obligations, You may act only | ||||||
|  |       on Your own behalf and on Your sole responsibility, not on behalf | ||||||
|  |       of any other Contributor, and only if You agree to indemnify, | ||||||
|  |       defend, and hold each Contributor harmless for any liability | ||||||
|  |       incurred by, or claims asserted against, such Contributor by reason | ||||||
|  |       of your accepting any such warranty or additional liability. | ||||||
|  | 
 | ||||||
|  |    END OF TERMS AND CONDITIONS | ||||||
|  | 
 | ||||||
|  |    APPENDIX: How to apply the Apache License to your work. | ||||||
|  | 
 | ||||||
|  |       To apply the Apache License to your work, attach the following | ||||||
|  |       boilerplate notice, with the fields enclosed by brackets "{}" | ||||||
|  |       replaced with your own identifying information. (Don't include | ||||||
|  |       the brackets!)  The text should be enclosed in the appropriate | ||||||
|  |       comment syntax for the file format. We also recommend that a | ||||||
|  |       file or class name and description of purpose be included on the | ||||||
|  |       same "printed page" as the copyright notice for easier | ||||||
|  |       identification within third-party archives. | ||||||
|  | 
 | ||||||
|  |    Copyright {yyyy} {name of copyright owner} | ||||||
|  | 
 | ||||||
|  |    Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |    you may not use this file except in compliance with the License. | ||||||
|  |    You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |        http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  |    Unless required by applicable law or agreed to in writing, software | ||||||
|  |    distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |    See the License for the specific language governing permissions and | ||||||
|  |    limitations under the License. | ||||||
|  | 
 | ||||||
							
								
								
									
										159
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								README.md
									
									
									
									
									
								
							| @ -1,4 +1,161 @@ | |||||||
|  | <!-- BANNER_TPL_BEGIN --> | ||||||
|  | 
 | ||||||
|  | About Daplie: We're taking back the Internet! | ||||||
|  | -------------- | ||||||
|  | 
 | ||||||
|  | Down with Google, Apple, and Facebook! | ||||||
|  | 
 | ||||||
|  | We're re-decentralizing the web and making it read-write again - one home cloud system at a time. | ||||||
|  | 
 | ||||||
|  | Tired of serving the Empire? Come join the Rebel Alliance: | ||||||
|  | 
 | ||||||
|  | <a href="mailto:jobs@daplie.com">jobs@daplie.com</a> | [Invest in Daplie on Wefunder](https://daplie.com/invest/) | [Pre-order Cloud](https://daplie.com/preorder/), The World's First Home Server for Everyone | ||||||
|  | 
 | ||||||
|  | <!-- BANNER_TPL_END --> | ||||||
|  | 
 | ||||||
| Goldilocks | Goldilocks | ||||||
| ========== | ========== | ||||||
| 
 | 
 | ||||||
| The webserver that's just right. | The node.js webserver that's just right. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | A simple HTTPS static file server with valid TLS (SSL) certs. | ||||||
|  | 
 | ||||||
|  | Comes bundled a valid certificate for localhost.daplie.me, | ||||||
|  | which is great for testing and development, and you can specify your own. | ||||||
|  | 
 | ||||||
|  | Also great for testing ACME certs from letsencrypt.org. | ||||||
|  | 
 | ||||||
|  | Install | ||||||
|  | ------- | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # v2 in npm | ||||||
|  | npm install -g goldilocks | ||||||
|  | 
 | ||||||
|  | # master in git (via ssh) | ||||||
|  | npm install -g git+ssh://git@git.daplie.com:Daplie/goldilocks.js | ||||||
|  | 
 | ||||||
|  | # master in git (unauthenticated) | ||||||
|  | npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | goldilocks | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | Serving /Users/foo/ at https://localhost.daplie.me:8443 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Usage | ||||||
|  | ----- | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | # Install | ||||||
|  | npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js | ||||||
|  | 
 | ||||||
|  | # Use tunnel | ||||||
|  | goldilocks --sites jane.daplie.me --agree-tos --email jane@example.com --tunnel | ||||||
|  | 
 | ||||||
|  | # BEFORE you access in a browser for the first time, use curl | ||||||
|  | # (because there's a concurrency bug in the greenlock setup) | ||||||
|  | curl https://jane.daplie.me | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Options: | ||||||
|  | 
 | ||||||
|  | * `-p <port>` - i.e. `sudo goldilocks -p 443` (defaults to 80+443 or 8443) | ||||||
|  | * `-d <dirpath>` - i.e. `goldilocks -d /tmp/` (defaults to `pwd`) | ||||||
|  |   * you can use `:hostname` as a template for multiple directories | ||||||
|  |   * Example A: `goldilocks -d /srv/www/:hostname --sites localhost.foo.daplie.me,localhost.bar.daplie.me` | ||||||
|  |   * Example B: `goldilocks -d ./:hostname/public/ --sites localhost.foo.daplie.me,localhost.bar.daplie.me` | ||||||
|  | * `-c <content>` - i.e. `server-https -c 'Hello, World! '` (defaults to directory index) | ||||||
|  | * `--express-app <path>` - path to a file the exports an express-style app (`function (req, res, next) { ... }`) | ||||||
|  | * `--livereload` - inject livereload into all html pages (see also: [fswatch](http://stackoverflow.com/a/13807906/151312)), but be careful if `<dirpath>` has thousands of files it will spike your CPU usage to 100% | ||||||
|  | 
 | ||||||
|  | * `--email <email>` - email to use for Let's Encrypt, Daplie DNS, Daplie Tunnel | ||||||
|  | * `--agree-tos` - agree to terms for Let's Encrypt, Daplie DNS | ||||||
|  | * `--sites <domain.tld>` comma-separated list of domains to respond to (default is `localhost.daplie.me`) | ||||||
|  |   * optionally you may include the path to serve with `|` such as `example.com|/tmp,example.net/srv/www` | ||||||
|  | * `--tunnel` - make world-visible (must use `--sites`) | ||||||
|  | 
 | ||||||
|  | Specifying a custom HTTPS certificate: | ||||||
|  | 
 | ||||||
|  | * `--key /path/to/privkey.pem` specifies the server private key | ||||||
|  | * `--cert /path/to/fullchain.pem` specifies the bundle of server certificate and all intermediate certificates | ||||||
|  | * `--root /path/to/root.pem` specifies the certificate authority(ies) | ||||||
|  | 
 | ||||||
|  | Note: `--root` may specify single cert or a bundle, and may be used multiple times like so: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | --root /path/to/primary-root.pem --root /path/to/cross-root.pem | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Other options: | ||||||
|  | 
 | ||||||
|  | * `--serve-root true` alias for `-c` with the contents of root.pem | ||||||
|  | * `--sites example.com` changes the servername logged to the console | ||||||
|  | * `--letsencrypt-certs example.com` sets and key, fullchain, and root to standard letsencrypt locations | ||||||
|  | 
 | ||||||
|  | Examples | ||||||
|  | -------- | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | goldilocks -p 1443 -c 'Hello from 1443' & | ||||||
|  | goldilocks -p 2443 -c 'Hello from 2443' & | ||||||
|  | goldilocks -p 3443 -d /tmp & | ||||||
|  | 
 | ||||||
|  | curl https://localhost.daplie.me:1443 | ||||||
|  | > Hello from 1443 | ||||||
|  | 
 | ||||||
|  | curl --insecure https://localhost:2443 | ||||||
|  | > Hello from 2443 | ||||||
|  | 
 | ||||||
|  | curl https://localhost.daplie.me:3443 | ||||||
|  | > [html index listing of /tmp] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And if you tested <http://localhost.daplie.me:3443> in a browser, | ||||||
|  | it would redirect to <https://localhost.daplie.me:3443> (on the same port). | ||||||
|  | 
 | ||||||
|  | (in curl it would just show an error message) | ||||||
|  | 
 | ||||||
|  | ### Testing ACME Let's Encrypt certs | ||||||
|  | 
 | ||||||
|  | In case you didn't know, you can get free https certificates from | ||||||
|  | [letsencrypt.org](https://letsencrypt.org) | ||||||
|  | (ACME letsencrypt) | ||||||
|  | and even a free subdomain from <https://freedns.afraid.org>. | ||||||
|  | 
 | ||||||
|  | If you want to quickly test the certificates you installed, | ||||||
|  | you can do so like this: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | goldilocks -p 8443 \ | ||||||
|  |   --letsencrypt-certs test.mooo.com \ | ||||||
|  |   --serve-root true | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | which is equilavent to | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | goldilocks -p 8443 \ | ||||||
|  |   --sites test.mooo.com | ||||||
|  |   --key /etc/letsencrypt/live/test.mooo.com/privkey.pem \ | ||||||
|  |   --cert /etc/letsencrypt/live/test.mooo.com/fullchain.pem \ | ||||||
|  |   --root /etc/letsencrypt/live/test.mooo.com/root.pem \ | ||||||
|  |   -c "$(cat 'sudo /etc/letsencrypt/live/test.mooo.com/root.pem')" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | and can be tested like so | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | curl --insecure https://test.mooo.com:8443 > ./root.pem | ||||||
|  | curl https://test.mooo.com:8843 --cacert ./root.pem | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | * [QuickStart Guide for Let's Encrypt](https://coolaj86.com/articles/lets-encrypt-on-raspberry-pi/) | ||||||
|  | * [QuickStart Guide for FreeDNS](https://coolaj86.com/articles/free-dns-hosting-with-freedns-afraid-org.html) | ||||||
|  | |||||||
							
								
								
									
										543
									
								
								bin/goldilocks.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										543
									
								
								bin/goldilocks.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,543 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | //var PromiseA = global.Promise;
 | ||||||
|  | var PromiseA = require('bluebird'); | ||||||
|  | var tls = require('tls'); | ||||||
|  | var https = require('httpolyglot'); | ||||||
|  | var http = require('http'); | ||||||
|  | var fs = require('fs'); | ||||||
|  | var path = require('path'); | ||||||
|  | var DDNS = require('ddns-cli'); | ||||||
|  | var httpPort = 80; | ||||||
|  | var httpsPort = 443; | ||||||
|  | var lrPort = 35729; | ||||||
|  | var portFallback = 8443; | ||||||
|  | var insecurePortFallback = 4080; | ||||||
|  | 
 | ||||||
|  | function showError(err, port) { | ||||||
|  |   if ('EACCES' === err.code) { | ||||||
|  |     console.error(err); | ||||||
|  |     console.warn("You do not have permission to use '" + port + "'."); | ||||||
|  |     console.warn("You can probably fix that by running as Administrator or root."); | ||||||
|  |   } | ||||||
|  |   else if ('EADDRINUSE' === err.code) { | ||||||
|  |     console.warn("Another server is already running on '" + port + "'."); | ||||||
|  |     console.warn("You can probably fix that by rebooting your computer (or stopping it if you know what it is)."); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function createInsecureServer(port, _delete_me_, opts) { | ||||||
|  |   return new PromiseA(function (realResolve) { | ||||||
|  |     var server = http.createServer(); | ||||||
|  | 
 | ||||||
|  |     function resolve() { | ||||||
|  |       realResolve(server); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     server.on('error', function (err) { | ||||||
|  |       if (opts.errorInsecurePort || opts.manualInsecurePort) { | ||||||
|  |         showError(err, port); | ||||||
|  |         process.exit(1); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       opts.errorInsecurePort = err.toString(); | ||||||
|  | 
 | ||||||
|  |       return createInsecureServer(insecurePortFallback, null, opts).then(resolve); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     server.on('request', opts.redirectApp); | ||||||
|  | 
 | ||||||
|  |     server.listen(port, function () { | ||||||
|  |       opts.insecurePort = port; | ||||||
|  |       resolve(); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function createServer(port, _delete_me_, content, opts) { | ||||||
|  |   function approveDomains(params, certs, cb) { | ||||||
|  |     // This is where you check your database and associated
 | ||||||
|  |     // email addresses with domains and agreements and such
 | ||||||
|  |     var domains = params.domains; | ||||||
|  |     //var p;
 | ||||||
|  |     console.log('approveDomains'); | ||||||
|  |     console.log(domains); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // The domains being approved for the first time are listed in opts.domains
 | ||||||
|  |     // Certs being renewed are listed in certs.altnames
 | ||||||
|  |     if (certs) { | ||||||
|  |       params.domains = certs.altnames; | ||||||
|  |       //p = PromiseA.resolve();
 | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       //params.email = opts.email;
 | ||||||
|  |       if (!opts.agreeTos) { | ||||||
|  |         console.error("You have not previously registered '" + domains + "' so you must specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service."); | ||||||
|  |         process.exit(1); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       params.agreeTos = opts.agreeTos; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ddns.token(params.email, domains[0])
 | ||||||
|  |     params.email = opts.email; | ||||||
|  |     params.refreshToken = opts.refreshToken; | ||||||
|  |     params.challengeType = 'dns-01'; | ||||||
|  |     params.cli = opts.argv; | ||||||
|  | 
 | ||||||
|  |     cb(null, { options: params, certs: certs }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return new PromiseA(function (realResolve) { | ||||||
|  |     var app = require('../lib/app.js'); | ||||||
|  | 
 | ||||||
|  |     var directive = { content: content, livereload: opts.livereload | ||||||
|  |       , sites: opts.sites | ||||||
|  |       , expressApp: opts.expressApp }; | ||||||
|  |     var insecureServer; | ||||||
|  | 
 | ||||||
|  |     function resolve() { | ||||||
|  |       realResolve({ | ||||||
|  |         plainServer: insecureServer | ||||||
|  |       , server: server | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // returns an instance of node-letsencrypt with additional helper methods
 | ||||||
|  |     var webrootPath = require('os').tmpdir(); | ||||||
|  |     var leChallengeFs = require('le-challenge-fs').create({ webrootPath: webrootPath }); | ||||||
|  |     //var leChallengeSni = require('le-challenge-sni').create({ webrootPath: webrootPath });
 | ||||||
|  |     var leChallengeDdns = require('le-challenge-ddns').create({ ttl: 1 }); | ||||||
|  |     var lex = require('greenlock-express').create({ | ||||||
|  |       // set to https://acme-v01.api.letsencrypt.org/directory in production
 | ||||||
|  |       server: opts.debug ? 'staging' : 'https://acme-v01.api.letsencrypt.org/directory' | ||||||
|  | 
 | ||||||
|  |     // If you wish to replace the default plugins, you may do so here
 | ||||||
|  |     //
 | ||||||
|  |     , challenges: { | ||||||
|  |         'http-01': leChallengeFs | ||||||
|  |       , 'tls-sni-01': leChallengeFs // leChallengeSni
 | ||||||
|  |       , 'dns-01': leChallengeDdns | ||||||
|  |       } | ||||||
|  |     , challengeType: (opts.tunnel ? 'http-01' : 'dns-01') | ||||||
|  |     , store: require('le-store-certbot').create({ | ||||||
|  |         webrootPath: webrootPath | ||||||
|  |       , configDir: path.join((opts.homedir || '~'), 'letsencrypt', 'etc') | ||||||
|  |       , homedir: opts.homedir | ||||||
|  |       }) | ||||||
|  |     , webrootPath: webrootPath | ||||||
|  | 
 | ||||||
|  |     // You probably wouldn't need to replace the default sni handler
 | ||||||
|  |     // See https://git.daplie.com/Daplie/le-sni-auto if you think you do
 | ||||||
|  |     //, sni: require('le-sni-auto').create({})
 | ||||||
|  | 
 | ||||||
|  |     , approveDomains: approveDomains | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     var secureContexts = { | ||||||
|  |       'localhost.daplie.me': null | ||||||
|  |     }; | ||||||
|  |     opts.httpsOptions.SNICallback = function (sni, cb ) { | ||||||
|  |       var tlsOptions; | ||||||
|  |       console.log('[https] sni', sni); | ||||||
|  | 
 | ||||||
|  |       // Static Certs
 | ||||||
|  |       if (/.*localhost.*\.daplie\.me/.test(sni.toLowerCase())) { | ||||||
|  |         // TODO implement
 | ||||||
|  |         if (!secureContexts[sni]) { | ||||||
|  |           tlsOptions = require('localhost.daplie.me-certificates').mergeTlsOptions(sni, {}); | ||||||
|  |         } | ||||||
|  |         if (tlsOptions) { | ||||||
|  |           secureContexts[sni] = tls.createSecureContext(tlsOptions); | ||||||
|  |         } | ||||||
|  |         cb(null, secureContexts[sni]); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Dynamic Certs
 | ||||||
|  |       lex.httpsOptions.SNICallback(sni, cb); | ||||||
|  |     }; | ||||||
|  |     var server = https.createServer(opts.httpsOptions); | ||||||
|  | 
 | ||||||
|  |     server.on('error', function (err) { | ||||||
|  |       if (opts.errorPort || opts.manualPort) { | ||||||
|  |         showError(err, port); | ||||||
|  |         process.exit(1); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       opts.errorPort = err.toString(); | ||||||
|  | 
 | ||||||
|  |       return createServer(portFallback, null, content, opts).then(resolve); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     server.listen(port, function () { | ||||||
|  |       opts.port = port; | ||||||
|  |       opts.redirectOptions.port = port; | ||||||
|  | 
 | ||||||
|  |       if (opts.livereload) { | ||||||
|  |         opts.lrPort = opts.lrPort || lrPort; | ||||||
|  |         var livereload = require('livereload'); | ||||||
|  |         var server2 = livereload.createServer({ | ||||||
|  |           https: opts.httpsOptions | ||||||
|  |         , port: opts.lrPort | ||||||
|  |         , exclusions: [ 'node_modules' ] | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         console.info("[livereload] watching " + opts.pubdir); | ||||||
|  |         console.warn("WARNING: If CPU usage spikes to 100% it's because too many files are being watched"); | ||||||
|  |         // TODO create map of directories to watch from opts.sites and iterate over it
 | ||||||
|  |         server2.watch(opts.pubdir); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // if we haven't disabled insecure port
 | ||||||
|  |       if ('false' !== opts.insecurePort) { | ||||||
|  |         // and both ports are the default
 | ||||||
|  |         if ((httpsPort === opts.port && httpPort === opts.insecurePort) | ||||||
|  |           // or other case
 | ||||||
|  |           || (httpPort !== opts.insecurePort && opts.port !== opts.insecurePort) | ||||||
|  |         ) { | ||||||
|  |           return createInsecureServer(opts.insecurePort, null, opts).then(function (_server) { | ||||||
|  |             insecureServer = _server; | ||||||
|  |             resolve(); | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       opts.insecurePort = opts.port; | ||||||
|  |       resolve(); | ||||||
|  |       return; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if ('function' === typeof app) { | ||||||
|  |       app = app(directive); | ||||||
|  |     } else if ('function' === typeof app.create) { | ||||||
|  |       app = app.create(directive); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     server.on('request', function (req, res) { | ||||||
|  |       console.log('[' + req.method + '] ' + req.url); | ||||||
|  |       if (!req.socket.encrypted && !/\/\.well-known\/acme-challenge\//.test(req.url)) { | ||||||
|  |         opts.redirectApp(req, res); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if ('function' === typeof app) { | ||||||
|  |         app(req, res); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       res.end('not ready'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return PromiseA.resolve(app).then(function (_app) { | ||||||
|  |       app = _app; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.createServer = createServer; | ||||||
|  | 
 | ||||||
|  | function run() { | ||||||
|  |   var defaultServername = 'localhost.daplie.me'; | ||||||
|  |   var minimist = require('minimist'); | ||||||
|  |   var argv = minimist(process.argv.slice(2)); | ||||||
|  |   var port = parseInt(argv.p || argv.port || argv._[0], 10) || httpsPort; | ||||||
|  |   var livereload = argv.livereload; | ||||||
|  |   var defaultWebRoot = path.resolve(argv['default-web-root'] || argv.d || argv._[1] || process.cwd()); | ||||||
|  |   var content = argv.c; | ||||||
|  |   var letsencryptHost = argv['letsencrypt-certs']; | ||||||
|  | 
 | ||||||
|  |   if (argv.V || argv.version || argv.v) { | ||||||
|  |     if (argv.v) { | ||||||
|  |       console.warn("flag -v is reserved for future use. Use -V or --version for version information."); | ||||||
|  |     } | ||||||
|  |     console.info('v' + require('../package.json').version); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (argv.servername && argv.sites) { | ||||||
|  |     throw new Error('specify only --sites, not --servername'); | ||||||
|  |   } | ||||||
|  |   argv.sites = argv.sites || argv.servername; | ||||||
|  | 
 | ||||||
|  |   // letsencrypt
 | ||||||
|  |   var httpsOptions = require('localhost.daplie.me-certificates').merge({}); | ||||||
|  |   var secureContext; | ||||||
|  | 
 | ||||||
|  |   var opts = { | ||||||
|  |     agreeTos: argv.agreeTos || argv['agree-tos'] | ||||||
|  |   , debug: argv.debug | ||||||
|  |   , device: argv.device | ||||||
|  |   , provider: (argv.provider && 'false' !== argv.provider) ? argv.provider : 'oauth3.org' | ||||||
|  |   , email: argv.email | ||||||
|  |   , httpsOptions: { | ||||||
|  |       key: httpsOptions.key | ||||||
|  |     , cert: httpsOptions.cert | ||||||
|  |     //, ca: httpsOptions.ca
 | ||||||
|  |     } | ||||||
|  |   , homedir: argv.homedir | ||||||
|  |   , argv: argv | ||||||
|  |   }; | ||||||
|  |   var peerCa; | ||||||
|  |   var p; | ||||||
|  | 
 | ||||||
|  |   opts.PromiseA = PromiseA; | ||||||
|  |   opts.httpsOptions.SNICallback = function (sni, cb) { | ||||||
|  |     if (!secureContext) { | ||||||
|  |       secureContext = tls.createSecureContext(opts.httpsOptions); | ||||||
|  |     } | ||||||
|  |     cb(null, secureContext); | ||||||
|  |     return; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   if (letsencryptHost) { | ||||||
|  |     // TODO remove in v3.x (aka goldilocks)
 | ||||||
|  |     argv.key = argv.key || '/etc/letsencrypt/live/' + letsencryptHost + '/privkey.pem'; | ||||||
|  |     argv.cert = argv.cert || '/etc/letsencrypt/live/' + letsencryptHost + '/fullchain.pem'; | ||||||
|  |     argv.root = argv.root || argv.chain || ''; | ||||||
|  |     argv.sites = argv.sites || letsencryptHost; | ||||||
|  |     argv['serve-root'] = argv['serve-root'] || argv['serve-chain']; | ||||||
|  |     // argv[express-app]
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (argv['serve-root'] && !argv.root) { | ||||||
|  |     console.error("You must specify bath --root to use --serve-root"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (argv.key || argv.cert || argv.root) { | ||||||
|  |     if (!argv.key || !argv.cert) { | ||||||
|  |       console.error("You must specify bath --key and --cert, and optionally --root (required with serve-root)"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!Array.isArray(argv.root)) { | ||||||
|  |       argv.root = [argv.root]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     opts.httpsOptions.key = fs.readFileSync(argv.key); | ||||||
|  |     opts.httpsOptions.cert = fs.readFileSync(argv.cert); | ||||||
|  | 
 | ||||||
|  |     // turn multiple-cert pemfile into array of cert strings
 | ||||||
|  |     peerCa = argv.root.reduce(function (roots, fullpath) { | ||||||
|  |       if (!fs.existsSync(fullpath)) { | ||||||
|  |         return roots; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return roots.concat(fs.readFileSync(fullpath, 'ascii') | ||||||
|  |       .split('-----END CERTIFICATE-----') | ||||||
|  |       .filter(function (ca) { | ||||||
|  |         return ca.trim(); | ||||||
|  |       }).map(function (ca) { | ||||||
|  |         return (ca + '-----END CERTIFICATE-----').trim(); | ||||||
|  |       })); | ||||||
|  |     }, []); | ||||||
|  | 
 | ||||||
|  |     // TODO * `--verify /path/to/root.pem` require peers to present certificates from said authority
 | ||||||
|  |     if (argv.verify) { | ||||||
|  |       opts.httpsOptions.ca = peerCa; | ||||||
|  |       opts.httpsOptions.requestCert = true; | ||||||
|  |       opts.httpsOptions.rejectUnauthorized = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (argv['serve-root']) { | ||||||
|  |       content = peerCa.join('\r\n'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   opts.sites = [ { name: defaultServername , path: '.' } ]; | ||||||
|  |   if (argv.sites) { | ||||||
|  |     opts._externalHost = false; | ||||||
|  |     opts.sites = argv.sites.split(',').map(function (name) { | ||||||
|  |       var nameparts = name.split('|'); | ||||||
|  |       var servername = nameparts.shift(); | ||||||
|  |       opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername); | ||||||
|  |       // TODO allow reverse proxy
 | ||||||
|  |       return { | ||||||
|  |         name: servername | ||||||
|  |         // there should always be a path
 | ||||||
|  |       , paths: nameparts.length && nameparts || [ | ||||||
|  |           defaultWebRoot.replace(/(:hostname|:servername)/g, servername) | ||||||
|  |         ] | ||||||
|  |         // TODO check for existing custom path before issuing with greenlock
 | ||||||
|  |       , _hasCustomPath: !!nameparts.length | ||||||
|  |       }; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   // TODO use arrays in all things
 | ||||||
|  |   opts._old_server_name = opts.sites[0].name; | ||||||
|  |   opts.pubdir = defaultWebRoot.replace(/(:hostname|:servername).*/, ''); | ||||||
|  | 
 | ||||||
|  |   if (argv.p || argv.port || argv._[0]) { | ||||||
|  |     opts.manualPort = true; | ||||||
|  |   } | ||||||
|  |   if (argv.t || argv.tunnel) { | ||||||
|  |     opts.tunnel = true; | ||||||
|  |   } | ||||||
|  |   if (argv.i || argv['insecure-port']) { | ||||||
|  |     opts.manualInsecurePort = true; | ||||||
|  |   } | ||||||
|  |   opts.insecurePort = parseInt(argv.i || argv['insecure-port'], 10) | ||||||
|  |     || argv.i || argv['insecure-port'] | ||||||
|  |     || httpPort | ||||||
|  |     ; | ||||||
|  |   opts.livereload = livereload; | ||||||
|  | 
 | ||||||
|  |   if (argv['express-app']) { | ||||||
|  |     opts.expressApp = require(path.resolve(process.cwd(), argv['express-app'])); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (opts.email || opts._externalHost) { | ||||||
|  |     if (!opts.agreeTos) { | ||||||
|  |       console.warn("You may need to specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service."); | ||||||
|  |     } | ||||||
|  |     if (!opts.email) { | ||||||
|  |       // TODO store email in .ddnsrc.json
 | ||||||
|  |       console.warn("You may need to specify --email to register with both the Let's Encrypt and Daplie DNS."); | ||||||
|  |     } | ||||||
|  |     p = DDNS.refreshToken({ | ||||||
|  |       email: opts.email | ||||||
|  |     , providerUrl: opts.provider | ||||||
|  |     , silent: true | ||||||
|  |     , homedir: opts.homedir | ||||||
|  |     }, { | ||||||
|  |       debug: false | ||||||
|  |     , email: opts.argv.email | ||||||
|  |     }).then(function (refreshToken) { | ||||||
|  |       opts.refreshToken = refreshToken; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   else { | ||||||
|  |     p = PromiseA.resolve(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return p.then(function () { | ||||||
|  | 
 | ||||||
|  |   // can be changed to tunnel external port
 | ||||||
|  |   opts.redirectOptions = { | ||||||
|  |     port: opts.port | ||||||
|  |   }; | ||||||
|  |   opts.redirectApp = require('redirect-https')(opts.redirectOptions); | ||||||
|  | 
 | ||||||
|  |   return createServer(port, null, content, opts).then(function (servers) { | ||||||
|  |     var p; | ||||||
|  |     var httpsUrl; | ||||||
|  |     var httpUrl; | ||||||
|  |     var promise; | ||||||
|  | 
 | ||||||
|  |     // TODO show all sites
 | ||||||
|  |     console.info(''); | ||||||
|  |     console.info('Serving ' + opts.pubdir + ' at '); | ||||||
|  |     console.info(''); | ||||||
|  | 
 | ||||||
|  |     // Port
 | ||||||
|  |     httpsUrl = 'https://' + opts._old_server_name; | ||||||
|  |     p = opts.port; | ||||||
|  |     if (httpsPort !== p) { | ||||||
|  |       httpsUrl += ':' + p; | ||||||
|  |     } | ||||||
|  |     console.info('\t' + httpsUrl); | ||||||
|  | 
 | ||||||
|  |     // Insecure Port
 | ||||||
|  |     httpUrl = 'http://' + opts._old_server_name; | ||||||
|  |     p = opts.insecurePort; | ||||||
|  |     if (httpPort !== p) { | ||||||
|  |       httpUrl += ':' + p; | ||||||
|  |     } | ||||||
|  |     console.info('\t' + httpUrl + ' (redirecting to https)'); | ||||||
|  |     console.info(''); | ||||||
|  | 
 | ||||||
|  |     if (!(argv.sites && (defaultServername !== argv.sites) && !(argv.key && argv.cert))) { | ||||||
|  |       // TODO what is this condition actually intending to test again?
 | ||||||
|  |       // (I think it can be replaced with if (!opts._externalHost) { ... }
 | ||||||
|  | 
 | ||||||
|  |       // ifaces
 | ||||||
|  |       opts.ifaces = require('../lib/local-ip.js').find(); | ||||||
|  |       promise = PromiseA.resolve(); | ||||||
|  |     } else { | ||||||
|  |       console.info("Attempting to resolve external connection for '" + opts._old_server_name + "'"); | ||||||
|  |       try { | ||||||
|  |         promise = require('../lib/match-ips.js').match(opts._old_server_name, opts); | ||||||
|  |       } catch(e) { | ||||||
|  |         console.warn("Upgrade to version 2.x to use automatic certificate issuance for '" + opts._old_server_name + "'"); | ||||||
|  |         promise = PromiseA.resolve(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return promise.then(function (matchingIps) { | ||||||
|  |       if (matchingIps) { | ||||||
|  |         if (!matchingIps.length) { | ||||||
|  |           console.info("Neither the attached nor external interfaces match '" + opts._old_server_name + "'"); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       opts.matchingIps = matchingIps || []; | ||||||
|  | 
 | ||||||
|  |       if (opts.matchingIps.length) { | ||||||
|  |         console.info(''); | ||||||
|  |         console.info('External IPs:'); | ||||||
|  |         console.info(''); | ||||||
|  |         opts.matchingIps.forEach(function (ip) { | ||||||
|  |           if ('IPv4' === ip.family) { | ||||||
|  |             httpsUrl = 'https://' + ip.address; | ||||||
|  |             if (httpsPort !== opts.port) { | ||||||
|  |               httpsUrl += ':' + opts.port; | ||||||
|  |             } | ||||||
|  |             console.info('\t' + httpsUrl); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             httpsUrl = 'https://[' + ip.address + ']'; | ||||||
|  |             if (httpsPort !== opts.port) { | ||||||
|  |               httpsUrl += ':' + opts.port; | ||||||
|  |             } | ||||||
|  |             console.info('\t' + httpsUrl); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       else if (!opts.tunnel) { | ||||||
|  |         console.info("External IP address does not match local IP address."); | ||||||
|  |         console.info("Use --tunnel to allow the people of the Internet to access your server."); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (opts.tunnel) { | ||||||
|  |         require('../lib/tunnel.js').create(opts, servers); | ||||||
|  |       } | ||||||
|  |       else if (opts.ddns) { | ||||||
|  |         require('../lib/ddns.js').create(opts, servers); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       Object.keys(opts.ifaces).forEach(function (iname) { | ||||||
|  |         var iface = opts.ifaces[iname]; | ||||||
|  | 
 | ||||||
|  |         if (iface.ipv4.length) { | ||||||
|  |           console.info(''); | ||||||
|  |           console.info(iname + ':'); | ||||||
|  | 
 | ||||||
|  |           httpsUrl = 'https://' + iface.ipv4[0].address; | ||||||
|  |           if (httpsPort !== opts.port) { | ||||||
|  |             httpsUrl += ':' + opts.port; | ||||||
|  |           } | ||||||
|  |           console.info('\t' + httpsUrl); | ||||||
|  | 
 | ||||||
|  |           if (iface.ipv6.length) { | ||||||
|  |             httpsUrl = 'https://[' + iface.ipv6[0].address + ']'; | ||||||
|  |             if (httpsPort !== opts.port) { | ||||||
|  |               httpsUrl += ':' + opts.port; | ||||||
|  |             } | ||||||
|  |             console.info('\t' + httpsUrl); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       console.info(''); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (require.main === module) { | ||||||
|  |   run(); | ||||||
|  | } | ||||||
							
								
								
									
										108
									
								
								lib/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								lib/app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | module.exports = function (opts) { | ||||||
|  |   var finalhandler = require('finalhandler'); | ||||||
|  |   var serveStatic = require('serve-static'); | ||||||
|  |   var serveIndex = require('serve-index'); | ||||||
|  | 
 | ||||||
|  |   var hostsMap = {}; | ||||||
|  |   var pathsMap = {}; | ||||||
|  |   var content = opts.content; | ||||||
|  |   var server; | ||||||
|  | 
 | ||||||
|  |   function addServer(hostname) { | ||||||
|  | 
 | ||||||
|  |     if (hostsMap[hostname]) { | ||||||
|  |       return hostsMap[hostname]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     opts.sites.forEach(function (site) { | ||||||
|  |       if (hostname !== site.name) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // path should exist before it gets to this point
 | ||||||
|  |       site.path = site.path || site.paths[0]; | ||||||
|  | 
 | ||||||
|  |       if (!pathsMap[site.path]) { | ||||||
|  |         pathsMap[site.path] = { | ||||||
|  |           serve: serveStatic(site.path) | ||||||
|  |         // TODO option for dotfiles
 | ||||||
|  |         , index: serveIndex(site.path) | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       hostsMap[hostname] = { | ||||||
|  |         serve: pathsMap[site.path].serve | ||||||
|  |       , index: pathsMap[site.path].index | ||||||
|  |       , app: site.app | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function _reloadWrite(data, enc, cb) { | ||||||
|  |     /*jshint validthis: true */ | ||||||
|  |     if (this.headersSent) { | ||||||
|  |       this.__write(data, enc, cb); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!/html/i.test(this.getHeader('Content-Type'))) { | ||||||
|  |       this.__write(data, enc, cb); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (this.getHeader('Content-Length')) { | ||||||
|  |       this.setHeader('Content-Length', this.getHeader('Content-Length') + this.__my_addLen); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.__write(this.__my_livereload); | ||||||
|  |     this.__write(data, enc, cb); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   addServer(opts.sites[0].name); | ||||||
|  | 
 | ||||||
|  |   return function (req, res) { | ||||||
|  |     if (content && '/' === req.url) { | ||||||
|  |       // res.setHeader('Content-Type', 'application/octet-stream');
 | ||||||
|  |       res.end(content); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     var done = finalhandler(req, res); | ||||||
|  |     var host = req.headers.host; | ||||||
|  |     var hostname = (host||'').split(':')[0] || opts.sites[0].name; | ||||||
|  | 
 | ||||||
|  |     function serveStatic(server) { | ||||||
|  |       if (server.expressApp) { | ||||||
|  |         server.expressApp(req, res, serveStatic); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       server.serve(req, res, function (err) { | ||||||
|  |         if (err) { return done(err); } | ||||||
|  |         server.index(req, res, done); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (opts.livereload) { | ||||||
|  |       res.__my_livereload = '<script src="//' | ||||||
|  |         + (host || opts.sites[0].name).split(':')[0] | ||||||
|  |         + ':35729/livereload.js?snipver=1"></script>'; | ||||||
|  |       res.__my_addLen = res.__my_livereload.length; | ||||||
|  | 
 | ||||||
|  |       // TODO modify prototype instead of each instance?
 | ||||||
|  |       res.__write = res.write; | ||||||
|  |       res.write = _reloadWrite; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     console.log('hostname:', hostname); | ||||||
|  | 
 | ||||||
|  |     addServer(hostname); | ||||||
|  |     server = hostsMap[hostname] || hostsMap[opts.sites[0].name]; | ||||||
|  |     serveStatic(server); | ||||||
|  | 
 | ||||||
|  |   }; | ||||||
|  | }; | ||||||
							
								
								
									
										88
									
								
								lib/ddns.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								lib/ddns.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | module.exports.create = function (opts/*, servers*/) { | ||||||
|  |   var PromiseA = opts.PromiseA; | ||||||
|  |   var dns = PromiseA.promisifyAll(require('dns')); | ||||||
|  | 
 | ||||||
|  |   return PromiseA.all([ | ||||||
|  |     dns.resolve4Async(opts._old_server_name).then(function (results) { | ||||||
|  |       return results; | ||||||
|  |     }, function () {}) | ||||||
|  |   , dns.resolve6Async(opts._old_server_name).then(function (results) { | ||||||
|  |       return results; | ||||||
|  |     }, function () {}) | ||||||
|  |   ]).then(function (results) { | ||||||
|  |     var ipv4 = results[0] || []; | ||||||
|  |     var ipv6 = results[1] || []; | ||||||
|  |     var record; | ||||||
|  | 
 | ||||||
|  |     opts.dnsRecords = { | ||||||
|  |       A: ipv4 | ||||||
|  |     , AAAA: ipv6 | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     Object.keys(opts.ifaces).some(function (ifacename) { | ||||||
|  |       var iface = opts.ifaces[ifacename]; | ||||||
|  | 
 | ||||||
|  |       return iface.ipv4.some(function (localIp) { | ||||||
|  |         return ipv4.some(function (remoteIp) { | ||||||
|  |           if (localIp.address === remoteIp) { | ||||||
|  |             record = localIp; | ||||||
|  |             return record; | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       }) || iface.ipv6.some(function (localIp) { | ||||||
|  |         return ipv6.forEach(function (remoteIp) { | ||||||
|  |           if (localIp.address === remoteIp) { | ||||||
|  |             record = localIp; | ||||||
|  |             return record; | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!record) { | ||||||
|  |       console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address."); | ||||||
|  |       console.info("Use --ddns to allow the people of the Internet to access your server."); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     opts.externalIps.ipv4.some(function (localIp) { | ||||||
|  |       return ipv4.some(function (remoteIp) { | ||||||
|  |         if (localIp.address === remoteIp) { | ||||||
|  |           record = localIp; | ||||||
|  |           return record; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     opts.externalIps.ipv6.some(function (localIp) { | ||||||
|  |       return ipv6.some(function (remoteIp) { | ||||||
|  |         if (localIp.address === remoteIp) { | ||||||
|  |           record = localIp; | ||||||
|  |           return record; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!record) { | ||||||
|  |       console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address."); | ||||||
|  |       console.info("Use --ddns to allow the people of the Internet to access your server."); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | if (require.main === module) { | ||||||
|  |   var opts = { | ||||||
|  |     _old_server_name: 'aj.daplie.me' | ||||||
|  |   , PromiseA: require('bluebird') | ||||||
|  |   }; | ||||||
|  |   // ifaces
 | ||||||
|  |   opts.ifaces = require('./local-ip.js').find(); | ||||||
|  |   console.log('opts.ifaces'); | ||||||
|  |   console.log(opts.ifaces); | ||||||
|  |   require('./match-ips.js').match(opts._old_server_name, opts).then(function (ips) { | ||||||
|  |     opts.matchingIps = ips.matchingIps || []; | ||||||
|  |     opts.externalIps = ips.externalIps; | ||||||
|  |     module.exports.create(opts); | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								lib/local-ip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/local-ip.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var os = require('os'); | ||||||
|  | 
 | ||||||
|  | module.exports.find = function (opts) { | ||||||
|  |   opts = opts || {}; | ||||||
|  |   opts.externals = opts.externals || []; | ||||||
|  | 
 | ||||||
|  |   var ifaceMap = os.networkInterfaces(); | ||||||
|  |   var newMap = {}; | ||||||
|  | 
 | ||||||
|  |   Object.keys(ifaceMap).forEach(function (iname) { | ||||||
|  |     var ifaces = ifaceMap[iname]; | ||||||
|  | 
 | ||||||
|  |     ifaces = ifaces.filter(function (iface) { | ||||||
|  |       return opts.externals.some(function (ip) { | ||||||
|  |         if (ip.address === iface.address) { | ||||||
|  |           ip.external = true; | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |       }) || (!iface.internal && !/^fe80/.test(iface.address) && !/^[0:]+$/.test(iface.mac)); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (!ifaces.length) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     newMap[iname] = newMap[iname] || { ipv4: [], ipv6: [] }; | ||||||
|  | 
 | ||||||
|  |     ifaces.forEach(function (addr) { | ||||||
|  |       addr.iface = iname; | ||||||
|  |       if ('IPv4' === addr.family) { | ||||||
|  |         newMap[iname].ipv4.push(addr); | ||||||
|  |       } | ||||||
|  |       else if ('IPv6' === addr.family) { | ||||||
|  |         newMap[iname].ipv6.push(addr); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return newMap; | ||||||
|  | 
 | ||||||
|  |   /* | ||||||
|  | https://[2601:681:300:92c0:2477:d58a:d69e:51a0]:8443
 | ||||||
|  | 
 | ||||||
|  |   console.log(''); | ||||||
|  | 
 | ||||||
|  |     console.log(''); | ||||||
|  |     console.log(iname); | ||||||
|  |     console.log(ifaces); | ||||||
|  |     console.log(''); | ||||||
|  |   */ | ||||||
|  | }; | ||||||
							
								
								
									
										117
									
								
								lib/match-ips.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								lib/match-ips.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var PromiseA = require('bluebird'); | ||||||
|  | 
 | ||||||
|  | module.exports.match = function (servername, opts) { | ||||||
|  |   return PromiseA.promisify(require('ipify'))().then(function (externalIp) { | ||||||
|  |     var dns = PromiseA.promisifyAll(require('dns')); | ||||||
|  | 
 | ||||||
|  |     opts.externalIps = [ { address: externalIp, family: 'IPv4' } ]; | ||||||
|  |     opts.ifaces = require('./local-ip.js').find({ externals: opts.externalIps }); | ||||||
|  |     opts.externalIfaces = Object.keys(opts.ifaces).reduce(function (all, iname) { | ||||||
|  |       var iface = opts.ifaces[iname]; | ||||||
|  | 
 | ||||||
|  |       iface.ipv4.forEach(function (addr) { | ||||||
|  |         if (addr.external) { | ||||||
|  |           addr.iface = iname; | ||||||
|  |           all.push(addr); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       iface.ipv6.forEach(function (addr) { | ||||||
|  |         if (addr.external) { | ||||||
|  |           addr.iface = iname; | ||||||
|  |           all.push(addr); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       return all; | ||||||
|  |     }, []).filter(Boolean); | ||||||
|  | 
 | ||||||
|  |     function resolveIps(hostname) { | ||||||
|  |       var allIps = []; | ||||||
|  | 
 | ||||||
|  |       return PromiseA.all([ | ||||||
|  |         dns.resolve4Async(hostname).then(function (records) { | ||||||
|  |             records.forEach(function (ip) { | ||||||
|  |               allIps.push({ | ||||||
|  |                 address: ip | ||||||
|  |               , family: 'IPv4' | ||||||
|  |               }); | ||||||
|  |             }); | ||||||
|  |           }, function () {}) | ||||||
|  |         , dns.resolve6Async(hostname).then(function (records) { | ||||||
|  |             records.forEach(function (ip) { | ||||||
|  |               allIps.push({ | ||||||
|  |                 address: ip | ||||||
|  |               , family: 'IPv6' | ||||||
|  |               }); | ||||||
|  |             }); | ||||||
|  |           }, function () {}) | ||||||
|  |       ]).then(function () { | ||||||
|  |         return allIps; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function resolveIpsAndCnames(hostname) { | ||||||
|  |       return PromiseA.all([ | ||||||
|  |         resolveIps(hostname) | ||||||
|  |       , dns.resolveCnameAsync(hostname).then(function (records) { | ||||||
|  |           return PromiseA.all(records.map(function (hostname) { | ||||||
|  |             return resolveIps(hostname); | ||||||
|  |           })).then(function (allIps) { | ||||||
|  |             return allIps.reduce(function (all, ips) { | ||||||
|  |               return all.concat(ips); | ||||||
|  |             }, []); | ||||||
|  |           }); | ||||||
|  |         }, function () { | ||||||
|  |           return []; | ||||||
|  |         }) | ||||||
|  |       ]).then(function (ips) { | ||||||
|  |         return ips.reduce(function (all, set) { | ||||||
|  |           return all.concat(set); | ||||||
|  |         }, []); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return resolveIpsAndCnames(servername).then(function (allIps) { | ||||||
|  |       var matchingIps = []; | ||||||
|  | 
 | ||||||
|  |       if (!allIps.length) { | ||||||
|  |         console.warn("Could not resolve '" + servername + "'"); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // { address, family }
 | ||||||
|  |       allIps.some(function (ip) { | ||||||
|  |         function match(addr) { | ||||||
|  |           if (ip.address === addr.address) { | ||||||
|  |             matchingIps.push(addr); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         opts.externalIps.forEach(match); | ||||||
|  |         // opts.externalIfaces.forEach(match);
 | ||||||
|  | 
 | ||||||
|  |         Object.keys(opts.ifaces).forEach(function (iname) { | ||||||
|  |           var iface = opts.ifaces[iname]; | ||||||
|  | 
 | ||||||
|  |           iface.ipv4.forEach(match); | ||||||
|  |           iface.ipv6.forEach(match); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return matchingIps.length; | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       matchingIps.externalIps = { | ||||||
|  |         ipv4: [ | ||||||
|  |           { address: externalIp | ||||||
|  |           , family: 'IPv4' | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       , ipv6: [ | ||||||
|  |         ] | ||||||
|  |       }; | ||||||
|  |       matchingIps.matchingIps = matchingIps; | ||||||
|  |       return matchingIps; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										144
									
								
								lib/tunnel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/tunnel.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | module.exports.create = function (opts, servers) { | ||||||
|  |   // servers = { plainserver, server }
 | ||||||
|  |   var Oauth3 = require('oauth3-cli'); | ||||||
|  |   var Tunnel = require('daplie-tunnel').create({ | ||||||
|  |     Oauth3: Oauth3 | ||||||
|  |   , PromiseA: opts.PromiseA | ||||||
|  |   , CLI: { | ||||||
|  |       init: function (rs, ws/*, state, options*/) { | ||||||
|  |         // noop
 | ||||||
|  |         return ws; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }).Tunnel; | ||||||
|  |   var stunnel = require('stunnel'); | ||||||
|  |   var killcount = 0; | ||||||
|  | 
 | ||||||
|  |   /* | ||||||
|  |   var Dup = { | ||||||
|  |     write: function (chunk, encoding, cb) { | ||||||
|  |       this.__my_socket.push(chunk, encoding); | ||||||
|  |       cb(); | ||||||
|  |     } | ||||||
|  |   , read: function (size) { | ||||||
|  |       var x = this.__my_socket.read(size); | ||||||
|  |       if (x) { this.push(x); } | ||||||
|  |     } | ||||||
|  |   , setTimeout: function () { | ||||||
|  |       console.log('TODO implement setTimeout on Duplex'); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var httpServer = require('http').createServer(function (req, res) { | ||||||
|  |     console.log('req.socket.encrypted', req.socket.encrypted); | ||||||
|  |     res.end('Hello, tunneled World!'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   var tlsServer = require('tls').createServer(opts.httpsOptions, function (tlsSocket) { | ||||||
|  |     console.log('tls connection'); | ||||||
|  |     // things get a little messed up here
 | ||||||
|  |     httpServer.emit('connection', tlsSocket); | ||||||
|  | 
 | ||||||
|  |     // try again
 | ||||||
|  |     //servers.server.emit('connection', tlsSocket);
 | ||||||
|  |   }); | ||||||
|  |   */ | ||||||
|  | 
 | ||||||
|  |   process.on('SIGINT', function () { | ||||||
|  |     killcount += 1; | ||||||
|  |     console.log('[quit] closing http and https servers'); | ||||||
|  |     if (killcount >= 3) { | ||||||
|  |       process.exit(1); | ||||||
|  |     } | ||||||
|  |     if (servers.server) { | ||||||
|  |       servers.server.close(); | ||||||
|  |     } | ||||||
|  |     if (servers.insecureServer) { | ||||||
|  |       servers.insecureServer.close(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return Tunnel.token({ | ||||||
|  |     refreshToken: opts.refreshToken | ||||||
|  |   , email: opts.email | ||||||
|  |   , domains: opts.sites.map(function (site) { | ||||||
|  |       return site.name; | ||||||
|  |     }) | ||||||
|  |   , device: { hostname: opts.devicename || opts.device } | ||||||
|  |   }).then(function (result) { | ||||||
|  |     // { jwt, tunnelUrl }
 | ||||||
|  |     var locals = []; | ||||||
|  |     opts.sites.map(function (site) { | ||||||
|  |       locals.push({ | ||||||
|  |         protocol: 'https' | ||||||
|  |       , hostname: site.name | ||||||
|  |       , port: opts.port | ||||||
|  |       }); | ||||||
|  |       locals.push({ | ||||||
|  |         protocol: 'http' | ||||||
|  |       , hostname: site.name | ||||||
|  |       , port: opts.insecurePort || opts.port | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |     return stunnel.connect({ | ||||||
|  |       token: result.jwt | ||||||
|  |     , stunneld: result.tunnelUrl | ||||||
|  |       // XXX TODO BUG // this is just for testing
 | ||||||
|  |     , insecure: /*opts.insecure*/ true | ||||||
|  |     , locals: locals | ||||||
|  |       // a simple passthru is proving to not be so simple
 | ||||||
|  |     , net: require('net') /* | ||||||
|  |       { | ||||||
|  |         createConnection: function (info, cb) { | ||||||
|  |           // data is the hello packet / first chunk
 | ||||||
|  |           // info = { data, servername, port, host, remoteAddress: { family, address, port } }
 | ||||||
|  | 
 | ||||||
|  |           var myDuplex = new (require('stream').Duplex)(); | ||||||
|  |           var myDuplex2 = new (require('stream').Duplex)(); | ||||||
|  |           // duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
 | ||||||
|  | 
 | ||||||
|  |           myDuplex2.__my_socket = myDuplex; | ||||||
|  |           myDuplex.__my_socket = myDuplex2; | ||||||
|  | 
 | ||||||
|  |           myDuplex2._write = Dup.write; | ||||||
|  |           myDuplex2._read = Dup.read; | ||||||
|  | 
 | ||||||
|  |           myDuplex._write = Dup.write; | ||||||
|  |           myDuplex._read = Dup.read; | ||||||
|  | 
 | ||||||
|  |           myDuplex.remoteFamily = info.remoteFamily; | ||||||
|  |           myDuplex.remoteAddress = info.remoteAddress; | ||||||
|  |           myDuplex.remotePort = info.remotePort; | ||||||
|  | 
 | ||||||
|  |           // socket.local{Family,Address,Port}
 | ||||||
|  |           myDuplex.localFamily = 'IPv4'; | ||||||
|  |           myDuplex.localAddress = '127.0.01'; | ||||||
|  |           myDuplex.localPort = info.port; | ||||||
|  | 
 | ||||||
|  |           myDuplex.setTimeout = Dup.setTimeout; | ||||||
|  | 
 | ||||||
|  |           // this doesn't seem to work so well
 | ||||||
|  |           //servers.server.emit('connection', myDuplex);
 | ||||||
|  | 
 | ||||||
|  |           // try a little more manual wrapping / unwrapping
 | ||||||
|  |           var firstByte = info.data[0]; | ||||||
|  |           if (firstByte < 32 || firstByte >= 127) { | ||||||
|  |             tlsServer.emit('connection', myDuplex); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             httpServer.emit('connection', myDuplex); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           if (cb) { | ||||||
|  |             process.nextTick(cb); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           return myDuplex2; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       //*/
 | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										60
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								package.json
									
									
									
									
									
								
							| @ -1,21 +1,55 @@ | |||||||
| { | { | ||||||
|   "name": "goldilocks", |   "name": "goldilocks", | ||||||
|   "version": "1.0.0-placeholder", |   "version": "2.2.0", | ||||||
|   "description": "The webserver that's just right.", |   "description": "The node.js webserver that's just right, Greenlock (HTTPS/TLS/SSL via ACME/Let's Encrypt) and tunneling (RVPN) included.", | ||||||
|   "keywords": [ |   "main": "bin/goldilocks.js", | ||||||
|     "greenlock" |  | ||||||
|   ], |  | ||||||
|   "main": "index.js", |  | ||||||
|   "scripts": { |  | ||||||
|     "test": "echo \"Error: no test specified\" && exit 1" |  | ||||||
|   }, |  | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
|     "url": "git@git.daplie.com:Daplie/goldilocks.git" |     "url": "git@git.daplie.com:Daplie/goldilocks.js.git" | ||||||
|   }, |   }, | ||||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", |   "author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)", | ||||||
|   "license": "(MIT OR Apache-2.0)", |   "license": "SEE LICENSE IN LICENSE.txt", | ||||||
|  |   "scripts": { "test": "node bin/goldilocks.js -p 8443 -d /tmp/" }, | ||||||
|  |   "bin": { "goldilocks": "./bin/goldilocks.js" }, | ||||||
|  |   "keywords": [ | ||||||
|  |     "https", | ||||||
|  |     "local", | ||||||
|  |     "localhost", | ||||||
|  |     "development", | ||||||
|  |     "dev", | ||||||
|  |     "tls", | ||||||
|  |     "ssl", | ||||||
|  |     "cert", | ||||||
|  |     "certs", | ||||||
|  |     "certificate", | ||||||
|  |     "certificates", | ||||||
|  |     "http", | ||||||
|  |     "express", | ||||||
|  |     "connect", | ||||||
|  |     "serve", | ||||||
|  |     "server" | ||||||
|  |   ], | ||||||
|  |   "bugs": { "url": "https://git.daplie.com/Daplie/server-https/issues" }, | ||||||
|  |   "homepage": "https://git.daplie.com/Daplie/goldilocks.js#readme", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "greenlock": "^2.1.11" |     "bluebird": "^3.4.6", | ||||||
|  |     "daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master", | ||||||
|  |     "ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master", | ||||||
|  |     "finalhandler": "^0.4.0", | ||||||
|  |     "httpolyglot": "^0.1.1", | ||||||
|  |     "ipify": "^1.1.0", | ||||||
|  |     "le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master", | ||||||
|  |     "le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master", | ||||||
|  |     "le-challenge-sni": "^2.0.1", | ||||||
|  |     "greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master", | ||||||
|  |     "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", | ||||||
|  |     "livereload": "^0.6.0", | ||||||
|  |     "localhost.daplie.me-certificates": "^1.3.0", | ||||||
|  |     "minimist": "^1.1.1", | ||||||
|  |     "oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master", | ||||||
|  |     "redirect-https": "^1.1.0", | ||||||
|  |     "serve-index": "^1.7.0", | ||||||
|  |     "serve-static": "^1.10.0", | ||||||
|  |     "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#master" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								stages/01-serve.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								stages/01-serve.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var https = require('httpolyglot'); | ||||||
|  | var httpsOptions = require('localhost.daplie.me-certificates').merge({}); | ||||||
|  | var httpsPort = 8443; | ||||||
|  | var redirectApp = require('redirect-https')({ | ||||||
|  |   port: httpsPort | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = https.createServer(httpsOptions); | ||||||
|  | 
 | ||||||
|  | server.on('request', function (req, res) { | ||||||
|  |   if (!req.socket.encrypted) { | ||||||
|  |     redirectApp(req, res); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   res.end("Hello, Encrypted World!"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | server.listen(httpsPort, function () { | ||||||
|  |   console.log('https://' + 'localhost.daplie.me' + (443 === httpsPort ? ':' : ':' + httpsPort)); | ||||||
|  | }); | ||||||
							
								
								
									
										17
									
								
								test-chain.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								test-chain.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | node serve.js \ | ||||||
|  |   --port 8443 \ | ||||||
|  |   --key node_modules/localhost.daplie.me-certificates/privkey.pem \ | ||||||
|  |   --cert node_modules/localhost.daplie.me-certificates/fullchain.pem \ | ||||||
|  |   --root node_modules/localhost.daplie.me-certificates/root.pem \ | ||||||
|  |   -c "$(cat node_modules/localhost.daplie.me-certificates/root.pem)" & | ||||||
|  | 
 | ||||||
|  | PID=$! | ||||||
|  | 
 | ||||||
|  | sleep 1 | ||||||
|  | curl -s --insecure http://localhost.daplie.me:8443 > ./root.pem | ||||||
|  | curl -s https://localhost.daplie.me:8443 --cacert ./root.pem | ||||||
|  | 
 | ||||||
|  | rm ./root.pem | ||||||
|  | kill $PID 2>/dev/null | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user