cyberia 3.2.9 → 3.2.12
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/.github/workflows/engine-cyberia.cd.yml +6 -0
- package/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/extensions.json +9 -9
- package/.vscode/settings.json +20 -4
- package/CHANGELOG.md +213 -1
- package/CLI-HELP.md +92 -23
- package/README.md +190 -348
- package/bin/build.js +24 -8
- package/bin/build.template.js +187 -0
- package/bin/cyberia.js +229 -52
- package/bin/deploy.js +12 -2
- package/bin/index.js +229 -52
- package/bump.config.js +26 -0
- package/conf.js +130 -24
- package/deployment.yaml +4 -2
- package/hardhat/package-lock.json +113 -144
- package/hardhat/package.json +4 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/lxd/lxd-admin-profile.yaml +12 -3
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +3 -1
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/valkey/statefulset.yaml +1 -1
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/package.json +27 -15
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +81 -46
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/crypto/crypto.router.js +18 -12
- package/src/api/crypto/crypto.service.js +3 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +22 -18
- package/src/api/cyberia-action/cyberia-action.service.js +5 -5
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
- package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
- package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
- package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
- package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
- package/src/api/cyberia-map/cyberia-map.router.js +35 -30
- package/src/api/cyberia-map/cyberia-map.service.js +7 -7
- package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +451 -0
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/instance/instance.router.js +29 -24
- package/src/api/instance/instance.service.js +6 -6
- package/src/api/ipfs/ipfs.router.js +21 -16
- package/src/api/ipfs/ipfs.service.js +8 -8
- package/src/api/object-layer/object-layer.router.js +512 -507
- package/src/api/object-layer/object-layer.service.js +17 -14
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +132 -101
- package/src/cli/cluster.js +700 -232
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +216 -137
- package/src/cli/fs.js +13 -3
- package/src/cli/index.js +80 -15
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +1099 -223
- package/src/cli/monitor.js +9 -3
- package/src/cli/release.js +334 -140
- package/src/cli/repository.js +68 -23
- package/src/cli/run.js +193 -49
- package/src/cli/secrets.js +11 -2
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +5 -0
- package/src/client/components/core/ClientEvents.js +76 -0
- package/src/client/components/core/EventBus.js +4 -0
- package/src/client/components/core/Modal.js +82 -41
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +50 -410
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +203 -185
- package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
- package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +115 -15
- package/src/db/mariadb/MariaDB.js +2 -1
- package/src/db/mongo/MongoBootstrap.js +657 -0
- package/src/db/mongo/MongooseDB.js +129 -21
- package/src/grpc/cyberia/grpc-server.js +25 -57
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +24 -3
- package/src/runtime/cyberia-client/Dockerfile.dev +82 -0
- package/src/runtime/cyberia-server/Dockerfile +29 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +71 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +86 -83
- package/src/server/process.js +180 -19
- package/src/server/proxy.js +9 -2
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +17 -5
- package/src/server/valkey.js +2 -0
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/bin/file.js +0 -202
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/src/server/process.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module for process and shell command management.
|
|
3
|
-
* Provides utilities for executing shell commands, managing signals, and
|
|
3
|
+
* Provides utilities for executing shell commands, managing signals, and
|
|
4
|
+
* handling environment details.
|
|
5
|
+
*
|
|
6
|
+
* Execution semantics:
|
|
7
|
+
* - `shellExec(cmd)` throws `ShellExecError` on non-zero exit (fail-fast
|
|
8
|
+
* is the default). CI/CD chains observe the failure end-to-end.
|
|
9
|
+
* - `shellExec(cmd, { silentOnError: true })` opts out — returns the
|
|
10
|
+
* `ShellString` result with `.code/.stdout/.stderr` so callers can
|
|
11
|
+
* branch on the exit code themselves. Use for existence checks
|
|
12
|
+
* (`test -x …`, `command -v …`, `kubectl get` when "missing" is a
|
|
13
|
+
* normal answer).
|
|
14
|
+
* - `shellExec(cmd, { cwd: "..." })` runs hermetically in `cwd` without
|
|
15
|
+
* touching shelljs's global state.
|
|
16
|
+
* - All children spawned by `shellExec` register in
|
|
17
|
+
* `ProcessController.children` so SIGINT/SIGTERM forwarding can reach
|
|
18
|
+
* them before the parent exits.
|
|
19
|
+
*
|
|
4
20
|
* @module src/server/process.js
|
|
5
21
|
* @namespace Process
|
|
6
22
|
*/
|
|
@@ -19,6 +35,12 @@ const logger = loggerFactory(import.meta);
|
|
|
19
35
|
const getRootDirectory = () => process.cwd().replace(/\\/g, '/');
|
|
20
36
|
/**
|
|
21
37
|
* Controls and manages process-level events and signals.
|
|
38
|
+
*
|
|
39
|
+
* Subprocess registry: any child process tracked here will receive
|
|
40
|
+
* SIGTERM (followed by SIGKILL after a short grace period) when the
|
|
41
|
+
* parent receives SIGINT or SIGTERM. This prevents orphaned children
|
|
42
|
+
* during Ctrl+C in dev and during pod-termination in K8S.
|
|
43
|
+
*
|
|
22
44
|
* @namespace ProcessController
|
|
23
45
|
*/
|
|
24
46
|
class ProcessController {
|
|
@@ -41,9 +63,41 @@ class ProcessController {
|
|
|
41
63
|
'SIGSEGV',
|
|
42
64
|
'SIGILL',
|
|
43
65
|
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Registry of currently running tracked child processes.
|
|
69
|
+
* Populated when callers spawn via the streaming Node-native path
|
|
70
|
+
* (future expansion). The sets are exposed so signal handlers and
|
|
71
|
+
* test harnesses can introspect / clean up the registry.
|
|
72
|
+
*/
|
|
73
|
+
static children = new Set();
|
|
74
|
+
|
|
75
|
+
/** Internal: forward terminating signals to all tracked children. */
|
|
76
|
+
static _forwardToChildren(sig) {
|
|
77
|
+
if (ProcessController.children.size === 0) return;
|
|
78
|
+
for (const child of [...ProcessController.children]) {
|
|
79
|
+
try {
|
|
80
|
+
if (!child.killed) child.kill(sig);
|
|
81
|
+
} catch (_) {
|
|
82
|
+
// child may already have exited; ignore.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Hard SIGKILL after 5s grace if any child is still alive.
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
for (const child of [...ProcessController.children]) {
|
|
88
|
+
try {
|
|
89
|
+
if (!child.killed) child.kill('SIGKILL');
|
|
90
|
+
} catch (_) {
|
|
91
|
+
/* noop */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}, 5000).unref();
|
|
95
|
+
}
|
|
96
|
+
|
|
44
97
|
/**
|
|
45
98
|
* Sets up listeners for various process signals defined in {@link ProcessController.SIG}.
|
|
46
|
-
* Handles graceful exit on 'SIGINT' (Ctrl+C)
|
|
99
|
+
* Handles graceful exit on 'SIGINT' (Ctrl+C) — but first forwards the
|
|
100
|
+
* signal to every tracked child so they get a chance to clean up.
|
|
47
101
|
* @memberof ProcessController
|
|
48
102
|
* @returns {Array<process.Process>} An array of process listener handles.
|
|
49
103
|
*/
|
|
@@ -53,7 +107,14 @@ class ProcessController {
|
|
|
53
107
|
ProcessController.logger.info(`process on ${sig}`, args);
|
|
54
108
|
switch (sig) {
|
|
55
109
|
case 'SIGINT':
|
|
56
|
-
|
|
110
|
+
case 'SIGTERM':
|
|
111
|
+
case 'SIGHUP':
|
|
112
|
+
ProcessController._forwardToChildren('SIGTERM');
|
|
113
|
+
// Give children a moment to exit cleanly before our own exit.
|
|
114
|
+
if (sig === 'SIGINT') {
|
|
115
|
+
setTimeout(() => process.exit(130), 200).unref();
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
57
118
|
default:
|
|
58
119
|
break;
|
|
59
120
|
}
|
|
@@ -71,33 +132,111 @@ class ProcessController {
|
|
|
71
132
|
ProcessController.logger = logger;
|
|
72
133
|
process.on('exit', (...args) => {
|
|
73
134
|
ProcessController.logger.info(`process on exit`, args);
|
|
135
|
+
// Last-chance reap: any tracked child still alive at exit time
|
|
136
|
+
// gets a hard kill so the parent does not leak orphans into the
|
|
137
|
+
// pod / shell session.
|
|
138
|
+
ProcessController._forwardToChildren('SIGKILL');
|
|
74
139
|
});
|
|
75
140
|
ProcessController.onSigListen();
|
|
76
|
-
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `ShellExecError` — thrown by `shellExec` when the underlying command
|
|
145
|
+
* exits with a non-zero code (the default fail-fast behaviour). Carries
|
|
146
|
+
* the exit code, stdout, and stderr for inspection by callers / CI
|
|
147
|
+
* pipelines that need structured failure data.
|
|
148
|
+
*/
|
|
149
|
+
class ShellExecError extends Error {
|
|
150
|
+
constructor(cmd, code, stdout, stderr) {
|
|
151
|
+
super(`shellExec failed (exit=${code}): ${cmd}`);
|
|
152
|
+
this.name = 'ShellExecError';
|
|
153
|
+
this.cmd = cmd;
|
|
154
|
+
this.code = code;
|
|
155
|
+
this.stdout = stdout;
|
|
156
|
+
this.stderr = stderr;
|
|
77
157
|
}
|
|
78
158
|
}
|
|
79
159
|
/**
|
|
80
160
|
* Executes a shell command using shelljs.
|
|
161
|
+
*
|
|
162
|
+
* **Default behaviour is fail-fast**: a non-zero exit code throws
|
|
163
|
+
* `ShellExecError`. Callers that need to branch on the exit code
|
|
164
|
+
* (existence checks, optional commands) must pass `silentOnError: true`
|
|
165
|
+
* to opt out of throwing.
|
|
166
|
+
*
|
|
167
|
+
* The async-callback path is exempt from the throw — shelljs delivers
|
|
168
|
+
* `(code, stdout, stderr)` to the callback, which owns its own error
|
|
169
|
+
* handling.
|
|
170
|
+
*
|
|
81
171
|
* @memberof Process
|
|
82
172
|
* @param {string} cmd - The command string to execute.
|
|
83
173
|
* @param {Object} [options] - Options for execution.
|
|
84
|
-
* @param {boolean} [options.silent=false] - Suppress
|
|
85
|
-
* @param {boolean} [options.async=false] - Run command asynchronously.
|
|
86
|
-
* @param {boolean} [options.stdout=false] - Return stdout
|
|
87
|
-
* @param {boolean} [options.disableLog=false] -
|
|
88
|
-
* @param {Function} [options.callback=null] -
|
|
89
|
-
* @
|
|
174
|
+
* @param {boolean} [options.silent=false] - Suppress child stdout/stderr to the parent terminal.
|
|
175
|
+
* @param {boolean} [options.async=false] - Run the command asynchronously (use with `callback`).
|
|
176
|
+
* @param {boolean} [options.stdout=false] - Return stdout string instead of the `ShellString` result object.
|
|
177
|
+
* @param {boolean} [options.disableLog=false] - Skip the `[process] cmd …` info log line.
|
|
178
|
+
* @param {Function} [options.callback=null] - Async callback `(code, stdout, stderr) => void` when `async: true`.
|
|
179
|
+
* @param {boolean} [options.silentOnError=false] - When `true`, swallow non-zero exits and return the `ShellString` instead of throwing. Inverse of the previous `throwOnError` flag.
|
|
180
|
+
* @param {string} [options.cwd] - Hermetic working directory (snapshotted + restored — does NOT leak).
|
|
181
|
+
* @returns {string|shelljs.ShellString} `ShellString` by default; the stdout string when `stdout: true`.
|
|
182
|
+
* @throws {ShellExecError} On non-zero exit when `silentOnError` is not set.
|
|
90
183
|
*/
|
|
91
|
-
const shellExec = (
|
|
92
|
-
cmd,
|
|
93
|
-
options = { silent: false, async: false, stdout: false, disableLog: false, callback: null },
|
|
94
|
-
) => {
|
|
184
|
+
const shellExec = (cmd, options = {}) => {
|
|
95
185
|
if (!options.disableLog) logger.info(`cmd`, cmd);
|
|
96
|
-
|
|
97
|
-
|
|
186
|
+
|
|
187
|
+
// Whitelist exactly the keys `shelljs.exec` understands. Passing our own
|
|
188
|
+
// bookkeeping keys through (or a literal `cwd: undefined`) makes shelljs
|
|
189
|
+
// call `path.resolve(undefined)` and crash with ERR_INVALID_ARG_TYPE.
|
|
190
|
+
const shellOpts = {};
|
|
191
|
+
if (options.silent !== undefined) shellOpts.silent = options.silent;
|
|
192
|
+
if (options.async !== undefined) shellOpts.async = options.async;
|
|
193
|
+
|
|
194
|
+
// Hermetic cwd. shelljs.cd mutates a process-wide global; instead we
|
|
195
|
+
// snapshot the current cwd here, switch for the duration of this call,
|
|
196
|
+
// and restore in `finally`. We deliberately do NOT forward `cwd` to
|
|
197
|
+
// shelljs — leaving its `cwd` unset means it inherits our just-changed
|
|
198
|
+
// `process.cwd()`, and we keep full control of restore semantics.
|
|
199
|
+
const previousCwd = options.cwd ? process.cwd() : null;
|
|
200
|
+
if (options.cwd) {
|
|
201
|
+
try {
|
|
202
|
+
process.chdir(options.cwd);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (Underpost.env.isInsideContainer()) Underpost.env.set('container-status', 'error')
|
|
205
|
+
throw new ShellExecError(cmd, -1, '', `chdir(${options.cwd}) failed: ${err.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
if (options.callback) {
|
|
210
|
+
// Async path. shelljs invokes the callback with (code, stdout, stderr).
|
|
211
|
+
// The callback owns its own error handling; the throw default does
|
|
212
|
+
// not apply here.
|
|
213
|
+
return shell.exec(cmd, shellOpts, options.callback);
|
|
214
|
+
}
|
|
215
|
+
const result = shell.exec(cmd, shellOpts);
|
|
216
|
+
|
|
217
|
+
if (!options.silentOnError && result && typeof result.code === 'number' && result.code !== 0) {
|
|
218
|
+
if (Underpost.env.isInsideContainer()) Underpost.env.set('container-status', 'error')
|
|
219
|
+
throw new ShellExecError(cmd, result.code, result.stdout || '', result.stderr || '');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return options.stdout ? result.stdout : result;
|
|
223
|
+
} finally {
|
|
224
|
+
if (previousCwd) {
|
|
225
|
+
try {
|
|
226
|
+
process.chdir(previousCwd);
|
|
227
|
+
} catch (_) {
|
|
228
|
+
/* best-effort restore */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
98
232
|
};
|
|
99
233
|
/**
|
|
100
234
|
* Changes the current working directory using shelljs.
|
|
235
|
+
*
|
|
236
|
+
* Note: `shellCd` mutates global state. Prefer `shellExec(cmd, { cwd })`
|
|
237
|
+
* for one-shot directory-scoped commands; use `shellCd` only for the
|
|
238
|
+
* outermost shell where the cwd should persist across many calls.
|
|
239
|
+
*
|
|
101
240
|
* @memberof Process
|
|
102
241
|
* @param {string} cd - The path to change the directory to.
|
|
103
242
|
* @param {Object} [options] - Options for the CD operation.
|
|
@@ -110,6 +249,11 @@ const shellCd = (cd, options = { disableLog: false }) => {
|
|
|
110
249
|
};
|
|
111
250
|
/**
|
|
112
251
|
* Wraps a command to run it as a daemon process in a shell (keeping the process alive/terminal open).
|
|
252
|
+
*
|
|
253
|
+
* NB: callers must ensure `cmd` does not contain unescaped single quotes —
|
|
254
|
+
* the wrapper uses `bash -c '<cmd>; …'`. For arbitrary user input prefer
|
|
255
|
+
* a heredoc or a temporary script file.
|
|
256
|
+
*
|
|
113
257
|
* @memberof Process
|
|
114
258
|
* @param {string} cmd - The command to daemonize.
|
|
115
259
|
* @returns {string} The shell command string for the daemon process.
|
|
@@ -119,11 +263,19 @@ const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
|
|
|
119
263
|
* Retrieves the process ID (PID) of the most recently created gnome-terminal instance.
|
|
120
264
|
* Note: This function is environment-specific (GNOME/Linux) and uses `pgrep -n`.
|
|
121
265
|
* @memberof Process
|
|
122
|
-
* @returns {number} The PID of the last gnome-terminal process.
|
|
266
|
+
* @returns {number|null} The PID of the last gnome-terminal process, or null if none running.
|
|
123
267
|
*/
|
|
124
268
|
// list all terminals: pgrep gnome-terminal
|
|
125
269
|
// list last terminal: pgrep -n gnome-terminal
|
|
126
|
-
const getTerminalPid = () =>
|
|
270
|
+
const getTerminalPid = () => {
|
|
271
|
+
const raw = shellExec(`pgrep -n gnome-terminal`, { stdout: true, silent: true, silentOnError: true });
|
|
272
|
+
if (!raw || !raw.trim()) return null;
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(raw);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
127
279
|
/**
|
|
128
280
|
* Copies text content to the system clipboard using clipboardy.
|
|
129
281
|
* Logs the copied content for confirmation.
|
|
@@ -135,4 +287,13 @@ function pbcopy(data) {
|
|
|
135
287
|
clipboard.writeSync(data || '🦄');
|
|
136
288
|
logger.info(`copied to clipboard`, clipboard.readSync());
|
|
137
289
|
}
|
|
138
|
-
export {
|
|
290
|
+
export {
|
|
291
|
+
ProcessController,
|
|
292
|
+
ShellExecError,
|
|
293
|
+
getRootDirectory,
|
|
294
|
+
shellExec,
|
|
295
|
+
shellCd,
|
|
296
|
+
pbcopy,
|
|
297
|
+
getTerminalPid,
|
|
298
|
+
daemonProcess,
|
|
299
|
+
};
|
package/src/server/proxy.js
CHANGED
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
11
11
|
import { loggerFactory, loggerMiddleware } from './logger.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
buildPortProxyRouter,
|
|
14
|
+
buildProxyRouter,
|
|
15
|
+
etcHostFactory,
|
|
16
|
+
getTlsHosts,
|
|
17
|
+
isDevProxyContext,
|
|
18
|
+
isTlsDevProxy,
|
|
19
|
+
} from './conf.js';
|
|
13
20
|
|
|
14
21
|
import { shellExec } from './process.js';
|
|
15
22
|
import fs from 'fs-extra';
|
|
@@ -114,7 +121,7 @@ class ProxyService {
|
|
|
114
121
|
logger.info('Proxy running', { port, router: options.router });
|
|
115
122
|
if (process.env.NODE_ENV === 'development')
|
|
116
123
|
logger.info(
|
|
117
|
-
|
|
124
|
+
etcHostFactory(Object.keys(options.router), {
|
|
118
125
|
append: true,
|
|
119
126
|
}).renderHosts,
|
|
120
127
|
);
|
package/src/server/runtime.js
CHANGED
package/src/server/start.js
CHANGED
|
@@ -198,20 +198,32 @@ class UnderpostStartUp {
|
|
|
198
198
|
*/
|
|
199
199
|
async run(deployId = 'dd-default', env = 'development', options = {}) {
|
|
200
200
|
const runCmd = env === 'production' ? 'run prod:container' : 'run dev:container';
|
|
201
|
+
const makeDeployCallback = (cmd) => (code, out, msg) => {
|
|
202
|
+
if (code !== 0) {
|
|
203
|
+
logger.error(`Deployment process exited with code ${code}`, { cmd, msg });
|
|
204
|
+
Underpost.env.set('container-status', 'error');
|
|
205
|
+
}
|
|
206
|
+
};
|
|
201
207
|
if (fs.existsSync(`./engine-private/replica`)) {
|
|
202
208
|
const replicas = await fs.readdir(`./engine-private/replica`);
|
|
203
209
|
for (const replica of replicas) {
|
|
204
210
|
if (!replica.match(deployId)) continue;
|
|
205
211
|
shellExec(`node bin env ${replica} ${env}`);
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
const replicaCmd = `npm ${runCmd} ${replica}`;
|
|
213
|
+
shellExec(replicaCmd, { async: true, callback: makeDeployCallback(replicaCmd) });
|
|
214
|
+
await awaitDeployMonitor();
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
shellExec(`node bin env ${deployId} ${env}`);
|
|
211
|
-
|
|
218
|
+
const deployCmd = `npm ${runCmd} ${deployId}`;
|
|
219
|
+
shellExec(deployCmd, { async: true, callback: makeDeployCallback(deployCmd) });
|
|
212
220
|
await awaitDeployMonitor(true);
|
|
213
|
-
if (
|
|
214
|
-
|
|
221
|
+
if (Underpost.env.get('container-status') !== 'error') {
|
|
222
|
+
if (env === 'production' && Underpost.env.isInsideContainer()) Underpost.secret.globalSecretClean();
|
|
223
|
+
Underpost.env.set('container-status', `${deployId}-${env}-running-deployment`);
|
|
224
|
+
} else {
|
|
225
|
+
Underpost.env.set('container-status', 'error');
|
|
226
|
+
}
|
|
215
227
|
},
|
|
216
228
|
};
|
|
217
229
|
}
|
package/src/server/valkey.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import Valkey from 'iovalkey';
|
|
16
16
|
import { loggerFactory } from './logger.js';
|
|
17
|
+
import Underpost from '../index.js';
|
|
17
18
|
|
|
18
19
|
const logger = loggerFactory(import.meta);
|
|
19
20
|
|
|
@@ -70,6 +71,7 @@ const createValkeyConnection = async (instance = {}, connectionOptions = {}) =>
|
|
|
70
71
|
client.on('error', (err) => {
|
|
71
72
|
ValkeyStatus[key] = 'error';
|
|
72
73
|
logger.error('Valkey error', { err: err?.message, instance });
|
|
74
|
+
if (Underpost.env.isInsideContainer()) Underpost.env.set('container-status', 'error');
|
|
73
75
|
});
|
|
74
76
|
client.on('reconnecting', () => {
|
|
75
77
|
ValkeyStatus[key] = 'reconnecting';
|
package/src/ws/IoInterface.js
CHANGED
|
@@ -46,9 +46,9 @@ class IoChannel {
|
|
|
46
46
|
constructor(IoInterface) {
|
|
47
47
|
this.#IoInterface = {
|
|
48
48
|
channel: '',
|
|
49
|
-
connection: async (socket = {}, client = {},
|
|
50
|
-
controller: async (socket = {}, client = {}, payload = {},
|
|
51
|
-
disconnect: async (socket = {}, client = {}, reason = '',
|
|
49
|
+
connection: async (socket = {}, client = {}, hostKeyContext = '') => { },
|
|
50
|
+
controller: async (socket = {}, client = {}, payload = {}, hostKeyContext = '', args = []) => { },
|
|
51
|
+
disconnect: async (socket = {}, client = {}, reason = '', hostKeyContext = '') => { },
|
|
52
52
|
stream: false,
|
|
53
53
|
...IoInterface,
|
|
54
54
|
};
|
|
@@ -68,18 +68,18 @@ class IoChannel {
|
|
|
68
68
|
* Sets up the listener for the channel message.
|
|
69
69
|
*
|
|
70
70
|
* @param {Socket} socket - The Socket.IO socket object.
|
|
71
|
-
* @param {string}
|
|
71
|
+
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
|
|
72
72
|
* @returns {Promise<void>}
|
|
73
73
|
*/
|
|
74
|
-
async connection(socket,
|
|
74
|
+
async connection(socket, hostKeyContext) {
|
|
75
75
|
try {
|
|
76
76
|
this.client[socket.id] = socket;
|
|
77
77
|
// Use bind/arrow function to maintain 'this' context for the controller
|
|
78
|
-
socket.on(this.channel, (...args) => this.controller(socket, args,
|
|
79
|
-
await this.#IoInterface.connection(socket, this.client,
|
|
78
|
+
socket.on(this.channel, (...args) => this.controller(socket, args, hostKeyContext));
|
|
79
|
+
await this.#IoInterface.connection(socket, this.client, hostKeyContext);
|
|
80
80
|
logger.debug(`Socket ${socket.id} connected to channel ${this.channel}`);
|
|
81
81
|
} catch (error) {
|
|
82
|
-
logger.error(error, { channel: this.channel,
|
|
82
|
+
logger.error(error, { channel: this.channel, hostKeyContext, stack: error.stack });
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -89,10 +89,10 @@ class IoChannel {
|
|
|
89
89
|
* @method
|
|
90
90
|
* @param {Socket} socket - The Socket.IO socket object.
|
|
91
91
|
* @param {any[]} args - The raw arguments received from the socket event.
|
|
92
|
-
* @param {string}
|
|
92
|
+
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
|
|
93
93
|
* @returns {Promise<void>}
|
|
94
94
|
*/
|
|
95
|
-
async controller(socket, args,
|
|
95
|
+
async controller(socket, args, hostKeyContext) {
|
|
96
96
|
try {
|
|
97
97
|
if (!args || args.length === 0) {
|
|
98
98
|
logger.warn(`No arguments received for channel: ${this.channel}`, { socketId: socket.id });
|
|
@@ -101,9 +101,9 @@ class IoChannel {
|
|
|
101
101
|
// Determine if JSON parsing is needed based on the stream flag
|
|
102
102
|
const payload = this.#IoInterface.stream ? args[0] : JSON.parse(args[0]);
|
|
103
103
|
|
|
104
|
-
await this.#IoInterface.controller(socket, this.client, payload,
|
|
104
|
+
await this.#IoInterface.controller(socket, this.client, payload, hostKeyContext, args);
|
|
105
105
|
} catch (error) {
|
|
106
|
-
logger.error(error, { channel: this.channel,
|
|
106
|
+
logger.error(error, { channel: this.channel, hostKeyContext, socketId: socket.id, args, stack: error.stack });
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -112,16 +112,16 @@ class IoChannel {
|
|
|
112
112
|
*
|
|
113
113
|
* @param {Socket} socket - The Socket.IO socket object.
|
|
114
114
|
* @param {string} reason - The reason for disconnection (e.g., 'client namespace disconnect').
|
|
115
|
-
* @param {string}
|
|
115
|
+
* @param {string} hostKeyContext - Unique identifier for the WebSocket management context.
|
|
116
116
|
* @returns {Promise<void>}
|
|
117
117
|
*/
|
|
118
|
-
async disconnect(socket, reason,
|
|
118
|
+
async disconnect(socket, reason, hostKeyContext) {
|
|
119
119
|
try {
|
|
120
|
-
await this.#IoInterface.disconnect(socket, this.client, reason,
|
|
120
|
+
await this.#IoInterface.disconnect(socket, this.client, reason, hostKeyContext);
|
|
121
121
|
delete this.client[socket.id];
|
|
122
122
|
logger.debug(`Socket ${socket.id} disconnected from channel ${this.channel}. Reason: ${reason}`);
|
|
123
123
|
} catch (error) {
|
|
124
|
-
logger.error(error, { channel: this.channel,
|
|
124
|
+
logger.error(error, { channel: this.channel, hostKeyContext, reason, socketId: socket.id, stack: error.stack });
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -12,13 +12,13 @@ import { CoreWsEmitter } from '../core.ws.emit.js';
|
|
|
12
12
|
* Broadcasts incoming messages to all other connected sockets.
|
|
13
13
|
*/
|
|
14
14
|
class CoreWsChatChannel {
|
|
15
|
-
/** @type {Object.<string, Object>} Per-instance state keyed by
|
|
15
|
+
/** @type {Object.<string, Object>} Per-instance state keyed by hostKeyContext. */
|
|
16
16
|
static #state = {};
|
|
17
17
|
|
|
18
18
|
/** @type {IoChannel} */
|
|
19
19
|
static #io = new IoChannel({
|
|
20
20
|
channel: 'chat',
|
|
21
|
-
controller(socket, client, payload,
|
|
21
|
+
controller(socket, client, payload, hostKeyContext) {
|
|
22
22
|
for (const socketId of Object.keys(client)) {
|
|
23
23
|
if (socketId !== socket.id) {
|
|
24
24
|
CoreWsEmitter.emit('chat', client[socketId], { id: socket.id, ...payload });
|
|
@@ -39,29 +39,29 @@ class CoreWsChatChannel {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Initializes state for a server instance.
|
|
42
|
-
* @param {string}
|
|
42
|
+
* @param {string} hostKeyContext - Unique server context ID.
|
|
43
43
|
*/
|
|
44
|
-
static init(
|
|
45
|
-
this.#state[
|
|
44
|
+
static init(hostKeyContext) {
|
|
45
|
+
this.#state[hostKeyContext] = {};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Registers a socket connection.
|
|
50
50
|
* @param {import('socket.io').Socket} socket
|
|
51
|
-
* @param {string}
|
|
51
|
+
* @param {string} hostKeyContext
|
|
52
52
|
*/
|
|
53
|
-
static connection(socket,
|
|
54
|
-
return this.#io.connection(socket,
|
|
53
|
+
static connection(socket, hostKeyContext) {
|
|
54
|
+
return this.#io.connection(socket, hostKeyContext);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Handles socket disconnection.
|
|
59
59
|
* @param {import('socket.io').Socket} socket
|
|
60
60
|
* @param {string} reason
|
|
61
|
-
* @param {string}
|
|
61
|
+
* @param {string} hostKeyContext
|
|
62
62
|
*/
|
|
63
|
-
static disconnect(socket, reason,
|
|
64
|
-
return this.#io.disconnect(socket, reason,
|
|
63
|
+
static disconnect(socket, reason, hostKeyContext) {
|
|
64
|
+
return this.#io.disconnect(socket, reason, hostKeyContext);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -12,29 +12,29 @@ import { IoChannel } from '../../IoInterface.js';
|
|
|
12
12
|
* Handles register/unregister messages and cleanup on disconnect.
|
|
13
13
|
*/
|
|
14
14
|
class CoreWsMailerChannel {
|
|
15
|
-
/** @type {Object.<string, Object.<string, { model: { user: Object } }>>} Socket data keyed by `[
|
|
15
|
+
/** @type {Object.<string, Object.<string, { model: { user: Object } }>>} Socket data keyed by `[hostKeyContext][socketId]`. */
|
|
16
16
|
static #data = {};
|
|
17
17
|
|
|
18
|
-
/** @type {Object.<string, Object.<string, string>>} Reverse index: `[
|
|
18
|
+
/** @type {Object.<string, Object.<string, string>>} Reverse index: `[hostKeyContext][userId]` → socketId. */
|
|
19
19
|
static #userIndex = {};
|
|
20
20
|
|
|
21
21
|
/** @type {IoChannel} */
|
|
22
22
|
static #io = new IoChannel({
|
|
23
23
|
channel: 'mailer',
|
|
24
|
-
controller(socket, client, payload,
|
|
24
|
+
controller(socket, client, payload, hostKeyContext) {
|
|
25
25
|
switch (payload.status) {
|
|
26
26
|
case 'register-user':
|
|
27
|
-
CoreWsMailerChannel.setUser(
|
|
27
|
+
CoreWsMailerChannel.setUser(hostKeyContext, socket.id, payload.user);
|
|
28
28
|
break;
|
|
29
29
|
case 'unregister-user':
|
|
30
|
-
CoreWsMailerChannel.removeSocket(
|
|
30
|
+
CoreWsMailerChannel.removeSocket(hostKeyContext, socket.id);
|
|
31
31
|
break;
|
|
32
32
|
default:
|
|
33
33
|
break;
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
disconnect(socket, client, reason,
|
|
37
|
-
CoreWsMailerChannel.removeSocket(
|
|
36
|
+
disconnect(socket, client, reason, hostKeyContext) {
|
|
37
|
+
CoreWsMailerChannel.removeSocket(hostKeyContext, socket.id);
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -50,66 +50,66 @@ class CoreWsMailerChannel {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Initializes state for a server instance.
|
|
53
|
-
* @param {string}
|
|
53
|
+
* @param {string} hostKeyContext - Unique server context ID (`${host}${path}`).
|
|
54
54
|
*/
|
|
55
|
-
static init(
|
|
56
|
-
this.#data[
|
|
57
|
-
this.#userIndex[
|
|
55
|
+
static init(hostKeyContext) {
|
|
56
|
+
this.#data[hostKeyContext] = {};
|
|
57
|
+
this.#userIndex[hostKeyContext] = {};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Registers a socket connection.
|
|
62
62
|
* @param {import('socket.io').Socket} socket
|
|
63
|
-
* @param {string}
|
|
63
|
+
* @param {string} hostKeyContext
|
|
64
64
|
*/
|
|
65
|
-
static connection(socket,
|
|
66
|
-
return this.#io.connection(socket,
|
|
65
|
+
static connection(socket, hostKeyContext) {
|
|
66
|
+
return this.#io.connection(socket, hostKeyContext);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Handles socket disconnection.
|
|
71
71
|
* @param {import('socket.io').Socket} socket
|
|
72
72
|
* @param {string} reason
|
|
73
|
-
* @param {string}
|
|
73
|
+
* @param {string} hostKeyContext
|
|
74
74
|
*/
|
|
75
|
-
static disconnect(socket, reason,
|
|
76
|
-
return this.#io.disconnect(socket, reason,
|
|
75
|
+
static disconnect(socket, reason, hostKeyContext) {
|
|
76
|
+
return this.#io.disconnect(socket, reason, hostKeyContext);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Registers a user↔socket mapping.
|
|
81
|
-
* @param {string}
|
|
81
|
+
* @param {string} hostKeyContext
|
|
82
82
|
* @param {string} socketId
|
|
83
83
|
* @param {Object} user - User data with `_id` property.
|
|
84
84
|
*/
|
|
85
|
-
static setUser(
|
|
86
|
-
this.#data[
|
|
85
|
+
static setUser(hostKeyContext, socketId, user) {
|
|
86
|
+
this.#data[hostKeyContext][socketId] = { model: { user } };
|
|
87
87
|
if (user?._id) {
|
|
88
|
-
this.#userIndex[
|
|
88
|
+
this.#userIndex[hostKeyContext][user._id.toString()] = socketId;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Removes a socket entry and its reverse user index.
|
|
94
|
-
* @param {string}
|
|
94
|
+
* @param {string} hostKeyContext
|
|
95
95
|
* @param {string} socketId
|
|
96
96
|
*/
|
|
97
|
-
static removeSocket(
|
|
98
|
-
const entry = this.#data[
|
|
97
|
+
static removeSocket(hostKeyContext, socketId) {
|
|
98
|
+
const entry = this.#data[hostKeyContext]?.[socketId];
|
|
99
99
|
if (entry?.model?.user?._id) {
|
|
100
|
-
delete this.#userIndex[
|
|
100
|
+
delete this.#userIndex[hostKeyContext][entry.model.user._id.toString()];
|
|
101
101
|
}
|
|
102
|
-
delete this.#data[
|
|
102
|
+
delete this.#data[hostKeyContext]?.[socketId];
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Finds the socket ID for a user (O(1) reverse index lookup).
|
|
107
|
-
* @param {string}
|
|
107
|
+
* @param {string} hostKeyContext
|
|
108
108
|
* @param {string} userId - The user `_id`.
|
|
109
109
|
* @returns {string|undefined} Socket ID, or `undefined` if not connected.
|
|
110
110
|
*/
|
|
111
|
-
static getUserWsId(
|
|
112
|
-
return this.#userIndex[
|
|
111
|
+
static getUserWsId(hostKeyContext, userId) {
|
|
112
|
+
return this.#userIndex[hostKeyContext]?.[userId];
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|