cyberia 3.2.22 → 3.2.70
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/.env.example +127 -68
- package/.github/workflows/cyberia-client.cd.yml +40 -0
- package/.github/workflows/cyberia-server.cd.yml +40 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
- package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
- package/.github/workflows/engine-cyberia.cd.yml +33 -24
- package/.github/workflows/engine-cyberia.ci.yml +13 -3
- package/.github/workflows/ghpkg.ci.yml +88 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/hardhat.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +10 -7
- package/.github/workflows/publish.ci.yml +2 -2
- package/.github/workflows/publish.cyberia.ci.yml +5 -16
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/CHANGELOG.md +301 -1
- package/CLI-HELP.md +71 -6
- package/Dockerfile +141 -43
- package/Dockerfile.dev +143 -0
- package/Dockerfile.test +165 -0
- package/README.md +1 -1
- package/baremetal/commission-workflows.json +1 -0
- package/bin/build.js +31 -0
- package/bin/cyberia.js +1080 -184
- package/bin/deploy.js +2 -2
- package/bin/index.js +1080 -184
- package/bump.config.js +1 -0
- package/compose.env +131 -0
- package/conf.js +18 -1
- package/deployment.yaml +2 -2
- package/docker-compose.yml +316 -0
- package/hardhat/hardhat.config.js +2 -2
- package/hardhat/package-lock.json +620 -2041
- package/hardhat/package.json +7 -5
- package/hardhat/scripts/deployObjectLayerToken.js +18 -18
- package/hardhat/test/ObjectLayerToken.js +378 -274
- package/ipfs/configure-ipfs.sh +13 -0
- 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 +2 -2
- package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/mongodb/entrypoint.sh +76 -0
- package/nginx.conf +87 -0
- package/package.json +31 -19
- package/pv-pvc.yaml +32 -0
- package/scripts/disk-clean.sh +85 -60
- package/scripts/kubeadm-node-setup.sh +317 -0
- package/scripts/rocky-kickstart.sh +877 -185
- package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
- package/scripts/test-monitor.sh +3 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
- package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
- package/src/api/cyberia-action/cyberia-action.model.js +21 -29
- package/src/api/cyberia-action/cyberia-action.router.js +42 -7
- package/src/api/cyberia-action/cyberia-action.service.js +20 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
- package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
- package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
- package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
- package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
- package/src/api/cyberia-map/cyberia-map.router.js +21 -6
- package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
- package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
- package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
- package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
- package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
- package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -129
- package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
- package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
- package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
- package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
- package/src/api/ipfs/ipfs.service.js +28 -15
- package/src/api/object-layer/object-layer.router.js +25 -15
- package/src/api/object-layer/object-layer.service.js +15 -20
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
- package/src/cli/baremetal.js +717 -16
- package/src/cli/cluster.js +80 -5
- package/src/cli/deploy.js +127 -11
- package/src/cli/docker-compose.js +648 -0
- package/src/cli/env.js +11 -2
- package/src/cli/fs.js +75 -30
- package/src/cli/image.js +129 -51
- package/src/cli/index.js +110 -6
- package/src/cli/kickstart.js +142 -20
- package/src/cli/release.js +46 -4
- package/src/cli/repository.js +238 -33
- package/src/cli/run.js +520 -114
- package/src/cli/secrets.js +82 -48
- package/src/cli/ssh.js +234 -0
- package/src/cli/static.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
- package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
- package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
- package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +45 -27
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
- package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +8 -1
- package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +5 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
- package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
- package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
- package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -6
- package/src/{server → client-builder}/client-build-docs.js +15 -5
- package/src/{server → client-builder}/client-build-live.js +3 -3
- package/src/{server → client-builder}/client-build.js +25 -22
- package/src/{server → client-builder}/client-dev-server.js +3 -3
- package/src/{server → client-builder}/client-icons.js +2 -2
- package/src/{server → client-builder}/ssr.js +5 -5
- package/src/client.build.js +1 -3
- package/src/client.dev.js +1 -1
- package/src/db/mongo/MongoBootstrap.js +12 -12
- package/src/grpc/cyberia/grpc-server.js +255 -70
- package/src/index.js +12 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
- package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +81 -0
- package/src/projects/cyberia/gemini-client.js +175 -0
- package/src/projects/cyberia/generate-saga.js +2107 -0
- package/src/{server → projects/cyberia}/ipfs-client.js +2 -2
- package/src/{server → projects/cyberia}/object-layer.js +12 -108
- package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
- package/src/{server → projects/cyberia}/shape-generator.js +2 -2
- package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
- package/src/runtime/cyberia-client/Dockerfile +27 -61
- package/src/runtime/cyberia-client/Dockerfile.dev +20 -12
- package/src/runtime/cyberia-server/Dockerfile +21 -20
- package/src/runtime/cyberia-server/Dockerfile.dev +17 -8
- package/src/runtime/engine-cyberia/Dockerfile +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
- package/src/runtime/engine-cyberia/compose.env +131 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
- package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
- package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
- package/src/runtime/engine-cyberia/nginx.conf +87 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/server/catalog.js +9 -14
- package/src/server/conf.js +3 -6
- package/src/server/runtime-status.js +18 -1
- package/src/server/start.js +12 -2
- package/src/server.js +6 -2
- package/test/cyberia-instance-conf-defaults.test.js +140 -0
- package/test/deploy-monitor.test.js +26 -10
- package/test/shape-generator.test.js +7 -1
- package/typedoc.dd-cyberia.json +3 -1
- package/typedoc.json +3 -1
- /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
- /package/src/{server → client-builder}/client-formatted.js +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exported singleton instance of the NginxService class.
|
|
3
|
+
* Manages dynamic generation of nginx reverse-proxy router configuration used
|
|
4
|
+
* by the Docker Compose development stack. Mirrors the conventions of
|
|
5
|
+
* {@link module:src/runtime/lampp/Lampp.js LamppService}.
|
|
6
|
+
* @module src/runtime/nginx/Nginx.js
|
|
7
|
+
* @namespace NginxService
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { loggerFactory } from '../../server/logger.js';
|
|
13
|
+
|
|
14
|
+
const logger = loggerFactory(import.meta);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @class NginxService
|
|
18
|
+
* @description Builds nginx `server` blocks (the router) for fronting upstream
|
|
19
|
+
* application services and writes the rendered configuration to disk. The
|
|
20
|
+
* router is accumulated in memory via {@link NginxService#createApp} and
|
|
21
|
+
* flushed with {@link NginxService#writeConf}, keeping config generation
|
|
22
|
+
* decoupled from the filesystem location it is written to.
|
|
23
|
+
* @memberof NginxService
|
|
24
|
+
*/
|
|
25
|
+
class NginxService {
|
|
26
|
+
/**
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @description Accumulated nginx `server { ... }` blocks (the router definition).
|
|
29
|
+
* @memberof NginxService
|
|
30
|
+
*/
|
|
31
|
+
router = '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @type {Set<string>}
|
|
35
|
+
* @description Upstream blocks keyed by upstream name to avoid duplicates.
|
|
36
|
+
* @memberof NginxService
|
|
37
|
+
*/
|
|
38
|
+
upstreams;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @type {boolean}
|
|
42
|
+
* @description Whether a default_server catch-all block has been emitted.
|
|
43
|
+
* @memberof NginxService
|
|
44
|
+
*/
|
|
45
|
+
hasDefaultServer;
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
this.reset();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resets the in-memory router, upstreams, and default-server flag.
|
|
53
|
+
* @method reset
|
|
54
|
+
* @returns {void}
|
|
55
|
+
* @memberof NginxService
|
|
56
|
+
*/
|
|
57
|
+
reset() {
|
|
58
|
+
this.router = '';
|
|
59
|
+
this.upstreams = new Map();
|
|
60
|
+
this.hasDefaultServer = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Appends a raw render fragment to the router string.
|
|
65
|
+
* @method appendRouter
|
|
66
|
+
* @param {string} render - The configuration fragment to append.
|
|
67
|
+
* @returns {string} The complete, updated router configuration string.
|
|
68
|
+
* @memberof NginxService
|
|
69
|
+
*/
|
|
70
|
+
appendRouter(render) {
|
|
71
|
+
if (!this.router) return (this.router = render);
|
|
72
|
+
return (this.router += render);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Clears the in-memory router configuration.
|
|
77
|
+
* @method removeRouter
|
|
78
|
+
* @returns {void}
|
|
79
|
+
* @memberof NginxService
|
|
80
|
+
*/
|
|
81
|
+
removeRouter() {
|
|
82
|
+
this.reset();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Registers a named upstream pointing at a container service:port.
|
|
87
|
+
* Idempotent: repeated names with the same target are collapsed.
|
|
88
|
+
* @method addUpstream
|
|
89
|
+
* @param {string} name - The upstream identifier.
|
|
90
|
+
* @param {string} service - The Docker service name (resolved via service discovery).
|
|
91
|
+
* @param {number} port - The upstream container port.
|
|
92
|
+
* @returns {string} The upstream name.
|
|
93
|
+
* @memberof NginxService
|
|
94
|
+
*/
|
|
95
|
+
addUpstream(name, service, port) {
|
|
96
|
+
this.upstreams.set(name, `upstream ${name} { server ${service}:${port}; }`);
|
|
97
|
+
return name;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Renders the standard proxy directive block shared by all locations,
|
|
102
|
+
* including websocket upgrade headers and forwarded headers.
|
|
103
|
+
* @method proxyLocation
|
|
104
|
+
* @param {string} location - The location path (e.g. '/', '/peer').
|
|
105
|
+
* @param {string} upstream - The upstream name to proxy to.
|
|
106
|
+
* @returns {string} The rendered `location { ... }` block.
|
|
107
|
+
* @memberof NginxService
|
|
108
|
+
*/
|
|
109
|
+
proxyLocation(location, upstream) {
|
|
110
|
+
return `
|
|
111
|
+
location ${location} {
|
|
112
|
+
proxy_pass http://${upstream};
|
|
113
|
+
proxy_set_header Host $host;
|
|
114
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
115
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
116
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
117
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
118
|
+
proxy_set_header Connection $connection_upgrade;
|
|
119
|
+
proxy_read_timeout 3600s;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Renders the `/healthz` location used by the proxy container healthcheck.
|
|
126
|
+
* @method healthLocation
|
|
127
|
+
* @returns {string} The rendered health-check location block.
|
|
128
|
+
* @memberof NginxService
|
|
129
|
+
*/
|
|
130
|
+
healthLocation() {
|
|
131
|
+
return `
|
|
132
|
+
location = /healthz {
|
|
133
|
+
access_log off;
|
|
134
|
+
return 200 "ok\\n";
|
|
135
|
+
add_header Content-Type text/plain;
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates an nginx virtual-host (`server`) entry for a host and appends it to
|
|
142
|
+
* the router. Each route maps a URL prefix to an upstream service:port with
|
|
143
|
+
* websocket support, mirroring the Contour HTTPProxy route model.
|
|
144
|
+
*
|
|
145
|
+
* @method createApp
|
|
146
|
+
* @param {object} options - Virtual host options.
|
|
147
|
+
* @param {string} options.host - The `server_name` (e.g. 'default.net').
|
|
148
|
+
* @param {number} [options.listen=80] - The listen port.
|
|
149
|
+
* @param {Array<{location: string, service: string, port: number}>} options.routes
|
|
150
|
+
* - Route table. Longer prefixes should precede '/' for correct matching.
|
|
151
|
+
* @param {boolean} [options.resetRouter] - Clear the router before appending.
|
|
152
|
+
* @returns {string} The complete, updated router configuration string.
|
|
153
|
+
* @memberof NginxService
|
|
154
|
+
*/
|
|
155
|
+
createApp({ host, listen = 80, routes = [], resetRouter = false }) {
|
|
156
|
+
if (resetRouter) this.removeRouter();
|
|
157
|
+
|
|
158
|
+
const safeHost = host.replace(/[^a-zA-Z0-9]/g, '_');
|
|
159
|
+
const locationBlocks = routes
|
|
160
|
+
.map(({ location, service, port }) => {
|
|
161
|
+
const upstreamName = this.addUpstream(`up_${safeHost}_${port}`, service, port);
|
|
162
|
+
return this.proxyLocation(location, upstreamName);
|
|
163
|
+
})
|
|
164
|
+
.join('');
|
|
165
|
+
|
|
166
|
+
this.appendRouter(`
|
|
167
|
+
server {
|
|
168
|
+
listen ${listen};
|
|
169
|
+
server_name ${host};
|
|
170
|
+
${this.healthLocation()}${locationBlocks}}
|
|
171
|
+
`);
|
|
172
|
+
|
|
173
|
+
return this.router;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Emits a `default_server` catch-all that forwards to the given upstream and
|
|
178
|
+
* answers the health probe for unmatched Host headers. Safe to call once.
|
|
179
|
+
*
|
|
180
|
+
* @method createDefaultServer
|
|
181
|
+
* @param {object} options - Default server options.
|
|
182
|
+
* @param {string} options.service - The Docker service name to fall back to.
|
|
183
|
+
* @param {number} options.port - The upstream container port.
|
|
184
|
+
* @param {number} [options.listen=80] - The listen port.
|
|
185
|
+
* @returns {string} The complete, updated router configuration string.
|
|
186
|
+
* @memberof NginxService
|
|
187
|
+
*/
|
|
188
|
+
createDefaultServer({ service, port, listen = 80 }) {
|
|
189
|
+
if (this.hasDefaultServer) return this.router;
|
|
190
|
+
this.hasDefaultServer = true;
|
|
191
|
+
const upstreamName = this.addUpstream('up_default', service, port);
|
|
192
|
+
this.appendRouter(`
|
|
193
|
+
server {
|
|
194
|
+
listen ${listen} default_server;
|
|
195
|
+
server_name _;
|
|
196
|
+
${this.healthLocation()}${this.proxyLocation('/', upstreamName)}}
|
|
197
|
+
`);
|
|
198
|
+
return this.router;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Renders the full nginx config document: the websocket connection map, all
|
|
203
|
+
* upstream blocks, the global `proxy_http_version`, and the accumulated router.
|
|
204
|
+
* @method render
|
|
205
|
+
* @returns {string} The complete nginx configuration file content.
|
|
206
|
+
* @memberof NginxService
|
|
207
|
+
*/
|
|
208
|
+
render() {
|
|
209
|
+
const upstreamBlocks = Array.from(this.upstreams.values()).join('\n');
|
|
210
|
+
return `# Generated by src/runtime/nginx/Nginx.js — do not hand-edit.
|
|
211
|
+
# Reverse proxy derived from manifests/deployment/*/proxy.yaml (Contour HTTPProxy).
|
|
212
|
+
# Upstreams resolve container services via the Docker internal network.
|
|
213
|
+
|
|
214
|
+
map $http_upgrade $connection_upgrade {
|
|
215
|
+
default upgrade;
|
|
216
|
+
'' close;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
${upstreamBlocks}
|
|
220
|
+
|
|
221
|
+
proxy_http_version 1.1;
|
|
222
|
+
${this.router}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Writes the rendered configuration to the given path, creating parent
|
|
227
|
+
* directories as needed. Idempotent and safe to rerun.
|
|
228
|
+
* @method writeConf
|
|
229
|
+
* @param {string} confPath - Absolute or relative destination path.
|
|
230
|
+
* @returns {string} The path written.
|
|
231
|
+
* @memberof NginxService
|
|
232
|
+
*/
|
|
233
|
+
writeConf(confPath) {
|
|
234
|
+
const target = path.resolve(confPath);
|
|
235
|
+
fs.mkdirpSync(path.dirname(target));
|
|
236
|
+
fs.writeFileSync(target, this.render(), 'utf8');
|
|
237
|
+
logger.info(`nginx config written`, { path: target, upstreams: this.upstreams.size });
|
|
238
|
+
return target;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @description Exported singleton instance of the NginxService class.
|
|
244
|
+
* @type {NginxService}
|
|
245
|
+
* @memberof NginxService
|
|
246
|
+
*/
|
|
247
|
+
const Nginx = new NginxService();
|
|
248
|
+
|
|
249
|
+
export { Nginx, NginxService };
|
|
250
|
+
export default Nginx;
|
package/src/server/catalog.js
CHANGED
|
@@ -18,14 +18,14 @@ import fs from 'fs-extra';
|
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
|
|
21
|
-
const catalogDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
|
|
23
21
|
/** Empty product catalog returned for deploy ids without a dedicated module. */
|
|
24
22
|
const EMPTY_CATALOG = {
|
|
25
23
|
sourceMoves: [],
|
|
26
24
|
privateConfPaths: [],
|
|
27
25
|
templatePaths: [],
|
|
28
26
|
stripPaths: [],
|
|
27
|
+
moves: [],
|
|
28
|
+
copies: [],
|
|
29
29
|
keywords: [],
|
|
30
30
|
description: '',
|
|
31
31
|
};
|
|
@@ -43,12 +43,11 @@ const EMPTY_CATALOG = {
|
|
|
43
43
|
const loadDeployCatalog = async (deployId) => {
|
|
44
44
|
const suffix = (deployId ?? '').split('dd-')[1];
|
|
45
45
|
if (!suffix) return EMPTY_CATALOG;
|
|
46
|
-
|
|
47
|
-
const mod = await import(
|
|
46
|
+
if (fs.existsSync(`./src/projects/${suffix}/catalog-${suffix}.js`)) {
|
|
47
|
+
const mod = await import(`../projects/${suffix}/catalog-${suffix}.js`);
|
|
48
48
|
return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
|
|
49
|
-
} catch {
|
|
50
|
-
return EMPTY_CATALOG;
|
|
51
49
|
}
|
|
50
|
+
return EMPTY_CATALOG;
|
|
52
51
|
};
|
|
53
52
|
|
|
54
53
|
/**
|
|
@@ -62,14 +61,10 @@ const loadDeployCatalog = async (deployId) => {
|
|
|
62
61
|
*/
|
|
63
62
|
const loadProductCatalogs = async () => {
|
|
64
63
|
const catalogs = [];
|
|
65
|
-
for (const file of fs.
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
70
|
-
} catch {
|
|
71
|
-
/* a malformed/removed product catalog must not break the base build */
|
|
72
|
-
}
|
|
64
|
+
for (const file of await fs.readdir('./src/projects')) {
|
|
65
|
+
if (file === 'underpost') continue;
|
|
66
|
+
const mod = await import(`../projects/${file}/catalog-${file}.js`);
|
|
67
|
+
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
73
68
|
}
|
|
74
69
|
return catalogs;
|
|
75
70
|
};
|
package/src/server/conf.js
CHANGED
|
@@ -476,9 +476,6 @@ const loadConf = (deployId = DEFAULT_DEPLOY_ID, subConf) => {
|
|
|
476
476
|
fs.removeSync(`${path}/.env.production`);
|
|
477
477
|
fs.removeSync(`${path}/.env.development`);
|
|
478
478
|
fs.removeSync(`${path}/.env.test`);
|
|
479
|
-
if (fs.existsSync(`${path}/typedoc.json`)) shellExec(`git checkout ${path}/typedoc.json`);
|
|
480
|
-
shellExec(`git checkout ${path}/package.json`);
|
|
481
|
-
shellExec(`git checkout ${path}/package-lock.json`);
|
|
482
479
|
return;
|
|
483
480
|
}
|
|
484
481
|
const folder = getConfFolder(deployId);
|
|
@@ -1337,7 +1334,7 @@ const mergeFile = async (parts = [], outputFilePath) => {
|
|
|
1337
1334
|
* @memberof ServerConfBuilder
|
|
1338
1335
|
*/
|
|
1339
1336
|
const getPathsSSR = (conf) => {
|
|
1340
|
-
const paths = ['src/client/ssr/
|
|
1337
|
+
const paths = ['src/client/ssr/RootDocument.js'];
|
|
1341
1338
|
for (const o of conf.head) paths.push(`src/client/ssr/head/${o}.js`);
|
|
1342
1339
|
for (const o of conf.body) paths.push(`src/client/ssr/body/${o}.js`);
|
|
1343
1340
|
for (const o of Object.keys(conf.mailer)) paths.push(`src/client/ssr/mailer/${conf.mailer[o]}.js`);
|
|
@@ -1928,7 +1925,8 @@ const syncDeployIdSources = (sourceMoves = []) => {
|
|
|
1928
1925
|
*/
|
|
1929
1926
|
const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template' } = {}) => {
|
|
1930
1927
|
const walk = (await import('ignore-walk')).default;
|
|
1931
|
-
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
1928
|
+
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
1929
|
+
await import('../projects/underpost/catalog-underpost.js');
|
|
1932
1930
|
const { loadProductCatalogs } = await import('./catalog.js');
|
|
1933
1931
|
const githubUsername = process.env.GITHUB_USERNAME;
|
|
1934
1932
|
|
|
@@ -1974,7 +1972,6 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
|
|
|
1974
1972
|
}
|
|
1975
1973
|
shellExec(`rm -rf ${toPath}/.github`);
|
|
1976
1974
|
shellExec(`rm -rf ${toPath}/manifests/deployment/dd-*`);
|
|
1977
|
-
shellExec(`rm -rf ${toPath}/src/server/catalog-*`);
|
|
1978
1975
|
|
|
1979
1976
|
fs.mkdirSync(`${toPath}/.github/workflows`, { recursive: true });
|
|
1980
1977
|
for (const restorePath of TEMPLATE_RESTORE_PATHS) {
|
|
@@ -40,6 +40,7 @@ const RUNTIME_STATUS = {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
const CONTAINER_STATUS_KEY = 'container-status';
|
|
43
|
+
const START_CONTAINER_STATUS_KEY = 'start-container-status';
|
|
43
44
|
const INTERNAL_STATUS_PATH = '/_internal/status';
|
|
44
45
|
const INTERNAL_READY_PATH = '/_internal/ready';
|
|
45
46
|
const INTERNAL_HEALTH_PATH = '/_internal/health';
|
|
@@ -118,6 +119,20 @@ const normalizeContainerStatus = (raw) => {
|
|
|
118
119
|
const getRuntimeStatus = () =>
|
|
119
120
|
normalizeContainerStatus(Underpost.env.get(CONTAINER_STATUS_KEY, undefined, { disableLog: true }));
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Reads the start-container-status env key — an insulated marker set by the
|
|
124
|
+
* start pipeline after it completes the running phase. Unlike container-status
|
|
125
|
+
* (which external scripts / backup failures may clobber), this key is written
|
|
126
|
+
* once and survives globalSecretClean. Used exclusively by the readinessProbe
|
|
127
|
+
* endpoint so K8s pod readiness is never derailed by lifecycle noise.
|
|
128
|
+
* @memberof RuntimeStatus
|
|
129
|
+
* @returns {string|undefined}
|
|
130
|
+
*/
|
|
131
|
+
const getStartContainerStatus = () => {
|
|
132
|
+
const raw = Underpost.env.get(START_CONTAINER_STATUS_KEY, undefined, { disableLog: true });
|
|
133
|
+
return raw && typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
|
|
134
|
+
};
|
|
135
|
+
|
|
121
136
|
/**
|
|
122
137
|
* Minimal, secret-free payload served by the internal status endpoint and used
|
|
123
138
|
* by the monitor for failure classification and observability.
|
|
@@ -188,7 +203,7 @@ const startInternalStatusServer = (port = resolveInternalStatusPort()) => {
|
|
|
188
203
|
case INTERNAL_HEALTH_PATH:
|
|
189
204
|
return sendJson(200, { status: 'ok' });
|
|
190
205
|
case INTERNAL_READY_PATH:
|
|
191
|
-
return
|
|
206
|
+
return getStartContainerStatus()
|
|
192
207
|
? sendJson(200, { status: RUNTIME_STATUS.RUNNING })
|
|
193
208
|
: sendJson(503, { status: getRuntimeStatus() ?? null });
|
|
194
209
|
case INTERNAL_STATUS_PATH:
|
|
@@ -220,6 +235,7 @@ const stopInternalStatusServer = () =>
|
|
|
220
235
|
export {
|
|
221
236
|
RUNTIME_STATUS,
|
|
222
237
|
CONTAINER_STATUS_KEY,
|
|
238
|
+
START_CONTAINER_STATUS_KEY,
|
|
223
239
|
INTERNAL_STATUS_PATH,
|
|
224
240
|
INTERNAL_READY_PATH,
|
|
225
241
|
INTERNAL_HEALTH_PATH,
|
|
@@ -228,6 +244,7 @@ export {
|
|
|
228
244
|
containerStatusValue,
|
|
229
245
|
normalizeContainerStatus,
|
|
230
246
|
getRuntimeStatus,
|
|
247
|
+
getStartContainerStatus,
|
|
231
248
|
runtimeStatusPayload,
|
|
232
249
|
setRuntimeStatus,
|
|
233
250
|
startInternalStatusServer,
|
package/src/server/start.js
CHANGED
|
@@ -8,7 +8,14 @@ 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 {
|
|
11
|
+
import {
|
|
12
|
+
RUNTIME_STATUS,
|
|
13
|
+
START_CONTAINER_STATUS_KEY,
|
|
14
|
+
setRuntimeStatus,
|
|
15
|
+
startInternalStatusServer,
|
|
16
|
+
deployStatusPort,
|
|
17
|
+
containerStatusValue,
|
|
18
|
+
} from './runtime-status.js';
|
|
12
19
|
import Underpost from '../index.js';
|
|
13
20
|
const logger = loggerFactory(import.meta);
|
|
14
21
|
|
|
@@ -241,7 +248,10 @@ class UnderpostStartUp {
|
|
|
241
248
|
const result = await awaitDeployMonitor(true);
|
|
242
249
|
if (result === true) {
|
|
243
250
|
if (env === 'production' && Underpost.env.isInsideContainer()) Underpost.secret.globalSecretClean();
|
|
244
|
-
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
setRuntimeStatus(deployId, env, RUNTIME_STATUS.RUNNING);
|
|
253
|
+
Underpost.env.set(START_CONTAINER_STATUS_KEY, containerStatusValue(deployId, env, RUNTIME_STATUS.RUNNING));
|
|
254
|
+
});
|
|
245
255
|
} else {
|
|
246
256
|
setRuntimeStatus(deployId, env, RUNTIME_STATUS.ERROR);
|
|
247
257
|
}
|
package/src/server.js
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
// https://nodejs.org/api
|
|
4
4
|
// https://expressjs.com/en/4x/api.html
|
|
5
5
|
|
|
6
|
+
import dotenv from 'dotenv';
|
|
6
7
|
import { loggerFactory } from './server/logger.js';
|
|
7
|
-
import { buildClient } from './
|
|
8
|
+
import { buildClient } from './client-builder/client-build.js';
|
|
8
9
|
import { buildRuntime } from './server/runtime.js';
|
|
9
10
|
import { ProcessController } from './server/process.js';
|
|
10
11
|
import { Config } from './server/conf.js';
|
|
12
|
+
|
|
13
|
+
dotenv.config();
|
|
14
|
+
|
|
11
15
|
await Config.build();
|
|
12
16
|
|
|
13
17
|
const logger = loggerFactory(import.meta);
|
|
14
18
|
|
|
15
19
|
await logger.setUpInfo();
|
|
16
20
|
|
|
17
|
-
await buildClient();
|
|
21
|
+
if (process.env.NODE_ENV === 'development') await buildClient();
|
|
18
22
|
|
|
19
23
|
await buildRuntime();
|
|
20
24
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import {
|
|
5
|
+
fillInstanceConfDefaults,
|
|
6
|
+
CYBERIA_INSTANCE_CONF_DEFAULTS,
|
|
7
|
+
} from '../src/api/cyberia-server-defaults/cyberia-server-defaults.js';
|
|
8
|
+
|
|
9
|
+
// The CyberiaInstanceConfSchema fields that must always be present after a
|
|
10
|
+
// backfill. Mirrors cyberia-instance-conf.model.js (minus instanceCode, which
|
|
11
|
+
// is the lookup key and is only present when the source doc carries it).
|
|
12
|
+
const SCHEMA_FIELDS = [
|
|
13
|
+
'tickRate',
|
|
14
|
+
'snapshotRate',
|
|
15
|
+
'aoiRadius',
|
|
16
|
+
'portalHoldTimeMs',
|
|
17
|
+
'portalSpawnRadius',
|
|
18
|
+
'entityBaseSpeed',
|
|
19
|
+
'entityBaseMaxLife',
|
|
20
|
+
'entityBaseActionCooldownMs',
|
|
21
|
+
'entityBaseMinActionCooldownMs',
|
|
22
|
+
'botAggroRange',
|
|
23
|
+
'defaultPlayerWidth',
|
|
24
|
+
'defaultPlayerHeight',
|
|
25
|
+
'playerBaseLifeRegenMin',
|
|
26
|
+
'playerBaseLifeRegenMax',
|
|
27
|
+
'sumStatsLimit',
|
|
28
|
+
'maxActiveLayers',
|
|
29
|
+
'initialLifeFraction',
|
|
30
|
+
'respawnDurationMs',
|
|
31
|
+
'collisionLifeLoss',
|
|
32
|
+
'economyRules',
|
|
33
|
+
'lifeRegenChance',
|
|
34
|
+
'maxChance',
|
|
35
|
+
'entityDefaults',
|
|
36
|
+
'statusIcons',
|
|
37
|
+
'skillConfig',
|
|
38
|
+
'skillRules',
|
|
39
|
+
'equipmentRules',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
describe('fillInstanceConfDefaults', () => {
|
|
43
|
+
it('fills every schema field from canonical defaults for an empty doc', () => {
|
|
44
|
+
const out = fillInstanceConfDefaults({});
|
|
45
|
+
for (const field of SCHEMA_FIELDS) {
|
|
46
|
+
expect(out, `missing field: ${field}`).to.have.property(field);
|
|
47
|
+
expect(out[field], `null/undefined field: ${field}`).to.not.equal(undefined);
|
|
48
|
+
expect(out[field], `null field: ${field}`).to.not.equal(null);
|
|
49
|
+
}
|
|
50
|
+
expect(out.tickRate).to.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.tickRate);
|
|
51
|
+
expect(out.economyRules).to.deep.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.economyRules);
|
|
52
|
+
expect(out.skillRules).to.deep.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.skillRules);
|
|
53
|
+
expect(out.equipmentRules).to.deep.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.equipmentRules);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles undefined / non-object input as an empty doc', () => {
|
|
57
|
+
for (const bad of [undefined, null, 42, 'x']) {
|
|
58
|
+
const out = fillInstanceConfDefaults(bad);
|
|
59
|
+
for (const field of SCHEMA_FIELDS) expect(out).to.have.property(field);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('preserves author-set scalar values, including falsy 0 / false / empty string', () => {
|
|
64
|
+
const out = fillInstanceConfDefaults({
|
|
65
|
+
tickRate: 30,
|
|
66
|
+
collisionLifeLoss: 0,
|
|
67
|
+
portalFee: 0,
|
|
68
|
+
});
|
|
69
|
+
expect(out.tickRate).to.equal(30);
|
|
70
|
+
expect(out.collisionLifeLoss).to.equal(0);
|
|
71
|
+
// Fields not overridden still come from defaults.
|
|
72
|
+
expect(out.snapshotRate).to.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.snapshotRate);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('backfills a missing nested sub-document (economyRules)', () => {
|
|
76
|
+
const out = fillInstanceConfDefaults({ tickRate: 60 });
|
|
77
|
+
expect(out.economyRules).to.deep.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.economyRules);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('fills only the missing keys of a partial nested sub-document', () => {
|
|
81
|
+
const out = fillInstanceConfDefaults({
|
|
82
|
+
economyRules: { botSpawnCoins: 999 },
|
|
83
|
+
});
|
|
84
|
+
expect(out.economyRules.botSpawnCoins).to.equal(999);
|
|
85
|
+
expect(out.economyRules.playerSpawnCoins).to.equal(
|
|
86
|
+
CYBERIA_INSTANCE_CONF_DEFAULTS.economyRules.playerSpawnCoins,
|
|
87
|
+
);
|
|
88
|
+
expect(out.economyRules.portalFee).to.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.economyRules.portalFee);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('keeps a present non-empty array and does not merge defaults into it', () => {
|
|
92
|
+
const custom = [{ entityType: 'player', liveItemIds: ['anon'], deadItemIds: [], dropItemIds: [] }];
|
|
93
|
+
const out = fillInstanceConfDefaults({ entityDefaults: custom });
|
|
94
|
+
expect(out.entityDefaults).to.deep.equal(custom);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('populates an empty skillConfig from defaults, normalised to schema shape (no `skills`)', () => {
|
|
98
|
+
for (const input of [{}, { skillConfig: [] }, { skillConfig: null }]) {
|
|
99
|
+
const out = fillInstanceConfDefaults(input);
|
|
100
|
+
expect(out.skillConfig.length, JSON.stringify(input)).to.be.greaterThan(0);
|
|
101
|
+
for (const entry of out.skillConfig) {
|
|
102
|
+
expect(entry).to.have.property('triggerItemId');
|
|
103
|
+
expect(entry).to.have.property('logicEventIds');
|
|
104
|
+
expect(entry).to.not.have.property('skills');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('keeps an author-set non-empty skillConfig verbatim', () => {
|
|
110
|
+
const custom = [{ triggerItemId: 'custom-weapon', logicEventIds: ['projectile'] }];
|
|
111
|
+
const out = fillInstanceConfDefaults({ skillConfig: custom });
|
|
112
|
+
expect(out.skillConfig).to.deep.equal(custom);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('fills an empty config array (entityDefaults) from defaults', () => {
|
|
116
|
+
const out = fillInstanceConfDefaults({ entityDefaults: [] });
|
|
117
|
+
expect(out.entityDefaults.length).to.be.greaterThan(0);
|
|
118
|
+
expect(out.entityDefaults).to.deep.equal(CYBERIA_INSTANCE_CONF_DEFAULTS.entityDefaults);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('preserves DB metadata (_id, instanceCode, timestamps)', () => {
|
|
122
|
+
const now = new Date().toISOString();
|
|
123
|
+
const out = fillInstanceConfDefaults({
|
|
124
|
+
_id: 'abc123',
|
|
125
|
+
instanceCode: 'amethyst-strata-expansion',
|
|
126
|
+
createdAt: now,
|
|
127
|
+
updatedAt: now,
|
|
128
|
+
});
|
|
129
|
+
expect(out._id).to.equal('abc123');
|
|
130
|
+
expect(out.instanceCode).to.equal('amethyst-strata-expansion');
|
|
131
|
+
expect(out.createdAt).to.equal(now);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not mutate the input document', () => {
|
|
135
|
+
const input = { economyRules: { botSpawnCoins: 5 } };
|
|
136
|
+
const snapshot = JSON.parse(JSON.stringify(input));
|
|
137
|
+
fillInstanceConfDefaults(input);
|
|
138
|
+
expect(input).to.deep.equal(snapshot);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -69,12 +69,10 @@ describe('Deploy monitor — two-phase state machine (e2e, real HTTP transport)'
|
|
|
69
69
|
process.env.npm_config_prefix = tmpPrefix;
|
|
70
70
|
|
|
71
71
|
Underpost.env.set('container-status', 'init');
|
|
72
|
+
Underpost.env.set('start-container-status', '');
|
|
72
73
|
const npmRoot = shellExec('npm root -g', { stdout: true, silent: true, disableLog: true }).trim();
|
|
73
74
|
envFile = path.join(npmRoot, 'underpost', '.env');
|
|
74
75
|
|
|
75
|
-
// Real in-pod internal status server: serves container-status from the same
|
|
76
|
-
// env file the runtime writes. Bound in this test process; the monitor's
|
|
77
|
-
// port-forward tunnel (localPort == INTERNAL_PORT) resolves straight to it.
|
|
78
76
|
process.env.UNDERPOST_INTERNAL_PORT = String(INTERNAL_PORT);
|
|
79
77
|
startInternalStatusServer(INTERNAL_PORT);
|
|
80
78
|
|
|
@@ -148,10 +146,11 @@ try {
|
|
|
148
146
|
beforeEach(() => {
|
|
149
147
|
// Deploy in flight: app not yet reporting running.
|
|
150
148
|
Underpost.env.set('container-status', INIT_STATUS);
|
|
149
|
+
// start-container-status is unset (empty) — the readinessProbe
|
|
150
|
+
// sees 503 until the start pipeline completes and stamps this key.
|
|
151
|
+
Underpost.env.set('start-container-status', '');
|
|
151
152
|
});
|
|
152
153
|
|
|
153
|
-
// Spawns the real monitorReadyRunner with the fake cluster on PATH; resolves
|
|
154
|
-
// with its exit code. `overrides` inject deterministic timing / target port.
|
|
155
154
|
const spawnMonitor = (overrides = {}) =>
|
|
156
155
|
new Promise((resolve) => {
|
|
157
156
|
const envVars = {
|
|
@@ -160,9 +159,6 @@ try {
|
|
|
160
159
|
POD_NAME,
|
|
161
160
|
npm_config_prefix: tmpPrefix,
|
|
162
161
|
UNDERPOST_INTERNAL_PORT: String(INTERNAL_PORT),
|
|
163
|
-
// Pin the tunnel's local port so the no-op fake port-forward + the real
|
|
164
|
-
// internal server (bound to INTERNAL_PORT in this process) resolve to the
|
|
165
|
-
// same address the monitor's HTTP GET targets.
|
|
166
162
|
UNDERPOST_PF_LOCAL_PORT: String(INTERNAL_PORT),
|
|
167
163
|
UNDERPOST_MONITOR_DELAY_MS: '100',
|
|
168
164
|
UNDERPOST_MONITOR_MAX_ITERATIONS: '60',
|
|
@@ -183,12 +179,15 @@ try {
|
|
|
183
179
|
|
|
184
180
|
it('success (default exec transport): both phases satisfied → monitor exits 0', async () => {
|
|
185
181
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
182
|
+
// start-container-status not needed for monitor (reads container-status);
|
|
183
|
+
// this test exercises the default exec transport path.
|
|
186
184
|
const code = await spawnMonitor({ FAKE_POD_READY: 'True' });
|
|
187
185
|
expect(code).to.equal(0);
|
|
188
186
|
});
|
|
189
187
|
|
|
190
188
|
it('success (opt-in http transport): both phases satisfied → monitor exits 0', async () => {
|
|
191
189
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
190
|
+
Underpost.env.set('start-container-status', RUNNING_STATUS);
|
|
192
191
|
const code = await spawnMonitor({ FAKE_POD_READY: 'True', MON_TRANSPORT: 'http' });
|
|
193
192
|
expect(code).to.equal(0);
|
|
194
193
|
});
|
|
@@ -202,13 +201,13 @@ try {
|
|
|
202
201
|
it('readiness mismatch: runtime running but pod not Ready → never succeeds (exits 1)', async () => {
|
|
203
202
|
// Phase 2 satisfied, Phase 1 not: success requires BOTH, so it must time out.
|
|
204
203
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
204
|
+
// start-container-status is NOT set — the readinessProbe would fail,
|
|
205
|
+
// but the monitor only checks container-status via exec/http transport.
|
|
205
206
|
const code = await spawnMonitor({ FAKE_POD_READY: 'False', UNDERPOST_MONITOR_MAX_ITERATIONS: '4' });
|
|
206
207
|
expect(code).to.equal(1);
|
|
207
208
|
});
|
|
208
209
|
|
|
209
210
|
it('transport failure (http): endpoint unreachable is never success (exits 1)', async () => {
|
|
210
|
-
// Opt into http and point the monitor at a port with no internal server; the
|
|
211
|
-
// HTTP read always fails, so runtime readiness is never confirmed → timeout.
|
|
212
211
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
213
212
|
const code = await spawnMonitor({
|
|
214
213
|
MON_TRANSPORT: 'http',
|
|
@@ -221,6 +220,8 @@ try {
|
|
|
221
220
|
|
|
222
221
|
it('timeout: runtime stuck initializing → monitor exits 1', async () => {
|
|
223
222
|
Underpost.env.set('container-status', INIT_STATUS);
|
|
223
|
+
// start-container-status remains unset — readinessProbe returns 503
|
|
224
|
+
// (correct, since the runtime is still initializing).
|
|
224
225
|
const code = await spawnMonitor({ FAKE_POD_READY: 'True', UNDERPOST_MONITOR_MAX_ITERATIONS: '4' });
|
|
225
226
|
expect(code).to.equal(1);
|
|
226
227
|
});
|
|
@@ -235,6 +236,21 @@ try {
|
|
|
235
236
|
expect(await monitorExit).to.equal(1);
|
|
236
237
|
});
|
|
237
238
|
|
|
239
|
+
it('readinessProbe: start-container-status set → returns 200 (probe passes)', async () => {
|
|
240
|
+
Underpost.env.set('start-container-status', RUNNING_STATUS);
|
|
241
|
+
const res = await fetch(`http://127.0.0.1:${INTERNAL_PORT}/_internal/ready`);
|
|
242
|
+
expect(res.status).to.equal(200);
|
|
243
|
+
const body = await res.json();
|
|
244
|
+
expect(body.status).to.equal('running-deployment');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('readinessProbe: start-container-status unset → returns 503 (probe fails)', async () => {
|
|
248
|
+
// Unset from beforeEach re-stamp; clear it.
|
|
249
|
+
Underpost.env.set('start-container-status', '');
|
|
250
|
+
const res = await fetch(`http://127.0.0.1:${INTERNAL_PORT}/_internal/ready`);
|
|
251
|
+
expect(res.status).to.equal(503);
|
|
252
|
+
});
|
|
253
|
+
|
|
238
254
|
// Custom instances (cyberia-*) gate on K8s Ready and read status via exec;
|
|
239
255
|
// their runtime never stamps `running-deployment` (stays `initializing`).
|
|
240
256
|
it('instance (kubernetes gate + exec): K8s Ready with initializing status → exits 0', async () => {
|