underpost 3.2.21 → 3.2.28
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/ghpkg.ci.yml +1 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +1 -1
- package/.github/workflows/publish.ci.yml +2 -2
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/CHANGELOG.md +178 -1
- package/CLI-HELP.md +58 -2
- package/README.md +3 -2
- package/baremetal/commission-workflows.json +1 -0
- package/bin/build.js +13 -1
- package/docker-compose.yml +224 -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-default-development/deployment.yaml +2 -2
- package/package.json +27 -15
- 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 +242 -78
- package/src/cli/baremetal.js +717 -16
- package/src/cli/cluster.js +3 -1
- package/src/cli/deploy.js +11 -10
- package/src/cli/docker-compose.js +591 -0
- package/src/cli/fs.js +35 -11
- package/src/cli/index.js +83 -0
- package/src/cli/kickstart.js +142 -20
- package/src/cli/monitor.js +114 -29
- package/src/cli/repository.js +75 -11
- package/src/cli/run.js +275 -6
- package/src/cli/ssh.js +190 -0
- package/src/cli/static.js +2 -2
- package/src/client/components/core/PanelForm.js +44 -44
- 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 -1
- package/src/client.dev.js +1 -1
- package/src/index.js +12 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/server/catalog.js +7 -14
- package/src/server/conf.js +61 -12
- package/src/server/start.js +17 -5
- package/src/server.js +1 -1
- package/test/deploy-monitor.test.js +33 -5
- package/typedoc.json +3 -1
- package/src/server/ipfs-client.js +0 -597
- /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,8 +18,6 @@ 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: [],
|
|
@@ -43,12 +41,11 @@ const EMPTY_CATALOG = {
|
|
|
43
41
|
const loadDeployCatalog = async (deployId) => {
|
|
44
42
|
const suffix = (deployId ?? '').split('dd-')[1];
|
|
45
43
|
if (!suffix) return EMPTY_CATALOG;
|
|
46
|
-
|
|
47
|
-
const mod = await import(
|
|
44
|
+
if (fs.existsSync(`./src/projects/${suffix}/catalog-${suffix}.js`)) {
|
|
45
|
+
const mod = await import(`../projects/${suffix}/catalog-${suffix}.js`);
|
|
48
46
|
return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
|
|
49
|
-
} catch {
|
|
50
|
-
return EMPTY_CATALOG;
|
|
51
47
|
}
|
|
48
|
+
return EMPTY_CATALOG;
|
|
52
49
|
};
|
|
53
50
|
|
|
54
51
|
/**
|
|
@@ -62,14 +59,10 @@ const loadDeployCatalog = async (deployId) => {
|
|
|
62
59
|
*/
|
|
63
60
|
const loadProductCatalogs = async () => {
|
|
64
61
|
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
|
-
}
|
|
62
|
+
for (const file of await fs.readdir('./src/projects')) {
|
|
63
|
+
if (file === 'underpost') continue;
|
|
64
|
+
const mod = await import(`../projects/${file}/catalog-${file}.js`);
|
|
65
|
+
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
73
66
|
}
|
|
74
67
|
return catalogs;
|
|
75
68
|
};
|
package/src/server/conf.js
CHANGED
|
@@ -1337,7 +1337,7 @@ const mergeFile = async (parts = [], outputFilePath) => {
|
|
|
1337
1337
|
* @memberof ServerConfBuilder
|
|
1338
1338
|
*/
|
|
1339
1339
|
const getPathsSSR = (conf) => {
|
|
1340
|
-
const paths = ['src/client/ssr/
|
|
1340
|
+
const paths = ['src/client/ssr/RootDocument.js'];
|
|
1341
1341
|
for (const o of conf.head) paths.push(`src/client/ssr/head/${o}.js`);
|
|
1342
1342
|
for (const o of conf.body) paths.push(`src/client/ssr/body/${o}.js`);
|
|
1343
1343
|
for (const o of Object.keys(conf.mailer)) paths.push(`src/client/ssr/mailer/${conf.mailer[o]}.js`);
|
|
@@ -1856,6 +1856,7 @@ const syncPrivateConf = (deployId, extraPaths = []) => {
|
|
|
1856
1856
|
if (!fs.existsSync(privateRepoPath)) {
|
|
1857
1857
|
shellExec(`cd .. && underpost clone ${privateGitUri}`, { silent: true });
|
|
1858
1858
|
} else {
|
|
1859
|
+
shellExec(`git config --global --add safe.directory '${dir.resolve(privateRepoPath)}'`);
|
|
1859
1860
|
shellExec(`cd ${privateRepoPath} && git checkout . && git clean -f -d && underpost pull . ${privateGitUri}`, {
|
|
1860
1861
|
silent: true,
|
|
1861
1862
|
});
|
|
@@ -1927,7 +1928,8 @@ const syncDeployIdSources = (sourceMoves = []) => {
|
|
|
1927
1928
|
*/
|
|
1928
1929
|
const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template' } = {}) => {
|
|
1929
1930
|
const walk = (await import('ignore-walk')).default;
|
|
1930
|
-
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
1931
|
+
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
1932
|
+
await import('../projects/underpost/catalog-underpost.js');
|
|
1931
1933
|
const { loadProductCatalogs } = await import('./catalog.js');
|
|
1932
1934
|
const githubUsername = process.env.GITHUB_USERNAME;
|
|
1933
1935
|
|
|
@@ -1941,15 +1943,9 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
|
|
|
1941
1943
|
)
|
|
1942
1944
|
).filter((p) => !p.startsWith('.git'));
|
|
1943
1945
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
} else {
|
|
1948
|
-
shellExec(`cd ${toPath} && git reset && git checkout . && git clean -f -d`);
|
|
1949
|
-
shellExec(`node bin pull ${toPath} ${githubUsername}/pwa-microservices-template`);
|
|
1950
|
-
shellExec(`sudo rm -rf ${toPath}/engine-private`);
|
|
1951
|
-
shellExec(`sudo rm -rf ${toPath}/logs`);
|
|
1952
|
-
}
|
|
1946
|
+
fs.removeSync(`${githubUsername}/pwa-microservices-template`);
|
|
1947
|
+
shellExec(`cd .. && node engine/bin clone ${githubUsername}/pwa-microservices-template`);
|
|
1948
|
+
|
|
1953
1949
|
shellExec(`cd ${toPath} && git config core.filemode false`);
|
|
1954
1950
|
|
|
1955
1951
|
for (const copyPath of sourceFiles) {
|
|
@@ -1979,7 +1975,6 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
|
|
|
1979
1975
|
}
|
|
1980
1976
|
shellExec(`rm -rf ${toPath}/.github`);
|
|
1981
1977
|
shellExec(`rm -rf ${toPath}/manifests/deployment/dd-*`);
|
|
1982
|
-
shellExec(`rm -rf ${toPath}/src/server/catalog-*`);
|
|
1983
1978
|
|
|
1984
1979
|
fs.mkdirSync(`${toPath}/.github/workflows`, { recursive: true });
|
|
1985
1980
|
for (const restorePath of TEMPLATE_RESTORE_PATHS) {
|
|
@@ -2052,6 +2047,59 @@ git add .`);
|
|
|
2052
2047
|
}
|
|
2053
2048
|
};
|
|
2054
2049
|
|
|
2050
|
+
/**
|
|
2051
|
+
* @method updatePrivateEngineTestRepo
|
|
2052
|
+
* @description Publishes a deploy id's freshly assembled template to its private
|
|
2053
|
+
* **test** source repo `engine-test-<idPart>` (separate from the production
|
|
2054
|
+
* `engine-<idPart>`). A pod started with `underpost start --build --private-test-repo`
|
|
2055
|
+
* clones this repo, so work-in-progress engine source can be tested end to end
|
|
2056
|
+
* without touching the production source. Mirrors {@link updatePrivateTemplateRepo}
|
|
2057
|
+
* but per-deploy-id and against the test repo.
|
|
2058
|
+
*
|
|
2059
|
+
* Assumes the deploy id template has already been assembled at the template path
|
|
2060
|
+
* (run `node bin/build <deployId>` first, or use `node bin/build <deployId> --update-private`).
|
|
2061
|
+
* @param {string} deployId - Concrete deploy id (e.g. `dd-core`).
|
|
2062
|
+
* @returns {Promise<void>}
|
|
2063
|
+
* @memberof ServerConfBuilder
|
|
2064
|
+
*/
|
|
2065
|
+
const updatePrivateEngineTestRepo = async (deployId) => {
|
|
2066
|
+
const username = process.env.GITHUB_USERNAME || 'underpostnet';
|
|
2067
|
+
const repoName = `engine-test-${deployId.split('-')[1]}`;
|
|
2068
|
+
const templatePath = '/home/dd/pwa-microservices-template';
|
|
2069
|
+
if (!fs.existsSync(templatePath))
|
|
2070
|
+
throw new Error(`updatePrivateEngineTestRepo: assemble the template first (node bin/build ${deployId})`);
|
|
2071
|
+
|
|
2072
|
+
// Detach the assembled working tree from any engine-build git history.
|
|
2073
|
+
shellExec(`sudo rm -rf ${templatePath}/.git`);
|
|
2074
|
+
|
|
2075
|
+
// Adopt the test repo's existing history when present (so the push is a delta);
|
|
2076
|
+
// otherwise publish a fresh history on first push.
|
|
2077
|
+
shellExec(`cd /home/dd && sudo rm -rf ./${repoName}.git && underpost clone --bare ${username}/${repoName}`, {
|
|
2078
|
+
silent: true,
|
|
2079
|
+
disableLog: true,
|
|
2080
|
+
silentOnError: true,
|
|
2081
|
+
});
|
|
2082
|
+
if (fs.existsSync(`/home/dd/${repoName}.git`)) shellExec(`mv /home/dd/${repoName}.git ${templatePath}/.git`);
|
|
2083
|
+
|
|
2084
|
+
// `git init` converts the moved bare repo into a normal work-tree repo (bare
|
|
2085
|
+
// clones have no work tree, so `git add` would fail), and bootstraps a fresh
|
|
2086
|
+
// repo on first publish. Idempotent — mirrors updatePrivateTemplateRepo.
|
|
2087
|
+
shellExec(`cd ${templatePath}
|
|
2088
|
+
git init
|
|
2089
|
+
git config user.name '${username}'
|
|
2090
|
+
git config user.email 'development@underpost.net'
|
|
2091
|
+
git add .`);
|
|
2092
|
+
|
|
2093
|
+
const hasChanges = shellExec(`node bin cmt ${templatePath} --has-changes`, {
|
|
2094
|
+
stdout: true,
|
|
2095
|
+
silent: true,
|
|
2096
|
+
disableLog: true,
|
|
2097
|
+
}).trim();
|
|
2098
|
+
if (hasChanges === '1')
|
|
2099
|
+
shellExec(`cd ${templatePath} && git commit -m 'Update ${repoName}' && underpost push . ${username}/${repoName}`);
|
|
2100
|
+
else logger.info('No changes to publish', { repoName });
|
|
2101
|
+
};
|
|
2102
|
+
|
|
2055
2103
|
export {
|
|
2056
2104
|
Config,
|
|
2057
2105
|
loadConf,
|
|
@@ -2104,4 +2152,5 @@ export {
|
|
|
2104
2152
|
syncDeployIdSources,
|
|
2105
2153
|
buildTemplate,
|
|
2106
2154
|
updatePrivateTemplateRepo,
|
|
2155
|
+
updatePrivateEngineTestRepo,
|
|
2107
2156
|
};
|
package/src/server/start.js
CHANGED
|
@@ -152,10 +152,16 @@ class UnderpostStartUp {
|
|
|
152
152
|
// observable through every lifecycle phase, including build and init. Bind
|
|
153
153
|
// the deployment-resolved port so it always matches the monitor's target.
|
|
154
154
|
startInternalStatusServer(deployStatusPort(deployId, env));
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
}
|
|
159
165
|
},
|
|
160
166
|
/**
|
|
161
167
|
* Run itc-scripts and builds client bundle.
|
|
@@ -167,6 +173,8 @@ class UnderpostStartUp {
|
|
|
167
173
|
* @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
|
|
168
174
|
* @param {boolean} options.pullBundle - When true, download pre-built client bundle from Cloudinary via pull-bundle (must be pushed first with push-bundle).
|
|
169
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.
|
|
170
178
|
* @memberof UnderpostStartUp
|
|
171
179
|
*/
|
|
172
180
|
async build(
|
|
@@ -175,7 +183,11 @@ class UnderpostStartUp {
|
|
|
175
183
|
options = { underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false, pullBundle: false },
|
|
176
184
|
) {
|
|
177
185
|
const buildBasePath = `/home/dd`;
|
|
178
|
-
|
|
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]}`;
|
|
179
191
|
if (!options.skipPullBase) {
|
|
180
192
|
shellExec(`cd ${buildBasePath} && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}`);
|
|
181
193
|
shellExec(`mkdir -p ${buildBasePath}/engine`);
|
package/src/server.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// https://expressjs.com/en/4x/api.html
|
|
5
5
|
|
|
6
6
|
import { loggerFactory } from './server/logger.js';
|
|
7
|
-
import { buildClient } from './
|
|
7
|
+
import { buildClient } from './client-builder/client-build.js';
|
|
8
8
|
import { buildRuntime } from './server/runtime.js';
|
|
9
9
|
import { ProcessController } from './server/process.js';
|
|
10
10
|
import { Config } from './server/conf.js';
|
|
@@ -108,6 +108,10 @@ if [[ "$verb" == "port-forward" ]]; then
|
|
|
108
108
|
sleep 30
|
|
109
109
|
exit 0
|
|
110
110
|
fi
|
|
111
|
+
if [[ "$verb" == "exec" ]]; then
|
|
112
|
+
grep -E '^container-status=' "$UNDERPOST_ENV_FILE" 2>/dev/null | tail -n1 | sed -E 's/^container-status=//'
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
111
115
|
exit 0
|
|
112
116
|
`,
|
|
113
117
|
);
|
|
@@ -118,10 +122,13 @@ exit 0
|
|
|
118
122
|
fs.writeFileSync(
|
|
119
123
|
monitorScriptPath,
|
|
120
124
|
`import Underpost from ${JSON.stringify(pathToFileURL(path.join(repoRoot, 'src/index.js')).href)};
|
|
125
|
+
const options = {};
|
|
126
|
+
if (process.env.MON_READY_GATE) options.readyGate = process.env.MON_READY_GATE;
|
|
127
|
+
if (process.env.MON_TRANSPORT) options.statusTransport = process.env.MON_TRANSPORT;
|
|
121
128
|
try {
|
|
122
129
|
await Underpost.monitor.monitorReadyRunner(${JSON.stringify(DEPLOY_ID)}, ${JSON.stringify(ENV)}, ${JSON.stringify(
|
|
123
130
|
TRAFFIC,
|
|
124
|
-
)}, [], 'default');
|
|
131
|
+
)}, [], 'default', options);
|
|
125
132
|
process.exit(0);
|
|
126
133
|
} catch (_) {
|
|
127
134
|
process.exit(1);
|
|
@@ -174,12 +181,18 @@ try {
|
|
|
174
181
|
});
|
|
175
182
|
});
|
|
176
183
|
|
|
177
|
-
it('success: both phases satisfied → monitor exits 0', async () => {
|
|
184
|
+
it('success (default exec transport): both phases satisfied → monitor exits 0', async () => {
|
|
178
185
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
179
186
|
const code = await spawnMonitor({ FAKE_POD_READY: 'True' });
|
|
180
187
|
expect(code).to.equal(0);
|
|
181
188
|
});
|
|
182
189
|
|
|
190
|
+
it('success (opt-in http transport): both phases satisfied → monitor exits 0', async () => {
|
|
191
|
+
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
192
|
+
const code = await spawnMonitor({ FAKE_POD_READY: 'True', MON_TRANSPORT: 'http' });
|
|
193
|
+
expect(code).to.equal(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
183
196
|
it('runtime failure: container-status=error → monitor exits 1', async () => {
|
|
184
197
|
const monitorExit = spawnMonitor({ FAKE_POD_READY: 'True' });
|
|
185
198
|
Underpost.env.set('container-status', 'error');
|
|
@@ -193,11 +206,12 @@ try {
|
|
|
193
206
|
expect(code).to.equal(1);
|
|
194
207
|
});
|
|
195
208
|
|
|
196
|
-
it('transport failure: endpoint unreachable is never success (exits 1)', async () => {
|
|
197
|
-
//
|
|
198
|
-
// fails, so runtime readiness is never confirmed
|
|
209
|
+
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.
|
|
199
212
|
Underpost.env.set('container-status', RUNNING_STATUS);
|
|
200
213
|
const code = await spawnMonitor({
|
|
214
|
+
MON_TRANSPORT: 'http',
|
|
201
215
|
UNDERPOST_INTERNAL_PORT: String(CLOSED_PORT),
|
|
202
216
|
UNDERPOST_PF_LOCAL_PORT: String(CLOSED_PORT),
|
|
203
217
|
UNDERPOST_MONITOR_MAX_ITERATIONS: '3',
|
|
@@ -220,4 +234,18 @@ try {
|
|
|
220
234
|
Underpost.env.set('container-status', BUILD_STATUS);
|
|
221
235
|
expect(await monitorExit).to.equal(1);
|
|
222
236
|
});
|
|
237
|
+
|
|
238
|
+
// Custom instances (cyberia-*) gate on K8s Ready and read status via exec;
|
|
239
|
+
// their runtime never stamps `running-deployment` (stays `initializing`).
|
|
240
|
+
it('instance (kubernetes gate + exec): K8s Ready with initializing status → exits 0', async () => {
|
|
241
|
+
Underpost.env.set('container-status', INIT_STATUS);
|
|
242
|
+
const code = await spawnMonitor({ FAKE_POD_READY: 'True', MON_READY_GATE: 'kubernetes', MON_TRANSPORT: 'exec' });
|
|
243
|
+
expect(code).to.equal(0);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('instance (kubernetes gate + exec): container-status=error → exits 1', async () => {
|
|
247
|
+
const monitorExit = spawnMonitor({ FAKE_POD_READY: 'True', MON_READY_GATE: 'kubernetes', MON_TRANSPORT: 'exec' });
|
|
248
|
+
Underpost.env.set('container-status', 'error');
|
|
249
|
+
expect(await monitorExit).to.equal(1);
|
|
250
|
+
});
|
|
223
251
|
});
|
package/typedoc.json
CHANGED