@colyseus/tools 0.15.41 → 0.15.43
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/package.json +5 -4
- package/post-deploy.js +64 -26
- package/report-stats.js +164 -0
package/README.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
<a href="https://npmjs.com/package/colyseus">
|
|
8
8
|
<img src="https://img.shields.io/npm/dm/colyseus.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETESWYxR33AAAAtElEQVQoz4WQMQrCQBRE38Z0QoTcwF4Qg1h4BO0sxGOk80iCtViksrIQRRBTewWxMI1mbELYjYu+4rPMDPtn12ChMT3gavb4US5Jym0tcBIta3oDHv4Gwmr7nC4QAxBrCdzM2q6XqUnm9m9r59h7Rc0n2pFv24k4ttGMUXW+sGELTJjSr7QDKuqLS6UKFChVWWuFkZw9Z2AAvAirKT+JTlppIRnd6XgaP4goefI2Shj++OnjB3tBmHYK8z9zAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTAyLTAxVDE4OjE3OjM3KzAxOjAwGQQixQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wMi0wMVQxODoxNzozNyswMTowMGhZmnkAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC">
|
|
9
9
|
</a>
|
|
10
|
-
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
|
|
11
|
-
<img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
12
|
-
</a>
|
|
13
10
|
<a href="http://chat.colyseus.io">
|
|
14
11
|
<img src="https://img.shields.io/discord/525739117951320081.svg?style=for-the-badge&colorB=7581dc&logo=discord&logoColor=white">
|
|
15
12
|
</a>
|
|
13
|
+
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
|
|
14
|
+
<img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
15
|
+
</a>
|
|
16
16
|
<h3>
|
|
17
17
|
Multiplayer Framework for Node.js. <br /><a href="https://docs.colyseus.io/">View documentation</a>
|
|
18
18
|
</h3>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
Colyseus is an Authoritative Multiplayer Framework for Node.js, with
|
|
22
|
-
available for the Web,
|
|
21
|
+
Colyseus is an Authoritative Multiplayer Framework for Node.js, with SDKs
|
|
22
|
+
available for the Web, Unity, Defold, Haxe, Cocos and Construct3. ([See official SDKs](https://docs.colyseus.io/client/))
|
|
23
23
|
|
|
24
24
|
The project focuses on providing synchronizable data structures for realtime and
|
|
25
25
|
turn-based games, matchmaking, and ease of usage both on the server-side and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/tools",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.43",
|
|
4
4
|
"description": "Colyseus Tools for Production",
|
|
5
5
|
"input": "./src/index.ts",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"typings": "./build/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
10
|
"colyseus-system-boot": "system-boot.js",
|
|
11
|
-
"colyseus-post-deploy": "post-deploy.js"
|
|
11
|
+
"colyseus-post-deploy": "post-deploy.js",
|
|
12
|
+
"colyseus-report-stats": "report-stats.js"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
@@ -35,14 +36,14 @@
|
|
|
35
36
|
"@types/cors": "^2.8.10",
|
|
36
37
|
"@types/dotenv": "^8.2.0",
|
|
37
38
|
"uwebsockets-express": "^1.1.10",
|
|
38
|
-
"@colyseus/uwebsockets-transport": "^0.15.
|
|
39
|
+
"@colyseus/uwebsockets-transport": "^0.15.9"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"node-os-utils": "^1.3.7",
|
|
42
43
|
"cors": "^2.8.5",
|
|
43
44
|
"dotenv": "^8.2.0",
|
|
44
45
|
"express": "^4.16.2",
|
|
45
|
-
"@colyseus/core": "^0.15.
|
|
46
|
+
"@colyseus/core": "^0.15.52",
|
|
46
47
|
"@colyseus/ws-transport": "^0.15.2"
|
|
47
48
|
},
|
|
48
49
|
"publishConfig": {
|
package/post-deploy.js
CHANGED
|
@@ -27,9 +27,14 @@ if (!CONFIG_FILE) {
|
|
|
27
27
|
pm2.list(function(err, apps) {
|
|
28
28
|
bailOnErr(err);
|
|
29
29
|
|
|
30
|
+
// TODO: flush previous logs (?)
|
|
31
|
+
// pm2.flush();
|
|
32
|
+
|
|
30
33
|
if (apps.length === 0) {
|
|
34
|
+
//
|
|
31
35
|
// first deploy
|
|
32
|
-
|
|
36
|
+
//
|
|
37
|
+
pm2.start(CONFIG_FILE, { ...opts }, () => onAppRunning());
|
|
33
38
|
|
|
34
39
|
} else {
|
|
35
40
|
|
|
@@ -37,51 +42,84 @@ pm2.list(function(err, apps) {
|
|
|
37
42
|
// detect if cwd has changed, and restart PM2 if it has
|
|
38
43
|
//
|
|
39
44
|
if (apps[0].pm2_env.pm_cwd !== pm2.cwd) {
|
|
40
|
-
|
|
41
45
|
//
|
|
42
46
|
// remove all and start again with new cwd
|
|
43
47
|
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// kill & start again
|
|
47
|
-
pm2.kill(function() {
|
|
48
|
-
pm2.start(CONFIG_FILE, { ...opts }, onAppRunning);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
});
|
|
48
|
+
restartAll();
|
|
52
49
|
|
|
53
50
|
} else {
|
|
54
51
|
//
|
|
55
52
|
// reload existing apps
|
|
56
53
|
//
|
|
57
|
-
|
|
58
|
-
bailOnErr(err);
|
|
59
|
-
|
|
60
|
-
const name = apps[0].name;
|
|
61
|
-
|
|
62
|
-
// scale app to use all CPUs available
|
|
63
|
-
if (apps.length !== maxCPU) {
|
|
64
|
-
pm2.scale(name, maxCPU, onAppRunning);
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
onAppRunning();
|
|
68
|
-
}
|
|
69
|
-
});
|
|
54
|
+
reloadAll();
|
|
70
55
|
}
|
|
71
|
-
|
|
72
56
|
}
|
|
73
57
|
});
|
|
74
58
|
|
|
75
|
-
function onAppRunning() {
|
|
59
|
+
function onAppRunning(reloadedAppIds) {
|
|
60
|
+
// reset reloaded app stats
|
|
61
|
+
if (reloadedAppIds) {
|
|
62
|
+
resetAppStats(reloadedAppIds);
|
|
63
|
+
}
|
|
76
64
|
updateColyseusBootService();
|
|
77
65
|
updateAndReloadNginx();
|
|
78
66
|
}
|
|
79
67
|
|
|
68
|
+
function restartAll () {
|
|
69
|
+
pm2.delete('all', function (err) {
|
|
70
|
+
// kill & start again
|
|
71
|
+
pm2.kill(function () {
|
|
72
|
+
pm2.start(CONFIG_FILE, { ...opts }, () => onAppRunning());
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reloadAll(retry = 0) {
|
|
78
|
+
pm2.reload(CONFIG_FILE, { ...opts }, function (err, apps) {
|
|
79
|
+
if (err) {
|
|
80
|
+
//
|
|
81
|
+
// Retry in case of "Reload in progress" error.
|
|
82
|
+
//
|
|
83
|
+
if (err.message === 'Reload in progress' && retry < 5) {
|
|
84
|
+
console.warn(err.message, ", retrying...");
|
|
85
|
+
setTimeout(() => reloadAll(retry + 1), 1000);
|
|
86
|
+
|
|
87
|
+
} else {
|
|
88
|
+
bailOnErr(err);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const name = apps[0].name;
|
|
95
|
+
const reloadedAppIds = apps.map(app => app.pm_id);
|
|
96
|
+
|
|
97
|
+
// scale app to use all CPUs available
|
|
98
|
+
if (apps.length !== maxCPU) {
|
|
99
|
+
pm2.scale(name, maxCPU, () => onAppRunning(reloadedAppIds));
|
|
100
|
+
|
|
101
|
+
} else {
|
|
102
|
+
onAppRunning(reloadedAppIds);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resetAppStats (reloadedAppIds) {
|
|
108
|
+
reloadedAppIds.forEach((pm_id) => {
|
|
109
|
+
pm2.reset(pm_id, (err, _) => {
|
|
110
|
+
if (err) {
|
|
111
|
+
console.error(err);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(`metrics re-set for app_id: ${pm_id}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
80
119
|
function updateColyseusBootService() {
|
|
81
120
|
//
|
|
82
121
|
// Update colyseus-boot.service to use the correct paths for the application
|
|
83
122
|
//
|
|
84
|
-
|
|
85
123
|
const COLYSEUS_CLOUD_BOOT_SERVICE = '/etc/systemd/system/colyseus-boot.service';
|
|
86
124
|
|
|
87
125
|
// ignore if no boot service found
|
package/report-stats.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const net = require('net');
|
|
5
|
+
const pm2 = require('pm2');
|
|
6
|
+
|
|
7
|
+
const COLYSEUS_CLOUD_URL = `${process.env.ENDPOINT}/vultr/stats`;
|
|
8
|
+
|
|
9
|
+
const FAILED_ATTEMPS_FILE = "/var/tmp/pm2-stats-attempts.txt";
|
|
10
|
+
const FETCH_TIMEOUT = 7000;
|
|
11
|
+
|
|
12
|
+
async function retryFailedAttempts() {
|
|
13
|
+
/**
|
|
14
|
+
* Retry cached failed attempts
|
|
15
|
+
*/
|
|
16
|
+
if (!fs.existsSync(FAILED_ATTEMPS_FILE)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Retry up to last 30 attempts
|
|
21
|
+
const failedAttempts = fs.readFileSync(FAILED_ATTEMPS_FILE, "utf8").split("\n").slice(-30);
|
|
22
|
+
|
|
23
|
+
for (const body of failedAttempts) {
|
|
24
|
+
// skip if empty
|
|
25
|
+
if (!body) { continue; }
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await fetch(COLYSEUS_CLOUD_URL, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"Authorization": `Bearer ${process.env.COLYSEUS_SECRET}`,
|
|
33
|
+
"ngrok-skip-browser-warning": "yes",
|
|
34
|
+
},
|
|
35
|
+
body,
|
|
36
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT)
|
|
37
|
+
});
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.unlinkSync(FAILED_ATTEMPS_FILE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchRetry(url, options, retries = 3) {
|
|
47
|
+
try {
|
|
48
|
+
return await fetch(url, options)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (retries === 1) throw err;
|
|
51
|
+
return await fetchRetry(url, options, retries - 1);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
pm2.Client.executeRemote('getMonitorData', {}, async function(err, list) {
|
|
56
|
+
const aggregate = { ccu: 0, roomcount: 0, };
|
|
57
|
+
const apps = {};
|
|
58
|
+
|
|
59
|
+
await Promise.all(list.map(async (item) => {
|
|
60
|
+
const env = item.pm2_env;
|
|
61
|
+
const app_id = env.pm_id;
|
|
62
|
+
const uptime = new Date(env.pm_uptime); // uptime in milliseconds
|
|
63
|
+
const axm_monitor = env.axm_monitor;
|
|
64
|
+
const restart_time = env.restart_time;
|
|
65
|
+
const status = env.status; // 'online', 'stopped', 'stopping', 'waiting restart', 'launching', 'errored'
|
|
66
|
+
const node_version = env.node_version;
|
|
67
|
+
|
|
68
|
+
const monit = {
|
|
69
|
+
cpu: item.monit.cpu, // 0 ~ 100 (%)
|
|
70
|
+
memory: item.monit.memory / 1024 / 1024, // in MB
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const version = {};
|
|
74
|
+
if (env.versioning) {
|
|
75
|
+
version.revision = env.versioning.revision;
|
|
76
|
+
version.comment = env.versioning.comment;
|
|
77
|
+
version.branch = env.versioning.branch;
|
|
78
|
+
version.update_time = env.versioning.update_time;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
aggregate.ccu += axm_monitor.ccu?.value ?? 0;
|
|
82
|
+
aggregate.roomcount += axm_monitor.roomcount?.value ?? 0;
|
|
83
|
+
|
|
84
|
+
const custom_monitor = {};
|
|
85
|
+
for (const originalKey in axm_monitor) {
|
|
86
|
+
const key = (originalKey.indexOf(" ") !== -1)
|
|
87
|
+
? axm_monitor[originalKey].type
|
|
88
|
+
: originalKey;
|
|
89
|
+
custom_monitor[key] = Number(axm_monitor[originalKey].value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// check if process .sock file is active
|
|
93
|
+
const socket_is_active = await checkSocketIsActive(`/run/colyseus/${(2567 + env.NODE_APP_INSTANCE)}.sock`);
|
|
94
|
+
|
|
95
|
+
apps[app_id] = {
|
|
96
|
+
pid: item.pid,
|
|
97
|
+
uptime,
|
|
98
|
+
app_id,
|
|
99
|
+
status,
|
|
100
|
+
restart_time,
|
|
101
|
+
...monit,
|
|
102
|
+
...custom_monitor,
|
|
103
|
+
version,
|
|
104
|
+
node_version,
|
|
105
|
+
socket_is_active,
|
|
106
|
+
};
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const fetchipv4 = await fetch("http://169.254.169.254/v1.json");
|
|
110
|
+
const ip = (await fetchipv4.json()).interfaces[0].ipv4.address;
|
|
111
|
+
|
|
112
|
+
const body = {
|
|
113
|
+
version: 1,
|
|
114
|
+
ip,
|
|
115
|
+
time: new Date(),
|
|
116
|
+
aggregate,
|
|
117
|
+
apps,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
console.log(body);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetchRetry(COLYSEUS_CLOUD_URL, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"Authorization": `Bearer ${process.env.COLYSEUS_SECRET}`,
|
|
128
|
+
"ngrok-skip-browser-warning": "yes",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(body),
|
|
131
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT)
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (response.status !== 200) {
|
|
135
|
+
throw new Error(`Failed to send stats to Colyseus Cloud. Status: ${response.status}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log("OK");
|
|
139
|
+
|
|
140
|
+
// Only retry failed attempts if the current attempt was successful
|
|
141
|
+
await retryFailedAttempts();
|
|
142
|
+
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.error("Failed to send stats to Colyseus Cloud. ");
|
|
145
|
+
|
|
146
|
+
// cache failed attempts
|
|
147
|
+
fs.appendFileSync(FAILED_ATTEMPS_FILE, JSON.stringify(body) + "\n");
|
|
148
|
+
|
|
149
|
+
} finally {
|
|
150
|
+
process.exit();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
function checkSocketIsActive(sockFilePath) {
|
|
155
|
+
return new Promise((resolve, _) => {
|
|
156
|
+
const client = net.createConnection({ path: sockFilePath, timeout: 5000 })
|
|
157
|
+
.on('connect', () => {
|
|
158
|
+
client.end(); // close the connection
|
|
159
|
+
resolve(true);
|
|
160
|
+
})
|
|
161
|
+
.on('error', () => resolve(false))
|
|
162
|
+
.on('timeout', () => resolve(false));
|
|
163
|
+
});
|
|
164
|
+
}
|