@underpostnet/cyberia 3.2.90 → 3.3.73
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 +16 -0
- package/.github/workflows/coverall.cyberia.ci.yml +107 -0
- package/.github/workflows/cyberia-client.cd.yml +6 -18
- package/.github/workflows/cyberia-server.cd.yml +6 -18
- 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 +6 -78
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +42 -21
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +2 -9
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +581 -2417
- package/CLI-HELP.md +416 -127
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +70 -43
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +372 -135
- package/bin/deploy.js +18 -104
- package/bin/index.js +372 -135
- package/compose.env +16 -0
- package/conf.js +93 -4
- package/deploy/cyberia-client/deploy.sh +47 -0
- package/deploy/cyberia-client/package.sh +18 -0
- package/deploy/cyberia-client/state.sh +14 -0
- package/deploy/cyberia-server/deploy.sh +47 -0
- package/deploy/cyberia-server/package.sh +18 -0
- package/deploy/cyberia-server/state.sh +14 -0
- package/deploy/dd-cyberia/deploy.sh +22 -0
- package/deploy/dd-cyberia/init.sh +55 -0
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +116 -0
- package/deploy/lib/github-actions-logging.sh +594 -0
- package/deploy/lib/host.sh +185 -0
- package/deploy/lib/state.sh +62 -0
- package/deploy/pwa-microservices-template/deploy.sh +45 -0
- package/deploy/release/deploy.sh +32 -0
- package/deployment.yaml +7 -2
- package/docker-compose.yml +31 -37
- 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 +104 -43
- package/hardhat/package.json +3 -3
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- 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 +83 -10
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/event-monitor.sh +56 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +30 -14
- package/scripts/kubeadm-node-setup.sh +40 -31
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/maas-setup.sh +8 -8
- package/scripts/nat-iptables.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
- 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 +1 -1
- package/src/api/cyberia-action/cyberia-action.service.js +2 -2
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- 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.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
- 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 +2 -2
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.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 +16 -29
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- 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.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 +204 -3
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +4 -17
- 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 +391 -0
- package/src/cli/baremetal.js +30 -36
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +242 -135
- package/src/cli/db.js +126 -67
- package/src/cli/deploy.js +158 -60
- 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.js +3 -3
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -7
- package/src/cli/index.js +426 -134
- 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 +73 -34
- package/src/cli/repository.js +564 -367
- package/src/cli/run.js +362 -654
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +651 -331
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +623 -0
- package/src/cli/wireguard.js +2930 -0
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +258 -211
- package/src/client/components/core/FileExplorer.js +0 -21
- 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/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- 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 +17 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
- 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/WHITE-PAPER.md +1 -1
- 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-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- 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 +61 -46
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +58 -44
- 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/MongooseDB.js +11 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +1 -1
- package/src/index.js +103 -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 +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +54 -6
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +15 -24
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +22 -3
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +1 -1
- package/src/projects/cyberia/object-layer.js +12 -22
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +13 -2
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +9 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
- package/src/runtime/engine-cyberia/compose.env +16 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
- 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 +117 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +465 -0
- package/src/server/build/testing.js +528 -0
- package/src/server/{dns.js → network/dns.js} +241 -14
- package/src/server/network/forward-proxy.js +541 -0
- package/src/server/{middlewares.js → network/middlewares.js} +59 -4
- 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/network/underpost-compression.js +186 -0
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/ops/cron.js +949 -0
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +108 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/ops/systemd.js +259 -0
- package/src/server/{conf.js → runtime/conf.js} +521 -520
- 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 -13
- 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/security/selinux.js +185 -0
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +1 -1
- package/src/server/{downloader.js → storage/downloader.js} +2 -2
- package/src/server/storage/repository.js +68 -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/cyberia-cli-plain-reads.test.js +30 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
- package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -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/integration/infra/1-security/selinux.test.js +69 -0
- 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 +1163 -0
- package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
- 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/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} +47 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -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 +334 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +979 -0
- package/test/unit/coverage-artifact.test.js +130 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +447 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/logger-redaction.test.js +112 -0
- package/test/unit/package.test.js +403 -0
- package/test/unit/prepare-host.test.js +299 -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 +193 -0
- package/vitest.config.js +58 -0
- package/.nycrc +0 -9
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/link-local-underpost-cli.sh +0 -6
- 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/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/cron.js +0 -476
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import SystemdService, {
|
|
5
|
+
homeDirectoryPathFactory,
|
|
6
|
+
journalctlCommandFactory,
|
|
7
|
+
nodeCandidatesFactory,
|
|
8
|
+
nodeProbeCommandFactory,
|
|
9
|
+
scriptProbeCommandFactory,
|
|
10
|
+
} from '../../../../src/server/ops/systemd.js';
|
|
11
|
+
|
|
12
|
+
describe('nodeCandidatesFactory', () => {
|
|
13
|
+
// systemd cannot enter /root or /home on an SELinux host, so a unit pointed at
|
|
14
|
+
// an nvm install there restarts on 203/EXEC forever while every ordinary
|
|
15
|
+
// permission check on the binary passes.
|
|
16
|
+
it('keeps a home-directory interpreter for last', () => {
|
|
17
|
+
expect(nodeCandidatesFactory({ execPath: '/root/.nvm/versions/node/v24.15.0/bin/node' })).to.deep.equal([
|
|
18
|
+
...SystemdService.NODE_PATHS,
|
|
19
|
+
'/root/.nvm/versions/node/v24.15.0/bin/node',
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('prefers the running interpreter when it is not in a home directory, without duplicating it', () => {
|
|
24
|
+
expect(nodeCandidatesFactory({ execPath: '/opt/node/bin/node' })).to.deep.equal([
|
|
25
|
+
'/opt/node/bin/node',
|
|
26
|
+
...SystemdService.NODE_PATHS,
|
|
27
|
+
]);
|
|
28
|
+
expect(nodeCandidatesFactory({ execPath: '/usr/bin/node' })).to.deep.equal([...SystemdService.NODE_PATHS]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('distinguishes a home directory from a lookalike', () => {
|
|
32
|
+
expect(homeDirectoryPathFactory('/home/dd/engine')).to.equal(true);
|
|
33
|
+
expect(homeDirectoryPathFactory('/root')).to.equal(true);
|
|
34
|
+
expect(homeDirectoryPathFactory('/opt/underpost/engine')).to.equal(false);
|
|
35
|
+
expect(homeDirectoryPathFactory('/homelab/engine')).to.equal(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('service probes', () => {
|
|
40
|
+
it('probes an interpreter through a transient unit, under the same constraints', () => {
|
|
41
|
+
expect(nodeProbeCommandFactory('/usr/bin/node', 'root')).to.equal(
|
|
42
|
+
'sudo systemd-run --quiet --collect --wait --uid=root --property=Type=oneshot /usr/bin/node --version',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('probes a script from its working directory, which is what catches an unreadable checkout', () => {
|
|
47
|
+
expect(
|
|
48
|
+
scriptProbeCommandFactory({
|
|
49
|
+
nodePath: '/usr/bin/node',
|
|
50
|
+
scriptPath: '/home/dd/engine/bin/index.js',
|
|
51
|
+
user: 'root',
|
|
52
|
+
workingDirectory: '/home/dd/engine',
|
|
53
|
+
}),
|
|
54
|
+
).to.equal(
|
|
55
|
+
'sudo systemd-run --quiet --collect --wait --uid=root --property=Type=oneshot ' +
|
|
56
|
+
'--property=WorkingDirectory=/home/dd/engine /usr/bin/node /home/dd/engine/bin/index.js --version',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('journalctlCommandFactory', () => {
|
|
62
|
+
it('reads the tail a failure report needs', () => {
|
|
63
|
+
expect(journalctlCommandFactory({ name: 'underpost-event.service', lines: 30 })).to.equal(
|
|
64
|
+
'journalctl -u underpost-event.service -n 30',
|
|
65
|
+
);
|
|
66
|
+
expect(journalctlCommandFactory({ name: 'underpost-event.service' })).to.equal(
|
|
67
|
+
'journalctl -u underpost-event.service',
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import Underpost from '../../../../src/index.js';
|
|
6
|
+
import { program } from '../../../../src/cli/index.js';
|
|
7
|
+
import { shellHarness } from '../../../support/shell-harness.js';
|
|
8
|
+
|
|
9
|
+
const host = () => Underpost.host;
|
|
10
|
+
|
|
11
|
+
describe('underpost-config secret', () => {
|
|
12
|
+
describe('env file sanitization', () => {
|
|
13
|
+
it('keeps NODE_ENV so the pod resolves the deployed environment', () => {
|
|
14
|
+
// Regression: NODE_ENV matched the `NODE_` reserved prefix and was stripped, so the
|
|
15
|
+
// Secret carried no environment, `--create-from-env` never saw one, and loadConf fell
|
|
16
|
+
// back to `development` on every deploy.
|
|
17
|
+
const sanitized = host().sanitizeEnvFile('NODE_ENV=production\nDEPLOY_ID=dd-core\n');
|
|
18
|
+
expect(sanitized).to.match(/^NODE_ENV=production$/m);
|
|
19
|
+
expect(sanitized).to.match(/^DEPLOY_ID=dd-core$/m);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('still strips shell- and Kubernetes-critical keys', () => {
|
|
23
|
+
const sanitized = host().sanitizeEnvFile(
|
|
24
|
+
['PATH=/injected/bin', 'HOME=/injected', 'NODE_OPTIONS=--max-old-space-size=8192', 'KUBERNETES_PORT=443'].join(
|
|
25
|
+
'\n',
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
for (const key of ['PATH', 'HOME', 'NODE_OPTIONS', 'KUBERNETES_PORT'])
|
|
29
|
+
expect(sanitized, key).to.not.match(new RegExp(`^${key}=`, 'm'));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('preserves blank lines and comments', () => {
|
|
33
|
+
expect(host().sanitizeEnvFile('# header\n\nA=1\n')).to.equal('# header\n\nA=1\n');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('published env file selection', () => {
|
|
38
|
+
it('targets the cron deploy env file for the requested environment', () => {
|
|
39
|
+
for (const env of ['production', 'development', 'test'])
|
|
40
|
+
expect(host().envPath(env)).to.match(new RegExp(`^\\./engine-private/conf/[a-zA-Z0-9._-]+/\\.env\\.${env}$`));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('resolves the same cron deploy for every environment', () => {
|
|
44
|
+
const deployOf = (env) => host().envPath(env).split('/')[3];
|
|
45
|
+
expect(deployOf('production')).to.equal(deployOf('development'));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('is owned by the secret layer, with no parallel deploy-side implementation', () => {
|
|
50
|
+
expect(host().apply).to.be.a('function');
|
|
51
|
+
expect(Underpost.deploy.configMap).to.equal(undefined);
|
|
52
|
+
// Every domain carries the identical canonical action set.
|
|
53
|
+
for (const domain of [Underpost.secret, Underpost.host, Underpost.app])
|
|
54
|
+
for (const action of ['setup', 'load', 'publish', 'apply', 'status', 'rotate', 'clean'])
|
|
55
|
+
expect(domain[action], action).to.be.a('function');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Regression: `apply`, `status` and `publish` read/wrote `envPath()` directly — the legacy,
|
|
59
|
+
// now-retired unsplit file — instead of composing through `read()`/`sourceLabel()`. On a node
|
|
60
|
+
// the scope migration has already run on, `deploy sync` failed at the exact line the real hub
|
|
61
|
+
// hit: `[host] configuration source not found: ./engine-private/conf/dd-cron/.env.production`,
|
|
62
|
+
// even though 86 keys were sitting right there in the scoped sources.
|
|
63
|
+
describe('post-migration source resolution', () => {
|
|
64
|
+
// The migrated node is a fixture rather than this checkout's private tree: what is asserted
|
|
65
|
+
// is how the three actions compose their source, and reading the real tree made these cases
|
|
66
|
+
// answer to a migration an operator may not have run, may be part-way through, or may not
|
|
67
|
+
// have cloned at all — the same reason `config-scope.test.js` fixes the state it asserts on.
|
|
68
|
+
const MIGRATED_NODE = {
|
|
69
|
+
host: { GITHUB_TOKEN: 'host-github' },
|
|
70
|
+
cron: { DDNS_API_KEY: 'cron-ddns' },
|
|
71
|
+
app: { DB_PASSWORD: 'app-secret' },
|
|
72
|
+
};
|
|
73
|
+
const scopedValues = () => Object.assign({}, ...Object.values(MIGRATED_NODE));
|
|
74
|
+
const renderScope = (values) =>
|
|
75
|
+
Object.entries(values)
|
|
76
|
+
.map(([key, value]) => `${key}=${value}\n`)
|
|
77
|
+
.join('');
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
const table = new Map(
|
|
81
|
+
Object.entries(MIGRATED_NODE).map(([scope, values]) => [
|
|
82
|
+
host().scopePath(scope, 'production'),
|
|
83
|
+
renderScope(values),
|
|
84
|
+
]),
|
|
85
|
+
);
|
|
86
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => table.has(`${filePath}`));
|
|
87
|
+
vi.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
|
88
|
+
const key = `${filePath}`;
|
|
89
|
+
if (!table.has(key)) throw Object.assign(new Error(`ENOENT: ${key}`), { code: 'ENOENT' });
|
|
90
|
+
return table.get(key);
|
|
91
|
+
});
|
|
92
|
+
vi.spyOn(fs, 'writeFileSync').mockImplementation((filePath, value) => table.set(`${filePath}`, `${value}`));
|
|
93
|
+
vi.spyOn(fs, 'mkdirpSync').mockImplementation(() => undefined);
|
|
94
|
+
vi.spyOn(fs, 'chmodSync').mockImplementation(() => undefined);
|
|
95
|
+
// `publish` writes the node's loaded runtime back out, so the store answers for it here.
|
|
96
|
+
vi.spyOn(host().store, 'list').mockReturnValue(scopedValues());
|
|
97
|
+
// `status` probes the cluster for the projection; the fixture answers for that too.
|
|
98
|
+
shellHarness();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => vi.restoreAllMocks());
|
|
102
|
+
|
|
103
|
+
it('does not require the legacy file once scoped sources cover the environment', () => {
|
|
104
|
+
expect(host().hasDualSource('production')).to.equal(false);
|
|
105
|
+
expect(fs.existsSync(host().envPath('production'))).to.equal(false);
|
|
106
|
+
expect(Object.keys(host().read('production')).length).to.be.greaterThan(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('apply resolves the source through read(), not the legacy path', () => {
|
|
110
|
+
expect(() => host().apply({ env: 'production', namespace: 'default', dryRun: true })).to.not.throw();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('status reports the scoped sources as present and authoritative', () => {
|
|
114
|
+
const report = host().status({ env: 'production', namespace: 'default' });
|
|
115
|
+
expect(report.sourcePresent).to.equal(true);
|
|
116
|
+
expect(report.keys).to.be.greaterThan(0);
|
|
117
|
+
expect(report.source).to.include('/scopes/');
|
|
118
|
+
expect(report.dualSource).to.equal(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('publish refuses to silently overwrite an existing scoped source', () => {
|
|
122
|
+
expect(() => host().publish({ env: 'production' })).to.throw('already exist');
|
|
123
|
+
expect(fs.existsSync(host().envPath('production'))).to.equal(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('publish never recreates the legacy source, even when forced', () => {
|
|
127
|
+
expect(() => host().publish({ env: 'production', force: true, dryRun: true })).to.not.throw();
|
|
128
|
+
expect(fs.existsSync(host().envPath('production'))).to.equal(false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('ipfs cluster origin credentials', () => {
|
|
134
|
+
const secret = () => Underpost.secret;
|
|
135
|
+
|
|
136
|
+
it('registers an origin for every managed secret, leaving none unmapped', () => {
|
|
137
|
+
// `--status` reports which source each managed Secret deploys from; a managed secret with
|
|
138
|
+
// no registered origin and no env mapping reports `unmapped` and tells an operator nothing.
|
|
139
|
+
for (const name of secret().managedSecrets()) {
|
|
140
|
+
const seeded = Object.keys(secret().seedSources(name)).length > 0;
|
|
141
|
+
const envMapped = Object.keys(secret().seedEnvKeys(name)).length > 0;
|
|
142
|
+
expect(seeded || envMapped, `${name} has no registered origin`).to.equal(true);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('resolves both IPFS origin files from the deploy secret area', () => {
|
|
147
|
+
const sources = secret().seedSources('ipfs-cluster-secret');
|
|
148
|
+
expect(Object.keys(sources).sort()).to.deep.equal(['bootstrap-peer-priv-key', 'cluster-secret']);
|
|
149
|
+
for (const path of Object.values(sources))
|
|
150
|
+
expect(path, path).to.match(/^\.\/engine-private\/(deploy\/)?ipfs-cluster-/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('projects exactly the data keys the StatefulSet mounts', () => {
|
|
154
|
+
const statefulset = fs.readFileSync(
|
|
155
|
+
new URL('../../../../manifests/ipfs/statefulset.yaml', import.meta.url),
|
|
156
|
+
'utf8',
|
|
157
|
+
);
|
|
158
|
+
const mounted = [...statefulset.matchAll(/name: ipfs-cluster-secret\s*\n\s*key: ([\w-]+)/g)].map((m) => m[1]);
|
|
159
|
+
expect(mounted.sort()).to.deep.equal(Object.keys(secret().seedSources('ipfs-cluster-secret')).sort());
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('keeps the public peer id in the ConfigMap and out of the Secret', () => {
|
|
163
|
+
const ipfsSource = fs.readFileSync(new URL('../../../../src/cli/ipfs.js', import.meta.url), 'utf8');
|
|
164
|
+
expect(ipfsSource).to.include('--from-literal=bootstrap-peer-id=${IDENTITY_JSON.id}');
|
|
165
|
+
// The private half must never be spelled into a command string.
|
|
166
|
+
expect(ipfsSource).to.not.include('--from-literal=bootstrap-peer-priv-key');
|
|
167
|
+
expect(ipfsSource).to.not.include('--from-literal=cluster-secret');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('names no credential path of its own', () => {
|
|
171
|
+
const ipfsSource = fs.readFileSync(new URL('../../../../src/cli/ipfs.js', import.meta.url), 'utf8');
|
|
172
|
+
expect(ipfsSource).to.include('Underpost.secret.seedSources(IPFS_SECRET_NAME)');
|
|
173
|
+
expect(ipfsSource).to.not.match(/['"`][^'"`]*engine-private\/ipfs-cluster/);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('host configuration store access boundary', () => {
|
|
178
|
+
const cliSource = fs.readFileSync(new URL('../../../../src/cli/index.js', import.meta.url), 'utf8');
|
|
179
|
+
const hostSource = fs.readFileSync(new URL('../../../../src/cli/host.js', import.meta.url), 'utf8');
|
|
180
|
+
|
|
181
|
+
it('reaches the store through the host domain rather than a command of its own', () => {
|
|
182
|
+
const domainsSource = fs.readFileSync(new URL('../../../../src/cli/domains.js', import.meta.url), 'utf8');
|
|
183
|
+
// A second top-level command over the same file is what `underpost config` was. The
|
|
184
|
+
// operators are the host domain's now, so there is one command addressing that store.
|
|
185
|
+
expect(cliSource).to.not.include("command('config')");
|
|
186
|
+
expect(domainsSource).to.include("const DOMAIN_STORE_OPERATORS = ['get', 'set', 'delete', 'list'];");
|
|
187
|
+
for (const operator of ['get', 'set', 'delete', 'list'])
|
|
188
|
+
expect(typeof Underpost.host[operator], operator).to.equal('function');
|
|
189
|
+
// `clean` is a canonical lifecycle action over the same file; `isInsideContainer` is a
|
|
190
|
+
// runtime predicate. Neither is a per-key operation, so neither is a store operator.
|
|
191
|
+
const operators = /const DOMAIN_STORE_OPERATORS = \[([^\]]*)\]/.exec(domainsSource)[1];
|
|
192
|
+
for (const operator of ['clean', 'isInsideContainer']) expect(operators, operator).to.not.include(operator);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('offers the key-level operators only to the domains that own a store', () => {
|
|
196
|
+
const domainsSource = fs.readFileSync(new URL('../../../../src/cli/domains.js', import.meta.url), 'utf8');
|
|
197
|
+
// The canonical seven are symmetric across every domain by construction; the operators are
|
|
198
|
+
// opt-in, so a domain owning no key-value store never grows a key argument. `host` and
|
|
199
|
+
// `state` own one each; `secret` and `app` own none.
|
|
200
|
+
expect(domainsSource).to.include('store = false');
|
|
201
|
+
const declared = (name) => {
|
|
202
|
+
const start = cliSource.indexOf(`name: '${name}',`);
|
|
203
|
+
return cliSource.slice(start, cliSource.indexOf('},', start)).includes('store: true');
|
|
204
|
+
};
|
|
205
|
+
for (const domain of ['host', 'state']) expect(declared(domain), domain).to.equal(true);
|
|
206
|
+
for (const domain of ['secret', 'app']) expect(declared(domain), domain).to.equal(false);
|
|
207
|
+
// And the command surface follows the declaration, not the API: `secret` happens to export
|
|
208
|
+
// a `list` of its own, which must stay off the command as an operator.
|
|
209
|
+
for (const domain of ['secret', 'app'])
|
|
210
|
+
expect(program.commands.find((command) => command.name() === domain).registeredArguments).to.have.lengthOf(1);
|
|
211
|
+
for (const domain of ['host', 'state'])
|
|
212
|
+
expect(program.commands.find((command) => command.name() === domain).registeredArguments).to.have.lengthOf(3);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('writes only the host configuration store, never a deployment env file', () => {
|
|
216
|
+
// A deployment env file is the app domain's durable source; the host store never writes one.
|
|
217
|
+
const storeBlock = hostSource.slice(
|
|
218
|
+
hostSource.indexOf('store: dotenvStoreFactory({'),
|
|
219
|
+
hostSource.indexOf('envPath(env = '),
|
|
220
|
+
);
|
|
221
|
+
expect(storeBlock).to.not.include('engine-private/conf/');
|
|
222
|
+
expect(storeBlock).to.include('${getUnderpostRootPath()}/.env');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('keeps the operators the instance status transport depends on', () => {
|
|
226
|
+
// Lifecycle hooks stamp `container-status` and the monitor reads it back over kubectl exec.
|
|
227
|
+
for (const operator of ['get', 'set']) expect(Underpost.host.store[operator]).to.be.a('function');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('container state store', () => {
|
|
232
|
+
const cliSource = fs.readFileSync(new URL('../../../../src/cli/index.js', import.meta.url), 'utf8');
|
|
233
|
+
const statusSource = fs.readFileSync(
|
|
234
|
+
new URL('../../../../src/server/runtime/runtime-status.js', import.meta.url),
|
|
235
|
+
'utf8',
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
it('resolves to its own file, never the host configuration store', () => {
|
|
239
|
+
expect(Underpost.state.path()).to.not.equal(Underpost.host.store.path());
|
|
240
|
+
expect(Underpost.state.path()).to.match(/\.state$/);
|
|
241
|
+
expect(Underpost.host.store.path()).to.match(/\.env$/);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('holds the boot latch, not the host configuration store', () => {
|
|
245
|
+
// `await-deploy` is container-scoped: set when a runtime starts configuring itself, cleared
|
|
246
|
+
// when it is listening. Held in the host store it was erased by `host load` (which cleans
|
|
247
|
+
// the file before repopulating it) and by the `host clean` the start pipeline runs once a
|
|
248
|
+
// production deployment is serving — both host-domain operations that know nothing about it.
|
|
249
|
+
const confSource = fs.readFileSync(new URL('../../../../src/server/runtime/conf.js', import.meta.url), 'utf8');
|
|
250
|
+
const runtimeSource = fs.readFileSync(
|
|
251
|
+
new URL('../../../../src/server/runtime/runtime.js', import.meta.url),
|
|
252
|
+
'utf8',
|
|
253
|
+
);
|
|
254
|
+
for (const [name, source] of [
|
|
255
|
+
['conf.js', confSource],
|
|
256
|
+
['runtime.js', runtimeSource],
|
|
257
|
+
['runtime-status.js', statusSource],
|
|
258
|
+
])
|
|
259
|
+
expect(source, name).to.not.include("store.set('await-deploy'");
|
|
260
|
+
expect(statusSource).to.include('Underpost.state.set(AWAIT_DEPLOY_KEY');
|
|
261
|
+
expect(statusSource).to.include('Underpost.state.delete(AWAIT_DEPLOY_KEY)');
|
|
262
|
+
// The key reaches the store through the contract module alone, never a literal at a call site.
|
|
263
|
+
for (const [name, source] of [
|
|
264
|
+
['conf.js', confSource],
|
|
265
|
+
['runtime.js', runtimeSource],
|
|
266
|
+
])
|
|
267
|
+
expect(source, name).to.not.include("'await-deploy'");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('routes every lifecycle transition through the state store', () => {
|
|
271
|
+
expect(statusSource).to.not.include('Underpost.host.store.');
|
|
272
|
+
for (const call of ['Underpost.state.set(CONTAINER_STATUS_KEY', 'Underpost.state.get(CONTAINER_STATUS_KEY'])
|
|
273
|
+
expect(statusSource, call).to.include(call);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('carries the key-level operators and the full canonical action set', () => {
|
|
277
|
+
// The fourth domain: the same seven verbs as the other three, plus the store operators the
|
|
278
|
+
// in-pod lifecycle hooks and the monitor transport call.
|
|
279
|
+
for (const operator of ['get', 'set', 'delete', 'list'])
|
|
280
|
+
expect(typeof Underpost.state[operator], operator).to.equal('function');
|
|
281
|
+
for (const action of ['setup', 'load', 'publish', 'apply', 'status', 'rotate', 'clean'])
|
|
282
|
+
expect(typeof Underpost.state[action], action).to.equal('function');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('is registered as a domain rather than as a command of its own', () => {
|
|
286
|
+
expect(cliSource).to.not.include("command('state')");
|
|
287
|
+
expect(cliSource).to.include("name: 'state',");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('is the only location the monitor exec transport reads', () => {
|
|
291
|
+
const monitorSource = fs.readFileSync(new URL('../../../../src/cli/monitor.js', import.meta.url), 'utf8');
|
|
292
|
+
expect(monitorSource).to.include('underpost state get container-status --plain');
|
|
293
|
+
expect(monitorSource).to.not.include('underpost config');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('latches a failure through one helper rather than a literal in each module', () => {
|
|
297
|
+
for (const module of [
|
|
298
|
+
'../../../../src/server/runtime/process.js',
|
|
299
|
+
'../../../../src/db/DataBaseProvider.js',
|
|
300
|
+
'../../../../src/db/valkey/Valkey.js',
|
|
301
|
+
'../../../../src/db/mariadb/MariaDB.js',
|
|
302
|
+
]) {
|
|
303
|
+
const source = fs.readFileSync(new URL(module, import.meta.url), 'utf8');
|
|
304
|
+
expect(source, module).to.include('latchRuntimeError()');
|
|
305
|
+
expect(source, module).to.not.include("set('container-status'");
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('image bootstrap ABI', () => {
|
|
311
|
+
const deploySource = fs.readFileSync(new URL('../../../../src/cli/deploy.js', import.meta.url), 'utf8');
|
|
312
|
+
const cmdBlock = deploySource.slice(deploySource.indexOf('if (!cmd)'), deploySource.indexOf('const packageJson'));
|
|
313
|
+
|
|
314
|
+
it('names only `start` in the pod command', () => {
|
|
315
|
+
// The pod command is executed by whatever underpost the image ships, which is only as new
|
|
316
|
+
// as the last npm publish. Naming a newer command there kills the pod before it can pull
|
|
317
|
+
// the source that would have defined it.
|
|
318
|
+
const invoked = [...cmdBlock.matchAll(/`underpost ([a-z-]+)/g)].map((m) => m[1]);
|
|
319
|
+
expect(invoked.length).to.be.greaterThan(0);
|
|
320
|
+
expect([...new Set(invoked)]).to.deep.equal(['start']);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('keeps the commands a deployed image drives the pulled source through', () => {
|
|
324
|
+
// A released `start` lifecycle shells out to these against the checkout it just cloned.
|
|
325
|
+
// `app` replaced the `env` alias here: any image whose baked lifecycle still names `env`
|
|
326
|
+
// must be rebuilt, so a re-added alias would hide that rather than fix it.
|
|
327
|
+
const names = program.commands.map((command) => command.name());
|
|
328
|
+
for (const command of ['app', 'client', 'run', 'start']) expect(names, command).to.include(command);
|
|
329
|
+
expect(names).to.not.include('env');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('container status read', () => {
|
|
334
|
+
const monitorSource = fs.readFileSync(new URL('../../../../src/cli/monitor.js', import.meta.url), 'utf8');
|
|
335
|
+
const read = monitorSource.slice(
|
|
336
|
+
monitorSource.indexOf('const CONTAINER_STATUS_READ'),
|
|
337
|
+
monitorSource.indexOf('const grafanaAdminSyncState'),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
it('reads container status from the state domain and from nowhere else', () => {
|
|
341
|
+
// The host configuration store is node-scoped and survives the container; container status
|
|
342
|
+
// is neither. Reading it there once let a stale value from a previous workload answer for
|
|
343
|
+
// the current one, so that path is gone rather than kept as a fallback.
|
|
344
|
+
expect(read).to.include('underpost state get container-status');
|
|
345
|
+
expect(read).to.not.include('underpost config');
|
|
346
|
+
expect(read).to.not.include('host get container-status');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('never fails the read itself when the pod has recorded nothing yet', () => {
|
|
350
|
+
// An image that has the command but has not recorded a status exits zero with empty
|
|
351
|
+
// output; one too old to carry it exits non-zero. Both must reach the caller as "no
|
|
352
|
+
// reading", which classifies the pod as unreadable rather than as a status.
|
|
353
|
+
expect(read).to.include('|| true');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('reports what the pod said instead of collapsing every failure to one label', () => {
|
|
357
|
+
const start = monitorSource.indexOf('readRuntimeStatusViaExec(podName, namespace) {');
|
|
358
|
+
const exec = monitorSource.slice(start, monitorSource.indexOf('async readRuntimeStatusViaHttp(', start));
|
|
359
|
+
expect(exec).to.include('result?.stderr');
|
|
360
|
+
expect(exec).to.include('exit ${result?.code');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('prints nothing for an absent key so an empty read is unambiguous', () => {
|
|
364
|
+
const storeSource = fs.readFileSync(new URL('../../../../src/cli/dotenv-store.js', import.meta.url), 'utf8');
|
|
365
|
+
expect(storeSource).to.include("console.log(stored ?? '')");
|
|
366
|
+
});
|
|
367
|
+
});
|