cyberia 3.2.9 → 3.2.22
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 +7 -0
- package/.github/workflows/engine-cyberia.ci.yml +14 -2
- package/.github/workflows/ghpkg.ci.yml +1 -0
- package/.github/workflows/npmpkg.ci.yml +10 -5
- 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 +363 -1
- package/CLI-HELP.md +975 -1061
- package/README.md +190 -348
- package/bin/build.js +102 -125
- package/bin/build.template.js +33 -0
- package/bin/cyberia.js +238 -56
- package/bin/deploy.js +16 -3
- package/bin/index.js +238 -56
- package/bump.config.js +26 -0
- package/conf.js +131 -24
- package/deployment.yaml +76 -2
- package/hardhat/package-lock.json +113 -144
- package/hardhat/package.json +4 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -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 +31 -19
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +81 -46
- package/scripts/link-local-underpost-cli.sh +6 -0
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/scripts/test-monitor.sh +250 -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 +458 -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 +291 -294
- package/src/cli/env.js +1 -4
- package/src/cli/fs.js +13 -3
- package/src/cli/image.js +58 -4
- package/src/cli/index.js +127 -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 +396 -9
- package/src/cli/release.js +355 -146
- package/src/cli/repository.js +169 -30
- package/src/cli/run.js +347 -117
- 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 +14 -10
- 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/ACTION-SYSTEM.md +55 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
- 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 +212 -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/QUEST-SYSTEM.md +23 -1
- 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 +130 -21
- package/src/grpc/cyberia/grpc-server.js +25 -57
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +10 -7
- package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
- package/src/runtime/cyberia-server/Dockerfile +11 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Dockerfile +3 -3
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/catalog-underpost.js +61 -0
- package/src/server/catalog.js +77 -0
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +496 -135
- package/src/server/ipfs-client.js +5 -3
- package/src/server/process.js +180 -19
- package/src/server/proxy.js +9 -2
- package/src/server/runtime-status.js +235 -0
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +44 -11
- 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/test/deploy-monitor.test.js +251 -0
- package/bin/file.js +0 -202
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
- package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
- package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
- 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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import stringify from 'fast-json-stable-stringify';
|
|
14
14
|
import { loggerFactory } from './logger.js';
|
|
15
|
+
import Underpost from '../index.js';
|
|
15
16
|
const logger = loggerFactory(import.meta);
|
|
16
17
|
const DEFAULT_IPFS_HTTP_TIMEOUT_MS = Number(process.env.IPFS_HTTP_TIMEOUT_MS || 10000);
|
|
17
18
|
const getRequestTimeoutMs = (kind = 'kubo') => {
|
|
@@ -46,21 +47,22 @@ const fetchWithTimeout = async (url, options = {}, { kind = 'kubo', label = url
|
|
|
46
47
|
* @returns {string}
|
|
47
48
|
*/
|
|
48
49
|
const getIpfsApiUrl = () =>
|
|
49
|
-
process.env.IPFS_API_URL ||
|
|
50
|
+
process.env.IPFS_API_URL ||
|
|
51
|
+
`http://${process.env.NODE_ENV === 'development' && !Underpost.env.isInsideContainer() ? 'localhost' : 'ipfs-cluster'}:5001`;
|
|
50
52
|
/**
|
|
51
53
|
* Base URL of the IPFS Cluster REST API (port 9094).
|
|
52
54
|
* @returns {string}
|
|
53
55
|
*/
|
|
54
56
|
const getClusterApiUrl = () =>
|
|
55
57
|
process.env.IPFS_CLUSTER_API_URL ||
|
|
56
|
-
`http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:9094`;
|
|
58
|
+
`http://${process.env.NODE_ENV === 'development' && !Underpost.env.isInsideContainer() ? 'localhost' : 'ipfs-cluster'}:9094`;
|
|
57
59
|
/**
|
|
58
60
|
* Base URL of the IPFS HTTP Gateway (port 8080).
|
|
59
61
|
* @returns {string}
|
|
60
62
|
*/
|
|
61
63
|
const getGatewayUrl = () =>
|
|
62
64
|
process.env.IPFS_GATEWAY_URL ||
|
|
63
|
-
`http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:8080`;
|
|
65
|
+
`http://${process.env.NODE_ENV === 'development' && !Underpost.env.isInsideContainer() ? 'localhost' : 'ipfs-cluster'}:8080`;
|
|
64
66
|
// ─────────────────────────────────────────────────────────
|
|
65
67
|
// Core: add content
|
|
66
68
|
// ─────────────────────────────────────────────────────────
|
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
|
);
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime status contract and the in-pod internal status endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the Underpost runtime readiness signal (Phase 2 of
|
|
5
|
+
* the two-phase deployment monitor). The runtime publishes its lifecycle here;
|
|
6
|
+
* the CD-side monitor (`src/cli/monitor.js`) reads it over HTTP via
|
|
7
|
+
* `kubectl port-forward`. Kubernetes pod readiness (Phase 1) is owned by kubelet
|
|
8
|
+
* and is intentionally not modeled in this module.
|
|
9
|
+
*
|
|
10
|
+
* Cross-process contract:
|
|
11
|
+
* - In-pod, the canonical value lives in the underpost root env key
|
|
12
|
+
* `container-status`, written by `start.js`. For non-error phases it carries
|
|
13
|
+
* the namespaced form `<deployId>-<env>-<phase>`; a fatal fault collapses to
|
|
14
|
+
* the bare value `error`.
|
|
15
|
+
* - The internal HTTP server exposes that value (normalized to the bare
|
|
16
|
+
* contract phase) and never exposes secrets, env dumps, or configuration.
|
|
17
|
+
*
|
|
18
|
+
* @module src/server/runtime-status.js
|
|
19
|
+
* @namespace RuntimeStatus
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import http from 'node:http';
|
|
23
|
+
import fs from 'fs-extra';
|
|
24
|
+
import dotenv from 'dotenv';
|
|
25
|
+
import Underpost from '../index.js';
|
|
26
|
+
import { loggerFactory } from './logger.js';
|
|
27
|
+
|
|
28
|
+
const logger = loggerFactory(import.meta);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Allowed runtime status contract values. These are the only Phase-2 signals
|
|
32
|
+
* the monitor reasons about.
|
|
33
|
+
* @memberof RuntimeStatus
|
|
34
|
+
*/
|
|
35
|
+
const RUNTIME_STATUS = {
|
|
36
|
+
BUILD: 'build-deployment',
|
|
37
|
+
INIT: 'initializing-deployment',
|
|
38
|
+
RUNNING: 'running-deployment',
|
|
39
|
+
ERROR: 'error',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const CONTAINER_STATUS_KEY = 'container-status';
|
|
43
|
+
const INTERNAL_STATUS_PATH = '/_internal/status';
|
|
44
|
+
const INTERNAL_READY_PATH = '/_internal/ready';
|
|
45
|
+
const INTERNAL_HEALTH_PATH = '/_internal/health';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves the internal status port. Defaults to the deployment base `PORT`
|
|
49
|
+
* (app instances bind `PORT + 1` upward, so the base port is free inside the
|
|
50
|
+
* pod). An explicit `UNDERPOST_INTERNAL_PORT` override wins.
|
|
51
|
+
* @memberof RuntimeStatus
|
|
52
|
+
* @returns {number|undefined}
|
|
53
|
+
*/
|
|
54
|
+
const resolveInternalStatusPort = () => {
|
|
55
|
+
const raw = process.env.UNDERPOST_INTERNAL_PORT || process.env.PORT;
|
|
56
|
+
const port = parseInt(raw);
|
|
57
|
+
return Number.isNaN(port) ? undefined : port;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Single source of truth for the internal status port of a specific deployment,
|
|
62
|
+
* used identically by the in-pod server bind (`start.js`) and the CD-side
|
|
63
|
+
* monitor target (`monitor.js`) so the two can never disagree.
|
|
64
|
+
*
|
|
65
|
+
* Resolution order: `UNDERPOST_INTERNAL_PORT` override → the deployment's
|
|
66
|
+
* `.env.<env>` `PORT` → the ambient `PORT`.
|
|
67
|
+
*
|
|
68
|
+
* @memberof RuntimeStatus
|
|
69
|
+
* @param {string} deployId
|
|
70
|
+
* @param {string} env
|
|
71
|
+
* @returns {number|undefined}
|
|
72
|
+
*/
|
|
73
|
+
const deployStatusPort = (deployId, env) => {
|
|
74
|
+
const override = parseInt(process.env.UNDERPOST_INTERNAL_PORT);
|
|
75
|
+
if (!Number.isNaN(override)) return override;
|
|
76
|
+
try {
|
|
77
|
+
const envPath = `./engine-private/conf/${deployId}/.env.${env}`;
|
|
78
|
+
if (fs.existsSync(envPath)) {
|
|
79
|
+
const port = parseInt(dotenv.parse(fs.readFileSync(envPath, 'utf8')).PORT);
|
|
80
|
+
if (!Number.isNaN(port)) return port;
|
|
81
|
+
}
|
|
82
|
+
} catch (_) {
|
|
83
|
+
/* fall through to ambient resolution */
|
|
84
|
+
}
|
|
85
|
+
return resolveInternalStatusPort();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builds the `container-status` env value for a lifecycle phase.
|
|
90
|
+
* @memberof RuntimeStatus
|
|
91
|
+
*/
|
|
92
|
+
const containerStatusValue = (deployId, env, phase) =>
|
|
93
|
+
phase === RUNTIME_STATUS.ERROR ? RUNTIME_STATUS.ERROR : `${deployId}-${env}-${phase}`;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalizes a raw `container-status` value to a bare contract phase.
|
|
97
|
+
* Strips the `<deployId>-<env>-` prefix; `error` and unknown/empty values are
|
|
98
|
+
* passed through (empty → undefined).
|
|
99
|
+
* @memberof RuntimeStatus
|
|
100
|
+
* @param {string} raw
|
|
101
|
+
* @returns {string|undefined}
|
|
102
|
+
*/
|
|
103
|
+
const normalizeContainerStatus = (raw) => {
|
|
104
|
+
if (!raw || typeof raw !== 'string') return undefined;
|
|
105
|
+
const value = raw.trim();
|
|
106
|
+
if (!value || value === 'undefined' || value.toLowerCase().includes('empty')) return undefined;
|
|
107
|
+
if (value === RUNTIME_STATUS.ERROR) return RUNTIME_STATUS.ERROR;
|
|
108
|
+
for (const phase of [RUNTIME_STATUS.BUILD, RUNTIME_STATUS.INIT, RUNTIME_STATUS.RUNNING])
|
|
109
|
+
if (value.endsWith(`-${phase}`)) return phase;
|
|
110
|
+
return value;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reads the current normalized runtime status from the env file.
|
|
115
|
+
* @memberof RuntimeStatus
|
|
116
|
+
* @returns {string|undefined}
|
|
117
|
+
*/
|
|
118
|
+
const getRuntimeStatus = () =>
|
|
119
|
+
normalizeContainerStatus(Underpost.env.get(CONTAINER_STATUS_KEY, undefined, { disableLog: true }));
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Minimal, secret-free payload served by the internal status endpoint and used
|
|
123
|
+
* by the monitor for failure classification and observability.
|
|
124
|
+
* @memberof RuntimeStatus
|
|
125
|
+
* @returns {{status: (string|null), deployId: (string|null), env: (string|null)}}
|
|
126
|
+
*/
|
|
127
|
+
const runtimeStatusPayload = () => ({
|
|
128
|
+
status: getRuntimeStatus() ?? null,
|
|
129
|
+
deployId: process.env.DEPLOY_ID ?? null,
|
|
130
|
+
env: process.env.NODE_ENV ?? null,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Emits a structured, secret-free deployment transition event.
|
|
135
|
+
* @memberof RuntimeStatus
|
|
136
|
+
*/
|
|
137
|
+
const emitRuntimeEvent = ({ deployId, env, phase }) => {
|
|
138
|
+
logger.info('runtime-status', {
|
|
139
|
+
deployId,
|
|
140
|
+
env,
|
|
141
|
+
phase: 'runtime',
|
|
142
|
+
status: phase,
|
|
143
|
+
timestamp: new Date().toISOString(),
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Publishes a runtime lifecycle phase to the cross-process contract.
|
|
149
|
+
* @memberof RuntimeStatus
|
|
150
|
+
* @param {string} deployId
|
|
151
|
+
* @param {string} env
|
|
152
|
+
* @param {string} phase - One of {@link RUNTIME_STATUS}.
|
|
153
|
+
*/
|
|
154
|
+
const setRuntimeStatus = (deployId, env, phase) => {
|
|
155
|
+
Underpost.env.set(CONTAINER_STATUS_KEY, containerStatusValue(deployId, env, phase));
|
|
156
|
+
emitRuntimeEvent({ deployId, env, phase });
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
let internalServer;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Starts the in-pod internal status server. Idempotent: repeated calls return
|
|
163
|
+
* the already-listening server. Exposes only the three internal endpoints and
|
|
164
|
+
* never serves secrets or configuration.
|
|
165
|
+
*
|
|
166
|
+
* GET /_internal/status → 200, `{status, deployId, env}` (monitor transport)
|
|
167
|
+
* GET /_internal/ready → 200 iff running-deployment, else 503 (readinessProbe)
|
|
168
|
+
* GET /_internal/health → 200 while the process is alive (livenessProbe)
|
|
169
|
+
*
|
|
170
|
+
* @memberof RuntimeStatus
|
|
171
|
+
* @param {number} [port]
|
|
172
|
+
* @returns {import('node:http').Server|undefined}
|
|
173
|
+
*/
|
|
174
|
+
const startInternalStatusServer = (port = resolveInternalStatusPort()) => {
|
|
175
|
+
if (internalServer) return internalServer;
|
|
176
|
+
if (!port) {
|
|
177
|
+
logger.warn('Internal status server not started: no resolvable port');
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const server = http.createServer((req, res) => {
|
|
181
|
+
const url = (req.url || '').split('?')[0];
|
|
182
|
+
const sendJson = (code, body) => {
|
|
183
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
184
|
+
res.end(JSON.stringify(body));
|
|
185
|
+
};
|
|
186
|
+
if (req.method !== 'GET') return sendJson(405, { error: 'method_not_allowed' });
|
|
187
|
+
switch (url) {
|
|
188
|
+
case INTERNAL_HEALTH_PATH:
|
|
189
|
+
return sendJson(200, { status: 'ok' });
|
|
190
|
+
case INTERNAL_READY_PATH:
|
|
191
|
+
return getRuntimeStatus() === RUNTIME_STATUS.RUNNING
|
|
192
|
+
? sendJson(200, { status: RUNTIME_STATUS.RUNNING })
|
|
193
|
+
: sendJson(503, { status: getRuntimeStatus() ?? null });
|
|
194
|
+
case INTERNAL_STATUS_PATH:
|
|
195
|
+
return sendJson(200, runtimeStatusPayload());
|
|
196
|
+
default:
|
|
197
|
+
return sendJson(404, { error: 'not_found' });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
server.on('error', (error) => logger.error('internal status server error', error?.message ?? error));
|
|
201
|
+
server.listen(port, () => logger.info(`Internal status endpoint listening on :${port}${INTERNAL_STATUS_PATH}`));
|
|
202
|
+
internalServer = server;
|
|
203
|
+
return internalServer;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Stops the internal status server if running. Returns a promise that resolves
|
|
208
|
+
* once the listener is closed. Primarily a test/teardown hook.
|
|
209
|
+
* @memberof RuntimeStatus
|
|
210
|
+
* @returns {Promise<void>}
|
|
211
|
+
*/
|
|
212
|
+
const stopInternalStatusServer = () =>
|
|
213
|
+
new Promise((resolve) => {
|
|
214
|
+
if (!internalServer) return resolve();
|
|
215
|
+
const server = internalServer;
|
|
216
|
+
internalServer = undefined;
|
|
217
|
+
server.close(() => resolve());
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
RUNTIME_STATUS,
|
|
222
|
+
CONTAINER_STATUS_KEY,
|
|
223
|
+
INTERNAL_STATUS_PATH,
|
|
224
|
+
INTERNAL_READY_PATH,
|
|
225
|
+
INTERNAL_HEALTH_PATH,
|
|
226
|
+
resolveInternalStatusPort,
|
|
227
|
+
deployStatusPort,
|
|
228
|
+
containerStatusValue,
|
|
229
|
+
normalizeContainerStatus,
|
|
230
|
+
getRuntimeStatus,
|
|
231
|
+
runtimeStatusPayload,
|
|
232
|
+
setRuntimeStatus,
|
|
233
|
+
startInternalStatusServer,
|
|
234
|
+
stopInternalStatusServer,
|
|
235
|
+
};
|
package/src/server/runtime.js
CHANGED
package/src/server/start.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from 'fs-extra';
|
|
|
8
8
|
import { awaitDeployMonitor } from './conf.js';
|
|
9
9
|
import { actionInitLog, loggerFactory } from './logger.js';
|
|
10
10
|
import { shellCd, shellExec } from './process.js';
|
|
11
|
+
import { RUNTIME_STATUS, setRuntimeStatus, startInternalStatusServer, deployStatusPort } from './runtime-status.js';
|
|
11
12
|
import Underpost from '../index.js';
|
|
12
13
|
const logger = loggerFactory(import.meta);
|
|
13
14
|
|
|
@@ -147,10 +148,20 @@ class UnderpostStartUp {
|
|
|
147
148
|
pullBundle: false,
|
|
148
149
|
},
|
|
149
150
|
) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
// Bring the internal status endpoint up first so Phase-2 readiness is
|
|
152
|
+
// observable through every lifecycle phase, including build and init. Bind
|
|
153
|
+
// the deployment-resolved port so it always matches the monitor's target.
|
|
154
|
+
startInternalStatusServer(deployStatusPort(deployId, env));
|
|
155
|
+
try {
|
|
156
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.BUILD);
|
|
157
|
+
if (options.build === true) await Underpost.start.build(deployId, env, options);
|
|
158
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.INIT);
|
|
159
|
+
if (options.run === true) await Underpost.start.run(deployId, env, options);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error('Deployment build/init failed', { deployId, env, message: error?.message });
|
|
162
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.ERROR);
|
|
163
|
+
if (!Underpost.env.isInsideContainer()) throw error;
|
|
164
|
+
}
|
|
154
165
|
},
|
|
155
166
|
/**
|
|
156
167
|
* Run itc-scripts and builds client bundle.
|
|
@@ -162,6 +173,8 @@ class UnderpostStartUp {
|
|
|
162
173
|
* @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
|
|
163
174
|
* @param {boolean} options.pullBundle - When true, download pre-built client bundle from Cloudinary via pull-bundle (must be pushed first with push-bundle).
|
|
164
175
|
* This flag is independent of skipFullBuild: it can be combined with skipFullBuild or used alone.
|
|
176
|
+
* @param {boolean} options.privateTestRepo - When true, clone `engine-test-<id>` (the private test source repo
|
|
177
|
+
* published by `node bin/build <deployId> --update-private`) instead of the production `engine-<id>` repo.
|
|
165
178
|
* @memberof UnderpostStartUp
|
|
166
179
|
*/
|
|
167
180
|
async build(
|
|
@@ -170,7 +183,11 @@ class UnderpostStartUp {
|
|
|
170
183
|
options = { underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false, pullBundle: false },
|
|
171
184
|
) {
|
|
172
185
|
const buildBasePath = `/home/dd`;
|
|
173
|
-
|
|
186
|
+
// `--private-test-repo` clones the isolated test source repo published by
|
|
187
|
+
// `node bin/build <deployId> --update-private`, instead of the production one.
|
|
188
|
+
const repoName = options?.privateTestRepo
|
|
189
|
+
? `engine-test-${deployId.split('-')[1]}`
|
|
190
|
+
: `engine-${deployId.split('-')[1]}`;
|
|
174
191
|
if (!options.skipPullBase) {
|
|
175
192
|
shellExec(`cd ${buildBasePath} && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}`);
|
|
176
193
|
shellExec(`mkdir -p ${buildBasePath}/engine`);
|
|
@@ -198,20 +215,36 @@ class UnderpostStartUp {
|
|
|
198
215
|
*/
|
|
199
216
|
async run(deployId = 'dd-default', env = 'development', options = {}) {
|
|
200
217
|
const runCmd = env === 'production' ? 'run prod:container' : 'run dev:container';
|
|
218
|
+
const makeDeployCallback = (cmd) => (code, out, msg) => {
|
|
219
|
+
if (code !== 0) {
|
|
220
|
+
logger.error(`Deployment process exited with code ${code}`, { cmd, msg });
|
|
221
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.ERROR);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
201
224
|
if (fs.existsSync(`./engine-private/replica`)) {
|
|
202
225
|
const replicas = await fs.readdir(`./engine-private/replica`);
|
|
203
226
|
for (const replica of replicas) {
|
|
204
227
|
if (!replica.match(deployId)) continue;
|
|
205
228
|
shellExec(`node bin env ${replica} ${env}`);
|
|
206
|
-
|
|
207
|
-
|
|
229
|
+
const replicaCmd = `npm ${runCmd} ${replica}`;
|
|
230
|
+
shellExec(replicaCmd, { async: true, callback: makeDeployCallback(replicaCmd) });
|
|
231
|
+
const result = await awaitDeployMonitor();
|
|
232
|
+
if (result !== true) {
|
|
233
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.ERROR);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
208
236
|
}
|
|
209
237
|
}
|
|
210
238
|
shellExec(`node bin env ${deployId} ${env}`);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
const deployCmd = `npm ${runCmd} ${deployId}`;
|
|
240
|
+
shellExec(deployCmd, { async: true, callback: makeDeployCallback(deployCmd) });
|
|
241
|
+
const result = await awaitDeployMonitor(true);
|
|
242
|
+
if (result === true) {
|
|
243
|
+
if (env === 'production' && Underpost.env.isInsideContainer()) Underpost.secret.globalSecretClean();
|
|
244
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.RUNNING);
|
|
245
|
+
} else {
|
|
246
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.ERROR);
|
|
247
|
+
}
|
|
215
248
|
},
|
|
216
249
|
};
|
|
217
250
|
}
|