Compare commits
No commits in common. "master" and "greenlock" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,7 +2,6 @@
|
|||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
.vscode
|
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"printWidth": 120,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"useTabs": true
|
|
||||||
}
|
|
396
LICENSE
396
LICENSE
@ -1,375 +1,21 @@
|
|||||||
Copyright 2015-2019 AJ ONeal
|
MIT License
|
||||||
|
|
||||||
Mozilla Public License Version 2.0
|
Copyright (c) 2016 Daplie, Inc
|
||||||
==================================
|
|
||||||
|
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
|
||||||
1.1. "Contributor"
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
means each individual or legal entity that creates, contributes to
|
furnished to do so, subject to the following conditions:
|
||||||
the creation of, or owns Covered Software.
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
1.2. "Contributor Version"
|
copies or substantial portions of the Software.
|
||||||
means the combination of the Contributions of others (if any) used
|
|
||||||
by a Contributor and that particular Contributor's Contribution.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
1.3. "Contribution"
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
means Covered Software of a particular Contributor.
|
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.4. "Covered Software"
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
means Source Code Form to which the initial Contributor has attached
|
SOFTWARE.
|
||||||
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.
|
|
||||||
|
478
README.md
478
README.md
@ -1,376 +1,208 @@
|
|||||||
# New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md)
|
<!-- BANNER_TPL_BEGIN -->
|
||||||
|
|
||||||
Greenlock v3 just came out of private beta **today** (Nov 1st, 2019).
|
About Daplie: We're taking back the Internet!
|
||||||
|
--------------
|
||||||
|
|
||||||
The code is complete and we're working on great documentation.
|
Down with Google, Apple, and Facebook!
|
||||||
|
|
||||||
Many **examples** and **full API** documentation are still coming.
|
We're re-decentralizing the web and making it read-write again - one home cloud system at a time.
|
||||||
|
|
||||||
# [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) is Let's Encrypt for Node
|
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
|
||||||
|
|
||||||
| Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub/)
|
<!-- BANNER_TPL_END -->
|
||||||
|
|
||||||
Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc.
|
greenlock-express (letsencrypt-express)
|
||||||
|
=================
|
||||||
|
|
||||||
### Let's Encrypt for Node, Express, etc
|
[](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals.
|
| [greenlock (lib)](https://git.daplie.com/Daplie/node-greenlock)
|
||||||
|
| [greenlock-cli](https://git.daplie.com/Daplie/greenlock-cli)
|
||||||
|
| **greenlock-express**
|
||||||
|
| [greenlock-cluster](https://git.daplie.com/Daplie/greenlock-cluster)
|
||||||
|
| [greenlock-koa](https://git.daplie.com/Daplie/greenlock-koa)
|
||||||
|
| [greenlock-hapi](https://git.daplie.com/Daplie/greenlock-hapi)
|
||||||
|
|
|
||||||
|
|
||||||
```js
|
Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function httpsWorker(glx) {
|
* Automatic Registration via SNI (`httpsOptions.SNICallback`)
|
||||||
// Serves on 80 and 443
|
* **registrations** require an **approval callback** in *production*
|
||||||
// Get's SSL certificates magically!
|
* Automatic Renewal (around 80 days)
|
||||||
|
* **renewals** are *fully automatic* and happen in the *background*, with **no downtime**
|
||||||
|
* Automatic vhost / virtual hosting
|
||||||
|
|
||||||
glx.serveApp(function(req, res) {
|
All you have to do is start the webserver and then visit it at its domain name.
|
||||||
res.end("Hello, Encrypted World!");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkg = require("./package.json");
|
Install
|
||||||
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
|
||||||
mkdir ~/my-project
|
npm install --save greenlock-express@2.x
|
||||||
pushd ~/my-project
|
|
||||||
npm init
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
**Important**: Use node v4.5+ or v6.x, node <= v4.4 has a [known bug](https://github.com/nodejs/node/issues/8053) in the `Buffer` implementation.
|
||||||
|
|
||||||
<details>
|
QuickStart
|
||||||
<summary>2. Create an http app (i.e. express)</summary>
|
==========
|
||||||
|
|
||||||
## 2. Create an http app (i.e. express)
|
Here's a completely working example that will get you started:
|
||||||
|
|
||||||
This example is shown with Express, but any node app will do. Greenlock
|
`app.js`:
|
||||||
works with everything.
|
```javascript
|
||||||
(or any node-style http app)
|
'use strict';
|
||||||
|
|
||||||
`my-express-app.js`:
|
require('greenlock-express').create({
|
||||||
|
|
||||||
```js
|
server: 'staging'
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// A plain, node-style app
|
, email: 'john.doe@example.com'
|
||||||
|
|
||||||
function myPlainNodeHttpApp(req, res) {
|
, agreeTos: true
|
||||||
res.end("Hello, Encrypted World!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap that plain app in express,
|
, approveDomains: [ 'example.com' ]
|
||||||
// because that's what you're used to
|
|
||||||
|
|
||||||
var express = require("express");
|
, app: require('express')().use('/', function (req, res) {
|
||||||
var app = express();
|
res.end('Hello, World!');
|
||||||
app.get("/", myPlainNodeHttpApp);
|
})
|
||||||
|
|
||||||
// export the app normally
|
}).listen(80, 443);
|
||||||
// do not .listen()
|
|
||||||
|
|
||||||
module.exports = app;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
Certificates will be stored in `~/letsencrypt`.
|
||||||
|
|
||||||
<details>
|
**Important**:
|
||||||
<summary>3. Serve with Greenlock Express</summary>
|
|
||||||
|
|
||||||
## 3. Serve with Greenlock Express
|
You must set `server` to `https://acme-v01.api.letsencrypt.org/directory` **after**
|
||||||
|
you have tested that your setup works.
|
||||||
|
|
||||||
Greenlock Express is designed with these goals in mind:
|
Why You Must Use 'staging' First
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
- Simplicity and ease-of-use
|
There are a number of common problems related to system configuration -
|
||||||
- Performance and scalability
|
firewalls, ports, permissions, etc - that you are likely to run up against
|
||||||
- Configurability and control
|
when using greenlock for your first time.
|
||||||
|
|
||||||
You can start with **near-zero configuration** and
|
In order to avoid being blocked by hitting rate limits with bad requests,
|
||||||
slowly add options for greater performance and customization
|
you should always test against the `'staging'` server
|
||||||
later, if you need them.
|
(`https://acme-staging.api.letsencrypt.org/directory`) first.
|
||||||
|
|
||||||
`server.js`:
|
Migrating from v1.x
|
||||||
|
===================
|
||||||
|
|
||||||
```js
|
Whereas v1.x had a few hundred lines of code, v2.x is a single small file of about 50 lines.
|
||||||
require("greenlock-express")
|
|
||||||
.init(getConfig)
|
|
||||||
.serve(worker);
|
|
||||||
|
|
||||||
function getConfig() {
|
A few important things to note:
|
||||||
return {
|
|
||||||
// uses name and version as part of the ACME client user-agent
|
|
||||||
// uses author as the contact for support notices
|
|
||||||
package: require("./package.json")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function worker(server) {
|
* Delete your v1.x `~/letsencrypt` directory, otherwise you get this:
|
||||||
// Works with any Node app (Express, etc)
|
* `{ type: 'urn:acme:error:malformed', detail: 'Parse error reading JWS', status: 400 }`
|
||||||
var app = require("my-express-app.js");
|
* `approveRegistration` has been replaced by `approveDomains`
|
||||||
server.serveApp(app);
|
* All of the behavior has moved to the various plugins, which each have their own options
|
||||||
|
* Use https and http directly, don't rely on the silly `.listen()` helper. It's just there for looks.
|
||||||
|
* `lex.createAcmeResponder()` is now `lex.middleware(require('redirect-https')())` or `lex.middleware(app)`
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
The oversimplified example was the bait
|
||||||
|
(because everyone seems to want an example that fits in 3 lines, even if it's terribly bad practices),
|
||||||
|
now here's the switch:
|
||||||
|
|
||||||
|
`serve.js`:
|
||||||
|
```javascript
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// returns an instance of node-greenlock with additional helper methods
|
||||||
|
var lex = require('greenlock-express').create({
|
||||||
|
// set to https://acme-v01.api.letsencrypt.org/directory in production
|
||||||
|
server: 'staging'
|
||||||
|
|
||||||
|
// If you wish to replace the default plugins, you may do so here
|
||||||
|
//
|
||||||
|
, challenges: { 'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }) }
|
||||||
|
, store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' })
|
||||||
|
|
||||||
|
// You probably wouldn't need to replace the default sni handler
|
||||||
|
// See https://git.daplie.com/Daplie/le-sni-auto if you think you do
|
||||||
|
//, sni: require('le-sni-auto').create({})
|
||||||
|
|
||||||
|
, approveDomains: approveDomains
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function approveDomains(opts, certs, cb) {
|
||||||
|
// This is where you check your database and associated
|
||||||
|
// email addresses with domains and agreements and such
|
||||||
|
|
||||||
|
|
||||||
|
// The domains being approved for the first time are listed in opts.domains
|
||||||
|
// Certs being renewed are listed in certs.altnames
|
||||||
|
if (certs) {
|
||||||
|
opts.domains = certs.altnames;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
opts.email = 'john.doe@example.com';
|
||||||
|
opts.agreeTos = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: you can also change other options such as `challengeType` and `challenge`
|
||||||
|
// opts.challengeType = 'http-01';
|
||||||
|
// opts.challenge = require('le-challenge-fs').create({});
|
||||||
|
|
||||||
|
cb(null, { options: opts, certs: certs });
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And start your server:
|
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
# Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS)
|
// handles acme-challenge and redirects to https
|
||||||
sudo setcap 'cap_net_bind_service=+ep' $(which node)
|
require('http').createServer(lex.middleware(require('redirect-https')())).listen(80, function () {
|
||||||
|
console.log("Listening for ACME http-01 challenges on", this.address());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var app = require('express')();
|
||||||
|
app.use('/', function (req, res) {
|
||||||
|
res.end('Hello, World!');
|
||||||
|
});
|
||||||
|
|
||||||
|
// handles your app
|
||||||
|
require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443, function () {
|
||||||
|
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
**Security Warning**:
|
||||||
# `npm start` will call `node ./server.js` by default
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
If you don't do proper checks in `approveDomains(opts, certs, cb)`
|
||||||
Greenlock v3.0.0
|
an attacker will spoof SNI packets with bad hostnames and that will
|
||||||
Greenlock Manager Config File: ~/.config/greenlock/manager.json
|
cause you to be rate-limited and or blocked from the ACME server.
|
||||||
Greenlock Storage Directory: ~/.config/greenlock/
|
|
||||||
|
|
||||||
Listening on 0.0.0.0:80 for ACME challenges and HTTPS redirects
|
|
||||||
Listening on 0.0.0.0:443 for secure traffic
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
API
|
||||||
|
===
|
||||||
|
|
||||||
<details>
|
This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO).
|
||||||
<summary>4. Manage SSL Certificates and Domains</summary>
|
|
||||||
|
|
||||||
## 4. Manage domains
|
The API is actually located at [node-greenlock options](https://git.daplie.com/Daplie/node-greenlock)
|
||||||
|
(because all options are simply passed through to `node-greenlock` proper without modification).
|
||||||
|
|
||||||
The management API is built to work with Databases, S3, etc.
|
The only "API" consists of two options, the rest is just a wrapper around `node-greenlock` to take LOC from 15 to 5:
|
||||||
|
|
||||||
HOWEVER, by default it starts with a simple config file.
|
* `opts.app` An express app in the format `function (req, res) { ... }` (no `next`).
|
||||||
|
* `lex.listen(plainPort, tlsPort)` Accepts port numbers (or arrays of port numbers) to listen on.
|
||||||
|
|
||||||
<!--
|
Brief overview of some simple options for `node-greenlock`:
|
||||||
This will update the config file (assuming the default fs-based management plugin):
|
|
||||||
-->
|
|
||||||
|
|
||||||
`~/.config/greenlock/manager.json`:
|
* `opts.server` set to https://acme-v01.api.letsencrypt.org/directory in production
|
||||||
|
* `opts.email` The default email to use to accept agreements.
|
||||||
```json
|
* `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first.
|
||||||
{
|
* `opts.approveDomains` can be either of:
|
||||||
"subscriberEmail": "letsencrypt-test@therootcompany.com",
|
* An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]`
|
||||||
"agreeToTerms": true,
|
* A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above)
|
||||||
"sites": {
|
* `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate.
|
||||||
"example.com": {
|
* `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.
|
||||||
"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
20
config.js
@ -1,20 +0,0 @@
|
|||||||
"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
35
demo.js
@ -1,35 +0,0 @@
|
|||||||
"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!");
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
# 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,39 +0,0 @@
|
|||||||
"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);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
"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;
|
|
@ -1,27 +0,0 @@
|
|||||||
"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);
|
|
22
examples/force-renew.js
Normal file
22
examples/force-renew.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
//require('greenlock-express')
|
||||||
|
require('../').create({
|
||||||
|
|
||||||
|
server: 'staging'
|
||||||
|
|
||||||
|
, email: 'john.doe@example.com'
|
||||||
|
|
||||||
|
, agreeTos: true
|
||||||
|
|
||||||
|
, approvedDomains: [ 'example.com', 'www.example.com' ]
|
||||||
|
|
||||||
|
, app: require('express')().use('/', function (req, res) {
|
||||||
|
res.end('Hello, World!');
|
||||||
|
})
|
||||||
|
|
||||||
|
, renewWithin: (91 * 24 * 60 * 60 * 1000)
|
||||||
|
, renewBy: (90 * 24 * 60 * 60 * 1000)
|
||||||
|
|
||||||
|
, debug: true
|
||||||
|
}).listen(80, 443);
|
@ -1,44 +0,0 @@
|
|||||||
"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);
|
|
@ -1,42 +0,0 @@
|
|||||||
"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);
|
|
@ -1,48 +0,0 @@
|
|||||||
"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);
|
|
@ -1,49 +0,0 @@
|
|||||||
"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,22 +0,0 @@
|
|||||||
# 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"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,32 +0,0 @@
|
|||||||
"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);
|
|
20
examples/simple.js
Normal file
20
examples/simple.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
//require('greenlock-express')
|
||||||
|
require('../').create({
|
||||||
|
|
||||||
|
server: 'staging'
|
||||||
|
|
||||||
|
, email: 'john.doe@example.com'
|
||||||
|
|
||||||
|
, agreeTos: true
|
||||||
|
|
||||||
|
, approvedDomains: [ 'example.com', 'www.example.com' ]
|
||||||
|
|
||||||
|
, app: require('express')().use('/', function (req, res) {
|
||||||
|
res.end('Hello, World!');
|
||||||
|
})
|
||||||
|
|
||||||
|
, debug: true
|
||||||
|
|
||||||
|
}).listen(80, 443);
|
@ -1,49 +0,0 @@
|
|||||||
// 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);
|
|
@ -1,3 +0,0 @@
|
|||||||
// 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+
|
|
@ -1,42 +0,0 @@
|
|||||||
"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,44 +0,0 @@
|
|||||||
"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
119
greenlock.js
@ -1,119 +0,0 @@
|
|||||||
"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;
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
"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) || "";
|
|
||||||
};
|
|
@ -1,139 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var SanitizeHost = module.exports;
|
|
||||||
var HttpMiddleware = require("./http-middleware.js");
|
|
||||||
|
|
||||||
SanitizeHost.create = function(gl, app) {
|
|
||||||
return function(req, res, next) {
|
|
||||||
function realNext() {
|
|
||||||
if ("function" === typeof app) {
|
|
||||||
app(req, res);
|
|
||||||
} else if ("function" === typeof next) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.statusCode = 500;
|
|
||||||
res.end("Error: no middleware assigned");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostname = HttpMiddleware.getHostname(req);
|
|
||||||
// Replace the hostname, and get the safe version
|
|
||||||
var safehost = HttpMiddleware.sanitizeHostname(req);
|
|
||||||
|
|
||||||
// if no hostname, move along
|
|
||||||
if (!hostname) {
|
|
||||||
realNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there were unallowed characters, complain
|
|
||||||
if (safehost.length !== hostname.length) {
|
|
||||||
res.statusCode = 400;
|
|
||||||
res.end("Malformed HTTP Header: 'Host: " + hostname + "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
|
|
||||||
if (req.socket.encrypted) {
|
|
||||||
if (req.socket && "string" === typeof req.socket.servername) {
|
|
||||||
// Workaround for https://github.com/nodejs/node/issues/22389
|
|
||||||
if (!SanitizeHost._checkServername(safehost, req.socket)) {
|
|
||||||
res.statusCode = 400;
|
|
||||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
||||||
res.end(
|
|
||||||
"<h1>Domain Fronting Error</h1>" +
|
|
||||||
"<p>This connection was secured using TLS/SSL for '" +
|
|
||||||
(req.socket.servername || "").toLowerCase() +
|
|
||||||
"'</p>" +
|
|
||||||
"<p>The HTTP request specified 'Host: " +
|
|
||||||
safehost +
|
|
||||||
"', which is (obviously) different.</p>" +
|
|
||||||
"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
else if (safehost && !gl._skip_fronting_check) {
|
|
||||||
|
|
||||||
// We used to print a log message here, but it turns out that it's
|
|
||||||
// really common for IoT devices to not use SNI (as well as many bots
|
|
||||||
// and such).
|
|
||||||
// It was common for the log message to pop up as the first request
|
|
||||||
// to the server, and that was confusing. So instead now we do nothing.
|
|
||||||
|
|
||||||
//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
|
|
||||||
//gl._skip_fronting_check = true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// carry on
|
|
||||||
realNext();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var warnDomainFronting = true;
|
|
||||||
var warnUnexpectedError = true;
|
|
||||||
SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
|
||||||
var servername = (tlsSocket.servername || "").toLowerCase();
|
|
||||||
|
|
||||||
// acceptable: older IoT devices may lack SNI support
|
|
||||||
if (!servername) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// acceptable: odd... but acceptable
|
|
||||||
if (!safeHost) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (safeHost === servername) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("function" !== typeof tlsSocket.getCertificate) {
|
|
||||||
// domain fronting attacks allowed
|
|
||||||
if (warnDomainFronting) {
|
|
||||||
// https://github.com/nodejs/node/issues/24095
|
|
||||||
console.warn(
|
|
||||||
"Warning: node " +
|
|
||||||
process.version +
|
|
||||||
" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater."
|
|
||||||
);
|
|
||||||
warnDomainFronting = false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// connection established with servername and session is re-used for allowed name
|
|
||||||
// See https://github.com/nodejs/node/issues/24095
|
|
||||||
var cert = tlsSocket.getCertificate();
|
|
||||||
try {
|
|
||||||
// TODO optimize / cache?
|
|
||||||
// *should* always have a string, right?
|
|
||||||
// *should* always be lowercase already, right?
|
|
||||||
//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
|
|
||||||
var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost;
|
|
||||||
if (isSubject) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dnsnames = (cert.subjectaltname || "").split(/,\s+/);
|
|
||||||
var inSanList = dnsnames.some(function(name) {
|
|
||||||
// always prefixed with "DNS:"
|
|
||||||
return safeHost === name.slice(4).toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (inSanList) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// not sure what else to do in this situation...
|
|
||||||
if (warnUnexpectedError) {
|
|
||||||
console.warn("Warning: encoutered error while performing domain fronting check: " + e.message);
|
|
||||||
warnUnexpectedError = false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
14
install.sh
14
install.sh
@ -1,14 +0,0 @@
|
|||||||
# This is just an example (but it works)
|
|
||||||
export NODE_PATH=$NPM_CONFIG_PREFIX/lib/node_modules
|
|
||||||
export NPM_CONFIG_PREFIX=/opt/node
|
|
||||||
curl -fsSL https://bit.ly/node-installer | bash
|
|
||||||
|
|
||||||
/opt/node/bin/node /opt/node/bin/npm config set scripts-prepend-node-path true
|
|
||||||
/opt/node/bin/node /opt/node/bin/npm ci
|
|
||||||
sudo setcap 'cap_net_bind_service=+ep' /opt/node/bin/node
|
|
||||||
/opt/node/bin/node /opt/node/bin/npm start
|
|
||||||
|
|
||||||
sudo rsync -av dist/etc/systemd/system/greenlock-express.service /etc/systemd/system/
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
|
||||||
sudo systemctl restart greenlock-express
|
|
66
lex.js
Normal file
66
lex.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// opts.approveDomains(options, certs, cb)
|
||||||
|
module.exports.create = function (opts) {
|
||||||
|
// accept all defaults for le.challenges, le.store, le.middleware
|
||||||
|
var le = require('greenlock').create(opts);
|
||||||
|
|
||||||
|
opts.app = opts.app || function (req, res) {
|
||||||
|
res.end("Hello, World!\nWith Love,\nLet's Encrypt Express");
|
||||||
|
};
|
||||||
|
|
||||||
|
opts.listen = function (plainPort, port) {
|
||||||
|
var PromiseA;
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
} catch(e) {
|
||||||
|
console.warn("Package 'bluebird' not installed. Using global.Promise instead");
|
||||||
|
console.warn("(want bluebird instead? npm install --save bluebird)");
|
||||||
|
PromiseA = global.Promise;
|
||||||
|
}
|
||||||
|
var promises = [];
|
||||||
|
var plainPorts = plainPort;
|
||||||
|
var ports = port;
|
||||||
|
var servers = [];
|
||||||
|
|
||||||
|
if (!plainPorts) {
|
||||||
|
plainPorts = 80;
|
||||||
|
}
|
||||||
|
if (!ports) {
|
||||||
|
ports = 443;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(plainPorts)) {
|
||||||
|
plainPorts = [ plainPorts ];
|
||||||
|
ports = [ ports ];
|
||||||
|
}
|
||||||
|
|
||||||
|
plainPorts.forEach(function (p) {
|
||||||
|
promises.push(new PromiseA(function (resolve, reject) {
|
||||||
|
require('http').createServer(le.middleware(require('redirect-https')())).listen(p, function () {
|
||||||
|
console.log("Handling ACME challenges and redirecting to https on plain port " + p);
|
||||||
|
resolve();
|
||||||
|
}).on('error', reject);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ports.forEach(function (p) {
|
||||||
|
promises.push(new PromiseA(function (resolve, reject) {
|
||||||
|
var server = require('https').createServer(le.httpsOptions, le.middleware(le.app)).listen(p, function () {
|
||||||
|
console.log("Handling ACME challenges and serving https " + p);
|
||||||
|
resolve();
|
||||||
|
}).on('error', reject);
|
||||||
|
servers.push(server);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Array.isArray(port)) {
|
||||||
|
servers = servers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return le;
|
||||||
|
};
|
@ -1,37 +0,0 @@
|
|||||||
"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
36
main.js
@ -1,36 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
// this is the stuff that should run in the main foreground process,
|
|
||||||
// whether it's single or master
|
|
||||||
|
|
||||||
var major = process.versions.node.split(".")[0];
|
|
||||||
var minor = process.versions.node.split(".")[1];
|
|
||||||
var _hasSetSecureContext = false;
|
|
||||||
var shouldUpgrade = false;
|
|
||||||
|
|
||||||
// TODO can we trust earlier versions as well?
|
|
||||||
if (major >= 12) {
|
|
||||||
_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext;
|
|
||||||
} else {
|
|
||||||
_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO document in issues
|
|
||||||
if (!_hasSetSecureContext) {
|
|
||||||
// TODO this isn't necessary if greenlock options are set with options.cert
|
|
||||||
console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext().");
|
|
||||||
console.warn(" The default certificate may not be set.");
|
|
||||||
shouldUpgrade = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (major < 11 || (11 === major && minor < 2)) {
|
|
||||||
// https://github.com/nodejs/node/issues/24095
|
|
||||||
console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate().");
|
|
||||||
console.warn(" This is necessary to guard against domain fronting attacks.");
|
|
||||||
shouldUpgrade = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldUpgrade) {
|
|
||||||
console.warn("Warning: Please upgrade to node v11.2.0 or greater.");
|
|
||||||
console.warn();
|
|
||||||
}
|
|
160
master.js
160
master.js
@ -1,160 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
require("./main.js");
|
|
||||||
|
|
||||||
var Master = module.exports;
|
|
||||||
|
|
||||||
var cluster = require("cluster");
|
|
||||||
var os = require("os");
|
|
||||||
var msgPrefix = "greenlock:";
|
|
||||||
|
|
||||||
Master.create = function(opts) {
|
|
||||||
var resolveCb;
|
|
||||||
var _readyCb;
|
|
||||||
var _kicked = false;
|
|
||||||
|
|
||||||
var greenlock = require("./greenlock.js").create(opts);
|
|
||||||
|
|
||||||
var ready = new Promise(function(resolve) {
|
|
||||||
resolveCb = resolve;
|
|
||||||
}).then(function(fn) {
|
|
||||||
_readyCb = fn;
|
|
||||||
return fn;
|
|
||||||
});
|
|
||||||
|
|
||||||
function kickoff() {
|
|
||||||
if (_kicked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_kicked = true;
|
|
||||||
|
|
||||||
Master._spawnWorkers(opts, greenlock);
|
|
||||||
|
|
||||||
ready.then(function(fn) {
|
|
||||||
// not sure what this API should be yet
|
|
||||||
fn();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var master = {
|
|
||||||
serve: function() {
|
|
||||||
kickoff();
|
|
||||||
return master;
|
|
||||||
},
|
|
||||||
master: function(fn) {
|
|
||||||
if (_readyCb) {
|
|
||||||
throw new Error("can't call master twice");
|
|
||||||
}
|
|
||||||
kickoff();
|
|
||||||
resolveCb(fn);
|
|
||||||
return master;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return master;
|
|
||||||
};
|
|
||||||
|
|
||||||
function range(n) {
|
|
||||||
n = parseInt(n, 10);
|
|
||||||
if (!n) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return new Array(n).join(",").split(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
Master._spawnWorkers = function(opts, greenlock) {
|
|
||||||
var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length;
|
|
||||||
|
|
||||||
// process rpc messages
|
|
||||||
// start when dead
|
|
||||||
var numWorkers = parseInt(opts.workers || opts.numWorkers, 10);
|
|
||||||
if (!numWorkers) {
|
|
||||||
if (numCpus <= 2) {
|
|
||||||
numWorkers = 2;
|
|
||||||
} else {
|
|
||||||
numWorkers = numCpus - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster.once("exit", function() {
|
|
||||||
setTimeout(function() {
|
|
||||||
process.exit(3);
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
var workers = range(numWorkers);
|
|
||||||
function next() {
|
|
||||||
if (!workers.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
workers.pop();
|
|
||||||
|
|
||||||
// for a nice aesthetic
|
|
||||||
setTimeout(function() {
|
|
||||||
Master._spawnWorker(opts, greenlock);
|
|
||||||
next();
|
|
||||||
}, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
Master._spawnWorker = function(opts, greenlock) {
|
|
||||||
var w = cluster.fork();
|
|
||||||
// automatically added to master's `cluster.workers`
|
|
||||||
w.once("exit", function(code, signal) {
|
|
||||||
// TODO handle failures
|
|
||||||
// Should test if the first starts successfully
|
|
||||||
// Should exit if failures happen too quickly
|
|
||||||
|
|
||||||
// For now just kill all when any die
|
|
||||||
if (signal) {
|
|
||||||
console.error("worker was killed by signal:", signal);
|
|
||||||
} else if (code !== 0) {
|
|
||||||
console.error("worker exited with error code:", code);
|
|
||||||
} else {
|
|
||||||
console.error("worker unexpectedly quit without exit code or signal");
|
|
||||||
}
|
|
||||||
process.exit(2);
|
|
||||||
|
|
||||||
//addWorker();
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleMessage(msg) {
|
|
||||||
if (0 !== (msg._id || "").indexOf(msgPrefix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ("string" !== typeof msg._funcname) {
|
|
||||||
// TODO developer error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rpc() {
|
|
||||||
return greenlock[msg._funcname](msg._input)
|
|
||||||
.then(function(result) {
|
|
||||||
w.send({
|
|
||||||
_id: msg._id,
|
|
||||||
_result: result
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(function(e) {
|
|
||||||
var error = new Error(e.message);
|
|
||||||
Object.getOwnPropertyNames(e).forEach(function(k) {
|
|
||||||
error[k] = e[k];
|
|
||||||
});
|
|
||||||
w.send({
|
|
||||||
_id: msg._id,
|
|
||||||
_error: error
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
rpc();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:");
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.on("message", handleMessage);
|
|
||||||
};
|
|
140
package-lock.json
generated
140
package-lock.json
generated
@ -1,140 +0,0 @@
|
|||||||
{
|
|
||||||
"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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
94
package.json
94
package.json
@ -1,51 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "@root/greenlock-express",
|
"name": "greenlock-express",
|
||||||
"version": "3.0.10",
|
"version": "2.0.7",
|
||||||
"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.",
|
"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.",
|
||||||
"main": "greenlock-express.js",
|
"main": "lex.js",
|
||||||
"homepage": "https://greenlock.domains",
|
"directories": {
|
||||||
"files": [
|
"example": "examples"
|
||||||
"*.js",
|
},
|
||||||
"lib",
|
"dependencies": {
|
||||||
"scripts"
|
"le-challenge-fs": "^2.0.4",
|
||||||
],
|
"le-sni-auto": "^2.0.1",
|
||||||
"scripts": {
|
"le-store-certbot": "^2.0.3",
|
||||||
"start": "node_todo server.js ./config.js",
|
"greenlock": "^2.1.9",
|
||||||
"test": "node_todo test/greenlock.js"
|
"localhost.daplie.com-certificates": "^1.2.3",
|
||||||
},
|
"redirect-https": "^1.1.0"
|
||||||
"directories": {
|
},
|
||||||
"example": "examples"
|
"devDependencies": {},
|
||||||
},
|
"scripts": {
|
||||||
"dependencies": {
|
"test": "node examples/serve.js"
|
||||||
"@root/greenlock": "^3.0.17",
|
},
|
||||||
"redirect-https": "^1.1.5"
|
"repository": {
|
||||||
},
|
"type": "git",
|
||||||
"trulyOptionalDependencies": {
|
"url": "git+https://git.daplie.com/Daplie/greenlock-cluster.git"
|
||||||
"http-proxy": "^1.17.0",
|
},
|
||||||
"express": "^4.16.3",
|
"keywords": [
|
||||||
"express-basic-auth": "^1.2.0",
|
"acme",
|
||||||
"finalhandler": "^1.1.1",
|
"cloud",
|
||||||
"serve-index": "^1.9.1",
|
"cluster",
|
||||||
"serve-static": "^1.13.2",
|
"free",
|
||||||
"ws": "^5.2.1"
|
"greenlock",
|
||||||
},
|
"https",
|
||||||
"devDependencies": {},
|
"le",
|
||||||
"repository": {
|
"letsencrypt",
|
||||||
"type": "git",
|
"multi-core",
|
||||||
"url": "https://git.rootprojects.org/root/greenlock-express.js.git"
|
"node",
|
||||||
},
|
"node.js",
|
||||||
"keywords": [
|
"scale",
|
||||||
"Let's Encrypt",
|
"ssl",
|
||||||
"ACME",
|
"tls"
|
||||||
"greenlock",
|
],
|
||||||
"Free SSL",
|
"author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)",
|
||||||
"Automated HTTPS",
|
"license": "(MIT OR Apache-2.0)",
|
||||||
"https",
|
"bugs": {
|
||||||
"tls"
|
"url": "https://git.daplie.com/Daplie/greenlock-cluster/issues"
|
||||||
],
|
},
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)",
|
"homepage": "https://git.daplie.com/Daplie/greenlock-cluster#readme"
|
||||||
"license": "MPL-2.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://git.rootprojects.org/root/greenlock-express.js/issues"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
#!/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
157
servers.js
@ -1,157 +0,0 @@
|
|||||||
"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
25
single.js
@ -1,25 +0,0 @@
|
|||||||
"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
194
sni.js
@ -1,194 +0,0 @@
|
|||||||
"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(".")
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
var Greenlock = require("../");
|
|
||||||
var greenlock = Greenlock.create({
|
|
||||||
version: "draft-11",
|
|
||||||
server: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
|
||||||
agreeTos: true,
|
|
||||||
approvedDomains: ["example.com", "www.example.com"],
|
|
||||||
configDir: require("path").join(require("os").tmpdir(), "acme"),
|
|
||||||
|
|
||||||
app: require("express")().use("/", function(req, res) {
|
|
||||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
||||||
res.end("Hello, World!\n\n💚 🔒.js");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var server1 = greenlock.listen(5080, 5443);
|
|
||||||
server1.on("listening", function() {
|
|
||||||
console.log("### THREE 3333 - All is well server1", this.address());
|
|
||||||
setTimeout(function() {
|
|
||||||
// so that the address() object doesn't disappear
|
|
||||||
server1.close();
|
|
||||||
server1.unencrypted.close();
|
|
||||||
}, 10);
|
|
||||||
});
|
|
||||||
setTimeout(function() {
|
|
||||||
var server2 = greenlock.listen(6080, 6443, function() {
|
|
||||||
console.log("### FIVE 55555 - Started server 2!");
|
|
||||||
setTimeout(function() {
|
|
||||||
server2.close();
|
|
||||||
server2.unencrypted.close();
|
|
||||||
server6.close();
|
|
||||||
server6.unencrypted.close();
|
|
||||||
server7.close();
|
|
||||||
server7.unencrypted.close();
|
|
||||||
setTimeout(function() {
|
|
||||||
// TODO greenlock needs a close event (and to listen to its server's close event)
|
|
||||||
process.exit(0);
|
|
||||||
}, 1000);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
server2.on("listening", function() {
|
|
||||||
console.log("### FOUR 44444 - All is well server2", server2.address());
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
var server3 = greenlock.listen(
|
|
||||||
22,
|
|
||||||
22,
|
|
||||||
function() {
|
|
||||||
console.error("Error: expected to get an error when launching plain server on port 22");
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
console.error("Error: expected to get an error when launching " + server3.type + " server on port 22");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
server3.unencrypted.on("error", function() {
|
|
||||||
console.log("Success: caught expected (plain) error");
|
|
||||||
});
|
|
||||||
server3.on("error", function() {
|
|
||||||
console.log("Success: caught expected " + server3.type + " error");
|
|
||||||
//server3.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
var server4 = greenlock.listen(
|
|
||||||
7080,
|
|
||||||
7443,
|
|
||||||
function() {
|
|
||||||
console.log("Success: server4: plain");
|
|
||||||
server4.unencrypted.close();
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
console.log("Success: server4: " + server4.type);
|
|
||||||
server4.close();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
var server5 = greenlock.listen(10080, 10443, function() {
|
|
||||||
console.log("Server 5 with one fn", this.address());
|
|
||||||
server5.close();
|
|
||||||
server5.unencrypted.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
var server6 = greenlock.listen("[::]:11080", "[::1]:11443");
|
|
||||||
|
|
||||||
var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock");
|
|
62
worker.js
62
worker.js
@ -1,62 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var Worker = module.exports;
|
|
||||||
// *very* generous, but well below the http norm of 120
|
|
||||||
var messageTimeout = 30 * 1000;
|
|
||||||
var msgPrefix = "greenlock:";
|
|
||||||
|
|
||||||
Worker.create = function() {
|
|
||||||
var greenlock = {};
|
|
||||||
["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) {
|
|
||||||
greenlock[k] = function(args) {
|
|
||||||
return rpc(k, args);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
var worker = {
|
|
||||||
serve: function(fn) {
|
|
||||||
var servers = require("./servers.js").create(greenlock);
|
|
||||||
fn(servers);
|
|
||||||
return worker;
|
|
||||||
},
|
|
||||||
master: function() {
|
|
||||||
// ignore
|
|
||||||
return worker;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return worker;
|
|
||||||
};
|
|
||||||
|
|
||||||
function rpc(funcname, msg) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
var rnd = Math.random()
|
|
||||||
.toString()
|
|
||||||
.slice(2)
|
|
||||||
.toString(16);
|
|
||||||
var id = msgPrefix + rnd;
|
|
||||||
var timeout;
|
|
||||||
|
|
||||||
function getResponse(msg) {
|
|
||||||
if (msg._id !== id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
process.removeListener("message", getResponse);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
resolve(msg._result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO keep a single listener than just responds
|
|
||||||
// via a collection of callbacks? or leave as is?
|
|
||||||
process.on("message", getResponse);
|
|
||||||
process.send({
|
|
||||||
_id: id,
|
|
||||||
_funcname: funcname,
|
|
||||||
_input: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
timeout = setTimeout(function() {
|
|
||||||
process.removeListener("message", getResponse);
|
|
||||||
reject(new Error("worker rpc request timeout"));
|
|
||||||
}, messageTimeout);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user