@colyseus/tools 0.16.15 → 0.16.17
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 +7 -4
- package/pm2/ActionMethods.js +47 -0
- package/pm2/post-deploy-agent.js +47 -18
- package/pm2/shared.js +94 -7
- 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.17",
|
|
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,12 +49,14 @@
|
|
|
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",
|
|
55
|
+
"@colyseus/bun-websockets": "^0.16.5",
|
|
53
56
|
"@colyseus/core": "^0.16.24",
|
|
54
|
-
"@colyseus/redis-driver": "^0.16.1",
|
|
55
57
|
"@colyseus/ws-transport": "^0.16.5",
|
|
58
|
+
"@colyseus/redis-driver": "^0.16.1",
|
|
56
59
|
"@colyseus/redis-presence": "^0.16.4",
|
|
57
|
-
"@colyseus/bun-websockets": "^0.16.5",
|
|
58
60
|
"@colyseus/uwebsockets-transport": "^0.16.10"
|
|
59
61
|
},
|
|
60
62
|
"peerDependencies": {
|
|
@@ -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,8 @@ function postDeploy(cwd, reply) {
|
|
|
73
74
|
|
|
74
75
|
// first deploy, start all processes
|
|
75
76
|
if (apps.length === 0) {
|
|
76
|
-
|
|
77
|
+
console.log("First deploy detected, starting all processes... pm2.start(config) => ", config);
|
|
78
|
+
return pm2.start(config, (err, result) => {
|
|
77
79
|
reply({ success: !err, message: err?.message });
|
|
78
80
|
updateAndSave(err, result);
|
|
79
81
|
});
|
|
@@ -82,7 +84,7 @@ function postDeploy(cwd, reply) {
|
|
|
82
84
|
//
|
|
83
85
|
// detect if cwd has changed, and restart PM2 if it has
|
|
84
86
|
//
|
|
85
|
-
if (apps[0].pm2_env.pm_cwd !== cwd) {
|
|
87
|
+
if (apps[0].pm2_env.pm_cwd !== config.cwd) {
|
|
86
88
|
console.log("App Root Directory changed. Restarting may take a bit longer...");
|
|
87
89
|
|
|
88
90
|
//
|
|
@@ -92,7 +94,7 @@ function postDeploy(cwd, reply) {
|
|
|
92
94
|
logIfError(err);
|
|
93
95
|
|
|
94
96
|
// start again
|
|
95
|
-
pm2.start(config,
|
|
97
|
+
pm2.start(config, (err, result) => {
|
|
96
98
|
reply({ success: !err, message: err?.message });
|
|
97
99
|
updateAndSave(err, result);
|
|
98
100
|
});
|
|
@@ -140,6 +142,20 @@ function postDeploy(cwd, reply) {
|
|
|
140
142
|
if (err) { return console.error(err); }
|
|
141
143
|
|
|
142
144
|
let numActiveApps = initialApps.length + restartingAppIds.size;
|
|
145
|
+
const maxActiveProcesses = config.instances;
|
|
146
|
+
|
|
147
|
+
// update config of newly spawned processes (memory limit, etc)
|
|
148
|
+
shared.listApps(function(err, apps) {
|
|
149
|
+
logIfError(err);
|
|
150
|
+
|
|
151
|
+
// get new processes excluding initialApps
|
|
152
|
+
const new_app_envs = shared.filterActiveApps(apps)
|
|
153
|
+
.map((app) => app.pm2_env)
|
|
154
|
+
.filter(app_env => !initialApps.find(init_app => init_app.pm_id === app_env.pm_id));
|
|
155
|
+
|
|
156
|
+
new_app_envs.forEach((app_env) =>
|
|
157
|
+
shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
|
|
158
|
+
});
|
|
143
159
|
|
|
144
160
|
/**
|
|
145
161
|
* - Write NGINX config to expose only the new active process
|
|
@@ -157,7 +173,7 @@ function postDeploy(cwd, reply) {
|
|
|
157
173
|
// (They make take from minutes up to hours to stop)
|
|
158
174
|
//
|
|
159
175
|
appsToStop.forEach((app_env) => {
|
|
160
|
-
if (numActiveApps <
|
|
176
|
+
if (numActiveApps < maxActiveProcesses) {
|
|
161
177
|
numActiveApps++;
|
|
162
178
|
|
|
163
179
|
restartingAppIds.add(app_env.pm_id);
|
|
@@ -167,6 +183,9 @@ function postDeploy(cwd, reply) {
|
|
|
167
183
|
|
|
168
184
|
// reset counter stats (restart_time=0)
|
|
169
185
|
pm2.reset(app_env.pm_id, logIfError);
|
|
186
|
+
|
|
187
|
+
// update process config (memory limit, etc)
|
|
188
|
+
shared.updateProcessConfig(app_env.pm_id, config, logIfError);
|
|
170
189
|
});
|
|
171
190
|
|
|
172
191
|
} else {
|
|
@@ -174,8 +193,8 @@ function postDeploy(cwd, reply) {
|
|
|
174
193
|
}
|
|
175
194
|
});
|
|
176
195
|
|
|
177
|
-
if (numActiveApps <
|
|
178
|
-
const missingOnlineApps =
|
|
196
|
+
if (numActiveApps < maxActiveProcesses) {
|
|
197
|
+
const missingOnlineApps = maxActiveProcesses - numActiveApps;
|
|
179
198
|
|
|
180
199
|
// console.log("Active apps is lower than MAX_ACTIVE_PROCESSES, will SCALE again =>", {
|
|
181
200
|
// missingOnlineApps,
|
|
@@ -183,11 +202,12 @@ function postDeploy(cwd, reply) {
|
|
|
183
202
|
// newNumTotalApps: numTotalApps + missingOnlineApps
|
|
184
203
|
// });
|
|
185
204
|
|
|
205
|
+
console.log("Scale up to", numTotalApps + missingOnlineApps);
|
|
186
206
|
pm2.scale(apps[0].name, numTotalApps + missingOnlineApps, updateAndSaveIfAllRunning);
|
|
187
207
|
}
|
|
188
208
|
};
|
|
189
209
|
|
|
190
|
-
const numHalfMaxActiveProcesses = Math.ceil(
|
|
210
|
+
const numHalfMaxActiveProcesses = Math.ceil(config.instances / 2);
|
|
191
211
|
|
|
192
212
|
/**
|
|
193
213
|
* Re-use previously stopped apps if available
|
|
@@ -206,6 +226,7 @@ function postDeploy(cwd, reply) {
|
|
|
206
226
|
|
|
207
227
|
// reset counter stats (restart_time=0)
|
|
208
228
|
pm2.reset(app_env.pm_id, logIfError);
|
|
229
|
+
shared.updateProcessConfig(app_env.pm_id, config, logIfError);
|
|
209
230
|
|
|
210
231
|
// TODO: set timeout here to exit if some processes are not restarting
|
|
211
232
|
|
|
@@ -229,6 +250,8 @@ function postDeploy(cwd, reply) {
|
|
|
229
250
|
|
|
230
251
|
numTotalApps = apps.length + numHalfMaxActiveProcesses;
|
|
231
252
|
|
|
253
|
+
console.log("Starting first half of new apps... pm2.scale(app, ", numTotalApps, ")");
|
|
254
|
+
|
|
232
255
|
// Ensure to scale to a number of processes where `numHalfMaxActiveProcesses` can start immediately.
|
|
233
256
|
pm2.scale(apps[0].name, numTotalApps, onFirstAppsStart.bind(undefined, initialApps));
|
|
234
257
|
}
|
|
@@ -244,12 +267,12 @@ function updateAndSaveIfAllRunning(err) {
|
|
|
244
267
|
if (err) { return console.error(err); }
|
|
245
268
|
|
|
246
269
|
updateAndReloadNginx((app_envs) => {
|
|
247
|
-
// console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/",
|
|
270
|
+
// console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/", config.instances);
|
|
248
271
|
|
|
249
272
|
//
|
|
250
273
|
// TODO: add timeout to exit here, in case some processes are not starting
|
|
251
274
|
//
|
|
252
|
-
if (app_envs.length ===
|
|
275
|
+
if (app_envs.length === appConfig.instances) {
|
|
253
276
|
complete();
|
|
254
277
|
}
|
|
255
278
|
});
|
|
@@ -272,18 +295,24 @@ function updateAndReloadNginx(cb) {
|
|
|
272
295
|
if (apps.length === 0) { err = "no apps running."; }
|
|
273
296
|
if (err) { return console.error(err); }
|
|
274
297
|
|
|
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);
|
|
298
|
+
const app_envs = shared.filterActiveApps(apps).map((app) => app.pm2_env);
|
|
278
299
|
|
|
279
300
|
writeNginxConfig(app_envs);
|
|
280
301
|
|
|
302
|
+
// update processes config (memory limit, etc)
|
|
303
|
+
app_envs.forEach((app_env) =>
|
|
304
|
+
shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
|
|
305
|
+
|
|
281
306
|
cb?.(app_envs);
|
|
282
307
|
});
|
|
283
308
|
}
|
|
284
309
|
|
|
285
310
|
function writeNginxConfig(app_envs) {
|
|
286
311
|
// console.log("writeNginxConfig: ", app_envs.map(app_env => app_env.NODE_APP_INSTANCE));
|
|
312
|
+
if (!fs.existsSync(shared.NGINX_SERVERS_CONFIG_FILE)) {
|
|
313
|
+
console.warn(`NGINX config file not found at ${shared.NGINX_SERVERS_CONFIG_FILE}, skipping NGINX config update.`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
287
316
|
|
|
288
317
|
const port = 2567;
|
|
289
318
|
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,7 +44,15 @@ async function getAppConfig(ecosystemFilePath) {
|
|
|
29
44
|
app.namespace = NAMESPACE;
|
|
30
45
|
app.exec_mode = "fork";
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
// default: number of CPU cores
|
|
48
|
+
if (app.instances === undefined) {
|
|
49
|
+
app.instances = MAX_ACTIVE_PROCESSES;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// default: restart if memory exceeds 512M
|
|
53
|
+
if (app.max_memory_restart === undefined) {
|
|
54
|
+
app.max_memory_restart = '512M';
|
|
55
|
+
}
|
|
33
56
|
|
|
34
57
|
app.time = true;
|
|
35
58
|
app.wait_ready = true;
|
|
@@ -50,17 +73,74 @@ async function getAppConfig(ecosystemFilePath) {
|
|
|
50
73
|
if (!app.kill_retry_time) {
|
|
51
74
|
app.kill_retry_time = 5000;
|
|
52
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
|
+
});
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
return config;
|
|
56
93
|
}
|
|
57
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
|
+
|
|
58
138
|
module.exports = {
|
|
59
139
|
/**
|
|
60
140
|
* Constants
|
|
61
141
|
*/
|
|
62
|
-
NGINX_SERVERS_CONFIG_FILE: '/etc/nginx/colyseus_servers.conf',
|
|
63
|
-
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/',
|
|
64
144
|
|
|
65
145
|
MAX_ACTIVE_PROCESSES,
|
|
66
146
|
NAMESPACE,
|
|
@@ -70,4 +150,11 @@ module.exports = {
|
|
|
70
150
|
*/
|
|
71
151
|
listApps,
|
|
72
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
|
+
),
|
|
73
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();
|