@underpostnet/cyberia 3.3.0 → 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/.github/workflows/coverall.cyberia.ci.yml +34 -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/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +565 -2510
- package/CLI-HELP.md +306 -147
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +66 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +308 -91
- package/bin/deploy.js +18 -104
- package/bin/index.js +308 -91
- package/conf.js +91 -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 +6 -26
- package/deploy/dd-cyberia/init.sh +35 -43
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +92 -170
- 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 +12 -39
- package/deploy/release/deploy.sh +9 -39
- package/deployment.yaml +7 -2
- package/docker-compose.yml +7 -7
- 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/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/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 +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-world.js +8 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +2 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +2 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- 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 +0 -26
- 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.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +3 -3
- 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 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +184 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +153 -71
- 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 -6
- package/src/cli/index.js +322 -142
- 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 +558 -363
- package/src/cli/run.js +331 -650
- 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 +199 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1111 -306
- 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-portal/AppShellCyberiaPortal.js +2 -1
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- 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/CYBERIA-CLI.md +32 -5
- 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/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 +38 -30
- 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 +7 -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 +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 +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +53 -10
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +1 -1
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +1 -1
- 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 +1 -1
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +9 -1
- 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/docker-compose.yml +7 -7
- 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} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +3 -3
- 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} +108 -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} +513 -422
- 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} +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} +1 -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/{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 +1163 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +609 -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/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 +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/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/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/environment.js +0 -98
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership of configuration keys, and the projections that carry a value out of its owner.
|
|
3
|
+
*
|
|
4
|
+
* The platform's domains are `app`, `host`, `secret` and `state`; this module does not add a
|
|
5
|
+
* fifth. What it adds is the missing half of the host domain's model: its durable source is one
|
|
6
|
+
* env file, but the values in it answer to different concerns with different consumers and very
|
|
7
|
+
* different blast radii. Provisioning credentials, registry credentials and the cron jobs' own
|
|
8
|
+
* connection settings were reaching every workload that read that file, because "the host
|
|
9
|
+
* environment" was the only granularity the system had.
|
|
10
|
+
*
|
|
11
|
+
* Ownership and visibility are separate questions, and only ownership is answered by pattern.
|
|
12
|
+
*
|
|
13
|
+
* {@link CONFIG_OWNERSHIP} says where a key lives. A family pattern is right for that: a new
|
|
14
|
+
* `MAAS_` key is a provisioning key wherever it came from.
|
|
15
|
+
*
|
|
16
|
+
* {@link SCOPE_ENTITLEMENTS} says what a workload may read, and is an exact key list. A pattern
|
|
17
|
+
* there would mean the next credential added to a family silently inherits that family's audience
|
|
18
|
+
* — `GITHUB_NEW_SENSITIVE_TOKEN` becoming cron-visible because `GITHUB_` once was. So the policy
|
|
19
|
+
* is readable from the table alone: what cron can see is what the table names, and nothing a
|
|
20
|
+
* naming convention implies.
|
|
21
|
+
*
|
|
22
|
+
* Classification is closed at both ends: {@link classifyConfigKeys} reports anything it cannot
|
|
23
|
+
* place instead of defaulting it into a scope, and an entitlement the table does not name is
|
|
24
|
+
* refused. An unknown key is a design question, not a value to guess at.
|
|
25
|
+
*
|
|
26
|
+
* @module src/server/runtime/config-scope.js
|
|
27
|
+
* @namespace ConfigScope
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @constant CONFIG_SCOPES
|
|
32
|
+
* @description The scopes a configuration key can belong to, and what consumes each.
|
|
33
|
+
* @memberof ConfigScope
|
|
34
|
+
*/
|
|
35
|
+
const CONFIG_SCOPES = Object.freeze({
|
|
36
|
+
runtime: 'Process settings a manifest states about a workload; never carried by a projection.',
|
|
37
|
+
host: 'Node-level operation: the edge, the observability stack, cluster mail and deployment targets.',
|
|
38
|
+
cron: 'The scheduled jobs: their APIs, their DNS providers and the connection they reach the fleet on.',
|
|
39
|
+
baremetal: 'Machine provisioning: MAAS, its database, and the network a commissioned node boots on.',
|
|
40
|
+
publishing: 'Release identities: the registries and package indexes a publish authenticates to.',
|
|
41
|
+
app: "A deployment's own runtime: its database, its sessions, its integrations.",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @constant CONFIG_OWNERSHIP
|
|
46
|
+
* @description Key patterns in resolution order, each naming its owner and the scopes it is
|
|
47
|
+
* projected into. First match wins, so a specific rule is placed above the family it narrows.
|
|
48
|
+
* @memberof ConfigScope
|
|
49
|
+
*/
|
|
50
|
+
const CONFIG_OWNERSHIP = Object.freeze([
|
|
51
|
+
{ match: /^(NODE_ENV|NODE_OPTIONS|DOTENV_CONFIG_QUIET|ENABLE_FILE_LOGS|TIME_ZONE)$/, owner: 'runtime' },
|
|
52
|
+
|
|
53
|
+
{ match: /^(VULTR_|DDNS_|HTTP_PLAIN_IP_URL$)/, owner: 'cron' },
|
|
54
|
+
|
|
55
|
+
{ match: /^(MAAS_|DB_PG_MAAS_)/, owner: 'baremetal' },
|
|
56
|
+
{ match: /^(TFTP_ROOT|NETMASK|NFS_EXPORT_PATH)$/, owner: 'baremetal' },
|
|
57
|
+
|
|
58
|
+
{ match: /^(NPM_|DOCKER_HUB_|POSTMAN_)/, owner: 'publishing' },
|
|
59
|
+
|
|
60
|
+
{ match: /^(GITHUB_|DEFAULT_SSH_|FORWARD_PROXY_|DEFAULT_DEPLOY_)/, owner: 'host' },
|
|
61
|
+
{ match: /^(GF_SECURITY_|CLUSTER_MAILER_)/, owner: 'host' },
|
|
62
|
+
{ match: /^UNDERPOST_EVENT_TOKEN$/, owner: 'host' },
|
|
63
|
+
|
|
64
|
+
{ match: /^(DB_|MARIADB_|VALKEY_)/, owner: 'app' },
|
|
65
|
+
{ match: /^(JWT_SECRET|ACCESS_EXPIRE_MINUTES|REFRESH_EXPIRE_MINUTES|PORT|DEPLOY_ID)$/, owner: 'app' },
|
|
66
|
+
{ match: /^(DEFAULT_ADMIN_|CLOUDINARY_|WP_)/, owner: 'app' },
|
|
67
|
+
{ match: /^(GEMINI_API_KEY|NVIDIA_API_KEY)$/, owner: 'app' },
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @constant SCOPE_ENTITLEMENTS
|
|
72
|
+
* @description Exactly what each workload scope may read, by key name.
|
|
73
|
+
*
|
|
74
|
+
* Every entry is a key some code in this repository reads. A value the workload does not consume
|
|
75
|
+
* is not listed, because projecting one is not merely waste: `*_SSH_KEY_PATH` named a path that
|
|
76
|
+
* only exists on a host, and handing it to a pod made the resolver prefer it over the Secret
|
|
77
|
+
* mount holding the actual key.
|
|
78
|
+
* @memberof ConfigScope
|
|
79
|
+
*/
|
|
80
|
+
const SCOPE_ENTITLEMENTS = Object.freeze({
|
|
81
|
+
cron: Object.freeze([
|
|
82
|
+
// vultr: the API it meters against, and the edge it toggles egress on.
|
|
83
|
+
'VULTR_API_KEY',
|
|
84
|
+
'VULTR_INSTANCE_ID',
|
|
85
|
+
'VULTR_BANDWIDTH_THRESHOLD',
|
|
86
|
+
'VULTR_VPS_IP',
|
|
87
|
+
'VULTR_SSH_USER',
|
|
88
|
+
'VULTR_SSH_PORT',
|
|
89
|
+
'FORWARD_PROXY_API_KEY',
|
|
90
|
+
'FORWARD_PROXY_HOST',
|
|
91
|
+
'FORWARD_PROXY_PORT',
|
|
92
|
+
// backup and vultr: the connection out of the pod. The key itself is a Secret volume.
|
|
93
|
+
'DEFAULT_SSH_USER',
|
|
94
|
+
'DEFAULT_SSH_HOST',
|
|
95
|
+
'DEFAULT_SSH_PORT',
|
|
96
|
+
// backup: the identity its git operations authenticate as.
|
|
97
|
+
'GITHUB_TOKEN',
|
|
98
|
+
'GITHUB_USERNAME',
|
|
99
|
+
// dns: the provider it updates and the host it probes.
|
|
100
|
+
'DDNS_HOST',
|
|
101
|
+
'DDNS_PROVIDER',
|
|
102
|
+
'DDNS_API_KEY',
|
|
103
|
+
'DDNS_USER',
|
|
104
|
+
'HTTP_PLAIN_IP_URL',
|
|
105
|
+
'DEFAULT_DEPLOY_HOST',
|
|
106
|
+
]),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The owning scope of a key, and the scopes it reaches.
|
|
111
|
+
* @param {string} key - Environment variable name.
|
|
112
|
+
* @returns {{owner: string, projectedTo: string[]}|null} Ownership, or null when unclassified.
|
|
113
|
+
* @memberof ConfigScope
|
|
114
|
+
*/
|
|
115
|
+
const configOwnershipFactory = (key) => {
|
|
116
|
+
const name = `${key || ''}`;
|
|
117
|
+
const rule = CONFIG_OWNERSHIP.find((entry) => entry.match.test(name));
|
|
118
|
+
if (!rule) return null;
|
|
119
|
+
const projectedTo = Object.entries(SCOPE_ENTITLEMENTS)
|
|
120
|
+
.filter(([scope, keys]) => scope !== rule.owner && keys.includes(name))
|
|
121
|
+
.map(([scope]) => scope);
|
|
122
|
+
return { owner: rule.owner, projectedTo };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether a scope receives a key.
|
|
127
|
+
*
|
|
128
|
+
* A scope with an entitlement list reads that list and nothing else — owning a key is not by
|
|
129
|
+
* itself permission to ship it to a workload, which is what keeps a new member of an owned family
|
|
130
|
+
* from arriving in a pod unreviewed. Scopes without a list are host-side and read what they own.
|
|
131
|
+
*
|
|
132
|
+
* `runtime` is never a pass-through: process settings are what a manifest states about the
|
|
133
|
+
* workload it defines, not credentials a projection carries.
|
|
134
|
+
* @param {string} key - Environment variable name.
|
|
135
|
+
* @param {string} scope - Scope name.
|
|
136
|
+
* @returns {boolean} True when the scope may see the key.
|
|
137
|
+
* @memberof ConfigScope
|
|
138
|
+
*/
|
|
139
|
+
const scopeReceivesKey = (key, scope) => {
|
|
140
|
+
const ownership = configOwnershipFactory(key);
|
|
141
|
+
if (!ownership) return false;
|
|
142
|
+
const entitlement = SCOPE_ENTITLEMENTS[scope];
|
|
143
|
+
if (entitlement) return entitlement.includes(`${key || ''}`);
|
|
144
|
+
return ownership.owner === scope;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Splits an environment into the scopes that receive each key.
|
|
149
|
+
*
|
|
150
|
+
* `unclassified` is the fail-closed half: a key no rule places is reported rather than dropped
|
|
151
|
+
* into a default scope, because the two wrong answers — hiding it from a consumer that needs it,
|
|
152
|
+
* or handing a credential to a workload that does not — are both silent.
|
|
153
|
+
* @param {Object<string, string>} values - Parsed environment.
|
|
154
|
+
* @returns {{scopes: Object<string, string[]>, unclassified: string[]}} Key names per scope.
|
|
155
|
+
* @memberof ConfigScope
|
|
156
|
+
*/
|
|
157
|
+
const classifyConfigKeys = (values = {}) => {
|
|
158
|
+
const scopes = Object.fromEntries(Object.keys(CONFIG_SCOPES).map((scope) => [scope, []]));
|
|
159
|
+
const unclassified = [];
|
|
160
|
+
|
|
161
|
+
for (const key of Object.keys(values)) {
|
|
162
|
+
const ownership = configOwnershipFactory(key);
|
|
163
|
+
if (!ownership) {
|
|
164
|
+
unclassified.push(key);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
scopes[ownership.owner].push(key);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { scopes, unclassified };
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The subset of an environment a scope is entitled to.
|
|
175
|
+
* @param {Object<string, string>} values - Parsed environment.
|
|
176
|
+
* @param {string} scope - Scope name.
|
|
177
|
+
* @returns {Object<string, string>} Entitled key/value pairs.
|
|
178
|
+
* @memberof ConfigScope
|
|
179
|
+
*/
|
|
180
|
+
const scopeValuesFactory = (values = {}, scope = '') =>
|
|
181
|
+
Object.fromEntries(Object.entries(values).filter(([key]) => scopeReceivesKey(key, scope)));
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Renders a scope rejection without disclosing the value that caused it.
|
|
185
|
+
* @param {object} params - Rejection context.
|
|
186
|
+
* @param {string} params.domain - Domain reporting the rejection.
|
|
187
|
+
* @param {string} [params.deployId] - Deploy the source belongs to.
|
|
188
|
+
* @param {string} [params.env] - Environment selector.
|
|
189
|
+
* @param {string} params.key - Key that was rejected.
|
|
190
|
+
* @param {string} params.reason - Why it was rejected.
|
|
191
|
+
* @returns {string} Diagnostic line, values omitted by construction.
|
|
192
|
+
* @memberof ConfigScope
|
|
193
|
+
*/
|
|
194
|
+
const configRejectionFactory = ({ domain, deployId = '', env = '', key, reason }) =>
|
|
195
|
+
[
|
|
196
|
+
'configuration source rejected:',
|
|
197
|
+
`domain=${domain}`,
|
|
198
|
+
deployId ? `deploy=${deployId}` : '',
|
|
199
|
+
env ? `env=${env}` : '',
|
|
200
|
+
`key=${key}`,
|
|
201
|
+
`reason=${reason}`,
|
|
202
|
+
]
|
|
203
|
+
.filter(Boolean)
|
|
204
|
+
.join(' ');
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
CONFIG_OWNERSHIP,
|
|
208
|
+
CONFIG_SCOPES,
|
|
209
|
+
SCOPE_ENTITLEMENTS,
|
|
210
|
+
classifyConfigKeys,
|
|
211
|
+
configOwnershipFactory,
|
|
212
|
+
configRejectionFactory,
|
|
213
|
+
scopeReceivesKey,
|
|
214
|
+
scopeValuesFactory,
|
|
215
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment and global installation path resolution.
|
|
3
|
+
* @module src/server/runtime/environment.js
|
|
4
|
+
* @namespace ServerEnvironment
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import nodePath from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { loggerFactory } from '../ops/logger.js';
|
|
14
|
+
|
|
15
|
+
const logger = loggerFactory(import.meta);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Node directory backing every hostPath PersistentVolume the deploy flow
|
|
19
|
+
* materializes (`<root>/<pv id>`), including the shared gateway's static tree.
|
|
20
|
+
* Cluster bring-up gives it the shared container label, because the pods that
|
|
21
|
+
* read these documents are unprivileged.
|
|
22
|
+
* @constant {string}
|
|
23
|
+
* @memberof ServerEnvironment
|
|
24
|
+
*/
|
|
25
|
+
const HOST_VOLUME_ROOT = '/home/dd/engine/volume';
|
|
26
|
+
const packageRoot = nodePath.resolve(nodePath.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
27
|
+
const envFileCache = new Map();
|
|
28
|
+
let rootEnvPath;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the global npm module directory.
|
|
32
|
+
* @returns {string}
|
|
33
|
+
* @memberof ServerEnvironment
|
|
34
|
+
*/
|
|
35
|
+
const getNpmRootPath = () => {
|
|
36
|
+
try {
|
|
37
|
+
return execFileSync('npm', ['root', '-g'], {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
40
|
+
}).trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the global Underpost installation directory.
|
|
48
|
+
* @returns {string}
|
|
49
|
+
* @memberof ServerEnvironment
|
|
50
|
+
*/
|
|
51
|
+
const getUnderpostRootPath = () => {
|
|
52
|
+
const npmRoot = getNpmRootPath();
|
|
53
|
+
return npmRoot ? `${npmRoot}/underpost` : `${packageRoot}/.underpost`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const readEnvFile = (path) => {
|
|
57
|
+
if (envFileCache.has(path)) return envFileCache.get(path);
|
|
58
|
+
let values = {};
|
|
59
|
+
try {
|
|
60
|
+
if (path && fs.existsSync(path)) values = dotenv.parse(fs.readFileSync(path, 'utf8'));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.warn('Ignoring unreadable env file', { target: path, message: error.message });
|
|
63
|
+
}
|
|
64
|
+
envFileCache.set(path, values);
|
|
65
|
+
return values;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const environmentValueFactory = (key) => {
|
|
69
|
+
const processValue = `${process.env[key] ?? ''}`.trim();
|
|
70
|
+
if (processValue) return processValue;
|
|
71
|
+
|
|
72
|
+
if (rootEnvPath === undefined) {
|
|
73
|
+
const underpostRoot = getUnderpostRootPath();
|
|
74
|
+
rootEnvPath = underpostRoot ? `${underpostRoot}/.env` : '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const path of ['./.env', rootEnvPath]) {
|
|
78
|
+
const value = `${readEnvFile(path)[key] ?? ''}`.trim();
|
|
79
|
+
if (value) return value;
|
|
80
|
+
}
|
|
81
|
+
return '';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Single source of truth for resolving a deployment environment name from CLI options.
|
|
86
|
+
* Explicit `--env` always wins; `--dev` is the development shorthand; otherwise the
|
|
87
|
+
* caller's `fallback` decides. Deliberately does not read `process.env.NODE_ENV`, so a
|
|
88
|
+
* developer shell that exports it cannot silently retarget a deploy — callers whose
|
|
89
|
+
* contract is ambient (the client build) pass it in as `fallback` themselves.
|
|
90
|
+
* @method deployEnvFactory
|
|
91
|
+
* @param {object} [options] - CLI options carrying the environment selection.
|
|
92
|
+
* @param {string} [options.env] - Explicit environment name.
|
|
93
|
+
* @param {boolean} [options.dev] - Development context shorthand.
|
|
94
|
+
* @param {string} [fallback='production'] - Value when no option selects an environment.
|
|
95
|
+
* @returns {string} `development` | `production` | `test` | custom env name.
|
|
96
|
+
* @memberof ServerEnvironment
|
|
97
|
+
*/
|
|
98
|
+
const deployEnvFactory = (options = {}, fallback = 'production') => {
|
|
99
|
+
const explicit = `${options?.env ?? ''}`.trim();
|
|
100
|
+
if (explicit) return explicit;
|
|
101
|
+
if (options?.dev === true) return 'development';
|
|
102
|
+
return fallback;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Renders environment values as dotenv file contents.
|
|
107
|
+
* @method renderEnv
|
|
108
|
+
* @param {Object<string, *>} envObj - Environment values keyed by variable name.
|
|
109
|
+
* @returns {string} Dotenv text, one `KEY=value` per line.
|
|
110
|
+
* @memberof ServerEnvironment
|
|
111
|
+
*/
|
|
112
|
+
const renderEnv = (envObj) =>
|
|
113
|
+
Object.keys(envObj)
|
|
114
|
+
.map((key) => `${key}=${envObj[key]}`)
|
|
115
|
+
.join('\n');
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Writes environment values as a dotenv file.
|
|
119
|
+
* @method writeEnv
|
|
120
|
+
* @param {string} envPath - Destination file path.
|
|
121
|
+
* @param {Object<string, *>} envObj - Environment values keyed by variable name.
|
|
122
|
+
* @returns {void}
|
|
123
|
+
* @memberof ServerEnvironment
|
|
124
|
+
*/
|
|
125
|
+
const writeEnv = (envPath, envObj) => fs.writeFileSync(envPath, renderEnv(envObj), 'utf8');
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether this process runs inside an OCI container image, by Kubernetes service injection or
|
|
129
|
+
* Docker's marker file.
|
|
130
|
+
*
|
|
131
|
+
* Lives in this module because it is an environment fact two unrelated concerns read: the
|
|
132
|
+
* container runtime state store ({@link UnderpostState.isInsideContainer}, which re-exports it)
|
|
133
|
+
* and the OCI env overlay {@link ServerConfBuilder.deployEnvContentFactory} resolves.
|
|
134
|
+
* @method isOciRuntime
|
|
135
|
+
* @returns {boolean} True when running inside a container.
|
|
136
|
+
* @memberof ServerEnvironment
|
|
137
|
+
*/
|
|
138
|
+
const isOciRuntime = () => !!process.env.KUBERNETES_SERVICE_HOST || fs.existsSync('/.dockerenv');
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
deployEnvFactory,
|
|
142
|
+
environmentValueFactory,
|
|
143
|
+
getNpmRootPath,
|
|
144
|
+
getUnderpostRootPath,
|
|
145
|
+
HOST_VOLUME_ROOT,
|
|
146
|
+
isOciRuntime,
|
|
147
|
+
renderEnv,
|
|
148
|
+
writeEnv,
|
|
149
|
+
};
|
|
@@ -17,14 +17,18 @@
|
|
|
17
17
|
* `ProcessController.children` so SIGINT/SIGTERM forwarding can reach
|
|
18
18
|
* them before the parent exits.
|
|
19
19
|
*
|
|
20
|
-
* @module src/server/process.js
|
|
20
|
+
* @module src/server/runtime/process.js
|
|
21
21
|
* @namespace Process
|
|
22
22
|
*/
|
|
23
23
|
// https://nodejs.org/api/process
|
|
24
24
|
import shell from 'shelljs';
|
|
25
|
-
import
|
|
25
|
+
import fs from 'fs-extra';
|
|
26
|
+
import nodePath from 'node:path';
|
|
27
|
+
import { loggerFactory, redactSensitiveText } from '../ops/logger.js';
|
|
26
28
|
import clipboard from 'clipboardy';
|
|
27
|
-
import Underpost from '
|
|
29
|
+
import Underpost from '../../index.js';
|
|
30
|
+
import { latchRuntimeError } from './runtime-status.js';
|
|
31
|
+
import { executionDecisionFactory } from '../build/execution.js';
|
|
28
32
|
const logger = loggerFactory(import.meta);
|
|
29
33
|
/**
|
|
30
34
|
* Gets the current working directory, replacing backslashes with forward slashes for consistency.
|
|
@@ -140,16 +144,26 @@ class ProcessController {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
+
* Quotes one value as a single shell argument.
|
|
148
|
+
*
|
|
149
|
+
* Single source of that quoting: a composed command reaches `sh -c`, so any
|
|
150
|
+
* value carrying spaces, commas or quotes has to survive one more parse than
|
|
151
|
+
* the caller wrote it for. Single quotes are literal in POSIX shells, and an
|
|
152
|
+
* embedded single quote is closed, escaped and reopened.
|
|
153
|
+
* @memberof Process
|
|
154
|
+
* @param {*} value - Value to pass as one argument.
|
|
155
|
+
* @returns {string} The value, quoted.
|
|
156
|
+
*/
|
|
157
|
+
const shellArgumentFactory = (value) => `'${`${value ?? ''}`.replaceAll("'", `'\\''`)}'`;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Error thrown by `shellExec` on a non-zero exit, with structured process output.
|
|
147
161
|
*/
|
|
148
162
|
class ShellExecError extends Error {
|
|
149
163
|
constructor(cmd, code, stdout, stderr) {
|
|
150
|
-
super(`shellExec failed (exit=${code}): ${cmd}`);
|
|
164
|
+
super(`shellExec failed (exit=${code}): ${redactSensitiveText(cmd)}`);
|
|
151
165
|
this.name = 'ShellExecError';
|
|
152
|
-
this.cmd = cmd;
|
|
166
|
+
this.cmd = redactSensitiveText(cmd);
|
|
153
167
|
this.code = code;
|
|
154
168
|
this.stdout = stdout;
|
|
155
169
|
this.stderr = stderr;
|
|
@@ -180,8 +194,46 @@ class ShellExecError extends Error {
|
|
|
180
194
|
* @returns {string|shelljs.ShellString} `ShellString` by default; the stdout string when `stdout: true`.
|
|
181
195
|
* @throws {ShellExecError} On non-zero exit when `silentOnError` is not set.
|
|
182
196
|
*/
|
|
197
|
+
/**
|
|
198
|
+
* The result a denied read hands back.
|
|
199
|
+
*
|
|
200
|
+
* Shaped as a `ShellString` so an elided command is indistinguishable from one that ran
|
|
201
|
+
* and found nothing — which is the answer the `silentOnError` call sites already handle.
|
|
202
|
+
* @returns {shelljs.ShellString} Empty successful result.
|
|
203
|
+
* @memberof Process
|
|
204
|
+
*/
|
|
205
|
+
const neutralShellResult = () => {
|
|
206
|
+
const result = shell.ShellString('');
|
|
207
|
+
result.code = 0;
|
|
208
|
+
result.stdout = '';
|
|
209
|
+
result.stderr = '';
|
|
210
|
+
return result;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Applies the active execution profile to one command.
|
|
215
|
+
*
|
|
216
|
+
* This is the single gate the profile system rests on: because every external
|
|
217
|
+
* invocation reaches it, an offline stage needs no per-call-site opt-out.
|
|
218
|
+
* @param {string} cmd - The command about to run.
|
|
219
|
+
* @param {Object} options - The `shellExec` options.
|
|
220
|
+
* @returns {null|{elided: true, result: *}} `null` when the command may run.
|
|
221
|
+
* @memberof Process
|
|
222
|
+
*/
|
|
223
|
+
const executionGate = (cmd, options) => {
|
|
224
|
+
const decision = executionDecisionFactory(cmd);
|
|
225
|
+
if (decision.permitted) return null;
|
|
226
|
+
logger.info(`[${decision.profile}] elided ${decision.capability}`, redactSensitiveText(cmd));
|
|
227
|
+
return { elided: true, result: options.stdout ? '' : neutralShellResult() };
|
|
228
|
+
};
|
|
229
|
+
|
|
183
230
|
const shellExec = (cmd, options = {}) => {
|
|
184
|
-
|
|
231
|
+
const gated = executionGate(cmd, options);
|
|
232
|
+
if (gated) {
|
|
233
|
+
if (options.callback) return options.callback(0, '', '');
|
|
234
|
+
return gated.result;
|
|
235
|
+
}
|
|
236
|
+
if (!options.disableLog) logger.info(`cmd`, redactSensitiveText(cmd));
|
|
185
237
|
|
|
186
238
|
// Whitelist exactly the keys `shelljs.exec` understands. Passing our own
|
|
187
239
|
// bookkeeping keys through (or a literal `cwd: undefined`) makes shelljs
|
|
@@ -189,6 +241,7 @@ const shellExec = (cmd, options = {}) => {
|
|
|
189
241
|
const shellOpts = {};
|
|
190
242
|
if (options.silent !== undefined) shellOpts.silent = options.silent;
|
|
191
243
|
if (options.async !== undefined) shellOpts.async = options.async;
|
|
244
|
+
if (options.env !== undefined) shellOpts.env = options.env;
|
|
192
245
|
|
|
193
246
|
// Hermetic cwd. shelljs.cd mutates a process-wide global; instead we
|
|
194
247
|
// snapshot the current cwd here, switch for the duration of this call,
|
|
@@ -200,7 +253,7 @@ const shellExec = (cmd, options = {}) => {
|
|
|
200
253
|
try {
|
|
201
254
|
process.chdir(options.cwd);
|
|
202
255
|
} catch (err) {
|
|
203
|
-
|
|
256
|
+
latchRuntimeError();
|
|
204
257
|
throw new ShellExecError(cmd, -1, '', `chdir(${options.cwd}) failed: ${err.message}`);
|
|
205
258
|
}
|
|
206
259
|
}
|
|
@@ -214,7 +267,7 @@ const shellExec = (cmd, options = {}) => {
|
|
|
214
267
|
const result = shell.exec(cmd, shellOpts);
|
|
215
268
|
|
|
216
269
|
if (!options.silentOnError && result && typeof result.code === 'number' && result.code !== 0) {
|
|
217
|
-
|
|
270
|
+
latchRuntimeError();
|
|
218
271
|
throw new ShellExecError(cmd, result.code, result.stdout || '', result.stderr || '');
|
|
219
272
|
}
|
|
220
273
|
|
|
@@ -229,6 +282,58 @@ const shellExec = (cmd, options = {}) => {
|
|
|
229
282
|
}
|
|
230
283
|
}
|
|
231
284
|
};
|
|
285
|
+
/**
|
|
286
|
+
* Runs a command as a child process and resolves when it exits, without blocking the event loop.
|
|
287
|
+
*
|
|
288
|
+
* Same contract as {@link shellExec}: a non-zero exit throws {@link ShellExecError} unless
|
|
289
|
+
* `silentOnError`, and `stdout` returns the captured output. The difference is that timers and
|
|
290
|
+
* sockets keep running while the child does — which is what lets the in-pod status endpoint
|
|
291
|
+
* answer during a build instead of going silent for its whole duration.
|
|
292
|
+
*
|
|
293
|
+
* @memberof Process
|
|
294
|
+
* @param {string} cmd - The command to run.
|
|
295
|
+
* @param {Object} [options] - Options.
|
|
296
|
+
* @param {boolean} [options.silent] - Suppress child output on the parent stdio.
|
|
297
|
+
* @param {boolean} [options.silentOnError] - Resolve instead of throwing on a non-zero exit.
|
|
298
|
+
* @param {boolean} [options.stdout] - Resolve with the captured stdout rather than the result.
|
|
299
|
+
* @param {boolean} [options.disableLog] - Suppress the command log line.
|
|
300
|
+
* @param {string} [options.cwd] - Directory to run in.
|
|
301
|
+
* @param {NodeJS.ProcessEnv} [options.env] - Environment passed only to the child process.
|
|
302
|
+
* @returns {Promise<*>} Resolves with the result, or the stdout when `stdout` is set.
|
|
303
|
+
*/
|
|
304
|
+
const shellExecAsync = (cmd, options = {}) =>
|
|
305
|
+
new Promise((resolve, reject) => {
|
|
306
|
+
const gated = executionGate(cmd, options);
|
|
307
|
+
if (gated) return resolve(options.stdout ? '' : { code: 0, stdout: '', stderr: '' });
|
|
308
|
+
if (!options.disableLog) logger.info(`cmd`, redactSensitiveText(cmd));
|
|
309
|
+
const shellOpts = { async: true };
|
|
310
|
+
if (options.silent !== undefined) shellOpts.silent = options.silent;
|
|
311
|
+
if (options.cwd) shellOpts.cwd = options.cwd;
|
|
312
|
+
if (options.env !== undefined) shellOpts.env = options.env;
|
|
313
|
+
shell.exec(cmd, shellOpts, (code, stdout, stderr) => {
|
|
314
|
+
if (code !== 0 && !options.silentOnError) {
|
|
315
|
+
latchRuntimeError();
|
|
316
|
+
return reject(new ShellExecError(cmd, code, stdout || '', stderr || ''));
|
|
317
|
+
}
|
|
318
|
+
resolve(options.stdout ? stdout : { code, stdout, stderr });
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Blocks the calling thread for `ms` milliseconds.
|
|
324
|
+
*
|
|
325
|
+
* Retry backoff around the synchronous `shellExec` chains (kubectl exec, pod
|
|
326
|
+
* readiness polling) has no async boundary to await on, so the wait has to
|
|
327
|
+
* block. `Atomics.wait` keeps the CPU idle instead of spinning.
|
|
328
|
+
*
|
|
329
|
+
* @memberof Process
|
|
330
|
+
* @param {number} ms - Milliseconds to block for. Non-positive values are a no-op.
|
|
331
|
+
* @returns {void}
|
|
332
|
+
*/
|
|
333
|
+
const sleepSync = (ms) => {
|
|
334
|
+
if (!(typeof ms === 'number') || !(ms > 0)) return;
|
|
335
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
336
|
+
};
|
|
232
337
|
/**
|
|
233
338
|
* Changes the current working directory using shelljs.
|
|
234
339
|
*
|
|
@@ -258,6 +363,38 @@ const shellCd = (cd, options = { disableLog: false }) => {
|
|
|
258
363
|
* @returns {string} The shell command string for the daemon process.
|
|
259
364
|
*/
|
|
260
365
|
const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Installs a root-owned host file at an explicit mode, reporting whether it changed.
|
|
369
|
+
*
|
|
370
|
+
* Staged then installed rather than written in place: the caller may run
|
|
371
|
+
* unprivileged while the destination is root-owned, and `install -m` sets the
|
|
372
|
+
* mode as it copies, so a 0600 file is never briefly readable at the default
|
|
373
|
+
* umask. Returning the changed flag is what lets callers restart a service only
|
|
374
|
+
* when its unit actually differs.
|
|
375
|
+
*
|
|
376
|
+
* @param {object} params
|
|
377
|
+
* @param {string} params.target - Destination path.
|
|
378
|
+
* @param {string} params.content - File contents.
|
|
379
|
+
* @param {string} [params.mode] - Octal mode.
|
|
380
|
+
* @param {boolean} [params.dryRun] - Report the destination instead of writing.
|
|
381
|
+
* @returns {boolean} True when the file changed.
|
|
382
|
+
* @memberof ProcessService
|
|
383
|
+
*/
|
|
384
|
+
const installRootFile = ({ target, content, mode = '0644', dryRun = false }) => {
|
|
385
|
+
const current = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
|
|
386
|
+
if (current === content) return false;
|
|
387
|
+
if (dryRun) {
|
|
388
|
+
logger.info(`[dry-run] write ${target} (mode ${mode})`, { bytes: content.length });
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
const staged = nodePath.join('/tmp', `underpost-root-file-${nodePath.basename(target)}-${process.pid}`);
|
|
392
|
+
fs.writeFileSync(staged, content, { mode: 0o600 });
|
|
393
|
+
shellExec(`sudo mkdir -p ${nodePath.dirname(target)}`, { silent: true });
|
|
394
|
+
shellExec(`sudo install -m ${mode} -o root -g root ${staged} ${target}`, { silent: true });
|
|
395
|
+
fs.removeSync(staged);
|
|
396
|
+
return true;
|
|
397
|
+
};
|
|
261
398
|
/**
|
|
262
399
|
* Retrieves the process ID (PID) of the most recently created gnome-terminal instance.
|
|
263
400
|
* Note: This function is environment-specific (GNOME/Linux) and uses `pgrep -n`.
|
|
@@ -287,11 +424,15 @@ function pbcopy(data) {
|
|
|
287
424
|
logger.info(`copied to clipboard`, clipboard.readSync());
|
|
288
425
|
}
|
|
289
426
|
export {
|
|
427
|
+
installRootFile,
|
|
290
428
|
ProcessController,
|
|
291
429
|
ShellExecError,
|
|
292
430
|
getRootDirectory,
|
|
431
|
+
shellArgumentFactory,
|
|
293
432
|
shellExec,
|
|
433
|
+
shellExecAsync,
|
|
294
434
|
shellCd,
|
|
435
|
+
sleepSync,
|
|
295
436
|
pbcopy,
|
|
296
437
|
getTerminalPid,
|
|
297
438
|
daemonProcess,
|