@colyseus/tools 0.16.0-preview.2 → 0.16.0-preview.4
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 +5 -5
- package/build/index.d.ts +1 -1
- package/build/index.js +42 -38
- package/build/index.js.map +3 -3
- package/build/index.mjs +42 -38
- package/build/index.mjs.map +2 -2
- package/package.json +28 -13
- package/pm2/post-deploy-agent.js +315 -0
- package/pm2/shared.js +73 -0
- package/post-deploy.js +97 -94
- package/report-stats.js +178 -0
- package/system-boot.js +89 -35
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PM2 Agent for no downtime deployments on Colyseus Cloud.
|
|
3
|
+
*
|
|
4
|
+
* How it works:
|
|
5
|
+
* - New process(es) are spawned (MAX_ACTIVE_PROCESSES/2)
|
|
6
|
+
* - NGINX configuration is updated so new traffic only goes through the new process
|
|
7
|
+
* - Old processes are asynchronously and gracefully stopped.
|
|
8
|
+
* - The rest of the processes are spawned/reactivated.
|
|
9
|
+
*/
|
|
10
|
+
const pm2 = require('pm2');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const cst = require('pm2/constants');
|
|
13
|
+
const io = require('@pm2/io');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const shared = require('./shared');
|
|
16
|
+
|
|
17
|
+
const opts = { env: process.env.NODE_ENV || "production", };
|
|
18
|
+
let config = undefined;
|
|
19
|
+
|
|
20
|
+
io.initModule({
|
|
21
|
+
pid: path.resolve('/var/run/colyseus-agent.pid'),
|
|
22
|
+
widget: {
|
|
23
|
+
type: 'generic',
|
|
24
|
+
logo: 'https://colyseus.io/images/logos/logo-dark-color.png',
|
|
25
|
+
theme : ['#9F1414', '#591313', 'white', 'white'],
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
pm2.connect(function(err) {
|
|
30
|
+
if (err) {
|
|
31
|
+
console.error(err.stack || err);
|
|
32
|
+
process.exit();
|
|
33
|
+
}
|
|
34
|
+
console.log('PM2 post-deploy agent is up and running...');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Remote actions
|
|
38
|
+
*/
|
|
39
|
+
io.action('post-deploy', async function (arg0, reply) {
|
|
40
|
+
const [cwd, ecosystemFilePath] = arg0.split(':');
|
|
41
|
+
console.log("Received 'post-deploy' action!", { cwd, config: ecosystemFilePath });
|
|
42
|
+
|
|
43
|
+
let replied = false;
|
|
44
|
+
|
|
45
|
+
//
|
|
46
|
+
// Override 'reply' to decrement amount of concurrent deployments
|
|
47
|
+
//
|
|
48
|
+
const onReply = function() {
|
|
49
|
+
if (replied) { return; }
|
|
50
|
+
replied = true;
|
|
51
|
+
reply.apply(null, arguments);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
config = await shared.getAppConfig(ecosystemFilePath);
|
|
56
|
+
opts.cwd = cwd;
|
|
57
|
+
postDeploy(cwd, onReply);
|
|
58
|
+
|
|
59
|
+
} catch (err) {
|
|
60
|
+
onReply({ success: false, message: err?.message });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const restartingAppIds = new Set();
|
|
66
|
+
|
|
67
|
+
function postDeploy(cwd, reply) {
|
|
68
|
+
shared.listApps(function(err, apps) {
|
|
69
|
+
if (err) {
|
|
70
|
+
console.error(err);
|
|
71
|
+
return reply({ success: false, message: err?.message });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// first deploy, start all processes
|
|
75
|
+
if (apps.length === 0) {
|
|
76
|
+
return pm2.start(config, {...opts}, (err, result) => {
|
|
77
|
+
reply({ success: !err, message: err?.message });
|
|
78
|
+
updateAndSave(err, result);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//
|
|
83
|
+
// detect if cwd has changed, and restart PM2 if it has
|
|
84
|
+
//
|
|
85
|
+
if (apps[0].pm2_env.pm_cwd !== cwd) {
|
|
86
|
+
console.log("App Root Directory changed. Restarting may take a bit longer...");
|
|
87
|
+
|
|
88
|
+
//
|
|
89
|
+
// remove all and start again with new cwd
|
|
90
|
+
//
|
|
91
|
+
return pm2.delete('all', function (err) {
|
|
92
|
+
logIfError(err);
|
|
93
|
+
|
|
94
|
+
// start again
|
|
95
|
+
pm2.start(config, { ...opts }, (err, result) => {
|
|
96
|
+
reply({ success: !err, message: err?.message });
|
|
97
|
+
updateAndSave(err, result);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Graceful restart logic:
|
|
104
|
+
* List of PM2 app envs to stop or restart
|
|
105
|
+
*/
|
|
106
|
+
const appsToStop = [];
|
|
107
|
+
const appsStopped = [];
|
|
108
|
+
let numAppsStopping = 0;
|
|
109
|
+
let numTotalApps = undefined;
|
|
110
|
+
|
|
111
|
+
apps.forEach((app) => {
|
|
112
|
+
const env = app.pm2_env;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Asynchronously teardown/stop processes with active connections
|
|
116
|
+
*/
|
|
117
|
+
if (env.status === cst.STOPPED_STATUS) {
|
|
118
|
+
appsStopped.push(env);
|
|
119
|
+
|
|
120
|
+
} else if (env.status !== cst.STOPPING_STATUS) {
|
|
121
|
+
appsToStop.push(env);
|
|
122
|
+
|
|
123
|
+
} else if (!restartingAppIds.has(env.pm_id)) {
|
|
124
|
+
numAppsStopping++;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* - Start new process
|
|
130
|
+
* - Update NGINX config to expose only the new process
|
|
131
|
+
* - Stop old processes
|
|
132
|
+
* - Spawn/reactivate the rest of the processes (shared.MAX_ACTIVE_PROCESSES)
|
|
133
|
+
*/
|
|
134
|
+
const onFirstAppsStart = async (initialApps, err, result) => {
|
|
135
|
+
/**
|
|
136
|
+
* release post-deploy action while proceeding with graceful restart of other processes
|
|
137
|
+
*/
|
|
138
|
+
reply({ success: !err, message: err?.message });
|
|
139
|
+
|
|
140
|
+
if (err) { return console.error(err); }
|
|
141
|
+
|
|
142
|
+
let numActiveApps = initialApps.length + restartingAppIds.size;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* - Write NGINX config to expose only the new active process
|
|
146
|
+
* - The old ones processes will go down asynchronously (or will be restarted)
|
|
147
|
+
*/
|
|
148
|
+
writeNginxConfig(initialApps);
|
|
149
|
+
|
|
150
|
+
//
|
|
151
|
+
// Wait 1.5 seconds to ensure NGINX is updated & reloaded
|
|
152
|
+
//
|
|
153
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
154
|
+
|
|
155
|
+
//
|
|
156
|
+
// Asynchronously stop/restart apps with active connections
|
|
157
|
+
// (They make take from minutes up to hours to stop)
|
|
158
|
+
//
|
|
159
|
+
appsToStop.forEach((app_env) => {
|
|
160
|
+
if (numActiveApps < shared.MAX_ACTIVE_PROCESSES) {
|
|
161
|
+
numActiveApps++;
|
|
162
|
+
|
|
163
|
+
restartingAppIds.add(app_env.pm_id);
|
|
164
|
+
pm2.restart(app_env.pm_id, (err, _) => {
|
|
165
|
+
restartingAppIds.delete(app_env.pm_id);
|
|
166
|
+
if (err) { return logIfError(err); }
|
|
167
|
+
|
|
168
|
+
// reset counter stats (restart_time=0)
|
|
169
|
+
pm2.reset(app_env.pm_id, logIfError);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
} else {
|
|
173
|
+
pm2.stop(app_env.pm_id, logIfError);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (numActiveApps < shared.MAX_ACTIVE_PROCESSES) {
|
|
178
|
+
const missingOnlineApps = shared.MAX_ACTIVE_PROCESSES - numActiveApps;
|
|
179
|
+
|
|
180
|
+
// console.log("Active apps is lower than MAX_ACTIVE_PROCESSES, will SCALE again =>", {
|
|
181
|
+
// missingOnlineApps,
|
|
182
|
+
// numActiveApps,
|
|
183
|
+
// newNumTotalApps: numTotalApps + missingOnlineApps
|
|
184
|
+
// });
|
|
185
|
+
|
|
186
|
+
pm2.scale(apps[0].name, numTotalApps + missingOnlineApps, updateAndSaveIfAllRunning);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const numHalfMaxActiveProcesses = Math.ceil(shared.MAX_ACTIVE_PROCESSES / 2);
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Re-use previously stopped apps if available
|
|
194
|
+
*/
|
|
195
|
+
if (appsStopped.length >= numHalfMaxActiveProcesses) {
|
|
196
|
+
const initialApps = appsStopped.splice(0, numHalfMaxActiveProcesses);
|
|
197
|
+
|
|
198
|
+
let numSucceeded = 0;
|
|
199
|
+
initialApps.forEach((app_env) => {
|
|
200
|
+
// console.log("pm2.restart => ", app_env.pm_id);
|
|
201
|
+
|
|
202
|
+
restartingAppIds.add(app_env.pm_id);
|
|
203
|
+
pm2.restart(app_env.pm_id, (err) => {
|
|
204
|
+
restartingAppIds.delete(app_env.pm_id);
|
|
205
|
+
if (err) { return replyIfError(err, reply); }
|
|
206
|
+
|
|
207
|
+
// reset counter stats (restart_time=0)
|
|
208
|
+
pm2.reset(app_env.pm_id, logIfError);
|
|
209
|
+
|
|
210
|
+
// TODO: set timeout here to exit if some processes are not restarting
|
|
211
|
+
|
|
212
|
+
numSucceeded++;
|
|
213
|
+
if (numSucceeded === initialApps.length) {
|
|
214
|
+
onFirstAppsStart(initialApps);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
} else {
|
|
220
|
+
/**
|
|
221
|
+
* Increment to +(MAX/2) processes
|
|
222
|
+
*/
|
|
223
|
+
let LAST_NODE_APP_INSTANCE = apps[apps.length - 1].pm2_env.NODE_APP_INSTANCE;
|
|
224
|
+
const initialApps = Array.from({ length: numHalfMaxActiveProcesses }).map((_, i) => {
|
|
225
|
+
const new_app_env = Object.assign({}, apps[0].pm2_env);
|
|
226
|
+
new_app_env.NODE_APP_INSTANCE = ++LAST_NODE_APP_INSTANCE;
|
|
227
|
+
return new_app_env;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
numTotalApps = apps.length + numHalfMaxActiveProcesses;
|
|
231
|
+
|
|
232
|
+
// Ensure to scale to a number of processes where `numHalfMaxActiveProcesses` can start immediately.
|
|
233
|
+
pm2.scale(apps[0].name, numTotalApps, onFirstAppsStart.bind(undefined, initialApps));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function updateAndSave() {
|
|
239
|
+
// console.log("updateAndExit");
|
|
240
|
+
updateAndReloadNginx(() => complete());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function updateAndSaveIfAllRunning(err) {
|
|
244
|
+
if (err) { return console.error(err); }
|
|
245
|
+
|
|
246
|
+
updateAndReloadNginx((app_envs) => {
|
|
247
|
+
// console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/", shared.MAX_ACTIVE_PROCESSES);
|
|
248
|
+
|
|
249
|
+
//
|
|
250
|
+
// TODO: add timeout to exit here, in case some processes are not starting
|
|
251
|
+
//
|
|
252
|
+
if (app_envs.length === shared.MAX_ACTIVE_PROCESSES) {
|
|
253
|
+
complete();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function updateAndReloadNginx(cb) {
|
|
259
|
+
//
|
|
260
|
+
// If you are self-hosting and reading this file, consider using the
|
|
261
|
+
// following in your self-hosted environment:
|
|
262
|
+
//
|
|
263
|
+
// #!/bin/bash
|
|
264
|
+
// # Requires fswatch (`apt install fswatch`)
|
|
265
|
+
// # Reload NGINX when colyseus_servers.conf changes
|
|
266
|
+
// fswatch /etc/nginx/colyseus_servers.conf -m poll_monitor --event=Updated | while read event
|
|
267
|
+
// do
|
|
268
|
+
// service nginx reload
|
|
269
|
+
// done
|
|
270
|
+
|
|
271
|
+
shared.listApps(function(err, apps) {
|
|
272
|
+
if (apps.length === 0) { err = "no apps running."; }
|
|
273
|
+
if (err) { return console.error(err); }
|
|
274
|
+
|
|
275
|
+
const app_envs = apps
|
|
276
|
+
.filter(app => app.pm2_env.status !== cst.STOPPING_STATUS && app.pm2_env.status !== cst.STOPPED_STATUS)
|
|
277
|
+
.map((app) => app.pm2_env);
|
|
278
|
+
|
|
279
|
+
writeNginxConfig(app_envs);
|
|
280
|
+
|
|
281
|
+
cb?.(app_envs);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function writeNginxConfig(app_envs) {
|
|
286
|
+
// console.log("writeNginxConfig: ", app_envs.map(app_env => app_env.NODE_APP_INSTANCE));
|
|
287
|
+
|
|
288
|
+
const port = 2567;
|
|
289
|
+
const addresses = [];
|
|
290
|
+
|
|
291
|
+
app_envs.forEach(function(app_env) {
|
|
292
|
+
addresses.push(`unix:${shared.PROCESS_UNIX_SOCK_PATH}${port + app_env.NODE_APP_INSTANCE}.sock`);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// write NGINX config
|
|
296
|
+
fs.writeFileSync(shared.NGINX_SERVERS_CONFIG_FILE, addresses.map(address => `server ${address};`).join("\n"), logIfError);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function complete() {
|
|
300
|
+
// "pm2 save"
|
|
301
|
+
pm2.dump(logIfError);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function logIfError (err) {
|
|
305
|
+
if (err) {
|
|
306
|
+
console.error(err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function replyIfError(err, reply) {
|
|
311
|
+
if (err) {
|
|
312
|
+
console.error(err);
|
|
313
|
+
reply({ success: false, message: err?.message });
|
|
314
|
+
}
|
|
315
|
+
}
|
package/pm2/shared.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const pm2 = require('pm2');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
const NAMESPACE = 'cloud';
|
|
5
|
+
const MAX_ACTIVE_PROCESSES = os.cpus().length;
|
|
6
|
+
|
|
7
|
+
function listApps(callback) {
|
|
8
|
+
pm2.list((err, apps) => {
|
|
9
|
+
if (err) { return callback(err);; }
|
|
10
|
+
|
|
11
|
+
// Filter out @colyseus/tools module (PM2 post-deploy agent)
|
|
12
|
+
apps = apps.filter(app => app.name !== '@colyseus/tools');
|
|
13
|
+
|
|
14
|
+
callback(err, apps);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getAppConfig(ecosystemFilePath) {
|
|
19
|
+
const module = await import(ecosystemFilePath);
|
|
20
|
+
const config = module.default;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tune PM2 app config
|
|
24
|
+
*/
|
|
25
|
+
if (config.apps && config.apps.length >= 0) {
|
|
26
|
+
const app = config.apps[0];
|
|
27
|
+
|
|
28
|
+
// app.name = "colyseus-app";
|
|
29
|
+
app.namespace = NAMESPACE;
|
|
30
|
+
app.exec_mode = "fork";
|
|
31
|
+
|
|
32
|
+
app.instances = MAX_ACTIVE_PROCESSES;
|
|
33
|
+
|
|
34
|
+
app.time = true;
|
|
35
|
+
app.wait_ready = true;
|
|
36
|
+
app.watch = false;
|
|
37
|
+
|
|
38
|
+
// default: merge logs into a single file
|
|
39
|
+
if (app.merge_logs === undefined) {
|
|
40
|
+
app.merge_logs = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// default: wait for 30 minutes before forcibly killing
|
|
44
|
+
// (prevent forcibly killing while rooms are still active)
|
|
45
|
+
if (!app.kill_timeout) {
|
|
46
|
+
app.kill_timeout = 30 * 60 * 1000;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// default: retry kill after 1 second
|
|
50
|
+
if (!app.kill_retry_time) {
|
|
51
|
+
app.kill_retry_time = 5000;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
/**
|
|
60
|
+
* Constants
|
|
61
|
+
*/
|
|
62
|
+
NGINX_SERVERS_CONFIG_FILE: '/etc/nginx/colyseus_servers.conf',
|
|
63
|
+
PROCESS_UNIX_SOCK_PATH: '/run/colyseus/',
|
|
64
|
+
|
|
65
|
+
MAX_ACTIVE_PROCESSES,
|
|
66
|
+
NAMESPACE,
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shared methods
|
|
70
|
+
*/
|
|
71
|
+
listApps,
|
|
72
|
+
getAppConfig,
|
|
73
|
+
}
|
package/post-deploy.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const pm2 = require('pm2');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
5
|
+
const shared = require('./pm2/shared');
|
|
6
6
|
|
|
7
|
-
const opts = { env: process.env.NODE_ENV || "production" };
|
|
8
|
-
const maxCPU = os.cpus().length;
|
|
9
|
-
|
|
10
|
-
// // allow deploying from other path as root.
|
|
11
|
-
// if (process.env.npm_config_local_prefix) {
|
|
12
|
-
// process.chdir(process.env.npm_config_local_prefix);
|
|
13
|
-
// pm2.cwd = process.env.npm_config_local_prefix;
|
|
14
|
-
// }
|
|
7
|
+
const opts = { env: process.env.NODE_ENV || "production", };
|
|
15
8
|
|
|
16
9
|
const CONFIG_FILE = [
|
|
17
10
|
'ecosystem.config.cjs',
|
|
@@ -20,88 +13,125 @@ const CONFIG_FILE = [
|
|
|
20
13
|
'pm2.config.js',
|
|
21
14
|
].find((filename) => fs.existsSync(path.resolve(pm2.cwd, filename)));
|
|
22
15
|
|
|
16
|
+
/**
|
|
17
|
+
* TODO: if not provided, auto-detect entry-point & dynamically generate ecosystem config
|
|
18
|
+
*/
|
|
23
19
|
if (!CONFIG_FILE) {
|
|
24
20
|
throw new Error('missing ecosystem config file. make sure to provide one with a valid "script" entrypoint file path.');
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
bailOnErr(err);
|
|
23
|
+
const CONFIG_FILE_PATH = `${pm2.cwd}/${CONFIG_FILE}`;
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Try to handle post-deploy via PM2 module first (pm2 install @colyseus/tools)
|
|
27
|
+
* If not available, fallback to legacy post-deploy script.
|
|
28
|
+
*/
|
|
29
|
+
pm2.trigger('@colyseus/tools', 'post-deploy', `${pm2.cwd}:${CONFIG_FILE_PATH}`, async function (err, result) {
|
|
30
|
+
if (err) {
|
|
31
|
+
console.log("Proceeding with legacy post-deploy script...");
|
|
32
|
+
postDeploy();
|
|
33
33
|
|
|
34
34
|
} else {
|
|
35
|
+
if (result[0].data?.return?.success === false) {
|
|
36
|
+
console.error(result[0].data?.return?.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} else {
|
|
39
|
+
console.log("Post-deploy success.");
|
|
40
|
+
process.exit();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (apps[0].pm2_env.pm_cwd !== pm2.cwd) {
|
|
45
|
+
async function postDeploy() {
|
|
46
|
+
shared.listApps(function (err, apps) {
|
|
47
|
+
bailOnErr(err);
|
|
40
48
|
|
|
49
|
+
if (apps.length === 0) {
|
|
41
50
|
//
|
|
42
|
-
//
|
|
51
|
+
// first deploy
|
|
43
52
|
//
|
|
44
|
-
pm2.
|
|
45
|
-
|
|
46
|
-
// kill & start again
|
|
47
|
-
pm2.kill(function() {
|
|
48
|
-
pm2.start(CONFIG_FILE, { ...opts }, onAppRunning);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
});
|
|
53
|
+
pm2.start(CONFIG_FILE_PATH, { ...opts }, () => onAppRunning());
|
|
52
54
|
|
|
53
55
|
} else {
|
|
56
|
+
|
|
54
57
|
//
|
|
55
|
-
//
|
|
58
|
+
// detect if cwd has changed, and restart PM2 if it has
|
|
56
59
|
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
60
|
+
if (apps[0].pm2_env.pm_cwd !== pm2.cwd) {
|
|
61
|
+
//
|
|
62
|
+
// remove all and start again with new cwd
|
|
63
|
+
//
|
|
64
|
+
restartAll();
|
|
65
|
+
|
|
66
|
+
} else {
|
|
67
|
+
//
|
|
68
|
+
// reload existing apps
|
|
69
|
+
//
|
|
70
|
+
reloadAll();
|
|
71
|
+
}
|
|
70
72
|
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
71
75
|
|
|
76
|
+
function onAppRunning(reloadedAppIds) {
|
|
77
|
+
// reset reloaded app stats
|
|
78
|
+
if (reloadedAppIds) {
|
|
79
|
+
resetAppStats(reloadedAppIds);
|
|
72
80
|
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
function onAppRunning() {
|
|
76
|
-
updateColyseusBootService();
|
|
77
81
|
updateAndReloadNginx();
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
function restartAll () {
|
|
85
|
+
pm2.delete('all', function (err) {
|
|
86
|
+
// kill & start again
|
|
87
|
+
pm2.kill(function () {
|
|
88
|
+
pm2.start(CONFIG_FILE_PATH, { ...opts }, () => onAppRunning());
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
84
92
|
|
|
85
|
-
|
|
93
|
+
function reloadAll(retry = 0) {
|
|
94
|
+
pm2.reload(CONFIG_FILE_PATH, { ...opts }, function (err, apps) {
|
|
95
|
+
if (err) {
|
|
96
|
+
//
|
|
97
|
+
// Retry in case of "Reload in progress" error.
|
|
98
|
+
//
|
|
99
|
+
if (err.message === 'Reload in progress' && retry < 5) {
|
|
100
|
+
console.warn(err.message, ", retrying...");
|
|
101
|
+
setTimeout(() => reloadAll(retry + 1), 1000);
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
103
|
+
} else {
|
|
104
|
+
bailOnErr(err);
|
|
105
|
+
}
|
|
91
106
|
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
94
109
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
const name = apps[0].name;
|
|
111
|
+
const reloadedAppIds = apps.map(app => app.pm_id);
|
|
112
|
+
|
|
113
|
+
// scale app to use all CPUs available
|
|
114
|
+
if (apps.length !== shared.MAX_ACTIVE_PROCESSES) {
|
|
115
|
+
pm2.scale(name, shared.MAX_ACTIVE_PROCESSES, () => onAppRunning(reloadedAppIds));
|
|
116
|
+
|
|
117
|
+
} else {
|
|
118
|
+
onAppRunning(reloadedAppIds);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
103
121
|
}
|
|
104
122
|
|
|
123
|
+
function resetAppStats (reloadedAppIds) {
|
|
124
|
+
reloadedAppIds.forEach((pm_id) => {
|
|
125
|
+
pm2.reset(pm_id, (err, _) => {
|
|
126
|
+
if (err) {
|
|
127
|
+
console.error(err);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`metrics re-set for app_id: ${pm_id}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
105
135
|
function updateAndReloadNginx() {
|
|
106
136
|
//
|
|
107
137
|
// If you are self-hosting and reading this file, consider using the
|
|
@@ -115,9 +145,7 @@ function updateAndReloadNginx() {
|
|
|
115
145
|
// service nginx reload
|
|
116
146
|
// done
|
|
117
147
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
pm2.list(function(err, apps) {
|
|
148
|
+
shared.listApps(function(err, apps) {
|
|
121
149
|
if (apps.length === 0) {
|
|
122
150
|
err = "no apps running.";
|
|
123
151
|
}
|
|
@@ -127,11 +155,11 @@ function updateAndReloadNginx() {
|
|
|
127
155
|
const addresses = [];
|
|
128
156
|
|
|
129
157
|
apps.forEach(function(app) {
|
|
130
|
-
addresses.push(`unix
|
|
158
|
+
addresses.push(`unix:${shared.PROCESS_UNIX_SOCK_PATH}${port + app.pm2_env.NODE_APP_INSTANCE}.sock`);
|
|
131
159
|
});
|
|
132
160
|
|
|
133
161
|
// write NGINX config
|
|
134
|
-
fs.writeFileSync(NGINX_SERVERS_CONFIG_FILE, addresses.map(address => `server ${address};`).join("\n"), bailOnErr);
|
|
162
|
+
fs.writeFileSync(shared.NGINX_SERVERS_CONFIG_FILE, addresses.map(address => `server ${address};`).join("\n"), bailOnErr);
|
|
135
163
|
|
|
136
164
|
// "pm2 save"
|
|
137
165
|
pm2.dump(function (err, ret) {
|
|
@@ -144,30 +172,6 @@ function updateAndReloadNginx() {
|
|
|
144
172
|
});
|
|
145
173
|
}
|
|
146
174
|
|
|
147
|
-
function detectPackageManager() {
|
|
148
|
-
const lockfiles = {
|
|
149
|
-
// npm
|
|
150
|
-
"npm exec": path.resolve(pm2.cwd, 'package-lock.json'),
|
|
151
|
-
|
|
152
|
-
// yarn
|
|
153
|
-
"yarn exec": path.resolve(pm2.cwd, 'yarn.lock'),
|
|
154
|
-
|
|
155
|
-
// pnpm
|
|
156
|
-
"pnpm exec": path.resolve(pm2.cwd, 'pnpm-lock.yaml'),
|
|
157
|
-
|
|
158
|
-
// bun
|
|
159
|
-
"bunx": path.resolve(pm2.cwd, 'bun.lockb'),
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
for (const [key, value] of Object.entries(lockfiles)) {
|
|
163
|
-
if (fs.existsSync(value)) {
|
|
164
|
-
return key;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return "npm";
|
|
169
|
-
}
|
|
170
|
-
|
|
171
175
|
function bailOnErr(err) {
|
|
172
176
|
if (err) {
|
|
173
177
|
console.error(err);
|
|
@@ -176,4 +180,3 @@ function bailOnErr(err) {
|
|
|
176
180
|
process.exit(1);
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
|
-
|