@underpostnet/cyberia 3.3.73 → 3.3.77
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 +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +10 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +1 -1
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +159 -161
- package/CLI-HELP.md +20 -13
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +96 -21
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -4
- package/deploy/cyberia-client/deploy.sh +10 -11
- package/deploy/cyberia-client/package.sh +0 -1
- package/deploy/cyberia-client/state.sh +3 -2
- package/deploy/cyberia-server/deploy.sh +10 -11
- package/deploy/cyberia-server/package.sh +0 -1
- package/deploy/cyberia-server/state.sh +3 -2
- package/deploy/dd-cyberia/deploy.sh +5 -5
- package/deploy/dd-cyberia/init.sh +15 -14
- package/deploy/dd-cyberia/package.sh +0 -1
- package/deploy/dd-cyberia/state.sh +2 -2
- package/deploy/dd-cyberia/sync-deploy.sh +139 -75
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +92 -76
- package/deploy/lib/host.sh +28 -19
- package/deploy/lib/state.sh +4 -1
- package/deploy/pwa-microservices-template/deploy.sh +10 -8
- package/deploy/release/deploy.sh +0 -2
- package/deployment.yaml +1 -1
- package/docker-compose.yml +23 -17
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
- package/docs/coverage/cyberia/base.css +362 -0
- package/docs/coverage/cyberia/block-navigation.js +82 -0
- package/docs/coverage/cyberia/favicon.png +0 -0
- package/docs/coverage/cyberia/index.html +161 -0
- package/docs/coverage/cyberia/prettify.css +101 -0
- package/docs/coverage/cyberia/prettify.js +937 -0
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
- package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
- package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
- package/docs/coverage/cyberia/sorter.js +205 -0
- package/hardhat/package-lock.json +2 -2
- package/hardhat/package.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/package.json +19 -17
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
- package/src/api/cyberia-action/cyberia-action.router.js +2 -2
- package/src/api/cyberia-action/cyberia-action.service.js +3 -4
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
- package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
- package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
- package/src/api/cyberia-map/cyberia-map.service.js +6 -2
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
- package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +10 -3
- package/src/api/object-layer/object-layer.service.js +113 -120
- package/src/cli/app.js +12 -1
- package/src/cli/client.js +2 -2
- package/src/cli/cluster.js +80 -18
- package/src/cli/deploy.js +215 -34
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +158 -342
- package/src/cli/index.js +22 -10
- package/src/cli/release.js +3 -1
- package/src/cli/repository.js +35 -9
- package/src/cli/run.js +80 -199
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Badge.js +0 -1
- package/src/client/components/core/CssCore.js +8 -1
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +77 -23
- package/src/client/components/core/ToolTip.js +15 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -4
- package/src/client/components/itemledger/AppShellItemledger.js +0 -2
- package/src/client/components/underpost/AppShellUnderpost.js +1 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/views/Cyberia404.js +3 -3
- package/src/client-builder/client-build-docs.js +52 -52
- package/src/client-builder/client-build.js +67 -44
- package/src/client-builder/client-bundle.js +347 -0
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/grpc/cyberia/grpc-server.js +5 -7
- package/src/index.js +1 -1
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +5 -14
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +3 -2
- package/src/projects/cyberia/gemini-client.js +3 -3
- package/src/projects/cyberia/generate-saga.js +4 -11
- package/src/projects/cyberia/hot-reload-trigger.js +17 -28
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +177 -257
- package/src/projects/cyberia/map-preview-generator.js +32 -68
- package/src/projects/cyberia/object-layer.js +152 -319
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +2 -18
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/runtime/cyberia-client/Dockerfile +8 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -2
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/server/build/coverage.js +113 -42
- package/src/server/build/package.js +55 -12
- package/src/server/build/testing.js +59 -9
- package/src/server/network/middlewares.js +8 -2
- package/src/server/network/underpost-gateway.js +113 -14
- package/src/server/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +40 -9
- package/src/server/runtime/start.js +11 -2
- package/src/server/storage/data-query.js +16 -0
- package/src/server/storage/downloader.js +62 -14
- package/src/server/storage/zip.js +153 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
- package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
- package/test/unit/catalog.test.js +13 -2
- package/test/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +58 -7
- package/test/unit/coverage-artifact.test.js +120 -29
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-data.test.js +314 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
- package/test/unit/deploy-log-table.test.js +76 -5
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/gateway-pod-fetch.test.js +95 -0
- package/test/unit/letsencrypt-issuer.test.js +101 -0
- package/test/unit/logger-redaction.test.js +29 -0
- package/test/unit/package.test.js +70 -16
- package/test/unit/prepare-host.test.js +84 -1
- package/test/unit/release-bump.test.js +3 -1
- package/test/unit/start-options.test.js +12 -0
- package/test/unit/test-tiers.test.js +27 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +11 -3
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client bundle transport: enumerates the zip artifacts a deployment's routes build, uploads
|
|
3
|
+
* them to file storage, and restores them into the served public directory.
|
|
4
|
+
*
|
|
5
|
+
* Push and pull are two directions of one contract, so both resolve their routes, bundle ids,
|
|
6
|
+
* storage keys and part files through the helpers here rather than re-deriving them. Route
|
|
7
|
+
* selection mirrors {@link buildClient}: a route that the build skips produces no artifact for
|
|
8
|
+
* either direction to carry.
|
|
9
|
+
* @module src/client-builder/client-bundle.js
|
|
10
|
+
* @namespace clientBundle
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import UnderpostFileStorage from '../cli/fs.js';
|
|
15
|
+
import * as dir from 'path';
|
|
16
|
+
import { cli } from '../server/build/execution.js';
|
|
17
|
+
import { loadConfServerJson, loadReplicas } from '../server/runtime/conf.js';
|
|
18
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
19
|
+
import { shellArgumentFactory, shellExec } from '../server/runtime/process.js';
|
|
20
|
+
import { clientBundleIdFactory, getZipPartPaths, mergeClientBuildZip, unzipClientBuild } from './client-build.js';
|
|
21
|
+
|
|
22
|
+
const logger = loggerFactory(import.meta);
|
|
23
|
+
|
|
24
|
+
/** @type {string} Directory `buildClient --build-zip` writes bundles to, and the only place push and pull look. */
|
|
25
|
+
const CLIENT_BUNDLE_DIRECTORY = './build';
|
|
26
|
+
|
|
27
|
+
/** @type {number} Part size in MB applied when the caller names none. */
|
|
28
|
+
const DEFAULT_SPLIT_MB = 8;
|
|
29
|
+
|
|
30
|
+
/** @type {string} `--split` value that turns splitting off. */
|
|
31
|
+
const SPLIT_DISABLED = 'none';
|
|
32
|
+
|
|
33
|
+
const CLIENT_BUNDLE_STORAGE_ID = 'bundle';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the conf.server path a deployment's routes are read from.
|
|
37
|
+
* @function clientBundleConfServerPath
|
|
38
|
+
* @param {string} deployId - The deployment identifier.
|
|
39
|
+
* @returns {string} Path to the deployment's `conf.server.json`.
|
|
40
|
+
* @memberof clientBundle
|
|
41
|
+
*/
|
|
42
|
+
const clientBundleConfServerPath = (deployId) => `./engine-private/conf/${deployId}/conf.server.json`;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the storage manifest that tracks a deployment's uploaded bundle parts. Push writes
|
|
46
|
+
* it and pull reads it, so both must name the same file.
|
|
47
|
+
* @function clientBundleStorageFilePath
|
|
48
|
+
* @param {string} deployId - The deployment identifier.
|
|
49
|
+
* @returns {string} Path to the deployment's bundle storage manifest.
|
|
50
|
+
* @memberof clientBundle
|
|
51
|
+
*/
|
|
52
|
+
const clientBundleStorageFilePath = (deployId) =>
|
|
53
|
+
UnderpostFileStorage.API.resolveManifest({ deployId, storageId: CLIENT_BUNDLE_STORAGE_ID });
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parses the comma-separated host filter both runners accept as their positional argument.
|
|
57
|
+
* @function clientBundleHostFilter
|
|
58
|
+
* @param {string} [hosts] - Comma-separated host names, or empty for every host.
|
|
59
|
+
* @returns {string[]} The requested hosts, empty when no filter was given.
|
|
60
|
+
* @memberof clientBundle
|
|
61
|
+
*/
|
|
62
|
+
const clientBundleHostFilter = (hosts = '') =>
|
|
63
|
+
`${hosts ?? ''}`
|
|
64
|
+
.split(',')
|
|
65
|
+
.map((host) => host.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the `--split` option into the flag `underpost client` expects.
|
|
70
|
+
* @function clientBundleSplitFlag
|
|
71
|
+
* @param {string|number} [split] - Part size in MB, or `none` to disable splitting.
|
|
72
|
+
* @returns {string} The flag to append, empty when splitting is disabled.
|
|
73
|
+
* @memberof clientBundle
|
|
74
|
+
*/
|
|
75
|
+
const clientBundleSplitFlag = (split) => {
|
|
76
|
+
const requested = `${split ?? ''}`.trim();
|
|
77
|
+
if (!requested) return `--split ${DEFAULT_SPLIT_MB}`;
|
|
78
|
+
if (requested === SPLIT_DISABLED) return '';
|
|
79
|
+
|
|
80
|
+
const splitMb = Number(requested);
|
|
81
|
+
if (Number.isFinite(splitMb) && splitMb > 0) return `--split ${splitMb}`;
|
|
82
|
+
|
|
83
|
+
logger.warn('invalid split option, falling back to the default', { split: requested, default: DEFAULT_SPLIT_MB });
|
|
84
|
+
return `--split ${DEFAULT_SPLIT_MB}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Enumerates the bundles a deployment's routes build to.
|
|
89
|
+
*
|
|
90
|
+
* A route is skipped exactly where {@link buildClient} skips it — redirects and
|
|
91
|
+
* rebuild-disabled routes emit nothing, and a `singleReplica` route's replicas are built under
|
|
92
|
+
* their own deploy ids, so they carry their own manifests rather than the parent's.
|
|
93
|
+
* @function clientBundleEntriesFactory
|
|
94
|
+
* @param {object} options
|
|
95
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
96
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
97
|
+
* @returns {Array<{host: string, routePath: string, buildId: string, zipPath: string, publicPath: string}>}
|
|
98
|
+
* One entry per bundle, in configuration order.
|
|
99
|
+
* @memberof clientBundle
|
|
100
|
+
*/
|
|
101
|
+
const clientBundleEntriesFactory = ({ deployId, hosts = [] }) => {
|
|
102
|
+
const confServerPath = clientBundleConfServerPath(deployId);
|
|
103
|
+
if (!fs.existsSync(confServerPath)) {
|
|
104
|
+
logger.warn('conf.server not found', { deployId, confServerPath });
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const confServer = loadReplicas(deployId, loadConfServerJson(confServerPath));
|
|
109
|
+
const selectedHosts = hosts.length > 0 ? hosts : Object.keys(confServer);
|
|
110
|
+
|
|
111
|
+
return selectedHosts.flatMap((host) => {
|
|
112
|
+
if (!confServer[host]) {
|
|
113
|
+
logger.warn('host not present in conf.server', { deployId, host });
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(confServer[host])
|
|
117
|
+
.filter((routePath) => {
|
|
118
|
+
const { redirect, disabledRebuild, singleReplica } = confServer[host][routePath] ?? {};
|
|
119
|
+
return !redirect && !disabledRebuild && !singleReplica;
|
|
120
|
+
})
|
|
121
|
+
.map((routePath) => {
|
|
122
|
+
const buildId = clientBundleIdFactory(host, routePath);
|
|
123
|
+
return {
|
|
124
|
+
host,
|
|
125
|
+
routePath,
|
|
126
|
+
buildId,
|
|
127
|
+
zipPath: `${CLIENT_BUNDLE_DIRECTORY}/${buildId}.zip`,
|
|
128
|
+
publicPath: routePath === '/' ? `public/${host}` : `public/${host}${routePath}`,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reads the storage manifest as a plain lookup, for the recorded size of each uploaded artifact.
|
|
136
|
+
* Read-only and tolerant: a manifest that is absent or unreadable disables verification rather
|
|
137
|
+
* than failing the pull, since the artifacts themselves are still what the bundle is made of.
|
|
138
|
+
* @function clientBundleManifestFactory
|
|
139
|
+
* @param {string} deployId - The deployment identifier.
|
|
140
|
+
* @returns {Object<string, {bytes?: number}>} The tracked entries, empty when unavailable.
|
|
141
|
+
* @memberof clientBundle
|
|
142
|
+
*/
|
|
143
|
+
const clientBundleManifestFactory = (deployId) => {
|
|
144
|
+
const manifestPath = clientBundleStorageFilePath(deployId);
|
|
145
|
+
try {
|
|
146
|
+
return UnderpostFileStorage.API.readManifest({ deployId, storageId: CLIENT_BUNDLE_STORAGE_ID }).storage;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.warn('bundle manifest unreadable, size verification skipped', { manifestPath, error: error?.message });
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The tracked keys that belong to one bundle.
|
|
155
|
+
*
|
|
156
|
+
* A bundle owns its zip and every split part of it, and nothing else: the id already carries the
|
|
157
|
+
* route, so `underpost.net-` and `underpost.net-peer` cannot claim each other's parts.
|
|
158
|
+
* @function clientBundleTrackedKeys
|
|
159
|
+
* @param {object} manifest - The bundle storage manifest.
|
|
160
|
+
* @param {string} buildId - The bundle's build id.
|
|
161
|
+
* @returns {string[]} The manifest keys for that bundle.
|
|
162
|
+
* @memberof clientBundle
|
|
163
|
+
*/
|
|
164
|
+
const clientBundleTrackedKeys = (manifest, buildId) =>
|
|
165
|
+
Object.keys(manifest).filter((key) =>
|
|
166
|
+
key.replace(/^\.\//, '').startsWith(`${CLIENT_BUNDLE_DIRECTORY.replace(/^\.\//, '')}/${buildId}.zip`),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Lists the local artifacts that make up one bundle: its split parts when the build split it,
|
|
171
|
+
* otherwise the single zip.
|
|
172
|
+
* @function clientBundleArtifactPaths
|
|
173
|
+
* @param {string} zipPath - The bundle's zip path.
|
|
174
|
+
* @returns {{zipPath: string, partPaths: string[], artifactPaths: string[]}} The resolved artifacts.
|
|
175
|
+
* @memberof clientBundle
|
|
176
|
+
*/
|
|
177
|
+
const clientBundleArtifactPaths = (zipPath) => {
|
|
178
|
+
const partPaths = fs.existsSync(dir.dirname(zipPath)) ? getZipPartPaths(zipPath) : [];
|
|
179
|
+
const artifactPaths = partPaths.length > 0 ? partPaths : fs.existsSync(zipPath) ? [zipPath] : [];
|
|
180
|
+
return { zipPath, partPaths, artifactPaths };
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Builds a deployment's client bundles and uploads them to file storage.
|
|
185
|
+
*
|
|
186
|
+
* The build and the uploads run as child processes because `underpost app load` writes the
|
|
187
|
+
* `.env` this deployment's credentials live in, and only a freshly started process reads it.
|
|
188
|
+
* @function pushClientBundle
|
|
189
|
+
* @param {object} options
|
|
190
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
191
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
192
|
+
* @param {string|number} [options.split] - Part size in MB, or `none` to upload one zip.
|
|
193
|
+
* @param {boolean} [options.dev] - Build for development; defaults to production.
|
|
194
|
+
* @returns {{pushed: number, skipped: number}} How many bundles were uploaded and skipped.
|
|
195
|
+
* @memberof clientBundle
|
|
196
|
+
*/
|
|
197
|
+
const pushClientBundle = ({ deployId, hosts = [], split, dev = false }) => {
|
|
198
|
+
const baseCommand = cli('underpost', { local: true });
|
|
199
|
+
const env = dev ? 'development' : 'production';
|
|
200
|
+
const splitFlag = clientBundleSplitFlag(split);
|
|
201
|
+
|
|
202
|
+
shellExec(`${baseCommand} app load --env ${env} --args deploy-id=${deployId}`);
|
|
203
|
+
shellExec(`${baseCommand} client ${deployId} --env ${env} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
204
|
+
|
|
205
|
+
const entries = clientBundleEntriesFactory({ deployId, hosts });
|
|
206
|
+
if (entries.length === 0) logger.warn('no bundle routes resolved', { deployId, hosts });
|
|
207
|
+
|
|
208
|
+
let pushed = 0;
|
|
209
|
+
let skipped = 0;
|
|
210
|
+
for (const { host, routePath, buildId, zipPath } of entries) {
|
|
211
|
+
const { artifactPaths } = clientBundleArtifactPaths(zipPath);
|
|
212
|
+
if (artifactPaths.length === 0) {
|
|
213
|
+
logger.warn('no bundle artifacts found', { host, routePath, buildId });
|
|
214
|
+
skipped++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
for (const artifactPath of artifactPaths) {
|
|
218
|
+
shellExec(
|
|
219
|
+
`${baseCommand} fs ${artifactPath} --deploy-id ${deployId} --storage-id ${CLIENT_BUNDLE_STORAGE_ID} --force`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Remove obsolete manifest entries only after all current artifacts upload.
|
|
224
|
+
const current = new Set(artifactPaths.map((artifactPath) => artifactPath.replace(/^\.\//, '')));
|
|
225
|
+
const stale = clientBundleTrackedKeys(clientBundleManifestFactory(deployId), buildId).filter(
|
|
226
|
+
(key) => !current.has(key.replace(/^\.\//, '')),
|
|
227
|
+
);
|
|
228
|
+
for (const key of stale) {
|
|
229
|
+
shellExec(
|
|
230
|
+
`${baseCommand} fs --tracked --key ${shellArgumentFactory(key)} ` +
|
|
231
|
+
`--deploy-id ${deployId} --storage-id ${CLIENT_BUNDLE_STORAGE_ID} --rm`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (stale.length > 0) logger.warn('removed bundle parts a smaller build no longer produces', { buildId, stale });
|
|
235
|
+
|
|
236
|
+
logger.info('pushed bundle', { host, routePath, artifacts: artifactPaths.length });
|
|
237
|
+
pushed++;
|
|
238
|
+
}
|
|
239
|
+
return { pushed, skipped };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Downloads a deployment's client bundles from file storage and installs them into the served
|
|
244
|
+
* public directory.
|
|
245
|
+
*
|
|
246
|
+
* Merging and extraction run in-process through the build module; only the download needs a
|
|
247
|
+
* child process, for the same `.env` reason {@link pushClientBundle} documents.
|
|
248
|
+
* @function pullClientBundle
|
|
249
|
+
* @param {object} options
|
|
250
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
251
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
252
|
+
* @param {boolean} [options.dev] - Pull the development environment; defaults to production.
|
|
253
|
+
* @returns {Promise<{pulled: number, skipped: number}>} How many bundles were installed and skipped.
|
|
254
|
+
* @memberof clientBundle
|
|
255
|
+
*/
|
|
256
|
+
const expectedBundleBytes = ({ manifest, partPaths }) => {
|
|
257
|
+
const sizes = partPaths.map((partPath) => manifest[clientBundleManifestKey(partPath)]?.bytes);
|
|
258
|
+
return sizes.every((bytes) => Number.isFinite(bytes)) ? sizes.reduce((total, bytes) => total + bytes, 0) : undefined;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The manifest key a locally pulled part corresponds to. A pull writes the downloaded archive as
|
|
263
|
+
* `<key>.zip`, so the wrapper suffix is what has to come off to address the entry that produced it.
|
|
264
|
+
* @function clientBundleManifestKey
|
|
265
|
+
* @param {string} partPath - Local path of a pulled part.
|
|
266
|
+
* @returns {string} The tracked storage key.
|
|
267
|
+
* @memberof clientBundle
|
|
268
|
+
*/
|
|
269
|
+
const clientBundleManifestKey = (partPath) => partPath.replace(/^\.\//, '').replace(/\.zip$/i, '');
|
|
270
|
+
|
|
271
|
+
const pullClientBundle = async ({ deployId, hosts = [], dev = false }) => {
|
|
272
|
+
const baseCommand = cli('underpost', { local: true });
|
|
273
|
+
const env = dev ? 'development' : 'production';
|
|
274
|
+
|
|
275
|
+
const entries = clientBundleEntriesFactory({ deployId, hosts });
|
|
276
|
+
if (entries.length === 0) {
|
|
277
|
+
logger.error('no bundle routes resolved', { deployId, hosts });
|
|
278
|
+
return { pulled: 0, skipped: 0 };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
shellExec(`${baseCommand} app load --env ${env} --args deploy-id=${deployId}`);
|
|
282
|
+
fs.mkdirSync(CLIENT_BUNDLE_DIRECTORY, { recursive: true });
|
|
283
|
+
shellExec(
|
|
284
|
+
`${baseCommand} fs ${CLIENT_BUNDLE_DIRECTORY.replace(/^\.\//, '')} --tracked --deploy-id ${deployId} ` +
|
|
285
|
+
`--storage-id ${CLIENT_BUNDLE_STORAGE_ID} --pull --omit-unzip`,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const manifest = clientBundleManifestFactory(deployId);
|
|
289
|
+
|
|
290
|
+
let pulled = 0;
|
|
291
|
+
let skipped = 0;
|
|
292
|
+
for (const { host, routePath, buildId, zipPath, publicPath } of entries) {
|
|
293
|
+
const { partPaths, artifactPaths } = clientBundleArtifactPaths(zipPath);
|
|
294
|
+
if (artifactPaths.length === 0) {
|
|
295
|
+
logger.warn('bundle not found, skipping', { host, routePath, buildId, zipPath });
|
|
296
|
+
skipped++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (partPaths.length > 0) {
|
|
301
|
+
const { mergedBytes } = await mergeClientBuildZip({ buildPrefix: zipPath, logger });
|
|
302
|
+
// The parts are reassembled blind, so a single stale or short one is only visible as a
|
|
303
|
+
// corrupt archive far downstream. The pushed sizes are what identify it here instead.
|
|
304
|
+
const expectedBytes = expectedBundleBytes({ manifest, partPaths });
|
|
305
|
+
if (expectedBytes !== undefined && expectedBytes !== mergedBytes)
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Bundle ${buildId} reassembled to ${mergedBytes} bytes, expected ${expectedBytes} from ${partPaths.length} pushed parts`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const { outputPath } = await unzipClientBuild({ buildPrefix: zipPath, logger });
|
|
311
|
+
|
|
312
|
+
fs.removeSync(zipPath);
|
|
313
|
+
for (const partPath of partPaths) fs.removeSync(partPath);
|
|
314
|
+
|
|
315
|
+
if (!fs.existsSync(outputPath)) {
|
|
316
|
+
logger.warn('extracted bundle directory not found, skipping', { host, routePath, outputPath });
|
|
317
|
+
skipped++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// The previous deployment's document root can be owned by the web server user, so the
|
|
322
|
+
// replace goes through sudo; the extracted tree this process just wrote does not.
|
|
323
|
+
shellExec(`sudo rm -rf ${publicPath}`);
|
|
324
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
325
|
+
fs.copySync(outputPath, publicPath);
|
|
326
|
+
|
|
327
|
+
logger.info('pulled bundle', { host, routePath, publicPath });
|
|
328
|
+
pulled++;
|
|
329
|
+
}
|
|
330
|
+
return { pulled, skipped };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export {
|
|
334
|
+
CLIENT_BUNDLE_DIRECTORY,
|
|
335
|
+
clientBundleTrackedKeys,
|
|
336
|
+
clientBundleManifestFactory,
|
|
337
|
+
clientBundleManifestKey,
|
|
338
|
+
DEFAULT_SPLIT_MB,
|
|
339
|
+
clientBundleArtifactPaths,
|
|
340
|
+
clientBundleConfServerPath,
|
|
341
|
+
clientBundleEntriesFactory,
|
|
342
|
+
clientBundleHostFilter,
|
|
343
|
+
clientBundleSplitFlag,
|
|
344
|
+
clientBundleStorageFilePath,
|
|
345
|
+
pullClientBundle,
|
|
346
|
+
pushClientBundle,
|
|
347
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongo Express Deployment Module
|
|
3
|
+
* @module src/db/mongo/MongoExpress
|
|
4
|
+
* @namespace MongoExpress
|
|
5
|
+
* @description Deploys the mongo-express web client used to inspect the cluster's MongoDB
|
|
6
|
+
* StatefulSet collections. The workload is a plain Deployment plus a ClusterIP Service, so the
|
|
7
|
+
* same manifests converge identically under Kind, Kubeadm and K3s; the only runtime-dependent
|
|
8
|
+
* decision is whether the deployed mongod enforces authentication, which selects the overlay.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { loggerFactory } from '../../server/ops/logger.js';
|
|
12
|
+
import { shellExec } from '../../server/runtime/process.js';
|
|
13
|
+
// Cyclic by construction (index -> cluster -> MongoExpress -> index), same as MongoBootstrap.js.
|
|
14
|
+
// Safe because the binding is only dereferenced inside method bodies, never at module scope.
|
|
15
|
+
import Underpost from '../../index.js';
|
|
16
|
+
import { MONGODB_DEFAULT_PORT, MONGODB_SERVICE_NAME, MONGODB_STATEFULSET_NAME } from './MongooseDB.js';
|
|
17
|
+
|
|
18
|
+
const logger = loggerFactory(import.meta);
|
|
19
|
+
|
|
20
|
+
const MONGO_EXPRESS_NAME = 'mongo-express';
|
|
21
|
+
const MONGO_EXPRESS_SERVICE_NAME = 'mongo-express-service';
|
|
22
|
+
const MONGO_EXPRESS_IMAGE = 'mongo-express:1.0.2';
|
|
23
|
+
const MONGO_EXPRESS_PORT = 8081;
|
|
24
|
+
const MONGO_EXPRESS_NODE_PORT = 32081;
|
|
25
|
+
const MONGO_EXPRESS_BASE_URL = '/mongo/';
|
|
26
|
+
// The UI reads the root credentials and gates its own session with the same pair, so it consumes
|
|
27
|
+
// the StatefulSet's secret rather than owning a second credential of its own.
|
|
28
|
+
const MONGO_EXPRESS_SECRET_NAME = 'mongodb-secret';
|
|
29
|
+
const MONGO_EXPRESS_MANIFEST_DIR = 'manifests/deployment/mongo-express';
|
|
30
|
+
const MONGO_EXPRESS_NO_AUTH_MANIFEST_DIR = 'manifests/deployment/mongo-express-no-auth';
|
|
31
|
+
|
|
32
|
+
// Literal and unescaped: kubectl's jsonpath quotes carry escape sequences of their own, and no
|
|
33
|
+
// mongod argument contains a pipe.
|
|
34
|
+
const MONGOD_ARGS_FIELD_SEPARATOR = '|';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parses the mongod argument vector reported by `kubectl -o jsonpath` for the StatefulSet.
|
|
38
|
+
* @param {string} raw - Raw jsonpath stdout: the StatefulSet name, then one field per argument.
|
|
39
|
+
* @returns {Array<string>|null} Argument vector, or null when no StatefulSet was reported.
|
|
40
|
+
* @memberof MongoExpress
|
|
41
|
+
*/
|
|
42
|
+
const parseMongodArgs = (raw) => {
|
|
43
|
+
const value = `${raw || ''}`;
|
|
44
|
+
if (!value.trim()) return null;
|
|
45
|
+
const [, ...args] = value.split(MONGOD_ARGS_FIELD_SEPARATOR);
|
|
46
|
+
return args.map((arg) => arg.trim()).filter(Boolean);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Decides whether mongo-express must authenticate against the deployed mongod.
|
|
51
|
+
*
|
|
52
|
+
* The deployed StatefulSet is the authority: `manifests/mongodb` starts mongod with `--auth` and
|
|
53
|
+
* `manifests/mongodb-4.4` does not, and either can be the one running regardless of which flag
|
|
54
|
+
* this invocation carries. The flags only answer for a cluster with no StatefulSet deployed yet,
|
|
55
|
+
* where `--mongodb` (the auth-enabled manifest set) is the default the platform installs.
|
|
56
|
+
* @param {object} [params={}] - Resolution inputs.
|
|
57
|
+
* @param {Array<string>|null} [params.args=null] - mongod argument vector, or null when undeployed.
|
|
58
|
+
* @param {object} [params.options={}] - Cluster options (`mongodb`, `mongodb4`).
|
|
59
|
+
* @returns {boolean} True when the admin credential pair must be projected.
|
|
60
|
+
* @memberof MongoExpress
|
|
61
|
+
*/
|
|
62
|
+
const mongoAuthEnabledFactory = ({ args = null, options = {} } = {}) => {
|
|
63
|
+
if (Array.isArray(args)) return args.includes('--auth');
|
|
64
|
+
return options.mongodb4 !== true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves the kustomization root the auth mode selects.
|
|
69
|
+
* @param {object} [params={}] - Path inputs.
|
|
70
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
71
|
+
* @param {boolean} [params.authEnabled=true] - Whether mongod enforces authentication.
|
|
72
|
+
* @returns {string} Kustomization directory path.
|
|
73
|
+
* @memberof MongoExpress
|
|
74
|
+
*/
|
|
75
|
+
const mongoExpressManifestPathFactory = ({ underpostRoot = '.', authEnabled = true } = {}) =>
|
|
76
|
+
`${underpostRoot}/${authEnabled ? MONGO_EXPRESS_MANIFEST_DIR : MONGO_EXPRESS_NO_AUTH_MANIFEST_DIR}`;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves the opt-in NodePort Service manifest path. It lives in the base directory and is left
|
|
80
|
+
* out of both kustomizations: exposing an admin UI on the node network is a caller's decision.
|
|
81
|
+
* @param {object} [params={}] - Path inputs.
|
|
82
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
83
|
+
* @returns {string} NodePort manifest path.
|
|
84
|
+
* @memberof MongoExpress
|
|
85
|
+
*/
|
|
86
|
+
const mongoExpressNodePortManifestFactory = ({ underpostRoot = '.' } = {}) =>
|
|
87
|
+
`${underpostRoot}/${MONGO_EXPRESS_MANIFEST_DIR}/mongo-express-nodeport.yaml`;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @class MongoExpress
|
|
91
|
+
* @description Static deployment surface for the mongo-express admin client.
|
|
92
|
+
* @memberof MongoExpress
|
|
93
|
+
*/
|
|
94
|
+
class MongoExpress {
|
|
95
|
+
/**
|
|
96
|
+
* Reads the mongod argument vector from the deployed MongoDB StatefulSet.
|
|
97
|
+
*
|
|
98
|
+
* The projection leads with the StatefulSet name so a deployed server that declares no arguments
|
|
99
|
+
* stays distinguishable from no server at all — one is mongod on its defaults, the other is a
|
|
100
|
+
* cluster with no data tier yet, and both would otherwise reduce to empty output.
|
|
101
|
+
* @param {string} [namespace='default'] - Namespace holding the StatefulSet.
|
|
102
|
+
* @returns {Array<string>|null} Argument vector, or null when no StatefulSet is deployed.
|
|
103
|
+
* @memberof MongoExpress
|
|
104
|
+
*/
|
|
105
|
+
static readMongodArgs(namespace = 'default') {
|
|
106
|
+
const raw = shellExec(
|
|
107
|
+
`kubectl get statefulset ${MONGODB_STATEFULSET_NAME} -n ${namespace} --ignore-not-found -o jsonpath=` +
|
|
108
|
+
`'{.metadata.name}{"${MONGOD_ARGS_FIELD_SEPARATOR}"}` +
|
|
109
|
+
`{range .spec.template.spec.containers[?(@.name=="${MONGODB_STATEFULSET_NAME}")].args[*]}` +
|
|
110
|
+
`{@}{"${MONGOD_ARGS_FIELD_SEPARATOR}"}{end}'`,
|
|
111
|
+
{ stdout: true, silent: true, silentOnError: true },
|
|
112
|
+
);
|
|
113
|
+
return parseMongodArgs(raw);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deploys mongo-express and, once it is Ready, applies the exposure and placement the caller
|
|
118
|
+
* asked for. Cluster-type agnostic by construction: only the image pull differs between Kind
|
|
119
|
+
* (loaded into the node's image store) and Kubeadm/K3s (pulled through the CRI).
|
|
120
|
+
* @param {object} [params={}] - Deployment parameters.
|
|
121
|
+
* @param {string} [params.namespace='default'] - Target namespace.
|
|
122
|
+
* @param {string} [params.underpostRoot='.'] - Repository root holding `manifests/`.
|
|
123
|
+
* @param {object} [params.options={}] - Cluster options (`pullImage`, `nodePort`, `nodeName`, `mongodb4`).
|
|
124
|
+
* @returns {Promise<boolean>} Whether the deployment reported Ready.
|
|
125
|
+
* @memberof MongoExpress
|
|
126
|
+
*/
|
|
127
|
+
static async deploy({ namespace = 'default', underpostRoot = '.', options = {} } = {}) {
|
|
128
|
+
// Secret before workload: the Deployment's secretKeyRef must resolve at pod admission, and
|
|
129
|
+
// the encrypted store stays the source of truth wherever a manifest exists.
|
|
130
|
+
if (!Underpost.secret.applyIfPresent(MONGO_EXPRESS_SECRET_NAME, namespace))
|
|
131
|
+
Underpost.secret.applyFromOriginSeed(MONGO_EXPRESS_SECRET_NAME, namespace);
|
|
132
|
+
|
|
133
|
+
if (options.pullImage) Underpost.cluster.pullImage(MONGO_EXPRESS_IMAGE, options);
|
|
134
|
+
|
|
135
|
+
const args = MongoExpress.readMongodArgs(namespace);
|
|
136
|
+
const authEnabled = mongoAuthEnabledFactory({ args, options });
|
|
137
|
+
const manifestPath = mongoExpressManifestPathFactory({ underpostRoot, authEnabled });
|
|
138
|
+
logger.info('Deploying mongo-express', {
|
|
139
|
+
namespace,
|
|
140
|
+
authEnabled,
|
|
141
|
+
manifestPath,
|
|
142
|
+
mongodb: args ? 'deployed' : 'not deployed',
|
|
143
|
+
});
|
|
144
|
+
shellExec(`kubectl apply -k ${manifestPath} -n ${namespace}`);
|
|
145
|
+
|
|
146
|
+
const ready = await Underpost.test.statusMonitor(MONGO_EXPRESS_NAME, 'Running', 'pods', 1000, 60 * 10);
|
|
147
|
+
if (!ready) {
|
|
148
|
+
logger.error('mongo-express did not report Running', { namespace });
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.nodePort)
|
|
153
|
+
shellExec(`kubectl apply -f ${mongoExpressNodePortManifestFactory({ underpostRoot })} -n ${namespace}`);
|
|
154
|
+
if (options.nodeName)
|
|
155
|
+
Underpost.cluster.pinToNode({
|
|
156
|
+
kind: 'deployment',
|
|
157
|
+
name: MONGO_EXPRESS_NAME,
|
|
158
|
+
namespace,
|
|
159
|
+
node: options.nodeName,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
logger.info('mongo-express ready', {
|
|
163
|
+
server: `${MONGODB_STATEFULSET_NAME}-0.${MONGODB_SERVICE_NAME}:${MONGODB_DEFAULT_PORT}`,
|
|
164
|
+
basicAuth: `secret/${MONGO_EXPRESS_SECRET_NAME}`,
|
|
165
|
+
clusterIp: `${MONGO_EXPRESS_SERVICE_NAME}:${MONGO_EXPRESS_PORT}${MONGO_EXPRESS_BASE_URL}`,
|
|
166
|
+
nodePort: options.nodePort ? `<node>:${MONGO_EXPRESS_NODE_PORT}${MONGO_EXPRESS_BASE_URL}` : null,
|
|
167
|
+
portForward: `kubectl port-forward -n ${namespace} svc/${MONGO_EXPRESS_SERVICE_NAME} ${MONGO_EXPRESS_PORT}:${MONGO_EXPRESS_PORT}`,
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
MONGO_EXPRESS_BASE_URL,
|
|
175
|
+
MONGO_EXPRESS_IMAGE,
|
|
176
|
+
MONGO_EXPRESS_NAME,
|
|
177
|
+
MONGO_EXPRESS_NODE_PORT,
|
|
178
|
+
MONGO_EXPRESS_PORT,
|
|
179
|
+
MONGO_EXPRESS_SECRET_NAME,
|
|
180
|
+
MONGO_EXPRESS_SERVICE_NAME,
|
|
181
|
+
MongoExpress,
|
|
182
|
+
mongoAuthEnabledFactory,
|
|
183
|
+
mongoExpressManifestPathFactory,
|
|
184
|
+
mongoExpressNodePortManifestFactory,
|
|
185
|
+
parseMongodArgs,
|
|
186
|
+
};
|
|
187
|
+
export default MongoExpress;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gRPC transport for the Cyberia Engine data pipeline.
|
|
3
3
|
*
|
|
4
|
-
* Runs
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* /api/cyberia-instance/boot/* — so both transports stay equivalent.
|
|
4
|
+
* Runs beside Express on its own port (default 50051). Thin adapter over
|
|
5
|
+
* src/projects/cyberia/instance-data.js, the same assembly the REST fallback
|
|
6
|
+
* at /api/cyberia-instance/boot/* serves, so both transports stay equivalent.
|
|
8
7
|
*
|
|
9
8
|
* @module src/grpc/cyberia/grpc-server.js
|
|
10
9
|
*/
|
|
@@ -16,7 +15,6 @@ import { fileURLToPath } from 'url';
|
|
|
16
15
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
17
16
|
import {
|
|
18
17
|
buildFallbackConfig,
|
|
19
|
-
buildFallbackInstanceConfig,
|
|
20
18
|
fetchFullInstance,
|
|
21
19
|
fetchMapData,
|
|
22
20
|
fetchObjectLayer,
|
|
@@ -131,7 +129,7 @@ class GrpcServer {
|
|
|
131
129
|
|
|
132
130
|
server.addService(proto.CyberiaDataService.service, buildHandlers(dbKey));
|
|
133
131
|
|
|
134
|
-
// gRPC
|
|
132
|
+
// gRPC stays on the cluster-internal network, so credentials are insecure.
|
|
135
133
|
const creds = grpc.ServerCredentials.createInsecure();
|
|
136
134
|
|
|
137
135
|
return new Promise((resolve, reject) => {
|
|
@@ -159,4 +157,4 @@ class GrpcServer {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
export { GrpcServer, buildFallbackConfig
|
|
160
|
+
export { GrpcServer, buildFallbackConfig };
|