@underpostnet/cyberia 3.3.0 → 3.3.76
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/.dockerignore +1 -0
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- 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/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- 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 +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- 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 +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -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 +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +8 -4
- 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.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -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 +2 -35
- 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/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- 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 +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- 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/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- 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/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- 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 +3 -19
- 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/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- 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 +64 -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/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- 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 +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- 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/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- 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/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -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-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/downloader.js +0 -73
- package/src/server/environment.js +0 -98
package/src/cli/host.js
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host domain: the complete node-level operational environment.
|
|
3
|
+
*
|
|
4
|
+
* Its durable source is the cron deploy's env file — SSH, registry, DNS, mail, cluster and
|
|
5
|
+
* deployment settings — resolved through `engine-private/deploy/dd.cron`, the same id the cron
|
|
6
|
+
* jobs run against, so this is the one configuration the whole cluster shares. Its local runtime
|
|
7
|
+
* is {@link UnderpostHost.API.store}, the host configuration store this domain owns end to end.
|
|
8
|
+
*
|
|
9
|
+
* Implements the canonical domain action set; see {@link UnderpostDomains.DOMAIN_ACTIONS}.
|
|
10
|
+
* @module src/cli/host.js
|
|
11
|
+
* @namespace UnderpostHost
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import dotenv from 'dotenv';
|
|
15
|
+
import fs from 'fs-extra';
|
|
16
|
+
|
|
17
|
+
import nodePath from 'node:path';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
CONFIG_SCOPES,
|
|
21
|
+
classifyConfigKeys,
|
|
22
|
+
configOwnershipFactory,
|
|
23
|
+
configRejectionFactory,
|
|
24
|
+
} from '../server/runtime/config-scope.js';
|
|
25
|
+
import { cronDeployIdResolve } from '../server/ops/cron.js';
|
|
26
|
+
import { domainContextFactory } from './domains.js';
|
|
27
|
+
import { dotenvStoreFactory } from './dotenv-store.js';
|
|
28
|
+
import { getUnderpostRootPath, writeEnv } from '../server/runtime/environment.js';
|
|
29
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
30
|
+
import { shellExec } from '../server/runtime/process.js';
|
|
31
|
+
import Underpost from '../index.js';
|
|
32
|
+
|
|
33
|
+
const logger = loggerFactory(import.meta);
|
|
34
|
+
|
|
35
|
+
const UNDERPOST_CONFIG_SECRET = 'underpost-config';
|
|
36
|
+
const DEFAULT_CRON_DEPLOY_ID = 'dd-cron';
|
|
37
|
+
const SCOPES_DIR = './engine-private/deploy/scopes';
|
|
38
|
+
|
|
39
|
+
// Shell- and Kubernetes-critical keys that must never round-trip through a published Secret:
|
|
40
|
+
// an injected PATH overrides the container image's own and breaks coreutils resolution in the
|
|
41
|
+
// pod. NODE_ENV is deliberately exempt — it is the deployment environment this domain exists to
|
|
42
|
+
// carry, and stripping it made every deploy fall back to `development`.
|
|
43
|
+
const RESERVED_ENV_KEYS = new Set([
|
|
44
|
+
'HOME',
|
|
45
|
+
'HOSTNAME',
|
|
46
|
+
'PATH',
|
|
47
|
+
'TERM',
|
|
48
|
+
'SHLVL',
|
|
49
|
+
'PWD',
|
|
50
|
+
'_',
|
|
51
|
+
'LANG',
|
|
52
|
+
'LANGUAGE',
|
|
53
|
+
'LC_ALL',
|
|
54
|
+
'container',
|
|
55
|
+
'SHELL',
|
|
56
|
+
'USER',
|
|
57
|
+
'LOGNAME',
|
|
58
|
+
'MAIL',
|
|
59
|
+
'OLDPWD',
|
|
60
|
+
'LESSOPEN',
|
|
61
|
+
'LESSCLOSE',
|
|
62
|
+
'LS_COLORS',
|
|
63
|
+
'DISPLAY',
|
|
64
|
+
'COLORTERM',
|
|
65
|
+
'EDITOR',
|
|
66
|
+
'VISUAL',
|
|
67
|
+
'TERM_PROGRAM',
|
|
68
|
+
'TERM_PROGRAM_VERSION',
|
|
69
|
+
'SSH_AUTH_SOCK',
|
|
70
|
+
'SSH_CLIENT',
|
|
71
|
+
'SSH_CONNECTION',
|
|
72
|
+
'SSH_TTY',
|
|
73
|
+
'XDG_SESSION_ID',
|
|
74
|
+
'XDG_RUNTIME_DIR',
|
|
75
|
+
'XDG_DATA_DIRS',
|
|
76
|
+
'XDG_CONFIG_DIRS',
|
|
77
|
+
'DBUS_SESSION_BUS_ADDRESS',
|
|
78
|
+
'GPG_AGENT_INFO',
|
|
79
|
+
'WINDOWID',
|
|
80
|
+
'DESKTOP_SESSION',
|
|
81
|
+
'SESSION_MANAGER',
|
|
82
|
+
'XAUTHORITY',
|
|
83
|
+
'WAYLAND_DISPLAY',
|
|
84
|
+
'which_declare',
|
|
85
|
+
]);
|
|
86
|
+
const RESERVED_ENV_KEY_PREFIXES = ['KUBERNETES_', 'npm_', 'NODE_'];
|
|
87
|
+
const PRESERVED_ENV_KEYS = new Set(['NODE_ENV']);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether a key must be kept out of a published Secret and out of the host configuration store.
|
|
91
|
+
* @param {string} key - Environment variable name.
|
|
92
|
+
* @returns {boolean} True when the key is reserved.
|
|
93
|
+
* @memberof UnderpostHost
|
|
94
|
+
*/
|
|
95
|
+
const isReservedEnvKey = (key) =>
|
|
96
|
+
!PRESERVED_ENV_KEYS.has(key) &&
|
|
97
|
+
(RESERVED_ENV_KEYS.has(key) || RESERVED_ENV_KEY_PREFIXES.some((prefix) => key.startsWith(prefix)));
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @class UnderpostHost
|
|
101
|
+
* @description Host/global infrastructure configuration domain.
|
|
102
|
+
* @memberof UnderpostHost
|
|
103
|
+
*/
|
|
104
|
+
class UnderpostHost {
|
|
105
|
+
static API = {
|
|
106
|
+
/**
|
|
107
|
+
* The host configuration store: a single host-scoped dotenv file next to the global installation,
|
|
108
|
+
* holding this node's resolved configuration.
|
|
109
|
+
*
|
|
110
|
+
* This domain's local runtime, so it lives here rather than in a module of its own: `load`
|
|
111
|
+
* rebuilds it from the durable source, `publish` reads it back out, `clean` withdraws it.
|
|
112
|
+
* Key-level access is `underpost host get|set|delete|list`, registered from
|
|
113
|
+
* {@link UnderpostDomains.DOMAIN_STORE_OPERATORS} — a different interaction shape than the
|
|
114
|
+
* canonical seven actions and deliberately outside them, but the same domain.
|
|
115
|
+
*
|
|
116
|
+
* Container runtime status is a different concern with a different lifetime — see
|
|
117
|
+
* {@link UnderpostState}.
|
|
118
|
+
* @type {ReturnType<typeof dotenvStoreFactory>}
|
|
119
|
+
* @memberof UnderpostHost
|
|
120
|
+
*/
|
|
121
|
+
store: dotenvStoreFactory({
|
|
122
|
+
path: () => `${getUnderpostRootPath()}/.env`,
|
|
123
|
+
label: 'host configuration',
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
// ── key-level store operators ───────────────────────────────────────────────────────────
|
|
127
|
+
// Delegates rather than aliases, so the command surface addresses this domain and never the
|
|
128
|
+
// store object directly. Container status is not among them: that key belongs to the state
|
|
129
|
+
// store, whose owner and lifetime are different — see {@link UnderpostState}.
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reads one host configuration key.
|
|
133
|
+
* @param {string} key - Key to read.
|
|
134
|
+
* @param {*} [value] - Unused; keeps the operator arity uniform.
|
|
135
|
+
* @param {object} [options] - `--plain`, `--copy`, `disableLog`.
|
|
136
|
+
* @returns {string|undefined} Stored value.
|
|
137
|
+
* @memberof UnderpostHost
|
|
138
|
+
*/
|
|
139
|
+
get(key, value, options = {}) {
|
|
140
|
+
return UnderpostHost.API.store.get(key, value, options);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Writes one host configuration key.
|
|
145
|
+
* @param {string} key - Key to write.
|
|
146
|
+
* @param {string} value - Value to write.
|
|
147
|
+
* @memberof UnderpostHost
|
|
148
|
+
*/
|
|
149
|
+
set(key, value) {
|
|
150
|
+
return UnderpostHost.API.store.set(key, value);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Removes one host configuration key.
|
|
155
|
+
* @param {string} key - Key to remove.
|
|
156
|
+
* @memberof UnderpostHost
|
|
157
|
+
*/
|
|
158
|
+
delete(key) {
|
|
159
|
+
return UnderpostHost.API.store.delete(key);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Lists the host configuration store, optionally narrowed by `--filter`.
|
|
164
|
+
* @param {*} [key] - Unused; keeps the operator arity uniform.
|
|
165
|
+
* @param {*} [value] - Unused; keeps the operator arity uniform.
|
|
166
|
+
* @param {object} [options] - `--filter`, `disableLog`.
|
|
167
|
+
* @returns {Object<string, string>} Stored values.
|
|
168
|
+
* @memberof UnderpostHost
|
|
169
|
+
*/
|
|
170
|
+
list(key, value, options = {}) {
|
|
171
|
+
return UnderpostHost.API.store.list(key, value, options);
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolves the durable source for an environment.
|
|
176
|
+
* @param {string} [env='production'] - Environment selector.
|
|
177
|
+
* @returns {string} Path to the cron deploy's `.env.<env>`.
|
|
178
|
+
* @memberof UnderpostHost
|
|
179
|
+
*/
|
|
180
|
+
envPath(env = 'production') {
|
|
181
|
+
return `./engine-private/conf/${cronDeployIdResolve() || DEFAULT_CRON_DEPLOY_ID}/.env.${env || 'production'}`;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The scoped durable sources present for an environment.
|
|
186
|
+
* @param {string} [env='production'] - Environment selector.
|
|
187
|
+
* @returns {Array<{scope: string, path: string}>} Sources that exist, in scope-table order.
|
|
188
|
+
* @memberof UnderpostHost
|
|
189
|
+
*/
|
|
190
|
+
scopedSources(env = 'production') {
|
|
191
|
+
return Object.keys(CONFIG_SCOPES)
|
|
192
|
+
.map((scope) => ({ scope, path: UnderpostHost.API.scopePath(scope, env) }))
|
|
193
|
+
.filter(({ path }) => fs.existsSync(path));
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parses the host configuration. An absent source reads as `{}` so callers can probe
|
|
198
|
+
* without branching on existence.
|
|
199
|
+
* @param {string} [env='production'] - Environment selector.
|
|
200
|
+
* @returns {Object<string, string>} Parsed host configuration.
|
|
201
|
+
* @memberof UnderpostHost
|
|
202
|
+
*/
|
|
203
|
+
read(env = 'production') {
|
|
204
|
+
// The scoped sources are authoritative once they exist. The unsplit file is read only until
|
|
205
|
+
// `setup` has split and verified it, and is retired at that point — a durable source that
|
|
206
|
+
// outlives its replacement is a second answer to the same question.
|
|
207
|
+
const scoped = UnderpostHost.API.scopedSources(env);
|
|
208
|
+
if (scoped.length > 0)
|
|
209
|
+
return scoped.reduce((values, { path }) => ({ ...values, ...dotenv.parse(fs.readFileSync(path, 'utf8')) }), {});
|
|
210
|
+
const envPath = UnderpostHost.API.envPath(env);
|
|
211
|
+
return fs.existsSync(envPath) ? dotenv.parse(fs.readFileSync(envPath, 'utf8')) : {};
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Where this environment's configuration is actually read from, for diagnostics.
|
|
216
|
+
* @param {string} [env='production'] - Environment selector.
|
|
217
|
+
* @returns {string} Scopes directory once split, else the unsplit source.
|
|
218
|
+
* @memberof UnderpostHost
|
|
219
|
+
*/
|
|
220
|
+
sourceLabel(env = 'production') {
|
|
221
|
+
const scoped = UnderpostHost.API.scopedSources(env);
|
|
222
|
+
return scoped.length > 0 ? `${SCOPES_DIR}/*.env.${env || 'production'}` : UnderpostHost.API.envPath(env);
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Strips reserved keys from raw env-file content before it becomes a Secret.
|
|
227
|
+
* Blank lines and comments are preserved.
|
|
228
|
+
* @param {string} envFileContent - Raw `.env.<env>` contents.
|
|
229
|
+
* @returns {string} Filtered content.
|
|
230
|
+
* @memberof UnderpostHost
|
|
231
|
+
*/
|
|
232
|
+
sanitizeEnvFile(envFileContent) {
|
|
233
|
+
return envFileContent
|
|
234
|
+
.split('\n')
|
|
235
|
+
.filter((line) => {
|
|
236
|
+
const trimmed = line.trimStart();
|
|
237
|
+
if (!trimmed || trimmed.startsWith('#')) return true;
|
|
238
|
+
const key = line.slice(0, line.indexOf('=')).trim();
|
|
239
|
+
return !key || !isReservedEnvKey(key);
|
|
240
|
+
})
|
|
241
|
+
.join('\n');
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Typed read of the Grafana administrator keys. They are host configuration because the
|
|
246
|
+
* admin identity is declared once for the cluster; the secret domain onboards them from here.
|
|
247
|
+
* @param {object} [options] - Options.
|
|
248
|
+
* @param {string} [options.env='production'] - Environment selector.
|
|
249
|
+
* @param {boolean} [options.required=true] - Throw when user or password is missing.
|
|
250
|
+
* @returns {{username: string, password: string, email: string, envPath: string}} Credentials.
|
|
251
|
+
* @memberof UnderpostHost
|
|
252
|
+
*/
|
|
253
|
+
grafanaAdmin(options = {}) {
|
|
254
|
+
const env = `${options.env ?? ''}`.trim() || (options.dev === true ? 'development' : 'production');
|
|
255
|
+
const values = UnderpostHost.API.read(env);
|
|
256
|
+
const credentials = {
|
|
257
|
+
username: `${values.GF_SECURITY_ADMIN_USER || ''}`.trim(),
|
|
258
|
+
password: `${values.GF_SECURITY_ADMIN_PASSWORD || ''}`,
|
|
259
|
+
email: `${values.GF_SECURITY_ADMIN_EMAIL || ''}`.trim(),
|
|
260
|
+
envPath: UnderpostHost.API.sourceLabel(env),
|
|
261
|
+
};
|
|
262
|
+
if (options.required !== false) {
|
|
263
|
+
const missing = [
|
|
264
|
+
!credentials.username && 'GF_SECURITY_ADMIN_USER',
|
|
265
|
+
!credentials.password && 'GF_SECURITY_ADMIN_PASSWORD',
|
|
266
|
+
].filter(Boolean);
|
|
267
|
+
if (missing.length > 0) throw new Error(`[grafana] missing ${missing.join(', ')} in ${credentials.envPath}`);
|
|
268
|
+
}
|
|
269
|
+
return credentials;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Where a configuration scope's durable source lives.
|
|
274
|
+
*
|
|
275
|
+
* Beside the deploy markers rather than inside a deployment's conf: these are the node's
|
|
276
|
+
* concerns, not any one deployment's, and the deployment that happens to carry them today is
|
|
277
|
+
* an accident of history the split exists to end.
|
|
278
|
+
* @param {string} scope - Scope name.
|
|
279
|
+
* @param {string} [env='production'] - Environment selector.
|
|
280
|
+
* @returns {string} Path to that scope's env file.
|
|
281
|
+
* @memberof UnderpostHost
|
|
282
|
+
*/
|
|
283
|
+
scopePath(scope, env = 'production') {
|
|
284
|
+
return `${SCOPES_DIR}/${scope}.env.${env || 'production'}`;
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Splits the host domain's durable source into one file per owning scope.
|
|
289
|
+
*
|
|
290
|
+
* Additive and idempotent: it writes the scoped sources and reports what it found, and never
|
|
291
|
+
* removes the source it read. The legacy file stays authoritative until an operator has
|
|
292
|
+
* compared the two — a migration that deletes the only copy of a fleet's credentials on the
|
|
293
|
+
* strength of a regex is not a migration worth having.
|
|
294
|
+
*
|
|
295
|
+
* Fails closed on a key no scope claims. Guessing an owner is how a provisioning credential
|
|
296
|
+
* ends up in a workload projection, which is the exposure this split exists to close.
|
|
297
|
+
* @param {object} context - Normalized domain context.
|
|
298
|
+
* @returns {{source: string, scopes: Object<string, number>, written: string[]}} What was split.
|
|
299
|
+
* @memberof UnderpostHost
|
|
300
|
+
*/
|
|
301
|
+
/**
|
|
302
|
+
* Writes an environment into the scoped durable sources, one file per owning scope.
|
|
303
|
+
*
|
|
304
|
+
* Fails closed on a key no scope claims: guessing an owner is how a provisioning credential
|
|
305
|
+
* ends up in a workload projection, which is the exposure the split exists to close.
|
|
306
|
+
* @param {Object<string, string>} values - Environment to distribute.
|
|
307
|
+
* @param {object} [context] - Normalized domain context.
|
|
308
|
+
* @returns {string[]} Paths written.
|
|
309
|
+
* @memberof UnderpostHost
|
|
310
|
+
*/
|
|
311
|
+
writeScopes(values, context = {}) {
|
|
312
|
+
const env = context.env || 'production';
|
|
313
|
+
const { scopes, unclassified } = classifyConfigKeys(values);
|
|
314
|
+
if (unclassified.length > 0)
|
|
315
|
+
throw new Error(
|
|
316
|
+
`[host] ${unclassified.length} key(s) belong to no scope, so their exposure cannot be decided:\n` +
|
|
317
|
+
unclassified
|
|
318
|
+
.map((key) =>
|
|
319
|
+
configRejectionFactory({
|
|
320
|
+
domain: 'host',
|
|
321
|
+
deployId: cronDeployIdResolve() || DEFAULT_CRON_DEPLOY_ID,
|
|
322
|
+
env,
|
|
323
|
+
key,
|
|
324
|
+
reason: 'no ownership rule matches; declare it in CONFIG_OWNERSHIP',
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
.join('\n'),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const written = [];
|
|
331
|
+
for (const [scope, keys] of Object.entries(scopes)) {
|
|
332
|
+
if (keys.length === 0) continue;
|
|
333
|
+
const target = UnderpostHost.API.scopePath(scope, env);
|
|
334
|
+
written.push(target);
|
|
335
|
+
if (context.dryRun) continue;
|
|
336
|
+
fs.mkdirpSync(nodePath.dirname(target));
|
|
337
|
+
writeEnv(target, Object.fromEntries(keys.map((key) => [key, values[key]])));
|
|
338
|
+
fs.chmodSync(target, 0o600);
|
|
339
|
+
}
|
|
340
|
+
return written;
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
split(context = {}) {
|
|
344
|
+
context = domainContextFactory(context);
|
|
345
|
+
const env = context.env || 'production';
|
|
346
|
+
const source = UnderpostHost.API.envPath(env);
|
|
347
|
+
if (!fs.existsSync(source)) {
|
|
348
|
+
// Retired, which is the finished state of this migration rather than a fault. Rerunning
|
|
349
|
+
// `setup` on a migrated node has nothing to split, and must not read that as a broken one.
|
|
350
|
+
const scoped = UnderpostHost.API.scopedSources(env);
|
|
351
|
+
if (scoped.length === 0) throw new Error(`[host] configuration source not found: ${source}`);
|
|
352
|
+
logger.info('Host configuration already split by scope', { env, scopes: scoped.length });
|
|
353
|
+
return { source, scopes: {}, written: [] };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const values = dotenv.parse(fs.readFileSync(source, 'utf8'));
|
|
357
|
+
const written = UnderpostHost.API.writeScopes(values, { ...context, env });
|
|
358
|
+
const counts = Object.fromEntries(
|
|
359
|
+
Object.entries(classifyConfigKeys(values).scopes).map(([scope, keys]) => [scope, keys.length]),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
logger.info(
|
|
363
|
+
context.dryRun ? '[dry-run] host configuration would be split by scope' : 'Host configuration split',
|
|
364
|
+
{ source, env, scopes: counts, written: written.length },
|
|
365
|
+
);
|
|
366
|
+
return { source, scopes: counts, written };
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Confirms a split is complete: every key of the durable source is present, once, in exactly
|
|
371
|
+
* the scope that owns it. Run before an operator retires the unsplit source.
|
|
372
|
+
* @param {object} context - Normalized domain context.
|
|
373
|
+
* @returns {{ok: boolean, source: string, missing: string[], misplaced: string[]}} Verdict.
|
|
374
|
+
* @memberof UnderpostHost
|
|
375
|
+
*/
|
|
376
|
+
verifySplit(context = {}) {
|
|
377
|
+
context = domainContextFactory(context);
|
|
378
|
+
const env = context.env || 'production';
|
|
379
|
+
const source = UnderpostHost.API.envPath(env);
|
|
380
|
+
if (!fs.existsSync(source)) return { ok: true, source, missing: [], misplaced: [] };
|
|
381
|
+
|
|
382
|
+
const values = dotenv.parse(fs.readFileSync(source, 'utf8'));
|
|
383
|
+
const missing = [];
|
|
384
|
+
const misplaced = [];
|
|
385
|
+
for (const key of Object.keys(values)) {
|
|
386
|
+
const ownership = configOwnershipFactory(key);
|
|
387
|
+
if (!ownership) {
|
|
388
|
+
missing.push(key);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const target = UnderpostHost.API.scopePath(ownership.owner, env);
|
|
392
|
+
const split = fs.existsSync(target) ? dotenv.parse(fs.readFileSync(target, 'utf8')) : {};
|
|
393
|
+
if (split[key] === undefined) missing.push(key);
|
|
394
|
+
else if (split[key] !== values[key]) misplaced.push(key);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const ok = missing.length === 0 && misplaced.length === 0;
|
|
398
|
+
logger[ok ? 'info' : 'error']('Host configuration split verification', {
|
|
399
|
+
source,
|
|
400
|
+
env,
|
|
401
|
+
keys: Object.keys(values).length,
|
|
402
|
+
missing: missing.length,
|
|
403
|
+
misplaced: misplaced.length,
|
|
404
|
+
...(ok ? {} : { keysMissing: missing, keysMisplaced: misplaced }),
|
|
405
|
+
});
|
|
406
|
+
if (!ok) process.exitCode = 1;
|
|
407
|
+
return { ok, source, missing, misplaced };
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Whether this environment still has an unsplit source beside its scoped ones.
|
|
412
|
+
* @param {string} [env='production'] - Environment selector.
|
|
413
|
+
* @returns {boolean} True while both layouts exist.
|
|
414
|
+
* @memberof UnderpostHost
|
|
415
|
+
*/
|
|
416
|
+
hasDualSource(env = 'production') {
|
|
417
|
+
return UnderpostHost.API.scopedSources(env).length > 0 && fs.existsSync(UnderpostHost.API.envPath(env));
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Removes the unsplit source once the scoped ones fully account for it.
|
|
422
|
+
*
|
|
423
|
+
* The gate is {@link UnderpostHost.verifySplit}, not the presence of the scope files: a split
|
|
424
|
+
* that dropped or altered a key would otherwise retire the only copy of it. Idempotent — an
|
|
425
|
+
* already-retired source is nothing to do, not an error.
|
|
426
|
+
* @param {object} context - Normalized domain context.
|
|
427
|
+
* @returns {{retired: boolean, source: string, reason: string}} What happened, and why.
|
|
428
|
+
* @memberof UnderpostHost
|
|
429
|
+
*/
|
|
430
|
+
retireLegacySource(context = {}) {
|
|
431
|
+
context = domainContextFactory(context);
|
|
432
|
+
const source = UnderpostHost.API.envPath(context.env);
|
|
433
|
+
if (!fs.existsSync(source)) return { retired: false, source, reason: 'already retired' };
|
|
434
|
+
|
|
435
|
+
const { ok, missing, misplaced } = UnderpostHost.API.verifySplit(context);
|
|
436
|
+
if (!ok) {
|
|
437
|
+
process.exitCode = 0;
|
|
438
|
+
logger.warn('Unsplit host source kept: the scoped sources do not account for it', {
|
|
439
|
+
source,
|
|
440
|
+
missing: missing.length,
|
|
441
|
+
misplaced: misplaced.length,
|
|
442
|
+
});
|
|
443
|
+
return { retired: false, source, reason: 'verification failed' };
|
|
444
|
+
}
|
|
445
|
+
if (context.dryRun) {
|
|
446
|
+
logger.info('[dry-run] unsplit host source would be retired', { source });
|
|
447
|
+
return { retired: false, source, reason: 'dry-run' };
|
|
448
|
+
}
|
|
449
|
+
fs.removeSync(source);
|
|
450
|
+
logger.info('Unsplit host source retired; the scoped sources are authoritative', { source });
|
|
451
|
+
return { retired: true, source, reason: 'verified' };
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
// ── canonical domain actions ────────────────────────────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Onboards the host domain: confirms the cron deploy resolves and its source exists, then
|
|
458
|
+
* loads it. Idempotent — repeating it re-converges rather than re-provisioning.
|
|
459
|
+
* @param {object} context - Normalized domain context.
|
|
460
|
+
* @returns {{deployId: string, source: string, keys: number}} What was onboarded.
|
|
461
|
+
* @memberof UnderpostHost
|
|
462
|
+
*/
|
|
463
|
+
setup(context = {}) {
|
|
464
|
+
context = domainContextFactory(context);
|
|
465
|
+
const deployId = cronDeployIdResolve() || DEFAULT_CRON_DEPLOY_ID;
|
|
466
|
+
const source = UnderpostHost.API.sourceLabel(context.env);
|
|
467
|
+
if (Object.keys(UnderpostHost.API.read(context.env)).length === 0)
|
|
468
|
+
throw new Error(`[host] configuration source not found: ${source}`);
|
|
469
|
+
if (context.dryRun) {
|
|
470
|
+
logger.info('[dry-run] host setup would load', { deployId, source });
|
|
471
|
+
return { deployId, source, keys: Object.keys(UnderpostHost.API.read(context.env)).length };
|
|
472
|
+
}
|
|
473
|
+
const { keys } = UnderpostHost.API.load(context);
|
|
474
|
+
// The scoped sources are what this domain provisions: they are what lets a projection hand a
|
|
475
|
+
// workload its own scope instead of the whole node environment. Split, verify, then retire —
|
|
476
|
+
// the unsplit file is removed only once every key of it is proven present and equal under its
|
|
477
|
+
// owner, so a failed classification leaves the original in place.
|
|
478
|
+
const split = UnderpostHost.API.split(context);
|
|
479
|
+
const retired = UnderpostHost.API.retireLegacySource(context);
|
|
480
|
+
return { deployId, source, keys, scopes: split.scopes, retired };
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Loads the host configuration into the host configuration store.
|
|
485
|
+
*
|
|
486
|
+
* Two sources, one meaning. On a node the env file is on disk. Inside a workload container it
|
|
487
|
+
* is not — `engine-private` is not cloned yet at that point in the boot — and the same
|
|
488
|
+
* configuration arrives as the container environment, injected from the Secret `apply` wrote.
|
|
489
|
+
* Preferring whichever is present makes one action correct in both places.
|
|
490
|
+
*
|
|
491
|
+
* Idempotent: the store is rebuilt from the source, so repeating converges.
|
|
492
|
+
* @param {object} context - Normalized domain context.
|
|
493
|
+
* @returns {{source: string, keys: number}} Where the configuration came from and how much.
|
|
494
|
+
* @memberof UnderpostHost
|
|
495
|
+
*/
|
|
496
|
+
load(context = {}) {
|
|
497
|
+
context = domainContextFactory(context);
|
|
498
|
+
const onDisk = UnderpostHost.API.read(context.env);
|
|
499
|
+
const fromFile = Object.keys(onDisk).length > 0;
|
|
500
|
+
const values = fromFile
|
|
501
|
+
? onDisk
|
|
502
|
+
: Object.fromEntries(Object.entries(process.env).filter(([key]) => !isReservedEnvKey(key)));
|
|
503
|
+
const source = fromFile ? UnderpostHost.API.sourceLabel(context.env) : 'container-env';
|
|
504
|
+
if (context.dryRun) {
|
|
505
|
+
logger.info('[dry-run] host load would replace the host configuration store', {
|
|
506
|
+
source,
|
|
507
|
+
keys: Object.keys(values).length,
|
|
508
|
+
});
|
|
509
|
+
return { source, keys: Object.keys(values).length };
|
|
510
|
+
}
|
|
511
|
+
UnderpostHost.API.store.clean();
|
|
512
|
+
for (const [key, value] of Object.entries(values)) UnderpostHost.API.store.set(key, value);
|
|
513
|
+
logger.info('Host configuration loaded', { source, keys: Object.keys(values).length });
|
|
514
|
+
return { source, keys: Object.keys(values).length };
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Writes the host configuration store back into the durable host configuration source.
|
|
519
|
+
*
|
|
520
|
+
* The inverse of `load`. Writes each key into the scope that owns it — there is no file that
|
|
521
|
+
* takes all of them — so this can never recreate the legacy unsplit source, migrated away from
|
|
522
|
+
* in {@link UnderpostHost.retireLegacySource}. `--force` still governs overwriting an existing
|
|
523
|
+
* scoped source, the same guarantee the single-file version made per file.
|
|
524
|
+
* @param {object} context - Normalized domain context.
|
|
525
|
+
* @returns {{target: string, keys: number}} What was written.
|
|
526
|
+
* @memberof UnderpostHost
|
|
527
|
+
*/
|
|
528
|
+
publish(context = {}) {
|
|
529
|
+
context = domainContextFactory(context);
|
|
530
|
+
const env = context.env || 'production';
|
|
531
|
+
const values = UnderpostHost.API.store.list(undefined, undefined, { disableLog: true });
|
|
532
|
+
const keys = Object.keys(values);
|
|
533
|
+
if (keys.length === 0) throw new Error('[host] the host configuration store is empty; nothing to publish');
|
|
534
|
+
if (!context.force) {
|
|
535
|
+
const existing = UnderpostHost.API.scopedSources(env).map(({ path }) => path);
|
|
536
|
+
if (existing.length > 0)
|
|
537
|
+
throw new Error(`[host] ${existing.join(', ')} already exist; re-run with --force to overwrite them`);
|
|
538
|
+
}
|
|
539
|
+
const written = UnderpostHost.API.writeScopes(values, { ...context, env });
|
|
540
|
+
const target = UnderpostHost.API.sourceLabel(env);
|
|
541
|
+
logger.info(context.dryRun ? '[dry-run] host publish would write' : 'Host configuration published', {
|
|
542
|
+
target,
|
|
543
|
+
keys: keys.length,
|
|
544
|
+
scopes: written.length,
|
|
545
|
+
});
|
|
546
|
+
return { target, keys: keys.length };
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Projects the host configuration into the cluster as the `underpost-config` Secret that
|
|
551
|
+
* workloads inject with `envFrom`.
|
|
552
|
+
*
|
|
553
|
+
* Applying it before a deploy is what makes the pod resolve the requested `NODE_ENV`: the
|
|
554
|
+
* container reads it back through `load`, so a stale Secret means a stale environment.
|
|
555
|
+
* Idempotent — delete-then-apply converges on the current source.
|
|
556
|
+
*
|
|
557
|
+
* Reads through {@link UnderpostHost.read}, the one composition of the durable source: scoped
|
|
558
|
+
* files where the migration has run, the legacy file otherwise. Staged on tmpfs rather than
|
|
559
|
+
* beside the source, because the source is no longer necessarily one file to stage beside.
|
|
560
|
+
* @param {object} context - Normalized domain context.
|
|
561
|
+
* @returns {{secret: string, namespace: string, env: string}} What was projected.
|
|
562
|
+
* @memberof UnderpostHost
|
|
563
|
+
*/
|
|
564
|
+
apply(context = {}) {
|
|
565
|
+
context = domainContextFactory(context);
|
|
566
|
+
const source = UnderpostHost.API.sourceLabel(context.env);
|
|
567
|
+
const values = UnderpostHost.API.read(context.env);
|
|
568
|
+
if (Object.keys(values).length === 0) throw new Error(`[host] configuration source not found: ${source}`);
|
|
569
|
+
if (context.dryRun) {
|
|
570
|
+
logger.info('[dry-run] host apply would publish the Secret', {
|
|
571
|
+
secret: UNDERPOST_CONFIG_SECRET,
|
|
572
|
+
namespace: context.namespace,
|
|
573
|
+
source,
|
|
574
|
+
});
|
|
575
|
+
return { secret: UNDERPOST_CONFIG_SECRET, namespace: context.namespace, env: context.env };
|
|
576
|
+
}
|
|
577
|
+
// `--from-env-file` turns every KEY=VALUE into a secret key the Deployment injects via
|
|
578
|
+
// `envFrom`, so reserved keys are filtered before the file is handed to kubectl.
|
|
579
|
+
const sanitized = Object.fromEntries(Object.entries(values).filter(([key]) => !isReservedEnvKey(key)));
|
|
580
|
+
const stageDir = '/dev/shm/underpost-host-apply';
|
|
581
|
+
fs.mkdirpSync(stageDir, { mode: 0o700 });
|
|
582
|
+
fs.chmodSync(stageDir, 0o700);
|
|
583
|
+
const sanitizedEnvPath = `${stageDir}/${context.namespace}.env`;
|
|
584
|
+
writeEnv(sanitizedEnvPath, sanitized);
|
|
585
|
+
fs.chmodSync(sanitizedEnvPath, 0o600);
|
|
586
|
+
try {
|
|
587
|
+
shellExec(`kubectl delete secret ${UNDERPOST_CONFIG_SECRET} -n ${context.namespace} --ignore-not-found`);
|
|
588
|
+
shellExec(
|
|
589
|
+
`kubectl create secret generic ${UNDERPOST_CONFIG_SECRET} --from-env-file=${sanitizedEnvPath} --dry-run=client -o yaml | kubectl apply -f - -n ${context.namespace}`,
|
|
590
|
+
);
|
|
591
|
+
} finally {
|
|
592
|
+
fs.removeSync(sanitizedEnvPath);
|
|
593
|
+
}
|
|
594
|
+
logger.info('Host configuration applied', { env: context.env, namespace: context.namespace, source });
|
|
595
|
+
return { secret: UNDERPOST_CONFIG_SECRET, namespace: context.namespace, env: context.env };
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Read-only report: which source is in effect, how many keys it carries, and whether the
|
|
600
|
+
* cluster projection exists.
|
|
601
|
+
* @param {object} context - Normalized domain context.
|
|
602
|
+
* @returns {object} Report.
|
|
603
|
+
* @memberof UnderpostHost
|
|
604
|
+
*/
|
|
605
|
+
status(context = {}) {
|
|
606
|
+
context = domainContextFactory(context);
|
|
607
|
+
const source = UnderpostHost.API.sourceLabel(context.env);
|
|
608
|
+
const keys = Object.keys(UnderpostHost.API.read(context.env)).length;
|
|
609
|
+
const legacyPresent = fs.existsSync(UnderpostHost.API.envPath(context.env));
|
|
610
|
+
const projected = shellExec(`kubectl get secret ${UNDERPOST_CONFIG_SECRET} -n ${context.namespace} -o name`, {
|
|
611
|
+
silent: true,
|
|
612
|
+
stdout: true,
|
|
613
|
+
silentOnError: true,
|
|
614
|
+
disableLog: true,
|
|
615
|
+
});
|
|
616
|
+
const report = {
|
|
617
|
+
domain: 'host',
|
|
618
|
+
env: context.env,
|
|
619
|
+
namespace: context.namespace,
|
|
620
|
+
deployId: cronDeployIdResolve() || 'dd-cron',
|
|
621
|
+
source,
|
|
622
|
+
sourcePresent: keys > 0,
|
|
623
|
+
keys,
|
|
624
|
+
dualSource: UnderpostHost.API.hasDualSource(context.env),
|
|
625
|
+
clusterSecret: `${projected ?? ''}`.trim() ? UNDERPOST_CONFIG_SECRET : null,
|
|
626
|
+
};
|
|
627
|
+
logger.info('host status', report);
|
|
628
|
+
if (legacyPresent) UnderpostHost.API.verifySplit(context);
|
|
629
|
+
return report;
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Replaces the live projection: deletes the `underpost-config` Secret and re-applies it, so
|
|
634
|
+
* consumers pick up a source that changed underneath them.
|
|
635
|
+
* @param {object} context - Normalized domain context.
|
|
636
|
+
* @returns {object} The re-applied projection.
|
|
637
|
+
* @memberof UnderpostHost
|
|
638
|
+
*/
|
|
639
|
+
rotate(context = {}) {
|
|
640
|
+
context = domainContextFactory(context);
|
|
641
|
+
if (context.dryRun) {
|
|
642
|
+
logger.info('[dry-run] host rotate would re-project the Secret', { namespace: context.namespace });
|
|
643
|
+
return { secret: UNDERPOST_CONFIG_SECRET, namespace: context.namespace, env: context.env };
|
|
644
|
+
}
|
|
645
|
+
shellExec(`kubectl delete secret ${UNDERPOST_CONFIG_SECRET} -n ${context.namespace} --ignore-not-found`);
|
|
646
|
+
return UnderpostHost.API.apply(context);
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Withdraws the host configuration from the local filesystem by clearing the host configuration store.
|
|
651
|
+
* Container runtime state lives in its own store and is untouched. `--force` also removes the
|
|
652
|
+
* ephemeral `engine-private` clone.
|
|
653
|
+
* @param {object} context - Normalized domain context.
|
|
654
|
+
* @returns {{keptKeys: Array<string>, removedPrivateRepo: boolean}} What was withdrawn.
|
|
655
|
+
* @memberof UnderpostHost
|
|
656
|
+
*/
|
|
657
|
+
clean(context = {}) {
|
|
658
|
+
context = domainContextFactory(context);
|
|
659
|
+
if (context.dryRun) {
|
|
660
|
+
logger.info('[dry-run] host clean would clear the host configuration store', { force: context.force });
|
|
661
|
+
return { removedPrivateRepo: false };
|
|
662
|
+
}
|
|
663
|
+
UnderpostHost.API.store.clean();
|
|
664
|
+
if (context.force) Underpost.repo.cleanupPrivateEngineRepo();
|
|
665
|
+
logger.info('Host configuration withdrawn', { removedPrivateRepo: context.force });
|
|
666
|
+
return { removedPrivateRepo: context.force };
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export default UnderpostHost;
|
|
672
|
+
export { isReservedEnvKey, UnderpostHost };
|