underpost 3.2.22 → 3.2.30

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.
Files changed (57) hide show
  1. package/.github/workflows/ghpkg.ci.yml +1 -1
  2. package/.github/workflows/gitlab.ci.yml +1 -1
  3. package/.github/workflows/npmpkg.ci.yml +1 -1
  4. package/.github/workflows/publish.ci.yml +2 -2
  5. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
  6. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  7. package/CHANGELOG.md +181 -1
  8. package/CLI-HELP.md +61 -2
  9. package/README.md +3 -2
  10. package/baremetal/commission-workflows.json +1 -0
  11. package/bin/build.js +1 -0
  12. package/docker-compose.yml +224 -0
  13. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  14. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  15. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  16. package/package.json +27 -15
  17. package/scripts/kubeadm-node-setup.sh +317 -0
  18. package/scripts/rocky-kickstart.sh +877 -185
  19. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  20. package/scripts/test-monitor.sh +3 -5
  21. package/src/cli/baremetal.js +717 -16
  22. package/src/cli/cluster.js +3 -1
  23. package/src/cli/deploy.js +118 -9
  24. package/src/cli/docker-compose.js +591 -0
  25. package/src/cli/env.js +11 -2
  26. package/src/cli/fs.js +35 -11
  27. package/src/cli/index.js +89 -0
  28. package/src/cli/kickstart.js +142 -20
  29. package/src/cli/repository.js +153 -11
  30. package/src/cli/run.js +285 -8
  31. package/src/cli/secrets.js +7 -2
  32. package/src/cli/ssh.js +234 -0
  33. package/src/cli/static.js +2 -2
  34. package/src/{server → client-builder}/client-build-docs.js +15 -5
  35. package/src/{server → client-builder}/client-build-live.js +3 -3
  36. package/src/{server → client-builder}/client-build.js +25 -22
  37. package/src/{server → client-builder}/client-dev-server.js +3 -3
  38. package/src/{server → client-builder}/client-icons.js +2 -2
  39. package/src/{server → client-builder}/ssr.js +5 -5
  40. package/src/client.build.js +1 -3
  41. package/src/client.dev.js +1 -1
  42. package/src/db/mongo/MongoBootstrap.js +12 -12
  43. package/src/index.js +12 -1
  44. package/src/mailer/EmailRender.js +1 -1
  45. package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
  46. package/src/runtime/express/Express.js +2 -2
  47. package/src/runtime/nginx/Nginx.js +250 -0
  48. package/src/server/catalog.js +7 -14
  49. package/src/server/conf.js +3 -3
  50. package/src/server/runtime-status.js +18 -1
  51. package/src/server/start.js +12 -2
  52. package/src/server.js +6 -2
  53. package/test/deploy-monitor.test.js +26 -10
  54. package/typedoc.json +3 -1
  55. package/src/server/ipfs-client.js +0 -599
  56. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  57. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -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/Render.js'];
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`);
@@ -1928,7 +1928,8 @@ const syncDeployIdSources = (sourceMoves = []) => {
1928
1928
  */
1929
1929
  const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template' } = {}) => {
1930
1930
  const walk = (await import('ignore-walk')).default;
1931
- const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } = await import('./catalog-underpost.js');
1931
+ const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
1932
+ await import('../projects/underpost/catalog-underpost.js');
1932
1933
  const { loadProductCatalogs } = await import('./catalog.js');
1933
1934
  const githubUsername = process.env.GITHUB_USERNAME;
1934
1935
 
@@ -1974,7 +1975,6 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
1974
1975
  }
1975
1976
  shellExec(`rm -rf ${toPath}/.github`);
1976
1977
  shellExec(`rm -rf ${toPath}/manifests/deployment/dd-*`);
1977
- shellExec(`rm -rf ${toPath}/src/server/catalog-*`);
1978
1978
 
1979
1979
  fs.mkdirSync(`${toPath}/.github/workflows`, { recursive: true });
1980
1980
  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 getRuntimeStatus() === RUNTIME_STATUS.RUNNING
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,
@@ -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 { RUNTIME_STATUS, setRuntimeStatus, startInternalStatusServer, deployStatusPort } from './runtime-status.js';
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
- setRuntimeStatus(deployId, env, RUNTIME_STATUS.RUNNING);
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 './server/client-build.js';
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
 
@@ -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 () => {
package/typedoc.json CHANGED
@@ -8,7 +8,9 @@
8
8
  "./src/ws",
9
9
  "./src/grpc",
10
10
  "./src/mailer",
11
- "./src/runtime"
11
+ "./src/runtime",
12
+ "./src/client-builder",
13
+ "./src/projects/underpost"
12
14
  ],
13
15
  "entryPointStrategy": "expand",
14
16
  "exclude": ["**/node_modules/**", "**/docs/**", "**/client/**"],