roster-server 1.9.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/index.js +1 -1
- package/package.json +4 -3
- package/skills/roster-server/SKILL.md +257 -0
- package/vendor/greenlock-express/.prettierrc +7 -0
- package/vendor/greenlock-express/LICENSE +375 -0
- package/vendor/greenlock-express/README.md +536 -0
- package/vendor/greenlock-express/WALKTHROUGH.md +256 -0
- package/vendor/greenlock-express/config.js +20 -0
- package/vendor/greenlock-express/demo.js +35 -0
- package/vendor/greenlock-express/examples/cluster/package.json +12 -0
- package/vendor/greenlock-express/examples/express/my-express-app.js +17 -0
- package/vendor/greenlock-express/examples/express/package.json +12 -0
- package/vendor/greenlock-express/examples/http/package.json +12 -0
- package/vendor/greenlock-express/examples/http-proxy/package.json +12 -0
- package/vendor/greenlock-express/examples/http2/package.json +12 -0
- package/vendor/greenlock-express/examples/https/package.json +12 -0
- package/vendor/greenlock-express/examples/quickstart/README.md +22 -0
- package/vendor/greenlock-express/examples/quickstart/package.json +12 -0
- package/vendor/greenlock-express/examples/socket.io/package.json +12 -0
- package/vendor/greenlock-express/examples/websockets/package.json +12 -0
- package/vendor/greenlock-express/greenlock-express.js +48 -0
- package/vendor/greenlock-express/greenlock-shim.js +72 -0
- package/vendor/greenlock-express/http-middleware.js +154 -0
- package/vendor/greenlock-express/https-middleware.js +139 -0
- package/vendor/greenlock-express/install.sh +14 -0
- package/vendor/greenlock-express/lib/compat.js +37 -0
- package/vendor/greenlock-express/main.js +32 -0
- package/vendor/greenlock-express/master.js +164 -0
- package/vendor/greenlock-express/package-lock.json +149 -0
- package/vendor/greenlock-express/package.json +51 -0
- package/vendor/greenlock-express/scripts/postinstall +77 -0
- package/vendor/greenlock-express/servers.js +171 -0
- package/vendor/greenlock-express/single.js +36 -0
- package/vendor/greenlock-express/sni.js +215 -0
- package/vendor/greenlock-express/worker.js +73 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var HttpMiddleware = module.exports;
|
|
4
|
+
var servernameRe = /^[a-z0-9\.\-]+$/i;
|
|
5
|
+
var challengePrefix = "/.well-known/acme-challenge/";
|
|
6
|
+
|
|
7
|
+
HttpMiddleware.create = function(gl, defaultApp) {
|
|
8
|
+
if (defaultApp && "function" !== typeof defaultApp) {
|
|
9
|
+
throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return function(req, res, next) {
|
|
13
|
+
var hostname = HttpMiddleware.sanitizeHostname(req);
|
|
14
|
+
|
|
15
|
+
req.on("error", function(err) {
|
|
16
|
+
explainError(gl, err, "http_01_middleware_socket", hostname);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Skip unless the path begins with /.well-known/acme-challenge/
|
|
20
|
+
if (!hostname || 0 !== req.url.indexOf(challengePrefix)) {
|
|
21
|
+
skipChallenge(req, res, next, defaultApp);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// HEADERS SENT DEBUG NOTE #2
|
|
26
|
+
// at this point, it's most likely Let's Encrypt server
|
|
27
|
+
// (or greenlock itself) performing the verification process
|
|
28
|
+
// Hmmm... perhaps we should change the greenlock prefix to test
|
|
29
|
+
// Anyway, we just got fast the first place where we could
|
|
30
|
+
// be sending headers.
|
|
31
|
+
|
|
32
|
+
var token = req.url.slice(challengePrefix.length);
|
|
33
|
+
|
|
34
|
+
var done = false;
|
|
35
|
+
var countA = 0;
|
|
36
|
+
var countB = 0;
|
|
37
|
+
gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token })
|
|
38
|
+
.catch(function(err) {
|
|
39
|
+
countA += 1;
|
|
40
|
+
// HEADERS SENT DEBUG NOTE #3
|
|
41
|
+
// This is the second possible time we could be sending headers
|
|
42
|
+
respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname);
|
|
43
|
+
done = true;
|
|
44
|
+
return { __done: true };
|
|
45
|
+
})
|
|
46
|
+
.then(function(result) {
|
|
47
|
+
countB += 1;
|
|
48
|
+
if (result && result.__done) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (done) {
|
|
52
|
+
console.error("Sanity check fail: `done` is in a quantum state of both true and false... huh?");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// HEADERS SENT DEBUG NOTE #4b
|
|
56
|
+
// This is the third/fourth possible time send headers
|
|
57
|
+
return respondWithGrace(res, result, hostname, token);
|
|
58
|
+
})
|
|
59
|
+
.catch(function(err) {
|
|
60
|
+
// HEADERS SENT DEBUG NOTE #5
|
|
61
|
+
// I really don't see how this can be possible.
|
|
62
|
+
// Every case appears to be accounted for
|
|
63
|
+
console.error();
|
|
64
|
+
console.error("[warning] Developer Error:" + (err.code || err.context || ""), countA, countB);
|
|
65
|
+
console.error(err.stack);
|
|
66
|
+
console.error();
|
|
67
|
+
console.error(
|
|
68
|
+
"This is probably the error that happens routinely on http2 connections, but we're not sure why."
|
|
69
|
+
);
|
|
70
|
+
console.error("To track the status or help contribute,");
|
|
71
|
+
console.error("visit: https://git.rootprojects.org/root/greenlock-express.js/issues/9");
|
|
72
|
+
console.error();
|
|
73
|
+
try {
|
|
74
|
+
res.end("Internal Server Error [1003]: See logs for details.");
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function skipChallenge(req, res, next, defaultApp) {
|
|
83
|
+
if ("function" === typeof defaultApp) {
|
|
84
|
+
defaultApp(req, res, next);
|
|
85
|
+
} else if ("function" === typeof next) {
|
|
86
|
+
next();
|
|
87
|
+
} else {
|
|
88
|
+
res.statusCode = 500;
|
|
89
|
+
res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function respondWithGrace(res, result, hostname, token) {
|
|
94
|
+
var keyAuth = result && result.keyAuthorization;
|
|
95
|
+
|
|
96
|
+
// HEADERS SENT DEBUG NOTE #4b
|
|
97
|
+
// This is (still) the third/fourth possible time we could be sending headers
|
|
98
|
+
if (keyAuth && "string" === typeof keyAuth) {
|
|
99
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
100
|
+
res.end(keyAuth);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
res.statusCode = 404;
|
|
105
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
106
|
+
res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function explainError(gl, err, ctx, hostname) {
|
|
110
|
+
if (!err.servername) {
|
|
111
|
+
err.servername = hostname;
|
|
112
|
+
}
|
|
113
|
+
if (!err.context) {
|
|
114
|
+
err.context = ctx;
|
|
115
|
+
}
|
|
116
|
+
// leaving this in the build for now because it will help with existing error reports
|
|
117
|
+
console.error("[warning] network connection error:", (err.context || "") + " " + err.message);
|
|
118
|
+
(gl.notify || gl._notify)("error", err);
|
|
119
|
+
return err;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function respondToError(gl, res, err, ctx, hostname) {
|
|
123
|
+
// HEADERS SENT DEBUG NOTE #3b
|
|
124
|
+
// This is (still) the second possible time we could be sending headers
|
|
125
|
+
err = explainError(gl, err, ctx, hostname);
|
|
126
|
+
res.statusCode = 500;
|
|
127
|
+
res.end("Internal Server Error [1004]: See logs for details.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
HttpMiddleware.getHostname = function(req) {
|
|
131
|
+
return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || "");
|
|
132
|
+
};
|
|
133
|
+
HttpMiddleware.sanitizeHostname = function(req) {
|
|
134
|
+
// we can trust XFH because spoofing causes no ham in this limited use-case scenario
|
|
135
|
+
// (and only telebit would be legitimately setting XFH)
|
|
136
|
+
var servername = HttpMiddleware.getHostname(req)
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.replace(/:.*/, "");
|
|
139
|
+
try {
|
|
140
|
+
req.hostname = servername;
|
|
141
|
+
} catch (e) {
|
|
142
|
+
// read-only express property
|
|
143
|
+
}
|
|
144
|
+
if (req.headers["x-forwarded-host"]) {
|
|
145
|
+
req.headers["x-forwarded-host"] = servername;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
req.headers.host = servername;
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// TODO is this a possible error?
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || "";
|
|
154
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var SanitizeHost = module.exports;
|
|
4
|
+
var HttpMiddleware = require("./http-middleware.js");
|
|
5
|
+
|
|
6
|
+
SanitizeHost.create = function(gl, app) {
|
|
7
|
+
return function(req, res, next) {
|
|
8
|
+
function realNext() {
|
|
9
|
+
if ("function" === typeof app) {
|
|
10
|
+
app(req, res);
|
|
11
|
+
} else if ("function" === typeof next) {
|
|
12
|
+
next();
|
|
13
|
+
} else {
|
|
14
|
+
res.statusCode = 500;
|
|
15
|
+
res.end("Error: no middleware assigned");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var hostname = HttpMiddleware.getHostname(req);
|
|
20
|
+
// Replace the hostname, and get the safe version
|
|
21
|
+
var safehost = HttpMiddleware.sanitizeHostname(req);
|
|
22
|
+
|
|
23
|
+
// if no hostname, move along
|
|
24
|
+
if (!hostname) {
|
|
25
|
+
realNext();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// if there were unallowed characters, complain
|
|
30
|
+
if (safehost.length !== hostname.length) {
|
|
31
|
+
res.statusCode = 400;
|
|
32
|
+
res.end("Malformed HTTP Header: 'Host: " + hostname + "'");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
|
|
37
|
+
if (req.socket.encrypted) {
|
|
38
|
+
if (req.socket && "string" === typeof req.socket.servername) {
|
|
39
|
+
// Workaround for https://github.com/nodejs/node/issues/22389
|
|
40
|
+
if (!SanitizeHost._checkServername(safehost, req.socket)) {
|
|
41
|
+
res.statusCode = 400;
|
|
42
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
43
|
+
res.end(
|
|
44
|
+
"<h1>Domain Fronting Error</h1>" +
|
|
45
|
+
"<p>This connection was secured using TLS/SSL for '" +
|
|
46
|
+
(req.socket.servername || "").toLowerCase() +
|
|
47
|
+
"'</p>" +
|
|
48
|
+
"<p>The HTTP request specified 'Host: " +
|
|
49
|
+
safehost +
|
|
50
|
+
"', which is (obviously) different.</p>" +
|
|
51
|
+
"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>"
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/*
|
|
57
|
+
else if (safehost && !gl._skip_fronting_check) {
|
|
58
|
+
|
|
59
|
+
// We used to print a log message here, but it turns out that it's
|
|
60
|
+
// really common for IoT devices to not use SNI (as well as many bots
|
|
61
|
+
// and such).
|
|
62
|
+
// It was common for the log message to pop up as the first request
|
|
63
|
+
// to the server, and that was confusing. So instead now we do nothing.
|
|
64
|
+
|
|
65
|
+
//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
|
|
66
|
+
//gl._skip_fronting_check = true;
|
|
67
|
+
}
|
|
68
|
+
*/
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// carry on
|
|
72
|
+
realNext();
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
var warnDomainFronting = true;
|
|
77
|
+
var warnUnexpectedError = true;
|
|
78
|
+
SanitizeHost._checkServername = function(safeHost, tlsSocket) {
|
|
79
|
+
var servername = (tlsSocket.servername || "").toLowerCase();
|
|
80
|
+
|
|
81
|
+
// acceptable: older IoT devices may lack SNI support
|
|
82
|
+
if (!servername) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// acceptable: odd... but acceptable
|
|
86
|
+
if (!safeHost) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (safeHost === servername) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ("function" !== typeof tlsSocket.getCertificate) {
|
|
94
|
+
// domain fronting attacks allowed
|
|
95
|
+
if (warnDomainFronting) {
|
|
96
|
+
// https://github.com/nodejs/node/issues/24095
|
|
97
|
+
console.warn(
|
|
98
|
+
"Warning: node " +
|
|
99
|
+
process.version +
|
|
100
|
+
" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater."
|
|
101
|
+
);
|
|
102
|
+
warnDomainFronting = false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// connection established with servername and session is re-used for allowed name
|
|
108
|
+
// See https://github.com/nodejs/node/issues/24095
|
|
109
|
+
var cert = tlsSocket.getCertificate();
|
|
110
|
+
try {
|
|
111
|
+
// TODO optimize / cache?
|
|
112
|
+
// *should* always have a string, right?
|
|
113
|
+
// *should* always be lowercase already, right?
|
|
114
|
+
//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
|
|
115
|
+
var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost;
|
|
116
|
+
if (isSubject) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var dnsnames = (cert.subjectaltname || "").split(/,\s+/);
|
|
121
|
+
var inSanList = dnsnames.some(function(name) {
|
|
122
|
+
// always prefixed with "DNS:"
|
|
123
|
+
return safeHost === name.slice(4).toLowerCase();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (inSanList) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// not sure what else to do in this situation...
|
|
131
|
+
if (warnUnexpectedError) {
|
|
132
|
+
console.warn("Warning: encoutered error while performing domain fronting check: " + e.message);
|
|
133
|
+
warnUnexpectedError = false;
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# This is just an example (but it works)
|
|
2
|
+
export NODE_PATH=$NPM_CONFIG_PREFIX/lib/node_modules
|
|
3
|
+
export NPM_CONFIG_PREFIX=/opt/node
|
|
4
|
+
curl -fsSL https://bit.ly/node-installer | bash
|
|
5
|
+
|
|
6
|
+
/opt/node/bin/node /opt/node/bin/npm config set scripts-prepend-node-path true
|
|
7
|
+
/opt/node/bin/node /opt/node/bin/npm ci
|
|
8
|
+
sudo setcap 'cap_net_bind_service=+ep' /opt/node/bin/node
|
|
9
|
+
/opt/node/bin/node /opt/node/bin/npm start
|
|
10
|
+
|
|
11
|
+
sudo rsync -av dist/etc/systemd/system/greenlock-express.service /etc/systemd/system/
|
|
12
|
+
sudo systemctl daemon-reload
|
|
13
|
+
|
|
14
|
+
sudo systemctl restart greenlock-express
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function requireBluebird() {
|
|
4
|
+
try {
|
|
5
|
+
return require("bluebird");
|
|
6
|
+
} catch (e) {
|
|
7
|
+
console.error("");
|
|
8
|
+
console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support.");
|
|
9
|
+
console.error("EASY FIX: `npm install --save bluebird`");
|
|
10
|
+
console.error("");
|
|
11
|
+
throw e;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if ("undefined" === typeof Promise) {
|
|
16
|
+
global.Promise = requireBluebird();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if ("function" !== typeof require("util").promisify) {
|
|
20
|
+
require("util").promisify = requireBluebird().promisify;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!console.debug) {
|
|
24
|
+
console.debug = console.log;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var fs = require("fs");
|
|
28
|
+
var fsAsync = {};
|
|
29
|
+
Object.keys(fs).forEach(function(key) {
|
|
30
|
+
var fn = fs[key];
|
|
31
|
+
if ("function" !== typeof fn || !/[a-z]/.test(key[0])) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
fsAsync[key] = require("util").promisify(fn);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
exports.fsAsync = fsAsync;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// this is the stuff that should run in the main foreground process,
|
|
4
|
+
// whether it's single or master
|
|
5
|
+
|
|
6
|
+
var major = parseInt(process.versions.node.split(".")[0], 10);
|
|
7
|
+
var minor = parseInt(process.versions.node.split(".")[1], 10) || 0;
|
|
8
|
+
var _hasSetSecureContext = false;
|
|
9
|
+
var shouldUpgrade = false;
|
|
10
|
+
|
|
11
|
+
// this applies to http2 as well (should exist in both or neither)
|
|
12
|
+
_hasSetSecureContext = major > 11 || (major === 11 && minor >= 2);
|
|
13
|
+
|
|
14
|
+
// TODO document in issues
|
|
15
|
+
if (!_hasSetSecureContext) {
|
|
16
|
+
// TODO this isn't necessary if greenlock options are set with options.cert
|
|
17
|
+
console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext().");
|
|
18
|
+
console.warn(" The default certificate may not be set.");
|
|
19
|
+
shouldUpgrade = true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (major < 11 || (11 === major && minor < 2)) {
|
|
23
|
+
// https://github.com/nodejs/node/issues/24095
|
|
24
|
+
console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate().");
|
|
25
|
+
console.warn(" This is necessary to guard against domain fronting attacks.");
|
|
26
|
+
shouldUpgrade = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (shouldUpgrade) {
|
|
30
|
+
console.warn("Warning: Please upgrade to node v11.2.0 or greater.");
|
|
31
|
+
console.warn();
|
|
32
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
require("./main.js");
|
|
4
|
+
|
|
5
|
+
var Master = module.exports;
|
|
6
|
+
|
|
7
|
+
var cluster = require("cluster");
|
|
8
|
+
var os = require("os");
|
|
9
|
+
var msgPrefix = "greenlock:";
|
|
10
|
+
|
|
11
|
+
Master.create = function(opts) {
|
|
12
|
+
var resolveCb;
|
|
13
|
+
var _readyCb;
|
|
14
|
+
var _kicked = false;
|
|
15
|
+
|
|
16
|
+
var greenlock = require("./greenlock-shim.js").create(opts);
|
|
17
|
+
|
|
18
|
+
var ready = new Promise(function(resolve) {
|
|
19
|
+
resolveCb = resolve;
|
|
20
|
+
}).then(function(fn) {
|
|
21
|
+
_readyCb = fn;
|
|
22
|
+
return fn;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function kickoff() {
|
|
26
|
+
if (_kicked) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
_kicked = true;
|
|
30
|
+
|
|
31
|
+
Master._spawnWorkers(opts, greenlock);
|
|
32
|
+
|
|
33
|
+
ready.then(function(fn) {
|
|
34
|
+
// not sure what this API should be yet
|
|
35
|
+
fn();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var master = {
|
|
40
|
+
ready: function() {
|
|
41
|
+
kickoff();
|
|
42
|
+
return master;
|
|
43
|
+
},
|
|
44
|
+
master: function(fn) {
|
|
45
|
+
if (_readyCb) {
|
|
46
|
+
throw new Error("can't call master twice");
|
|
47
|
+
}
|
|
48
|
+
kickoff();
|
|
49
|
+
resolveCb(fn);
|
|
50
|
+
return master;
|
|
51
|
+
},
|
|
52
|
+
serve: function(fn) {
|
|
53
|
+
// ignore
|
|
54
|
+
master.ready(fn);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return master;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function range(n) {
|
|
61
|
+
n = parseInt(n, 10);
|
|
62
|
+
if (!n) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
return new Array(n).join(",").split(",");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Master._spawnWorkers = function(opts, greenlock) {
|
|
69
|
+
var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length;
|
|
70
|
+
|
|
71
|
+
// process rpc messages
|
|
72
|
+
// start when dead
|
|
73
|
+
var numWorkers = parseInt(opts.workers || opts.numWorkers, 10);
|
|
74
|
+
if (!numWorkers) {
|
|
75
|
+
if (numCpus <= 2) {
|
|
76
|
+
numWorkers = 2;
|
|
77
|
+
} else {
|
|
78
|
+
numWorkers = numCpus - 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cluster.once("exit", function() {
|
|
83
|
+
setTimeout(function() {
|
|
84
|
+
process.exit(3);
|
|
85
|
+
}, 100);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
var workers = range(numWorkers);
|
|
89
|
+
function next() {
|
|
90
|
+
if (!workers.length) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
workers.pop();
|
|
94
|
+
|
|
95
|
+
// for a nice aesthetic
|
|
96
|
+
setTimeout(function() {
|
|
97
|
+
Master._spawnWorker(opts, greenlock);
|
|
98
|
+
next();
|
|
99
|
+
}, 250);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
next();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Master._spawnWorker = function(opts, greenlock) {
|
|
106
|
+
var w = cluster.fork();
|
|
107
|
+
// automatically added to master's `cluster.workers`
|
|
108
|
+
w.once("exit", function(code, signal) {
|
|
109
|
+
// TODO handle failures
|
|
110
|
+
// Should test if the first starts successfully
|
|
111
|
+
// Should exit if failures happen too quickly
|
|
112
|
+
|
|
113
|
+
// For now just kill all when any die
|
|
114
|
+
if (signal) {
|
|
115
|
+
console.error("worker was killed by signal:", signal);
|
|
116
|
+
} else if (code !== 0) {
|
|
117
|
+
console.error("worker exited with error code:", code);
|
|
118
|
+
} else {
|
|
119
|
+
console.error("worker unexpectedly quit without exit code or signal");
|
|
120
|
+
}
|
|
121
|
+
process.exit(2);
|
|
122
|
+
|
|
123
|
+
//addWorker();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function handleMessage(msg) {
|
|
127
|
+
if (0 !== (msg._id || "").indexOf(msgPrefix)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if ("string" !== typeof msg._funcname) {
|
|
131
|
+
// TODO developer error
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function rpc() {
|
|
136
|
+
return greenlock[msg._funcname](msg._input)
|
|
137
|
+
.then(function(result) {
|
|
138
|
+
w.send({
|
|
139
|
+
_id: msg._id,
|
|
140
|
+
_result: result
|
|
141
|
+
});
|
|
142
|
+
})
|
|
143
|
+
.catch(function(e) {
|
|
144
|
+
var error = new Error(e.message);
|
|
145
|
+
Object.getOwnPropertyNames(e).forEach(function(k) {
|
|
146
|
+
error[k] = e[k];
|
|
147
|
+
});
|
|
148
|
+
w.send({
|
|
149
|
+
_id: msg._id,
|
|
150
|
+
_error: error
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
rpc();
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:");
|
|
159
|
+
console.error(e);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
w.on("message", handleMessage);
|
|
164
|
+
};
|