underpost 3.2.80 → 3.2.90
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/CHANGELOG.md +182 -1
- package/CLI-HELP.md +37 -16
- package/README.md +2 -2
- package/bin/deploy.js +18 -16
- package/docker-compose.yml +1 -1
- 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/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/package.json +17 -17
- package/scripts/nat-iptables.sh +10 -4
- package/scripts/test-monitor.sh +4 -3
- package/src/cli/cluster.js +740 -55
- package/src/cli/db.js +2 -2
- package/src/cli/deploy.js +1679 -174
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/image.js +15 -6
- package/src/cli/index.js +124 -35
- package/src/cli/ipfs.js +82 -11
- package/src/cli/monitor.js +1 -1
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +2161 -420
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +8 -28
- package/src/client-builder/client-build.js +94 -11
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +47 -32
- package/src/index.js +1 -1
- package/src/server/conf.js +1208 -70
- package/src/server/cri.js +70 -0
- package/src/server/underpost-gateway.js +1073 -0
- package/src/server/underpost-ingress.js +364 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +469 -0
- package/test/underpost-ingress.test.js +253 -0
|
@@ -12,7 +12,6 @@ import fs from 'fs-extra';
|
|
|
12
12
|
import nodePath from 'path';
|
|
13
13
|
import { getRootDirectory, shellExec } from '../server/process.js';
|
|
14
14
|
import { loggerFactory } from '../server/logger.js';
|
|
15
|
-
import { loadInstanceTopology } from '../server/conf.js';
|
|
16
15
|
import Nginx from '../runtime/nginx/Nginx.js';
|
|
17
16
|
|
|
18
17
|
const logger = loggerFactory(import.meta);
|
|
@@ -82,9 +81,10 @@ class UnderpostDockerCompose {
|
|
|
82
81
|
* Resolves the canonical directory for a custom docker-compose workflow,
|
|
83
82
|
* keyed by `--deploy-id` + `--docker-compose-id`:
|
|
84
83
|
* `engine-private/conf/<deploy-id>/docker-compose/<docker-compose-id>`.
|
|
85
|
-
* This directory ships its own `docker-compose.yml`, `compose.env`, and
|
|
86
|
-
*
|
|
87
|
-
* `--docker-compose-id` is not set (the default,
|
|
84
|
+
* This directory ships its own `docker-compose.yml`, `compose.env`, and any
|
|
85
|
+
* project-specific bind-mounted files (all used as-is, never generated).
|
|
86
|
+
* Returns null when `--docker-compose-id` is not set (the default,
|
|
87
|
+
* self-generating workflow).
|
|
88
88
|
* @param {object} options - CLI options.
|
|
89
89
|
* @returns {string|null} Repo-root-relative canonical dir, or null.
|
|
90
90
|
* @memberof UnderpostDockerCompose
|
|
@@ -100,8 +100,8 @@ class UnderpostDockerCompose {
|
|
|
100
100
|
* so behavior is independent of the caller's working directory.
|
|
101
101
|
*
|
|
102
102
|
* Custom workflow (`--docker-compose-id`): the compose file, env-file, and
|
|
103
|
-
* bind-mounted config
|
|
104
|
-
*
|
|
103
|
+
* project-owned bind-mounted config all live in the canonical dir, so compose
|
|
104
|
+
* runs with `--project-directory` pinned there and no app-override.
|
|
105
105
|
* @param {object} options - CLI options.
|
|
106
106
|
* @returns {string} The base command string (without a subcommand).
|
|
107
107
|
* @memberof UnderpostDockerCompose
|
|
@@ -192,7 +192,7 @@ services:
|
|
|
192
192
|
MONGO_IMAGE=mongo:latest
|
|
193
193
|
VALKEY_IMAGE=valkey/valkey:latest
|
|
194
194
|
APP_IMAGE=underpost/underpost-engine
|
|
195
|
-
APP_TAG=v3.2.
|
|
195
|
+
APP_TAG=v3.2.90
|
|
196
196
|
PROXY_IMAGE=nginx:stable-alpine
|
|
197
197
|
PROMETHEUS_IMAGE=prom/prometheus:latest
|
|
198
198
|
GRAFANA_IMAGE=grafana/grafana:latest
|
|
@@ -362,150 +362,6 @@ datasources:
|
|
|
362
362
|
`;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
/**
|
|
366
|
-
* Expands the deploy's `multiInstance` topology (from conf.instances.json, via
|
|
367
|
-
* {@link loadInstanceTopology}) into the per-variant descriptors the compose
|
|
368
|
-
* generators consume. Application-agnostic: it only reads variant code/slug/
|
|
369
|
-
* path and the default code. Returns null when the deploy is single-instance.
|
|
370
|
-
* @param {object} options - CLI options.
|
|
371
|
-
* @returns {?{defaultCode: string, variants: Array<object>}}
|
|
372
|
-
* @memberof UnderpostDockerCompose
|
|
373
|
-
*/
|
|
374
|
-
static instanceTopology(options = {}) {
|
|
375
|
-
const deployId = options.deployId || DEFAULT_DEPLOY_ID;
|
|
376
|
-
let topology = null;
|
|
377
|
-
try {
|
|
378
|
-
topology = loadInstanceTopology(deployId);
|
|
379
|
-
} catch {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
if (!topology || !Array.isArray(topology.variants) || topology.variants.length === 0) return null;
|
|
383
|
-
const defaultCode = topology.default || topology.variants[0].code;
|
|
384
|
-
const variants = topology.variants.map((v) => {
|
|
385
|
-
const path = v.path || '/';
|
|
386
|
-
const isDefault = !(v.slug || '');
|
|
387
|
-
return {
|
|
388
|
-
code: v.code,
|
|
389
|
-
slug: v.slug || '',
|
|
390
|
-
path,
|
|
391
|
-
isDefault,
|
|
392
|
-
// Service/container suffix: empty for the default variant so it keeps
|
|
393
|
-
// the historic `cyberia-server` / `cyberia-client` names (and their
|
|
394
|
-
// published host ports); `-forest`, `-test`, … for the rest.
|
|
395
|
-
suffix: v.slug ? `-${v.slug}` : '',
|
|
396
|
-
// Backend prefix strip, mirroring the K8s pathRewritePolicy: the Go
|
|
397
|
-
// server serves `/ws` at its root, so a `/FOREST` prefix is stripped.
|
|
398
|
-
strip: path !== '/',
|
|
399
|
-
};
|
|
400
|
-
});
|
|
401
|
-
return { defaultCode, variants };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Renders the nginx reverse-proxy config for the multi-instance Cyberia stack
|
|
406
|
-
* as a SINGLE localhost gateway (dev compose is single-host; production uses
|
|
407
|
-
* the K8s HTTPProxy, not this file). All three tiers share one origin, routed
|
|
408
|
-
* by URL sub-path so `http://localhost/` works with no /etc/hosts:
|
|
409
|
-
* /api/*, /assets/* -> engine-cyberia (shared content authority)
|
|
410
|
-
* /<slug>/ws -> cyberia-server-<slug> (prefix stripped; the Go
|
|
411
|
-
* runtime is instance-agnostic, selected by
|
|
412
|
-
* INSTANCE_CODE), and /ws -> the default server
|
|
413
|
-
* /<slug>, / -> cyberia-client-<slug> (prefix kept; the WASM
|
|
414
|
-
* reads the instance from the URL)
|
|
415
|
-
* Upstreams resolve lazily through Docker's embedded DNS so a variant that is
|
|
416
|
-
* briefly down never fails nginx startup. Returns null for single-instance.
|
|
417
|
-
* @param {object} options - CLI options.
|
|
418
|
-
* @returns {?string} nginx.conf content, or null.
|
|
419
|
-
* @memberof UnderpostDockerCompose
|
|
420
|
-
*/
|
|
421
|
-
static instancesNginxContent(options = {}) {
|
|
422
|
-
const topology = UnderpostDockerCompose.instanceTopology(options);
|
|
423
|
-
if (!topology) return null;
|
|
424
|
-
const { variants } = topology;
|
|
425
|
-
|
|
426
|
-
const headers = ` proxy_set_header Host $host;
|
|
427
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
428
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
429
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
430
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
431
|
-
proxy_set_header Connection $connection_upgrade;
|
|
432
|
-
proxy_read_timeout 3600s;`;
|
|
433
|
-
|
|
434
|
-
const proxyBlock = (variable, upstream, extra = '') =>
|
|
435
|
-
` set ${variable} ${upstream};
|
|
436
|
-
${extra} proxy_pass http://${variable};
|
|
437
|
-
${headers}`;
|
|
438
|
-
|
|
439
|
-
// Websocket tier: /<path>/ws -> server (prefix stripped); default -> = /ws.
|
|
440
|
-
// Longest-prefix ensures /<path>/ws beats the /<path> client location.
|
|
441
|
-
const wsLocations = variants
|
|
442
|
-
.map((v) => {
|
|
443
|
-
const upstream = `cyberia-server${v.suffix}:8081`;
|
|
444
|
-
const varName = `$up_server_${v.slug || 'default'}`;
|
|
445
|
-
if (v.isDefault) return ` location = /ws {\n${proxyBlock(varName, upstream)}\n }`;
|
|
446
|
-
return ` location ${v.path}/ws {\n${proxyBlock(
|
|
447
|
-
varName,
|
|
448
|
-
upstream,
|
|
449
|
-
` rewrite ^${v.path}/(.*)$ /$1 break;\n`,
|
|
450
|
-
)}\n }`;
|
|
451
|
-
})
|
|
452
|
-
.join('\n\n');
|
|
453
|
-
|
|
454
|
-
// Presentation tier: /<path> -> client (prefix kept); default -> catch-all /.
|
|
455
|
-
const clientLocations = variants
|
|
456
|
-
.map((v) => {
|
|
457
|
-
const upstream = `cyberia-client${v.suffix}:8081`;
|
|
458
|
-
const varName = `$up_client_${v.slug || 'default'}`;
|
|
459
|
-
if (v.isDefault) return ` location / {\n${proxyBlock(varName, upstream)}\n }`;
|
|
460
|
-
return ` location = ${v.path} { return 301 ${v.path}/; }
|
|
461
|
-
location ${v.path} {\n${proxyBlock(varName, upstream)}\n }`;
|
|
462
|
-
})
|
|
463
|
-
.join('\n\n');
|
|
464
|
-
|
|
465
|
-
const deployId = options.deployId || DEFAULT_DEPLOY_ID;
|
|
466
|
-
return `# Generated by 'underpost docker-compose --generate --deploy-id ${deployId} --docker-compose-id ${options.dockerComposeId || 'cyberia'}' — do not hand-edit.
|
|
467
|
-
# Source of truth: engine-private/conf/${deployId}/conf.instances.json (multiInstance).
|
|
468
|
-
# Single localhost gateway (dev compose). Access http://localhost/ (default),
|
|
469
|
-
# http://localhost/<PATH> per variant. Client origins point back here
|
|
470
|
-
# (compose.env CYBERIA_WS_ORIGIN=ws://localhost, CYBERIA_ENGINE_API_ORIGIN=http://localhost).
|
|
471
|
-
|
|
472
|
-
map $http_upgrade $connection_upgrade {
|
|
473
|
-
default upgrade;
|
|
474
|
-
'' close;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
# Docker embedded DNS: resolve upstream names per request so a variant that is
|
|
478
|
-
# briefly down never fails nginx startup or config reload.
|
|
479
|
-
resolver 127.0.0.11 ipv6=off valid=10s;
|
|
480
|
-
|
|
481
|
-
proxy_http_version 1.1;
|
|
482
|
-
|
|
483
|
-
server {
|
|
484
|
-
listen 80 default_server;
|
|
485
|
-
server_name localhost 127.0.0.1 _;
|
|
486
|
-
|
|
487
|
-
location = /healthz {
|
|
488
|
-
access_log off;
|
|
489
|
-
return 200 "ok\\n";
|
|
490
|
-
add_header Content-Type text/plain;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
# Shared content authority: REST API + engine-served assets (fonts, ui-icons).
|
|
494
|
-
location /api/ {
|
|
495
|
-
${proxyBlock('$up_engine', 'engine-cyberia:4005')}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
location /assets/ {
|
|
499
|
-
${proxyBlock('$up_engine', 'engine-cyberia:4005')}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
${wsLocations}
|
|
503
|
-
|
|
504
|
-
${clientLocations}
|
|
505
|
-
}
|
|
506
|
-
`;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
365
|
/**
|
|
510
366
|
* Renders all dynamic supporting files: the nginx reverse-proxy config (from
|
|
511
367
|
* PROXY_HOSTS), the monitoring configs (Prometheus + Grafana datasource), and
|
|
@@ -516,25 +372,17 @@ ${clientLocations}
|
|
|
516
372
|
* @memberof UnderpostDockerCompose
|
|
517
373
|
*/
|
|
518
374
|
static generate(options = {}) {
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
// bootstrap) and, for a multi-instance deploy, nginx.conf (per-variant
|
|
523
|
-
// sub-path routing derived from the conf.instances.json multiInstance block,
|
|
524
|
-
// kept in sync with the service tiers hand-authored in docker-compose.yml).
|
|
375
|
+
// A named custom workflow is fully owned by its canonical directory. The
|
|
376
|
+
// generic CLI validates the standard entry files but never imports project
|
|
377
|
+
// modules or generates application-specific nginx, env, or helper content.
|
|
525
378
|
const composeIdBase = UnderpostDockerCompose.composeIdBase(options);
|
|
526
379
|
if (composeIdBase) {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (nginx) {
|
|
534
|
-
const nginxPath = UnderpostDockerCompose.resolve(`${composeIdBase}/nginx.conf`);
|
|
535
|
-
fs.writeFileSync(nginxPath, nginx, 'utf8');
|
|
536
|
-
logger.info('multi-instance nginx.conf written', { path: nginxPath });
|
|
537
|
-
}
|
|
380
|
+
const required = ['docker-compose.yml', 'compose.env'].map((name) =>
|
|
381
|
+
UnderpostDockerCompose.resolve(`${composeIdBase}/${name}`),
|
|
382
|
+
);
|
|
383
|
+
const missing = required.filter((path) => !fs.existsSync(path));
|
|
384
|
+
if (missing.length) throw new Error(`Custom Docker Compose workflow is missing: ${missing.join(', ')}`);
|
|
385
|
+
logger.info('custom Docker Compose workflow uses canonical files as-is', { path: composeIdBase });
|
|
538
386
|
return;
|
|
539
387
|
}
|
|
540
388
|
|
|
@@ -644,17 +492,10 @@ ${clientLocations}
|
|
|
644
492
|
silentOnError: true,
|
|
645
493
|
});
|
|
646
494
|
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
// nginx.conf is left in place so the mount stays valid and --generate/--up
|
|
650
|
-
// rewrites it.
|
|
495
|
+
// A named custom workflow owns every file beneath its canonical directory.
|
|
496
|
+
// Reset tears down Docker resources but never prunes project-owned files.
|
|
651
497
|
const composeIdBase = UnderpostDockerCompose.composeIdBase(options);
|
|
652
498
|
if (composeIdBase) {
|
|
653
|
-
const mongoEntrypointPath = UnderpostDockerCompose.resolve(`${composeIdBase}/mongodb/entrypoint.sh`);
|
|
654
|
-
if (fs.existsSync(mongoEntrypointPath)) {
|
|
655
|
-
fs.removeSync(mongoEntrypointPath);
|
|
656
|
-
logger.info('removed generated artifact', { path: mongoEntrypointPath });
|
|
657
|
-
}
|
|
658
499
|
logger.info('Docker Compose reset complete. Run `--up` to recreate the stack.');
|
|
659
500
|
return;
|
|
660
501
|
}
|
|
@@ -699,7 +540,7 @@ ${clientLocations}
|
|
|
699
540
|
* @param {boolean} [options.shell] - Open an interactive shell in `target` (default: app).
|
|
700
541
|
* @param {string} [options.exec] - General-purpose passthrough docker compose subcommand.
|
|
701
542
|
* @param {string} [options.deployId] - Deployment to run as the app (default: dd-default). `dd-default` self-bootstraps a fresh engine; any other id runs the standard `underpost start` command.
|
|
702
|
-
* @param {string} [options.dockerComposeId] - Custom-workflow selector. When set, use the canonical stack at `engine-private/conf/<deploy-id>/docker-compose/<docker-compose-id>/` (docker-compose.yml + compose.env
|
|
543
|
+
* @param {string} [options.dockerComposeId] - Custom-workflow selector. When set, use the canonical stack at `engine-private/conf/<deploy-id>/docker-compose/<docker-compose-id>/` (docker-compose.yml + compose.env, used as-is), skipping nginx/env/app-override generation.
|
|
703
544
|
* @param {string} [options.env] - Deployment environment for non-default deploy ids (default: development).
|
|
704
545
|
* @param {string} [options.composeFile] - Override compose file path.
|
|
705
546
|
* @param {string} [options.envFile] - Override env-file path.
|
package/src/cli/image.js
CHANGED
|
@@ -12,6 +12,7 @@ import { loggerFactory } from '../server/logger.js';
|
|
|
12
12
|
import Underpost from '../index.js';
|
|
13
13
|
import { getNpmRootPath } from '../server/conf.js';
|
|
14
14
|
import { shellExec } from '../server/process.js';
|
|
15
|
+
import { crictlCommandFactory } from '../server/cri.js';
|
|
15
16
|
|
|
16
17
|
const logger = loggerFactory(import.meta);
|
|
17
18
|
|
|
@@ -225,6 +226,7 @@ class UnderpostImage {
|
|
|
225
226
|
* @param {object} options - Options for the image retrieval.
|
|
226
227
|
* @param {boolean} options.spec - Whether to retrieve images from the pod specifications.
|
|
227
228
|
* @param {string} options.namespace - Kubernetes namespace to filter pods.
|
|
229
|
+
* @param {boolean} [options.k3s] - Resolve the CRI endpoint as K3s' embedded containerd.
|
|
228
230
|
* @returns {Array<object>} - Array of objects containing pod names and their corresponding images.
|
|
229
231
|
* @memberof UnderpostImage
|
|
230
232
|
*/
|
|
@@ -245,10 +247,17 @@ class UnderpostImage {
|
|
|
245
247
|
}))
|
|
246
248
|
.filter((o) => o.image);
|
|
247
249
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
// Outside kind, target the live CRI endpoint explicitly: /etc/crictl.yaml
|
|
251
|
+
// may still point at a CRI-O socket the host no longer runs.
|
|
252
|
+
const raw = shellExec(
|
|
253
|
+
node === 'kind-worker'
|
|
254
|
+
? `docker exec -i ${node} crictl images`
|
|
255
|
+
: crictlCommandFactory('images', options || {}),
|
|
256
|
+
{
|
|
257
|
+
stdout: true,
|
|
258
|
+
silent: true,
|
|
259
|
+
},
|
|
260
|
+
);
|
|
252
261
|
|
|
253
262
|
const heads = raw
|
|
254
263
|
.split(`\n`)[0]
|
|
@@ -312,7 +321,7 @@ class UnderpostImage {
|
|
|
312
321
|
shellExec(`docker exec -i kind-control-plane crictl rmi ${imageName}`);
|
|
313
322
|
shellExec(`docker exec -i kind-worker crictl rmi ${imageName}`);
|
|
314
323
|
} else if (kubeadm === true) {
|
|
315
|
-
shellExec(`
|
|
324
|
+
shellExec(crictlCommandFactory(`rmi ${imageName}`));
|
|
316
325
|
} else if (k3s === true) {
|
|
317
326
|
shellExec(`sudo k3s ctr images rm ${imageName}`);
|
|
318
327
|
}
|
|
@@ -381,7 +390,7 @@ class UnderpostImage {
|
|
|
381
390
|
shellExec(`docker pull ${image}`);
|
|
382
391
|
shellExec(`sudo kind load docker-image ${image}`);
|
|
383
392
|
} else {
|
|
384
|
-
shellExec(`
|
|
393
|
+
shellExec(crictlCommandFactory(`pull ${image}`, { k3s: targetK3s }));
|
|
385
394
|
}
|
|
386
395
|
},
|
|
387
396
|
};
|
package/src/cli/index.js
CHANGED
|
@@ -264,7 +264,8 @@ program
|
|
|
264
264
|
.option('--reset', `Deletes all clusters and prunes all related data and caches.`)
|
|
265
265
|
.option(
|
|
266
266
|
'--reset-mongodb',
|
|
267
|
-
`Performs a hard cleanup of only MongoDB-related resources (StatefulSet, PVCs/PVs, Secrets, ConfigMaps, caches) without restarting the whole node
|
|
267
|
+
`Performs a hard cleanup of only MongoDB-related resources (StatefulSet, PVCs/PVs, Secrets, ConfigMaps, caches) without restarting the whole node. ` +
|
|
268
|
+
`Combined with --mongodb it instead wipes the retained hostPath volumes as part of that deploy, so the replica set starts from empty data.`,
|
|
268
269
|
)
|
|
269
270
|
.option('--mariadb', 'Initializes the cluster with a MariaDB statefulset.')
|
|
270
271
|
.option('--mysql', 'Initializes the cluster with a MySQL statefulset.')
|
|
@@ -275,6 +276,16 @@ program
|
|
|
275
276
|
.option('--valkey', 'Initializes the cluster with a Valkey service.')
|
|
276
277
|
.option('--ipfs', 'Initializes the cluster with an ipfs-cluster statefulset.')
|
|
277
278
|
.option('--contour', 'Initializes the cluster with Project Contour base HTTPProxy and Envoy.')
|
|
279
|
+
.option(
|
|
280
|
+
'--gateway-api',
|
|
281
|
+
'Initializes the cluster with the Gateway API control plane (CRDs, Envoy Gateway, GatewayClass) used by generated HTTPRoute + QUIC/HTTP3 manifests. ' +
|
|
282
|
+
'With --dev the data plane binds the listener ports on the host network for direct browser access.',
|
|
283
|
+
)
|
|
284
|
+
.option('--gateway-class <name>', 'GatewayClass name to provision (default "eg").')
|
|
285
|
+
.option(
|
|
286
|
+
'--ingress-node <node-name>',
|
|
287
|
+
'Dedicated node for underpost-ingress when both routing stacks coexist. Workload placement flags do not move it.',
|
|
288
|
+
)
|
|
278
289
|
.option(
|
|
279
290
|
'--node-port',
|
|
280
291
|
'Exposes enabled ready services (e.g. MongoDB 4.4, Valkey) to the host/public network via their NodePort Service manifest.',
|
|
@@ -332,7 +343,6 @@ program
|
|
|
332
343
|
.option('--remove', 'Deletes specified deployments and their associated services.')
|
|
333
344
|
.option('--sync', 'Synchronizes deployment environment variables, ports, and replica counts.')
|
|
334
345
|
.option('--info-router', 'Displays the current router structure and configuration.')
|
|
335
|
-
.option('--expose', 'Exposes services matching the provided deployment ID list.')
|
|
336
346
|
.option('--cert', 'Resets TLS/SSL certificate secrets for deployments.')
|
|
337
347
|
.option('--cert-hosts <hosts>', 'Resets TLS/SSL certificate secrets for specified hosts.')
|
|
338
348
|
.option(
|
|
@@ -342,6 +352,10 @@ program
|
|
|
342
352
|
'Enables TLS in the Contour HTTPProxy virtualhost without requiring a production ClusterIssuer.',
|
|
343
353
|
)
|
|
344
354
|
.option('--node <node>', 'Sets optional node for deployment operations.')
|
|
355
|
+
.option(
|
|
356
|
+
'--ingress-node <node-name>',
|
|
357
|
+
'Explicitly relocates the shared host-network ingress; ordinary --node placement never moves it.',
|
|
358
|
+
)
|
|
345
359
|
.option(
|
|
346
360
|
'--ssh-key-path <path>',
|
|
347
361
|
'Private key path for node SSH operations. Currently used when shipping a hostPath volume to a remote target node over SSH. Defaults to engine-private/deploy/id_rsa.',
|
|
@@ -350,6 +364,10 @@ program
|
|
|
350
364
|
'--build-manifest',
|
|
351
365
|
'Builds Kubernetes YAML manifests, including deployments, services, proxies, and secrets.',
|
|
352
366
|
)
|
|
367
|
+
.option(
|
|
368
|
+
'--sync-static',
|
|
369
|
+
'Places the SSR status pages and intercepted contexts in the gateway static utility tree, so the edge serves them instead of the application pods. Prefers the running workload and falls back to this checkout, so it can seed the tree before the deployment exists and refresh it once the deployment is Ready.',
|
|
370
|
+
)
|
|
353
371
|
.option('--replicas <replicas>', 'Sets a custom number of replicas for deployments.')
|
|
354
372
|
.option('--image <image>', 'Sets a custom image for deployments.')
|
|
355
373
|
.option('--versions <deployment-versions>', 'A comma-separated list of custom deployment versions.')
|
|
@@ -362,35 +380,31 @@ program
|
|
|
362
380
|
.option('--retry-count <count>', 'Sets HTTPProxy per-route retry count (e.g., 3).')
|
|
363
381
|
.option('--retry-per-try-timeout <duration>', 'Sets HTTPProxy retry per-try timeout (e.g., "150ms").')
|
|
364
382
|
.option('--disable-update-deployment', 'Disables updates to deployments.')
|
|
365
|
-
.option(
|
|
383
|
+
.option(
|
|
384
|
+
'--disable-runtime-probes',
|
|
385
|
+
'Deprecated compatibility flag; readiness probes remain mandatory. Use --tcp-probes for legacy workloads.',
|
|
386
|
+
)
|
|
366
387
|
.option('--tcp-probes', 'Generates legacy TCP socket probes instead of HTTP internal-status probes (migration).')
|
|
367
388
|
.option('--disable-update-proxy', 'Disables updates to proxies.')
|
|
368
389
|
.option('--disable-deployment-proxy', 'Disables proxies of deployments.')
|
|
369
|
-
.option('--disable-update-volume', 'Disables updates to volume mounts during deployment.')
|
|
370
390
|
.option(
|
|
371
|
-
'--
|
|
372
|
-
'
|
|
391
|
+
'--gateway-api',
|
|
392
|
+
'Routes through the Gateway API stack (Gateway + HTTPRoute) instead of the Contour HTTPProxy. ' +
|
|
393
|
+
'Both manifest sets are always generated; this selects which one is applied.',
|
|
373
394
|
)
|
|
395
|
+
.option('--gateway-class <name>', 'GatewayClass name for generated Gateway manifests (default "eg").')
|
|
396
|
+
.option(
|
|
397
|
+
'--disable-http3',
|
|
398
|
+
'Omits the QUIC/HTTP3 listener config and the Alt-Svc advertisement from Gateway API manifests.',
|
|
399
|
+
)
|
|
400
|
+
.option('--quic-port <port>', 'UDP port advertised for QUIC/HTTP3 in generated Gateway API manifests (default 443).')
|
|
401
|
+
.option('--disable-update-volume', 'Disables updates to volume mounts during deployment.')
|
|
374
402
|
.option('--kubeadm', 'Enables the kubeadm context for deployment operations.')
|
|
375
403
|
.option('--k3s', 'Enables the k3s context for deployment operations.')
|
|
376
404
|
.option('--kind', 'Enables the kind context for deployment operations.')
|
|
377
405
|
.option('--git-clean', 'Runs git clean on volume mount paths before copying.')
|
|
378
406
|
.option('--disable-update-underpost-config', 'Disables updates to Underpost configuration during deployment.')
|
|
379
407
|
.option('--namespace <namespace>', 'Kubernetes namespace for deployment operations (defaults to "default").')
|
|
380
|
-
.option('--kind-type <kind-type>', 'Specifies the Kind cluster type for deployment operations.')
|
|
381
|
-
.option('--port <port>', 'Sets up port forwarding from local to remote ports.')
|
|
382
|
-
.option(
|
|
383
|
-
'--expose-port <port>',
|
|
384
|
-
'Sets the local:remote port to expose when --expose is active (overrides auto-detected service port).',
|
|
385
|
-
)
|
|
386
|
-
.option(
|
|
387
|
-
'--expose-local-port <port>',
|
|
388
|
-
'Sets a different local port for --expose (e.g. 80) while keeping the remote service port. Useful for /etc/hosts local access without specifying a port in the browser.',
|
|
389
|
-
)
|
|
390
|
-
.option(
|
|
391
|
-
'--local-proxy',
|
|
392
|
-
'Forward all service TCP ports locally and start the Node.js path-routing proxy. Enables full path-based routing (e.g. /wp alongside /) without needing --expose-local-port. Requires --expose.',
|
|
393
|
-
)
|
|
394
408
|
.option('--cmd <cmd>', 'Custom initialization command for deployment (comma-separated commands).')
|
|
395
409
|
.option(
|
|
396
410
|
'--skip-full-build',
|
|
@@ -404,30 +418,71 @@ program
|
|
|
404
418
|
'--image-pull-policy <policy>',
|
|
405
419
|
'Override container imagePullPolicy in the generated deployment manifest (Always, IfNotPresent, Never). Defaults to Never for localhost/ images and IfNotPresent otherwise.',
|
|
406
420
|
)
|
|
407
|
-
.option(
|
|
408
|
-
'--tls',
|
|
409
|
-
'Enables TLS for the local proxy started by --expose --local-proxy. ' +
|
|
410
|
-
'The proxy will serve HTTPS on port 443 using self-signed certificates resolved from the local SSL store. ' +
|
|
411
|
-
'Use together with --expose and --local-proxy.',
|
|
412
|
-
)
|
|
413
421
|
.description('Manages application deployments, defaulting to deploying development pods.')
|
|
414
422
|
.action(Underpost.deploy.callback);
|
|
415
423
|
|
|
416
424
|
program
|
|
417
425
|
.command('secret')
|
|
418
|
-
.argument(
|
|
426
|
+
.argument(
|
|
427
|
+
'[platform]',
|
|
428
|
+
`The secret management platform. Options: ${Object.keys(Underpost.secret).join(', ')}. Defaults to "sops".`,
|
|
429
|
+
'sops',
|
|
430
|
+
)
|
|
419
431
|
.option('--init', 'Initializes the secrets platform environment.')
|
|
420
432
|
.option('--create-from-file <path-env-file>', 'Creates secrets from a specified environment file.')
|
|
421
433
|
.option('--create-from-env', 'Creates secrets from container environment variables (envFrom: secretRef).')
|
|
422
434
|
.option('--global-clean', 'Removes all filesystem traces of secrets (engine-private, .env, conf cache).')
|
|
423
435
|
.option('--list', 'Lists all available secrets for the platform.')
|
|
436
|
+
.option(
|
|
437
|
+
'--encrypt <plaintext-path>',
|
|
438
|
+
'Encrypts a plaintext Secret manifest into the Git-tracked SOPS store and shreds the source (sops platform).',
|
|
439
|
+
)
|
|
440
|
+
.option(
|
|
441
|
+
'--apply',
|
|
442
|
+
'Decrypts stored SOPS manifests and streams them into kubectl apply, without writing plaintext to disk (sops platform).',
|
|
443
|
+
)
|
|
444
|
+
.option('--namespace <namespace>', 'Kubernetes namespace for secret operations (defaults to "default").')
|
|
445
|
+
.option(
|
|
446
|
+
'--install-tools',
|
|
447
|
+
'Installs the sops and age host binaries only, without running a full cluster host initialization.',
|
|
448
|
+
)
|
|
449
|
+
.option(
|
|
450
|
+
'--rotate',
|
|
451
|
+
'Re-keys every stored SOPS manifest onto --recipient. Secret values are unchanged, so no workload restart is needed.',
|
|
452
|
+
)
|
|
453
|
+
.option('--recipient <age-public-key>', 'Incoming Age public recipient for --rotate.')
|
|
454
|
+
.option(
|
|
455
|
+
'--prune-recipients',
|
|
456
|
+
'With --rotate, makes --recipient the only recipient, revoking every previous key (use after a key compromise). ' +
|
|
457
|
+
'Requires --force, and revokes CI/CD keys too unless they are named in --keep-recipients.',
|
|
458
|
+
)
|
|
459
|
+
.option(
|
|
460
|
+
'--keep-recipients <age-public-keys>',
|
|
461
|
+
'Comma-separated recipients to retain while --prune-recipients revokes the rest (e.g. the CI/CD key).',
|
|
462
|
+
)
|
|
463
|
+
.option(
|
|
464
|
+
'--purge <secret-name>',
|
|
465
|
+
'Emergency removal: deletes the live Kubernetes Secret and takes its encrypted manifest out of the store.',
|
|
466
|
+
)
|
|
467
|
+
.option(
|
|
468
|
+
'--force',
|
|
469
|
+
'Confirms the irreversible variant: deletes the manifest instead of archiving it (--purge), ' +
|
|
470
|
+
'revokes recipients (--rotate --prune-recipients), or replaces an existing manifest (--encrypt).',
|
|
471
|
+
)
|
|
472
|
+
.option('--dry-run', 'Reports what --apply, --rotate, or --purge would do without changing anything.')
|
|
424
473
|
.description(`Manages secrets for various platforms.`)
|
|
425
|
-
.action((
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
if (
|
|
429
|
-
if (
|
|
430
|
-
if (
|
|
474
|
+
.action((platform, options) => {
|
|
475
|
+
// Host tooling install is platform-independent, so it resolves before any platform lookup.
|
|
476
|
+
if (options.installTools) return Underpost.secret.sops.installTooling();
|
|
477
|
+
if (options.globalClean) return Underpost.secret.globalSecretClean();
|
|
478
|
+
if (options.rotate) return Underpost.secret[platform].rotate(options.recipient, options);
|
|
479
|
+
if (options.purge) return Underpost.secret[platform].purge(options.purge, options);
|
|
480
|
+
if (options.encrypt) return Underpost.secret[platform].encrypt(options.encrypt, options.namespace, options);
|
|
481
|
+
if (options.apply) return Underpost.secret[platform].apply(options.namespace, options);
|
|
482
|
+
if (options.createFromFile) return Underpost.secret[platform].createFromEnvFile(options.createFromFile);
|
|
483
|
+
if (options.createFromEnv) return Underpost.secret[platform].createFromContainerEnv();
|
|
484
|
+
if (options.list) return Underpost.secret[platform].list();
|
|
485
|
+
if (options.init) return Underpost.secret[platform].init();
|
|
431
486
|
});
|
|
432
487
|
|
|
433
488
|
program
|
|
@@ -652,11 +707,24 @@ program
|
|
|
652
707
|
.option('--replicas <replicas>', 'Sets a custom number of replicas for deployment.')
|
|
653
708
|
.option('--pod-name <pod-name>', 'Optional: Specifies the pod name for execution.')
|
|
654
709
|
.option('--node-name <node-name>', 'Optional: Specifies the node name for execution.')
|
|
710
|
+
.option(
|
|
711
|
+
'--ingress-node <node-name>',
|
|
712
|
+
'Dedicated node for the host-network underpost-ingress listener. Workload --node-name never relocates it.',
|
|
713
|
+
)
|
|
655
714
|
.option(
|
|
656
715
|
'--ssh-key-path <path>',
|
|
657
716
|
'Optional: Private key path for node SSH operations, forwarded to volume shipping over SSH. Defaults to engine-private/deploy/id_rsa.',
|
|
658
717
|
)
|
|
659
718
|
.option('--port <port>', 'Optional: Specifies the port for execution.')
|
|
719
|
+
.option(
|
|
720
|
+
'--expose-container-ports <ports>',
|
|
721
|
+
'Comma-separated Service/container ports; multiple matched resources consume values by resource index.',
|
|
722
|
+
)
|
|
723
|
+
.option(
|
|
724
|
+
'--expose-host-ports <ports>',
|
|
725
|
+
'Comma-separated host ports paired with container ports by resource/port index.',
|
|
726
|
+
)
|
|
727
|
+
.option('--local-proxy', 'Starts the development path proxy after the expose runner creates its port-forwards.')
|
|
660
728
|
.option('--etc-hosts', 'Enables etc-hosts context for the runner execution.')
|
|
661
729
|
.option('--volume-host-path <volume-host-path>', 'Optional: Specifies the volume host path for test execution.')
|
|
662
730
|
.option('--volume-mount-path <volume-mount-path>', 'Optional: Specifies the volume mount path for test execution.')
|
|
@@ -692,7 +760,10 @@ program
|
|
|
692
760
|
'--resource-template-id <resource-template-id >',
|
|
693
761
|
'Specifies a resource template ID for the runner execution.',
|
|
694
762
|
)
|
|
695
|
-
.option(
|
|
763
|
+
.option(
|
|
764
|
+
'--expose',
|
|
765
|
+
'Enables exposure-only behavior in compatible runners; the expose runner itself does not require this flag.',
|
|
766
|
+
)
|
|
696
767
|
.option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
|
|
697
768
|
.option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
|
|
698
769
|
.option('--cmd-cron-jobs <cmd-cron-jobs>', 'Pre-script commands to run before cron job execution.')
|
|
@@ -704,7 +775,10 @@ program
|
|
|
704
775
|
.option('--kubeadm', 'Sets the kubeadm cluster context for the runner execution.')
|
|
705
776
|
.option('--k3s', 'Sets the k3s cluster context for the runner execution.')
|
|
706
777
|
.option('--kind', 'Sets the kind cluster context for the runner execution.')
|
|
707
|
-
.option(
|
|
778
|
+
.option(
|
|
779
|
+
'--traffic <traffic>',
|
|
780
|
+
'Blue/green traffic colour to bake into generated manifests (default: blue). `stop` accepts a comma list, e.g. blue,green.',
|
|
781
|
+
)
|
|
708
782
|
.option('--git-clean', 'Runs git clean on volume mount paths before copying.')
|
|
709
783
|
.option('--deploy-id <deploy-id>', 'Sets deploy id context for the runner execution.')
|
|
710
784
|
.option('--user <user>', 'Sets user context for the runner execution.')
|
|
@@ -718,6 +792,21 @@ program
|
|
|
718
792
|
.option('--timeout-idle <duration>', 'Sets HTTPProxy per-route idle timeout (e.g., "10s", "infinity").')
|
|
719
793
|
.option('--retry-count <count>', 'Sets HTTPProxy per-route retry count (e.g., 3).')
|
|
720
794
|
.option('--retry-per-try-timeout <duration>', 'Sets HTTPProxy retry per-try timeout (e.g., "150ms").')
|
|
795
|
+
.option(
|
|
796
|
+
'--gateway-api',
|
|
797
|
+
'Routes through the Gateway API stack (Gateway + HTTPRoute) instead of the Contour HTTPProxy. ' +
|
|
798
|
+
'Both manifest sets are always generated; this selects which one is applied.',
|
|
799
|
+
)
|
|
800
|
+
.option(
|
|
801
|
+
'--disable-gateway-api',
|
|
802
|
+
'Falls back to the Contour HTTPProxy stack in runners where the Gateway API is the default (cluster).',
|
|
803
|
+
)
|
|
804
|
+
.option('--gateway-class <name>', 'GatewayClass name for generated Gateway manifests (default "eg").')
|
|
805
|
+
.option(
|
|
806
|
+
'--disable-http3',
|
|
807
|
+
'Omits the QUIC/HTTP3 listener config and the Alt-Svc advertisement from Gateway API manifests.',
|
|
808
|
+
)
|
|
809
|
+
.option('--quic-port <port>', 'UDP port advertised for QUIC/HTTP3 in generated Gateway API manifests (default 443).')
|
|
721
810
|
.option('--disable-private-conf-update', 'Disables updates to private configuration during execution.')
|
|
722
811
|
.option('--logs', 'Streams logs during the runner execution.')
|
|
723
812
|
.option('--monitor-status <status>', 'Sets the status to monitor for pod/resource (default: "Running").')
|