@colyseus/tools 0.16.16 → 0.16.18
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/package.json +8 -5
- package/pm2/ActionMethods.js +47 -0
- package/pm2/post-deploy-agent.js +46 -18
- package/pm2/shared.js +91 -6
- package/post-deploy.js +8 -4
- package/system-boot.js +83 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/tools",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.18",
|
|
4
4
|
"description": "Simplify the development and production settings for your Colyseus project.",
|
|
5
5
|
"input": "./src/index.ts",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -49,13 +49,15 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/cors": "^2.8.10",
|
|
51
51
|
"@types/dotenv": "^8.2.0",
|
|
52
|
+
"pm2": "^6.0.14",
|
|
53
|
+
"mocha": "^11.7.5",
|
|
52
54
|
"uwebsockets-express": "^1.1.10",
|
|
53
|
-
"@colyseus/redis-presence": "^0.16.4",
|
|
54
55
|
"@colyseus/redis-driver": "^0.16.1",
|
|
56
|
+
"@colyseus/ws-transport": "^0.16.5",
|
|
55
57
|
"@colyseus/core": "^0.16.24",
|
|
56
|
-
"@colyseus/bun-websockets": "^0.16.5",
|
|
57
58
|
"@colyseus/uwebsockets-transport": "^0.16.10",
|
|
58
|
-
"@colyseus/
|
|
59
|
+
"@colyseus/bun-websockets": "^0.16.5",
|
|
60
|
+
"@colyseus/redis-presence": "^0.16.4"
|
|
59
61
|
},
|
|
60
62
|
"peerDependencies": {
|
|
61
63
|
"@colyseus/core": "0.16.x",
|
|
@@ -73,6 +75,7 @@
|
|
|
73
75
|
}
|
|
74
76
|
],
|
|
75
77
|
"scripts": {
|
|
76
|
-
"start": "tsx example/app.ts"
|
|
78
|
+
"start": "tsx example/app.ts",
|
|
79
|
+
"test": "mocha --require tsx test/pm2-deployment.test.ts"
|
|
77
80
|
}
|
|
78
81
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monkey-patch for PM2's ActionMethods.js
|
|
3
|
+
* Adds custom methods like updateProcessConfig without modifying the original file.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const Utility = require('../Utility');
|
|
8
|
+
|
|
9
|
+
// Import the original ActionMethods module
|
|
10
|
+
const originalActionMethods = require('./ActionMethods_orig.js');
|
|
11
|
+
|
|
12
|
+
module.exports = function(God) {
|
|
13
|
+
// Apply original ActionMethods
|
|
14
|
+
originalActionMethods(God);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Update process configuration without restart.
|
|
18
|
+
* Merges opts.env.current_conf into proc.pm2_env.
|
|
19
|
+
* @method updateProcessConfig
|
|
20
|
+
* @param {Object} opts - { id: pm_id, env: { current_conf: { ... } } }
|
|
21
|
+
* @param {Function} cb
|
|
22
|
+
*/
|
|
23
|
+
God.updateProcessConfig = function updateProcessConfig(opts, cb) {
|
|
24
|
+
var id = opts.id;
|
|
25
|
+
|
|
26
|
+
if (typeof(id) === 'undefined')
|
|
27
|
+
return cb(God.logAndGenerateError('opts.id not passed to updateProcessConfig', opts));
|
|
28
|
+
if (!(id in God.clusters_db))
|
|
29
|
+
return cb(God.logAndGenerateError('Process id unknown'), {});
|
|
30
|
+
|
|
31
|
+
const proc = God.clusters_db[id];
|
|
32
|
+
|
|
33
|
+
if (!proc || !proc.pm2_env)
|
|
34
|
+
return cb(God.logAndGenerateError('Process not found or missing pm2_env'), {});
|
|
35
|
+
|
|
36
|
+
// Merge env into pm2_env.env
|
|
37
|
+
if (opts.env) {
|
|
38
|
+
Utility.extend(proc.pm2_env, opts.env);
|
|
39
|
+
Utility.extend(proc.pm2_env.env, opts.env); // why both?
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Merge current_conf directly into pm2_env
|
|
43
|
+
Utility.extendExtraConfig(proc, opts);
|
|
44
|
+
|
|
45
|
+
return cb(null, Utility.clone(proc.pm2_env));
|
|
46
|
+
};
|
|
47
|
+
};
|
package/pm2/post-deploy-agent.js
CHANGED
|
@@ -14,8 +14,7 @@ const io = require('@pm2/io');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const shared = require('./shared');
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
let config = undefined;
|
|
17
|
+
let appConfig = undefined;
|
|
19
18
|
|
|
20
19
|
io.initModule({
|
|
21
20
|
pid: path.resolve('/var/run/colyseus-agent.pid'),
|
|
@@ -52,9 +51,11 @@ pm2.connect(function(err) {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
try {
|
|
55
|
-
config = await shared.getAppConfig(ecosystemFilePath);
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const config = await shared.getAppConfig(ecosystemFilePath);
|
|
55
|
+
|
|
56
|
+
appConfig = { ...config.apps[0], cwd };
|
|
57
|
+
|
|
58
|
+
postDeploy(appConfig, onReply);
|
|
58
59
|
|
|
59
60
|
} catch (err) {
|
|
60
61
|
onReply({ success: false, message: err?.message });
|
|
@@ -64,7 +65,7 @@ pm2.connect(function(err) {
|
|
|
64
65
|
|
|
65
66
|
const restartingAppIds = new Set();
|
|
66
67
|
|
|
67
|
-
function postDeploy(
|
|
68
|
+
function postDeploy(config, reply) {
|
|
68
69
|
shared.listApps(function(err, apps) {
|
|
69
70
|
if (err) {
|
|
70
71
|
console.error(err);
|
|
@@ -73,7 +74,7 @@ function postDeploy(cwd, reply) {
|
|
|
73
74
|
|
|
74
75
|
// first deploy, start all processes
|
|
75
76
|
if (apps.length === 0) {
|
|
76
|
-
return pm2.start(config,
|
|
77
|
+
return pm2.start(config, (err, result) => {
|
|
77
78
|
reply({ success: !err, message: err?.message });
|
|
78
79
|
updateAndSave(err, result);
|
|
79
80
|
});
|
|
@@ -82,7 +83,7 @@ function postDeploy(cwd, reply) {
|
|
|
82
83
|
//
|
|
83
84
|
// detect if cwd has changed, and restart PM2 if it has
|
|
84
85
|
//
|
|
85
|
-
if (apps[0].pm2_env.pm_cwd !== cwd) {
|
|
86
|
+
if (apps[0].pm2_env.pm_cwd !== config.cwd) {
|
|
86
87
|
console.log("App Root Directory changed. Restarting may take a bit longer...");
|
|
87
88
|
|
|
88
89
|
//
|
|
@@ -92,7 +93,7 @@ function postDeploy(cwd, reply) {
|
|
|
92
93
|
logIfError(err);
|
|
93
94
|
|
|
94
95
|
// start again
|
|
95
|
-
pm2.start(config,
|
|
96
|
+
pm2.start(config, (err, result) => {
|
|
96
97
|
reply({ success: !err, message: err?.message });
|
|
97
98
|
updateAndSave(err, result);
|
|
98
99
|
});
|
|
@@ -140,6 +141,20 @@ function postDeploy(cwd, reply) {
|
|
|
140
141
|
if (err) { return console.error(err); }
|
|
141
142
|
|
|
142
143
|
let numActiveApps = initialApps.length + restartingAppIds.size;
|
|
144
|
+
const maxActiveProcesses = config.instances;
|
|
145
|
+
|
|
146
|
+
// update config of newly spawned processes (memory limit, etc)
|
|
147
|
+
shared.listApps(function(err, apps) {
|
|
148
|
+
logIfError(err);
|
|
149
|
+
|
|
150
|
+
// get new processes excluding initialApps
|
|
151
|
+
const new_app_envs = shared.filterActiveApps(apps)
|
|
152
|
+
.map((app) => app.pm2_env)
|
|
153
|
+
.filter(app_env => !initialApps.find(init_app => init_app.pm_id === app_env.pm_id));
|
|
154
|
+
|
|
155
|
+
new_app_envs.forEach((app_env) =>
|
|
156
|
+
shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
|
|
157
|
+
});
|
|
143
158
|
|
|
144
159
|
/**
|
|
145
160
|
* - Write NGINX config to expose only the new active process
|
|
@@ -157,7 +172,7 @@ function postDeploy(cwd, reply) {
|
|
|
157
172
|
// (They make take from minutes up to hours to stop)
|
|
158
173
|
//
|
|
159
174
|
appsToStop.forEach((app_env) => {
|
|
160
|
-
if (numActiveApps <
|
|
175
|
+
if (numActiveApps < maxActiveProcesses) {
|
|
161
176
|
numActiveApps++;
|
|
162
177
|
|
|
163
178
|
restartingAppIds.add(app_env.pm_id);
|
|
@@ -167,6 +182,9 @@ function postDeploy(cwd, reply) {
|
|
|
167
182
|
|
|
168
183
|
// reset counter stats (restart_time=0)
|
|
169
184
|
pm2.reset(app_env.pm_id, logIfError);
|
|
185
|
+
|
|
186
|
+
// update process config (memory limit, etc)
|
|
187
|
+
shared.updateProcessConfig(app_env.pm_id, config, logIfError);
|
|
170
188
|
});
|
|
171
189
|
|
|
172
190
|
} else {
|
|
@@ -174,8 +192,8 @@ function postDeploy(cwd, reply) {
|
|
|
174
192
|
}
|
|
175
193
|
});
|
|
176
194
|
|
|
177
|
-
if (numActiveApps <
|
|
178
|
-
const missingOnlineApps =
|
|
195
|
+
if (numActiveApps < maxActiveProcesses) {
|
|
196
|
+
const missingOnlineApps = maxActiveProcesses - numActiveApps;
|
|
179
197
|
|
|
180
198
|
// console.log("Active apps is lower than MAX_ACTIVE_PROCESSES, will SCALE again =>", {
|
|
181
199
|
// missingOnlineApps,
|
|
@@ -183,11 +201,12 @@ function postDeploy(cwd, reply) {
|
|
|
183
201
|
// newNumTotalApps: numTotalApps + missingOnlineApps
|
|
184
202
|
// });
|
|
185
203
|
|
|
204
|
+
console.log("Scale up to", numTotalApps + missingOnlineApps);
|
|
186
205
|
pm2.scale(apps[0].name, numTotalApps + missingOnlineApps, updateAndSaveIfAllRunning);
|
|
187
206
|
}
|
|
188
207
|
};
|
|
189
208
|
|
|
190
|
-
const numHalfMaxActiveProcesses = Math.ceil(
|
|
209
|
+
const numHalfMaxActiveProcesses = Math.ceil(config.instances / 2);
|
|
191
210
|
|
|
192
211
|
/**
|
|
193
212
|
* Re-use previously stopped apps if available
|
|
@@ -206,6 +225,7 @@ function postDeploy(cwd, reply) {
|
|
|
206
225
|
|
|
207
226
|
// reset counter stats (restart_time=0)
|
|
208
227
|
pm2.reset(app_env.pm_id, logIfError);
|
|
228
|
+
shared.updateProcessConfig(app_env.pm_id, config, logIfError);
|
|
209
229
|
|
|
210
230
|
// TODO: set timeout here to exit if some processes are not restarting
|
|
211
231
|
|
|
@@ -229,6 +249,8 @@ function postDeploy(cwd, reply) {
|
|
|
229
249
|
|
|
230
250
|
numTotalApps = apps.length + numHalfMaxActiveProcesses;
|
|
231
251
|
|
|
252
|
+
console.log("Starting first half of new apps... pm2.scale(app, ", numTotalApps, ")");
|
|
253
|
+
|
|
232
254
|
// Ensure to scale to a number of processes where `numHalfMaxActiveProcesses` can start immediately.
|
|
233
255
|
pm2.scale(apps[0].name, numTotalApps, onFirstAppsStart.bind(undefined, initialApps));
|
|
234
256
|
}
|
|
@@ -244,12 +266,12 @@ function updateAndSaveIfAllRunning(err) {
|
|
|
244
266
|
if (err) { return console.error(err); }
|
|
245
267
|
|
|
246
268
|
updateAndReloadNginx((app_envs) => {
|
|
247
|
-
// console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/",
|
|
269
|
+
// console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/", config.instances);
|
|
248
270
|
|
|
249
271
|
//
|
|
250
272
|
// TODO: add timeout to exit here, in case some processes are not starting
|
|
251
273
|
//
|
|
252
|
-
if (app_envs.length ===
|
|
274
|
+
if (app_envs.length === appConfig.instances) {
|
|
253
275
|
complete();
|
|
254
276
|
}
|
|
255
277
|
});
|
|
@@ -272,18 +294,24 @@ function updateAndReloadNginx(cb) {
|
|
|
272
294
|
if (apps.length === 0) { err = "no apps running."; }
|
|
273
295
|
if (err) { return console.error(err); }
|
|
274
296
|
|
|
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);
|
|
297
|
+
const app_envs = shared.filterActiveApps(apps).map((app) => app.pm2_env);
|
|
278
298
|
|
|
279
299
|
writeNginxConfig(app_envs);
|
|
280
300
|
|
|
301
|
+
// update processes config (memory limit, etc)
|
|
302
|
+
app_envs.forEach((app_env) =>
|
|
303
|
+
shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
|
|
304
|
+
|
|
281
305
|
cb?.(app_envs);
|
|
282
306
|
});
|
|
283
307
|
}
|
|
284
308
|
|
|
285
309
|
function writeNginxConfig(app_envs) {
|
|
286
310
|
// console.log("writeNginxConfig: ", app_envs.map(app_env => app_env.NODE_APP_INSTANCE));
|
|
311
|
+
if (!fs.existsSync(shared.NGINX_SERVERS_CONFIG_FILE)) {
|
|
312
|
+
console.warn(`NGINX config file not found at ${shared.NGINX_SERVERS_CONFIG_FILE}, skipping NGINX config update.`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
287
315
|
|
|
288
316
|
const port = 2567;
|
|
289
317
|
const addresses = [];
|
package/pm2/shared.js
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
const pm2 = require('pm2');
|
|
2
|
+
const cst = require('pm2/constants');
|
|
2
3
|
const os = require('os');
|
|
3
4
|
|
|
4
5
|
const NAMESPACE = 'cloud';
|
|
5
|
-
const MAX_ACTIVE_PROCESSES = os.cpus().length;
|
|
6
|
+
const MAX_ACTIVE_PROCESSES = Number(process.env.MAX_ACTIVE_PROCESSES || os.cpus().length);
|
|
7
|
+
|
|
8
|
+
const CONFIG_KEYS = [
|
|
9
|
+
'max_memory_restart',
|
|
10
|
+
'kill_timeout',
|
|
11
|
+
'kill_retry_time',
|
|
12
|
+
'wait_ready',
|
|
13
|
+
'merge_logs',
|
|
14
|
+
'cron_restart',
|
|
15
|
+
'autorestart',
|
|
16
|
+
'exp_backoff_restart_delay',
|
|
17
|
+
'restart_delay',
|
|
18
|
+
];
|
|
6
19
|
|
|
7
20
|
function listApps(callback) {
|
|
8
21
|
pm2.list((err, apps) => {
|
|
9
|
-
if (err) { return callback(err)
|
|
22
|
+
if (err) { return callback(err); }
|
|
10
23
|
|
|
11
24
|
// Filter out @colyseus/tools module (PM2 post-deploy agent)
|
|
12
25
|
apps = apps.filter(app => app.name !== '@colyseus/tools');
|
|
@@ -16,8 +29,10 @@ function listApps(callback) {
|
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
async function getAppConfig(ecosystemFilePath) {
|
|
19
|
-
|
|
20
|
-
const
|
|
32
|
+
// Clear require cache to force reload of the config file
|
|
33
|
+
const resolvedPath = require.resolve(ecosystemFilePath);
|
|
34
|
+
delete require.cache[resolvedPath];
|
|
35
|
+
const config = require(ecosystemFilePath);
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
38
|
* Tune PM2 app config
|
|
@@ -29,10 +44,16 @@ async function getAppConfig(ecosystemFilePath) {
|
|
|
29
44
|
app.namespace = NAMESPACE;
|
|
30
45
|
app.exec_mode = "fork";
|
|
31
46
|
|
|
47
|
+
// default: number of CPU cores
|
|
32
48
|
if (app.instances === undefined) {
|
|
33
49
|
app.instances = MAX_ACTIVE_PROCESSES;
|
|
34
50
|
}
|
|
35
51
|
|
|
52
|
+
// default: restart if memory exceeds 512M
|
|
53
|
+
if (app.max_memory_restart === undefined) {
|
|
54
|
+
app.max_memory_restart = '512M';
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
app.time = true;
|
|
37
58
|
app.wait_ready = true;
|
|
38
59
|
app.watch = false;
|
|
@@ -52,17 +73,74 @@ async function getAppConfig(ecosystemFilePath) {
|
|
|
52
73
|
if (!app.kill_retry_time) {
|
|
53
74
|
app.kill_retry_time = 5000;
|
|
54
75
|
}
|
|
76
|
+
|
|
77
|
+
// Ensure these config values are also set in app.env so they take priority
|
|
78
|
+
// over inherited environment variables during PM2's Utility.extend() merge.
|
|
79
|
+
// This prevents the parent process's environment (e.g., @colyseus/tools agent)
|
|
80
|
+
// from overwriting the app's config values like max_memory_restart.
|
|
81
|
+
if (!app.env) {
|
|
82
|
+
app.env = {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
CONFIG_KEYS.forEach((key) => {
|
|
86
|
+
if (app[key] !== undefined) {
|
|
87
|
+
app.env[key] = convertValue(app[key]);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
return config;
|
|
58
93
|
}
|
|
59
94
|
|
|
95
|
+
function convertValue(value) {
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
const conversionUnit = {
|
|
98
|
+
// bytes
|
|
99
|
+
'G': 1024 * 1024 * 1024,
|
|
100
|
+
'M': 1024 * 1024,
|
|
101
|
+
'K': 1024,
|
|
102
|
+
// milliseconds
|
|
103
|
+
'h': 60 * 60 * 1000,
|
|
104
|
+
'm': 60 * 1000,
|
|
105
|
+
's': 1000
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (!conversionUnit[value.slice(-1)]) {
|
|
109
|
+
return parseInt(value, 10);
|
|
110
|
+
|
|
111
|
+
} else {
|
|
112
|
+
return parseFloat(value.slice(0, -1)) * (conversionUnit[value.slice(-1)]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update process configuration without restart.
|
|
120
|
+
* This patches pm2_env directly via a custom God method.
|
|
121
|
+
* @param {number|string} pm_id - Process ID to update
|
|
122
|
+
* @param {Object} config - Configuration object (e.g., { max_memory_restart: '512M', kill_timeout: 30000 })
|
|
123
|
+
* @param {Function} cb - Callback(err, updatedEnv)
|
|
124
|
+
*/
|
|
125
|
+
function updateProcessConfig(pm_id, config, cb) {
|
|
126
|
+
const env = {};
|
|
127
|
+
|
|
128
|
+
CONFIG_KEYS.forEach((key) => {
|
|
129
|
+
if (config[key] !== undefined) {
|
|
130
|
+
env[key] = convertValue(config[key]);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const opts = { id: pm_id, env, };
|
|
135
|
+
pm2.Client.executeRemote('updateProcessConfig', opts, cb);
|
|
136
|
+
}
|
|
137
|
+
|
|
60
138
|
module.exports = {
|
|
61
139
|
/**
|
|
62
140
|
* Constants
|
|
63
141
|
*/
|
|
64
|
-
NGINX_SERVERS_CONFIG_FILE: '/etc/nginx/colyseus_servers.conf',
|
|
65
|
-
PROCESS_UNIX_SOCK_PATH: '/run/colyseus/',
|
|
142
|
+
NGINX_SERVERS_CONFIG_FILE: process.env.NGINX_CONFIG_FILE || '/etc/nginx/colyseus_servers.conf',
|
|
143
|
+
PROCESS_UNIX_SOCK_PATH: process.env.UNIX_SOCK_PATH || '/run/colyseus/',
|
|
66
144
|
|
|
67
145
|
MAX_ACTIVE_PROCESSES,
|
|
68
146
|
NAMESPACE,
|
|
@@ -72,4 +150,11 @@ module.exports = {
|
|
|
72
150
|
*/
|
|
73
151
|
listApps,
|
|
74
152
|
getAppConfig,
|
|
153
|
+
|
|
154
|
+
updateProcessConfig,
|
|
155
|
+
|
|
156
|
+
filterActiveApps: (apps) => apps.filter(app =>
|
|
157
|
+
app.pm2_env.status !== cst.STOPPING_STATUS &&
|
|
158
|
+
app.pm2_env.status !== cst.STOPPED_STATUS
|
|
159
|
+
),
|
|
75
160
|
}
|
package/post-deploy.js
CHANGED
|
@@ -90,7 +90,11 @@ function restartAll () {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function reloadAll(retry = 0) {
|
|
93
|
+
async function reloadAll(retry = 0) {
|
|
94
|
+
// Read user's ecosystem config to get the desired number of instances
|
|
95
|
+
const config = await shared.getAppConfig(CONFIG_FILE_PATH);
|
|
96
|
+
const desiredInstances = config.apps?.[0]?.instances || shared.MAX_ACTIVE_PROCESSES;
|
|
97
|
+
|
|
94
98
|
pm2.reload(CONFIG_FILE_PATH, { ...opts }, function (err, apps) {
|
|
95
99
|
if (err) {
|
|
96
100
|
//
|
|
@@ -110,9 +114,9 @@ function reloadAll(retry = 0) {
|
|
|
110
114
|
const name = apps[0].name;
|
|
111
115
|
const reloadedAppIds = apps.map(app => app.pm_id);
|
|
112
116
|
|
|
113
|
-
// scale app to
|
|
114
|
-
if (apps.length !==
|
|
115
|
-
pm2.scale(name,
|
|
117
|
+
// scale app to the number of instances specified in user's config
|
|
118
|
+
if (apps.length !== desiredInstances) {
|
|
119
|
+
pm2.scale(name, desiredInstances, () => onAppRunning(reloadedAppIds));
|
|
116
120
|
|
|
117
121
|
} else {
|
|
118
122
|
onAppRunning(reloadedAppIds);
|
package/system-boot.js
CHANGED
|
@@ -2,8 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
5
6
|
const { execSync } = require('child_process');
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Apply PM2 monkey-patch for custom functionality (e.g., updateProcessConfig)
|
|
10
|
+
* This patches the globally installed PM2 used by the system.
|
|
11
|
+
*
|
|
12
|
+
* Strategy: Rename ActionMethods.js -> ActionMethods_orig.js and replace with
|
|
13
|
+
* our wrapper that imports the original and adds custom methods.
|
|
14
|
+
*/
|
|
15
|
+
function applyPM2Patches() {
|
|
16
|
+
try {
|
|
17
|
+
// Find global PM2 installation
|
|
18
|
+
const globalPM2Path = execSync('npm root -g', { encoding: 'utf8' }).trim() + '/pm2';
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(globalPM2Path)) {
|
|
21
|
+
console.log('[PM2 Patch] Global PM2 not found, skipping...');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const godPath = path.join(globalPM2Path, 'lib/God');
|
|
26
|
+
const actionMethodsPath = path.join(godPath, 'ActionMethods.js');
|
|
27
|
+
const actionMethodsOrigPath = path.join(godPath, 'ActionMethods_orig.js');
|
|
28
|
+
const ourActionMethodsPath = path.join(__dirname, 'pm2', 'ActionMethods.js');
|
|
29
|
+
|
|
30
|
+
// Check if our patch file exists
|
|
31
|
+
if (!fs.existsSync(ourActionMethodsPath)) {
|
|
32
|
+
console.log('[PM2 Patch] Custom ActionMethods.js not found, skipping...');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if already patched (ActionMethods_orig.js exists)
|
|
37
|
+
if (!fs.existsSync(actionMethodsOrigPath)) {
|
|
38
|
+
// Rename original ActionMethods.js -> ActionMethods_orig.js
|
|
39
|
+
console.log('[PM2 Patch] Renaming original ActionMethods.js -> ActionMethods_orig.js');
|
|
40
|
+
fs.renameSync(actionMethodsPath, actionMethodsOrigPath);
|
|
41
|
+
|
|
42
|
+
} else {
|
|
43
|
+
console.log('[PM2 Patch] Already patched, skipping...');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Copy our ActionMethods.js wrapper
|
|
47
|
+
console.log(`[PM2 Patch] Installing custom ActionMethods.js wrapper (${actionMethodsPath})`);
|
|
48
|
+
fs.copyFileSync(ourActionMethodsPath, actionMethodsPath);
|
|
49
|
+
|
|
50
|
+
// Patch Daemon.js to expose "updateProcessConfig" method
|
|
51
|
+
const daemonPath = path.join(globalPM2Path, 'lib/Daemon.js');
|
|
52
|
+
console.log(`[PM2 Patch] Patching Daemon.js to expose updateProcessConfig (${daemonPath})`);
|
|
53
|
+
if (fs.existsSync(daemonPath)) {
|
|
54
|
+
let daemonContent = fs.readFileSync(daemonPath, 'utf8');
|
|
55
|
+
|
|
56
|
+
// Check if updateProcessConfig is already exposed
|
|
57
|
+
if (!daemonContent.includes('updateProcessConfig')) {
|
|
58
|
+
// Find server.expose({ and add updateProcessConfig after it
|
|
59
|
+
const exposePattern = 'server.expose({';
|
|
60
|
+
const exposeIndex = daemonContent.indexOf(exposePattern);
|
|
61
|
+
|
|
62
|
+
if (exposeIndex !== -1) {
|
|
63
|
+
const insertPos = exposeIndex + exposePattern.length;
|
|
64
|
+
daemonContent = daemonContent.slice(0, insertPos) +
|
|
65
|
+
'\n updateProcessConfig : God.updateProcessConfig,' +
|
|
66
|
+
daemonContent.slice(insertPos);
|
|
67
|
+
fs.writeFileSync(daemonPath, daemonContent);
|
|
68
|
+
console.log(`[PM2 Patch] Daemon.js patched successfully.`);
|
|
69
|
+
} else {
|
|
70
|
+
console.warn('[PM2 Patch] Could not find server.expose pattern in Daemon.js');
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.log('[PM2 Patch] Daemon.js already has updateProcessConfig exposed.');
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
console.warn('[PM2 Patch] Daemon.js not found at', daemonPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('[PM2 Patch] PM2 patches applied successfully.');
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn('[PM2 Patch] Failed to apply PM2 patches:', error.message);
|
|
83
|
+
// Don't exit - patches are optional enhancements
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
7
87
|
const NGINX_LIMITS_CONFIG_FILE = '/etc/nginx/colyseus_limits.conf';
|
|
8
88
|
const LIMITS_CONF_FILE = '/etc/security/limits.d/colyseus.conf';
|
|
9
89
|
const SYSCTL_FILE = '/etc/sysctl.d/local.conf';
|
|
@@ -117,4 +197,7 @@ net.ipv4.tcp_keepalive_probes = 3
|
|
|
117
197
|
}
|
|
118
198
|
}
|
|
119
199
|
|
|
200
|
+
// Apply PM2 patches before configuring system limits
|
|
201
|
+
applyPM2Patches();
|
|
202
|
+
|
|
120
203
|
configureSystemLimits();
|