@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
package/src/cli/run.js
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* @namespace UnderpostRun
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { daemonProcess, pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
7
|
+
import { daemonProcess, pbcopy, shellArgumentFactory, shellCd, shellExec } from '../server/runtime/process.js';
|
|
8
|
+
import { cli, withExecutionProfile } from '../server/build/execution.js';
|
|
9
|
+
import { syncDeployPackages } from '../server/build/package.js';
|
|
10
|
+
import { RUNTIME_STATUS } from '../server/runtime/runtime-status.js';
|
|
8
11
|
import {
|
|
9
12
|
awaitDeployMonitor,
|
|
10
|
-
buildKindPorts,
|
|
11
13
|
clusterContextFactory,
|
|
12
14
|
clusterTypeFactory,
|
|
13
15
|
dispatchBuildInstanceEnv,
|
|
@@ -22,38 +24,41 @@ import {
|
|
|
22
24
|
generateSecurePassword,
|
|
23
25
|
instanceHttpRouteRulesFactory,
|
|
24
26
|
instanceInterceptStatusesFactory,
|
|
27
|
+
instanceEnvFilePath,
|
|
25
28
|
instancePortFactory,
|
|
26
29
|
instanceProxyRoutesFactory,
|
|
27
30
|
instanceStatusPageEntriesFactory,
|
|
28
|
-
isDeployRunnerContext,
|
|
29
31
|
loadConfInstances,
|
|
30
32
|
loadProjectInstanceEnvBuilder,
|
|
31
33
|
loadConfServerJson,
|
|
32
34
|
loadReplicas,
|
|
33
|
-
resolveDeployList,
|
|
34
35
|
resolveEnvScoped,
|
|
35
36
|
selectConfInstances,
|
|
36
37
|
waitForPort,
|
|
37
38
|
clusterInstancesFactory,
|
|
38
39
|
deployTrafficEntriesFactory,
|
|
39
|
-
|
|
40
|
+
publicIngressProbeFactory,
|
|
40
41
|
hostIngressFactsFactory,
|
|
41
42
|
hostRenderInstancesFactory,
|
|
42
43
|
instanceTrafficPlanFactory,
|
|
43
44
|
trafficTableRowsFactory,
|
|
44
45
|
isTrafficServingFactory,
|
|
45
46
|
nextTrafficFactory,
|
|
47
|
+
redeployPlanFactory,
|
|
46
48
|
stopPlanFactory,
|
|
47
49
|
trafficFromRoutingInfoFactory,
|
|
48
|
-
} from '../server/conf.js';
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
50
|
+
} from '../server/runtime/conf.js';
|
|
51
|
+
import { buildKindPorts, deployRoutesExists, readDeployRoutes, resolveDeployList } from '../server/network/router.js';
|
|
52
|
+
import { cronDeployIdResolve } from '../server/ops/cron.js';
|
|
53
|
+
import { deployEnvFactory, getNpmRootPath, writeEnv } from '../server/runtime/environment.js';
|
|
54
|
+
import { actionInitLog, loggerFactory } from '../server/ops/logger.js';
|
|
52
55
|
|
|
53
56
|
import fs from 'fs-extra';
|
|
54
57
|
import { range, s4, setPad, timer } from '../client/components/core/CommonJs.js';
|
|
55
58
|
|
|
56
59
|
import os from 'os';
|
|
60
|
+
import nodePath from 'node:path';
|
|
61
|
+
import { domainContextFactory } from './domains.js';
|
|
57
62
|
import Underpost from '../index.js';
|
|
58
63
|
import dotenv from 'dotenv';
|
|
59
64
|
import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
|
|
@@ -69,7 +74,7 @@ import {
|
|
|
69
74
|
readHostInstanceRegistry,
|
|
70
75
|
writeHostInstanceRegistry,
|
|
71
76
|
writeHostServerConf,
|
|
72
|
-
} from '../server/underpost-gateway.js';
|
|
77
|
+
} from '../server/network/underpost-gateway.js';
|
|
73
78
|
const logger = loggerFactory(import.meta);
|
|
74
79
|
|
|
75
80
|
/**
|
|
@@ -89,6 +94,9 @@ const logger = loggerFactory(import.meta);
|
|
|
89
94
|
* @property {string} volumeHostPath - The host path for the volume.
|
|
90
95
|
* @property {string} volumeMountPath - The mount path for the volume.
|
|
91
96
|
* @property {string} imageName - The name of the image to run.
|
|
97
|
+
* @property {string} runtimeImage - `src/runtime/<name>` image family the cluster runner brings up (`cluster`).
|
|
98
|
+
* @property {string} image - Container image the deployment pulls and runs (`sync`).
|
|
99
|
+
* @property {string} versions - Comma-separated blue/green deployment versions (`sync`).
|
|
92
100
|
* @property {string} containerName - The name of the container to run.
|
|
93
101
|
* @property {string} namespace - The namespace to run in.
|
|
94
102
|
* @property {string} timeoutResponse - The response timeout duration.
|
|
@@ -137,6 +145,7 @@ const logger = loggerFactory(import.meta);
|
|
|
137
145
|
* @property {string} user - The user to run as.
|
|
138
146
|
* @property {string} group - The group to use.
|
|
139
147
|
* @property {string} pid - The process ID.
|
|
148
|
+
* @property {string} repoEnginePrivate - Private configuration repository the pull runner checks out, overriding the pairing derived from the engine source.
|
|
140
149
|
* @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
|
|
141
150
|
* @property {string} monitorStatus - The monitor status option.
|
|
142
151
|
* @property {string} monitorStatusKindType - The monitor status kind type option.
|
|
@@ -183,6 +192,9 @@ const DEFAULT_OPTION = {
|
|
|
183
192
|
volumeHostPath: '',
|
|
184
193
|
volumeMountPath: '',
|
|
185
194
|
imageName: '',
|
|
195
|
+
image: '',
|
|
196
|
+
runtimeImage: '',
|
|
197
|
+
versions: '',
|
|
186
198
|
containerName: '',
|
|
187
199
|
namespace: 'default',
|
|
188
200
|
timeoutResponse: '',
|
|
@@ -231,6 +243,7 @@ const DEFAULT_OPTION = {
|
|
|
231
243
|
user: '',
|
|
232
244
|
group: '',
|
|
233
245
|
pid: '',
|
|
246
|
+
repoEnginePrivate: '',
|
|
234
247
|
disablePrivateConfUpdate: false,
|
|
235
248
|
monitorStatus: '',
|
|
236
249
|
monitorStatusKindType: '',
|
|
@@ -271,33 +284,6 @@ const DEFAULT_OPTION = {
|
|
|
271
284
|
* @memberof UnderpostRun
|
|
272
285
|
*/
|
|
273
286
|
|
|
274
|
-
// Secrets `sops-setup` onboards when no explicit list is passed: the full self-hosted data tier.
|
|
275
|
-
// `mongodb-keyfile` is listed alongside `mongodb-secret` because the MongoDB StatefulSet mounts
|
|
276
|
-
// it as a volume for intra-replica-set auth and will not start without it, so onboarding the
|
|
277
|
-
// credentials alone would leave Mongo broken.
|
|
278
|
-
const SOPS_SETUP_DEFAULT_SECRETS = ['postgres-secret', 'mariadb-secret', 'mongodb-secret', 'mongodb-keyfile'];
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Produces a value for a Secret data key that has no origin seed file and no `--args` override.
|
|
282
|
-
* Key-aware because the data tier does not want one shape of secret: a replica-set keyfile is a
|
|
283
|
-
* long base64 blob, a username is an identifier, and everything else is a password.
|
|
284
|
-
* @param {string} key - Secret data key (e.g. 'password', 'username', 'mongodb-keyfile').
|
|
285
|
-
* @returns {string} Generated value.
|
|
286
|
-
* @memberof UnderpostRun
|
|
287
|
-
*/
|
|
288
|
-
const generateSeedValue = (key) => {
|
|
289
|
-
if (key === 'username') return 'admin';
|
|
290
|
-
// MongoDB keyfile: 6-1024 base64 characters shared by every replica-set member. Newlines are
|
|
291
|
-
// stripped so the value round-trips identically through YAML and through
|
|
292
|
-
// MongoBootstrap.readCredential, which strips them too.
|
|
293
|
-
if (key === 'mongodb-keyfile')
|
|
294
|
-
return shellExec(`openssl rand -base64 756`, { stdout: true, silent: true, disableLog: true }).replace(
|
|
295
|
-
/\r?\n/g,
|
|
296
|
-
'',
|
|
297
|
-
);
|
|
298
|
-
return generateSecurePassword(24);
|
|
299
|
-
};
|
|
300
|
-
|
|
301
287
|
class UnderpostRun {
|
|
302
288
|
/**
|
|
303
289
|
* @static
|
|
@@ -326,13 +312,12 @@ class UnderpostRun {
|
|
|
326
312
|
throw new Error(`Invalid Kubernetes node name: ${options.nodeName}`);
|
|
327
313
|
const env = options.dev ? 'development' : 'production';
|
|
328
314
|
const requestedDeploys = `${path || options.deployId || ''}`.trim();
|
|
329
|
-
const routerPath = './engine-private/deploy/dd.router';
|
|
330
315
|
const confRoot = './engine-private/conf';
|
|
331
316
|
const deployIds = [
|
|
332
317
|
...new Set(
|
|
333
318
|
requestedDeploys
|
|
334
319
|
? resolveDeployList(requestedDeploys)
|
|
335
|
-
:
|
|
320
|
+
: deployRoutesExists()
|
|
336
321
|
? resolveDeployList('dd')
|
|
337
322
|
: fs.existsSync(confRoot)
|
|
338
323
|
? fs
|
|
@@ -488,7 +473,7 @@ class UnderpostRun {
|
|
|
488
473
|
* @memberof UnderpostRun
|
|
489
474
|
*/
|
|
490
475
|
'dev-cluster': (path, options = DEFAULT_OPTION) => {
|
|
491
|
-
const baseCommand =
|
|
476
|
+
const baseCommand = cli('underpost', { local: true });
|
|
492
477
|
const mongoHosts = ['mongodb-0.mongodb-service'];
|
|
493
478
|
let primaryMongoHost = 'mongodb-0.mongodb-service';
|
|
494
479
|
const clusterType = clusterTypeFactory(options);
|
|
@@ -826,52 +811,21 @@ class UnderpostRun {
|
|
|
826
811
|
return results;
|
|
827
812
|
},
|
|
828
813
|
|
|
829
|
-
/**
|
|
830
|
-
* @method dev-hosts-expose
|
|
831
|
-
* @description Deploys a specified service in development mode with `/etc/hosts` modification for local access.
|
|
832
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID to deploy).
|
|
833
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
834
|
-
* @memberof UnderpostRun
|
|
835
|
-
*/
|
|
836
|
-
'dev-hosts-expose': async (path, options = DEFAULT_OPTION) => {
|
|
837
|
-
shellExec(`node bin deploy ${path} development --disable-update-deployment --disable-update-proxy --kubeadm`);
|
|
838
|
-
// /etc/hosts is written here, not by `deploy`: that command has no
|
|
839
|
-
// --etc-hosts option, and the `etc-hosts` runner already resolves a
|
|
840
|
-
// deploy's hosts from its conf.server.json.
|
|
841
|
-
await UnderpostRun.RUNNERS['etc-hosts']('', { ...options, deployId: path });
|
|
842
|
-
},
|
|
843
|
-
|
|
844
|
-
/**
|
|
845
|
-
* @method dev-hosts-restore
|
|
846
|
-
* @description Restores the `/etc/hosts` file to its original state after modifications made during development deployments.
|
|
847
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
848
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
849
|
-
* @memberof UnderpostRun
|
|
850
|
-
*/
|
|
851
|
-
'dev-hosts-restore': (path, options = DEFAULT_OPTION) => {
|
|
852
|
-
// Rewrite /etc/hosts with the loopback block alone, dropping the deploy
|
|
853
|
-
// host entries `dev-hosts-expose` (and the `cluster` runner) added.
|
|
854
|
-
const hostListenResult = etcHostFactory([]);
|
|
855
|
-
logger.info(hostListenResult.renderHosts);
|
|
856
|
-
},
|
|
857
|
-
|
|
858
814
|
/**
|
|
859
815
|
* @method cluster-build
|
|
860
|
-
* @description
|
|
861
|
-
*
|
|
816
|
+
* @description Resets the workspace and regenerates every routed deploy's default
|
|
817
|
+
* configuration, optionally committing the result to both repositories.
|
|
818
|
+
*
|
|
819
|
+
* Single-replica materialization is not done here: `deploy --sync` builds those folders
|
|
820
|
+
* as part of the deploy itself (`getDataDeploy({ buildSingleReplica: true })`).
|
|
821
|
+
* @param {string} path - `cmt` to commit the regenerated configuration; otherwise unused.
|
|
862
822
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
863
823
|
* @memberof UnderpostRun
|
|
864
824
|
*/
|
|
865
825
|
'cluster-build': (path, options = DEFAULT_OPTION) => {
|
|
866
|
-
const nodeOptions =
|
|
867
|
-
(options.nodeName ? ` --node-name ${options.nodeName}` : '') +
|
|
868
|
-
(options.sshKeyPath ? ` --ssh-key-path ${options.sshKeyPath}` : '');
|
|
869
826
|
shellExec(`node bin run clean`);
|
|
870
|
-
shellExec(`node bin
|
|
871
|
-
shellExec(`node bin
|
|
872
|
-
shellExec(`node bin env clean`);
|
|
873
|
-
for (const deployId of fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').split(','))
|
|
874
|
-
shellExec(`node bin new --default-conf --deploy-id ${deployId.trim()}`);
|
|
827
|
+
shellExec(`node bin app clean`);
|
|
828
|
+
for (const deployId of readDeployRoutes()) shellExec(`node bin new --default-conf --deploy-id ${deployId}`);
|
|
875
829
|
if (path === 'cmt') {
|
|
876
830
|
shellExec(`git add . && underpost cmt . build cluster-build`);
|
|
877
831
|
shellExec(`cd engine-private && git add . && underpost cmt . build cluster-build`);
|
|
@@ -887,14 +841,14 @@ class UnderpostRun {
|
|
|
887
841
|
* @memberof UnderpostRun
|
|
888
842
|
*/
|
|
889
843
|
'template-deploy': (path = '', options = DEFAULT_OPTION) => {
|
|
890
|
-
const baseCommand =
|
|
844
|
+
const baseCommand = cli('underpost', { local: true });
|
|
891
845
|
shellExec(`npm run security:secrets`);
|
|
892
846
|
const reportPath = './gitleaks-report.json';
|
|
893
847
|
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
894
848
|
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
895
849
|
return;
|
|
896
850
|
}
|
|
897
|
-
|
|
851
|
+
Underpost.repo.fastForwardEnginePair(baseCommand);
|
|
898
852
|
shellExec(`${baseCommand} run shared-dir`);
|
|
899
853
|
|
|
900
854
|
// Capture the sanitized message from the last N commits (--from-n-commit, default 1) for
|
|
@@ -964,14 +918,14 @@ class UnderpostRun {
|
|
|
964
918
|
* @memberof UnderpostRun
|
|
965
919
|
*/
|
|
966
920
|
'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
|
|
967
|
-
const baseCommand =
|
|
921
|
+
const baseCommand = cli('underpost', { local: true });
|
|
968
922
|
shellExec(`npm run security:secrets`);
|
|
969
923
|
const reportPath = './gitleaks-report.json';
|
|
970
924
|
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
971
925
|
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
972
926
|
return;
|
|
973
927
|
}
|
|
974
|
-
|
|
928
|
+
Underpost.repo.fastForwardEnginePair(baseCommand);
|
|
975
929
|
shellExec(`${baseCommand} run shared-dir`);
|
|
976
930
|
|
|
977
931
|
// Capture the sanitized message from the last N commits (--from-n-commit, default 1).
|
|
@@ -1015,47 +969,51 @@ class UnderpostRun {
|
|
|
1015
969
|
},
|
|
1016
970
|
/**
|
|
1017
971
|
* @method pull
|
|
1018
|
-
* @description
|
|
1019
|
-
*
|
|
972
|
+
* @description Brings the node's engine checkout and its private configuration to the tip of
|
|
973
|
+
* their repositories, at `/home/dd/engine` and `/home/dd/engine/engine-private`.
|
|
974
|
+
*
|
|
975
|
+
* With no argument this is the monorepo pair, `engine` and `engine-private`. Naming a source
|
|
976
|
+
* repository deploys that source instead, and its private conf repository is derived from the
|
|
977
|
+
* conf id they share rather than passed separately — `underpostnet/engine-test-lampp` pairs
|
|
978
|
+
* with `underpostnet/engine-lampp-private`, as does `underpostnet/engine-lampp`.
|
|
979
|
+
* `--repo-engine-private` overrides that derivation for a conf repository the pairing cannot
|
|
980
|
+
* name, and is the flag `deploy/lib/host.sh` forwards `ENGINE_SRC_PRIVATE_REPO` through.
|
|
981
|
+
*
|
|
982
|
+
* Both checkouts are replaced rather than merged, by the same `--switch-repo` route the fleet
|
|
983
|
+
* sync takes — so a node moves onto a test source repo and back without being reprovisioned,
|
|
984
|
+
* and one that has drifted onto commits of its own is brought back instead of refused.
|
|
985
|
+
*
|
|
986
|
+
* The replacement checkout is installed before this returns: it is a different commit than
|
|
987
|
+
* the one the caller started from, and nothing that follows can run against uninstalled
|
|
988
|
+
* dependencies.
|
|
989
|
+
* @param {string} path - Engine source repository, as `owner/repo` or a clone URL. Defaults to
|
|
990
|
+
* the `GITHUB_USERNAME` monorepo.
|
|
1020
991
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1021
992
|
* @memberof UnderpostRun
|
|
1022
993
|
*/
|
|
1023
|
-
pull: (path, options = DEFAULT_OPTION) => {
|
|
994
|
+
pull: (path = '', options = DEFAULT_OPTION) => {
|
|
1024
995
|
// shellExec is fail-fast by default — any non-zero exit throws and
|
|
1025
996
|
// propagates up to the workflow step. No per-call flag required.
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
* @method release-deploy
|
|
1045
|
-
* @description Executes deployment (`underpost run deploy`) for all deployment IDs listed in `./engine-private/deploy/dd.router`.
|
|
1046
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1047
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1048
|
-
* @memberof UnderpostRun
|
|
1049
|
-
*/
|
|
1050
|
-
'release-deploy': (path, options = DEFAULT_OPTION) => {
|
|
1051
|
-
actionInitLog();
|
|
1052
|
-
shellExec(`underpost --version`);
|
|
1053
|
-
shellCd(`/home/dd/engine`);
|
|
1054
|
-
for (const _deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
|
|
1055
|
-
const deployId = _deployId.trim();
|
|
1056
|
-
shellExec(`underpost run deploy ${deployId}`, { async: true });
|
|
1057
|
-
}
|
|
997
|
+
const { engine: source, enginePrivate: privateRepo } = Underpost.repo.enginePairFactory({
|
|
998
|
+
engine: path,
|
|
999
|
+
enginePrivate: options.repoEnginePrivate,
|
|
1000
|
+
});
|
|
1001
|
+
logger.info('Pulling engine source', { source, privateRepo });
|
|
1002
|
+
|
|
1003
|
+
const engineRoot = `/home/dd/engine`;
|
|
1004
|
+
// Cleaning is the in-process API rather than a shell round trip through the
|
|
1005
|
+
// global `underpost`: that bin is linked at the very checkout being replaced,
|
|
1006
|
+
// so a tree left without its dependencies made the pre-clean — and with it the
|
|
1007
|
+
// whole pull — exit non-zero on exactly the node that needed replacing.
|
|
1008
|
+
if (fs.existsSync(engineRoot)) Underpost.repo.clean({ paths: [engineRoot, `${engineRoot}/engine-private`] });
|
|
1009
|
+
Underpost.repo.syncCheckout({ path: engineRoot, repo: source });
|
|
1010
|
+
Underpost.repo.syncCheckout({ path: `${engineRoot}/engine-private`, repo: privateRepo });
|
|
1011
|
+
// The checkout that lands here is a different commit than the one this process
|
|
1012
|
+
// started from, and its package.json is only a description until it is installed.
|
|
1013
|
+
// Everything after this — the linked global bin included — runs from this tree.
|
|
1014
|
+
shellExec(`cd ${engineRoot} && npm install`);
|
|
1058
1015
|
},
|
|
1016
|
+
|
|
1059
1017
|
/**
|
|
1060
1018
|
* @method ssh-deploy
|
|
1061
1019
|
* @description Dispatches the corresponding CD workflow for SSH-based deployment, replacing empty commits with workflow_dispatch.
|
|
@@ -1125,36 +1083,35 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1125
1083
|
},
|
|
1126
1084
|
/**
|
|
1127
1085
|
* @method sync
|
|
1128
|
-
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`)
|
|
1086
|
+
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`).
|
|
1087
|
+
*
|
|
1088
|
+
* Every input is a flag: `--deploy-id`, `--replicas`, `--versions`, `--image`, `--node-name`.
|
|
1129
1089
|
*
|
|
1130
1090
|
* Forwards `--image-pull-policy <policy>` to the underlying `deploy --build-manifest` invocation when `options.imagePullPolicy` is set,
|
|
1131
1091
|
* which then plumbs through `buildManifest` and `deploymentYamlPartsFactory` to override the container `imagePullPolicy` in the generated
|
|
1132
1092
|
* `deployment.yaml`. Useful when you want to force `Always` so the kubelet re-pulls a mutable tag on every rollout. Example:
|
|
1133
|
-
* `node bin run sync dd-core --kubeadm --image-pull-policy Always`
|
|
1134
|
-
*
|
|
1093
|
+
* `node bin run sync --deploy-id dd-core --kubeadm --image-pull-policy Always`
|
|
1094
|
+
*
|
|
1095
|
+
* Runs in one of two modes. Given `--deploy-id`, it deploys. Passed `--build`, or no
|
|
1096
|
+
* `--deploy-id` at all, it renders the manifests and the router table and stops before
|
|
1097
|
+
* touching the cluster:
|
|
1098
|
+
* `node bin run --dev --build sync --deploy-id dd-default`
|
|
1099
|
+
* @param {string} [path] - Unused; every input is a flag.
|
|
1135
1100
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1136
1101
|
* @memberof UnderpostRun
|
|
1137
1102
|
*/
|
|
1138
1103
|
sync: async (path, options = DEFAULT_OPTION) => {
|
|
1139
|
-
|
|
1140
|
-
const env = options.dev ? 'development' : 'production';
|
|
1104
|
+
const env = deployEnvFactory(options);
|
|
1141
1105
|
options = { ...options, gatewayApi: gatewayApiEnabledFactory(options) };
|
|
1142
|
-
const baseCommand = '
|
|
1106
|
+
const baseCommand = cli('underpost', { local: true });
|
|
1143
1107
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1144
1108
|
const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
];
|
|
1152
|
-
let [deployId, replicas, versions, image, node] = path ? path.split(',') : defaultPath;
|
|
1153
|
-
deployId = deployId ? deployId : defaultPath[0];
|
|
1154
|
-
replicas = replicas ? replicas : defaultPath[1];
|
|
1155
|
-
versions = versions ? versions.replaceAll('+', ',') : defaultPath[2];
|
|
1156
|
-
image = image ? image : defaultPath[3];
|
|
1157
|
-
node = node ? node : options.nodeName ? options.nodeName : defaultPath[4];
|
|
1109
|
+
const deploying = !options.build && !!options.deployId;
|
|
1110
|
+
const deployId = options.deployId || 'dd-default';
|
|
1111
|
+
const replicas = options.replicas || 1;
|
|
1112
|
+
const image = options.image || '';
|
|
1113
|
+
const node = options.nodeName || (!options.kubeadm && !options.k3s ? 'kind-control-plane' : os.hostname());
|
|
1114
|
+
let versions = options.versions || '';
|
|
1158
1115
|
shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
|
|
1159
1116
|
|
|
1160
1117
|
if (image && !image.startsWith('localhost'))
|
|
@@ -1165,7 +1122,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1165
1122
|
k3s: options.k3s,
|
|
1166
1123
|
});
|
|
1167
1124
|
|
|
1168
|
-
if (
|
|
1125
|
+
if (deploying) {
|
|
1169
1126
|
if (!options.disablePrivateConfUpdate) {
|
|
1170
1127
|
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
1171
1128
|
if (!validVersion) throw new Error('Version mismatch');
|
|
@@ -1183,7 +1140,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1183
1140
|
}
|
|
1184
1141
|
}
|
|
1185
1142
|
|
|
1186
|
-
const currentTraffic =
|
|
1143
|
+
const currentTraffic = deploying
|
|
1187
1144
|
? Underpost.deploy.getCurrentTraffic(deployId, {
|
|
1188
1145
|
namespace: options.namespace,
|
|
1189
1146
|
env,
|
|
@@ -1207,14 +1164,12 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1207
1164
|
});
|
|
1208
1165
|
|
|
1209
1166
|
const ignorePods =
|
|
1210
|
-
|
|
1167
|
+
deploying && targetTraffic
|
|
1211
1168
|
? Underpost.kubectl.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace).map((p) => p.NAME)
|
|
1212
1169
|
: [];
|
|
1213
1170
|
|
|
1214
1171
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
1215
|
-
const cmdString = options.cmd
|
|
1216
|
-
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
1217
|
-
: '';
|
|
1172
|
+
const cmdString = options.cmd ? ` --cmd ${shellArgumentFactory(options.cmd)}` : '';
|
|
1218
1173
|
const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
|
|
1219
1174
|
|
|
1220
1175
|
const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
|
|
@@ -1223,11 +1178,22 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1223
1178
|
const sshKeyPathFlag = options.sshKeyPath ? ` --ssh-key-path ${options.sshKeyPath}` : '';
|
|
1224
1179
|
const gatewayApiFlags = Underpost.deploy.gatewayApiFlagsFactory(options);
|
|
1225
1180
|
|
|
1181
|
+
// Retarget `underpost-config` at this sync's environment before anything is
|
|
1182
|
+
// deployed. `underpost host load` reads it back inside the
|
|
1183
|
+
// pod, so a stale Secret makes the container resolve the wrong NODE_ENV.
|
|
1184
|
+
if (deploying) {
|
|
1185
|
+
Underpost.host.apply(domainContextFactory({ env, namespace: options.namespace }));
|
|
1186
|
+
Underpost.app.apply(
|
|
1187
|
+
domainContextFactory({ env, namespace: options.namespace, args: { 'deploy-id': deployId } }),
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1226
1191
|
// A direct sync owns the same gateway-first contract as the full cluster
|
|
1227
1192
|
// runner. Build the host-side SSR documents before generating routes unless
|
|
1228
|
-
// the caller explicitly selected a pre-built bundle workflow.
|
|
1229
|
-
|
|
1230
|
-
|
|
1193
|
+
// the caller explicitly selected a pre-built bundle workflow. `--env` is
|
|
1194
|
+
// explicit: the SSR pass must render for the target environment, never for
|
|
1195
|
+
// whatever NODE_ENV the calling shell happens to carry.
|
|
1196
|
+
if (deploying && !options.skipFullBuild) shellExec(`${baseCommand} client ${deployId} --env ${env} --ssr`);
|
|
1231
1197
|
|
|
1232
1198
|
shellExec(
|
|
1233
1199
|
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
@@ -1237,7 +1203,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1237
1203
|
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag}${imagePullPolicyFlag}${sshKeyPathFlag}${gatewayApiFlags} ${deployId} ${env}`,
|
|
1238
1204
|
);
|
|
1239
1205
|
|
|
1240
|
-
if (
|
|
1206
|
+
if (deploying) {
|
|
1241
1207
|
if (options.gatewayApi) {
|
|
1242
1208
|
const namespace = options.namespace || 'default';
|
|
1243
1209
|
const gatewayRoot = Underpost.deploy.underpostGatewayRootFactory(options);
|
|
@@ -1308,6 +1274,48 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1308
1274
|
);
|
|
1309
1275
|
},
|
|
1310
1276
|
|
|
1277
|
+
/**
|
|
1278
|
+
* @method net-tables
|
|
1279
|
+
* @description Prints the host's network tables: interfaces and links (`ip -br link show`) with state, MAC address, and flags, followed by IP addresses (`ip -br addr show`) with IPv4/IPv6, each aligned via `column -t`.
|
|
1280
|
+
* @param {string} path - The input value, identifier, or path for the operation (unused).
|
|
1281
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow (unused).
|
|
1282
|
+
* @memberof UnderpostRun
|
|
1283
|
+
*/
|
|
1284
|
+
'net-tables': (path, options = DEFAULT_OPTION) => {
|
|
1285
|
+
logger.info(`INTERFACES & LINKS`);
|
|
1286
|
+
shellExec(`(echo "INTERFACE STATE MAC FLAGS"; ip -br link show) | column -t`);
|
|
1287
|
+
logger.info(`IP ADDRESSES`);
|
|
1288
|
+
shellExec(`(echo "INTERFACE STATE IPV4 IPV6"; ip -br addr show) | column -t`);
|
|
1289
|
+
logger.info('KERNEL ROUTING TABLE');
|
|
1290
|
+
shellExec(`(echo "DESTINATION GATEWAY INTERFACE PROTO SCOPE METRIC"; \
|
|
1291
|
+
ip route show | awk '{
|
|
1292
|
+
dst = $1; gw = "on-link"; dev = "-"; proto = "-"; scope = "-"; metric = "-";
|
|
1293
|
+
for (i=1; i<=NF; i++) {
|
|
1294
|
+
if ($i == "via") gw = $(i+1);
|
|
1295
|
+
if ($i == "dev") dev = $(i+1);
|
|
1296
|
+
if ($i == "proto") proto = $(i+1);
|
|
1297
|
+
if ($i == "scope") scope = $(i+1);
|
|
1298
|
+
if ($i == "metric") metric = $(i+1);
|
|
1299
|
+
}
|
|
1300
|
+
print dst, gw, dev, proto, scope, metric;
|
|
1301
|
+
}') | column -t`);
|
|
1302
|
+
logger.info(`ACTIVE NAT FLOWS`);
|
|
1303
|
+
shellExec(`(echo "CHAIN TYPE TARGET_IP_PORT PACKETS BYTES"; \
|
|
1304
|
+
sudo nft list table ip nat | awk '
|
|
1305
|
+
/chain (PREROUTING|POSTROUTING)/ {chain=$2}
|
|
1306
|
+
/dnat to|masquerade|snat to/ && !/packets 0/ {
|
|
1307
|
+
type = ($0 ~ "dnat") ? "DNAT" : (($0 ~ "masquerade") ? "MASQUERADE" : "SNAT");
|
|
1308
|
+
target = "N/A";
|
|
1309
|
+
pkts = "0"; bytes = "0";
|
|
1310
|
+
for(i=1; i<=NF; i++) {
|
|
1311
|
+
if ($i == "packets") pkts = $(i+1);
|
|
1312
|
+
if ($i == "bytes") bytes = $(i+1);
|
|
1313
|
+
if ($i == "to") target = $(i+1);
|
|
1314
|
+
}
|
|
1315
|
+
print chain, type, target, pkts, bytes;
|
|
1316
|
+
}') | column -t`);
|
|
1317
|
+
},
|
|
1318
|
+
|
|
1311
1319
|
/**
|
|
1312
1320
|
* @method stop
|
|
1313
1321
|
* @description Deletes colour-suffixed Deployments and their Services, leaving routing untouched.
|
|
@@ -1419,8 +1427,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1419
1427
|
? options.timezone
|
|
1420
1428
|
: path
|
|
1421
1429
|
? path
|
|
1422
|
-
: Underpost.
|
|
1423
|
-
? Underpost.
|
|
1430
|
+
: Underpost.host.store.get('TIME_ZONE', undefined, { disableLog: true })
|
|
1431
|
+
? Underpost.host.store.get('TIME_ZONE')
|
|
1424
1432
|
: process.env.TIME_ZONE
|
|
1425
1433
|
? process.env.TIME_ZONE
|
|
1426
1434
|
: 'America/New_York';
|
|
@@ -1563,7 +1571,6 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1563
1571
|
// Probe the exact public URL represented by each host/path row. PWA rows
|
|
1564
1572
|
// can carry several configured paths in one cell, while instance rows
|
|
1565
1573
|
// carry one; cache by URL so shared rows never repeat network work.
|
|
1566
|
-
const shellArg = (value) => `'${`${value}`.replaceAll("'", "'\\''")}'`;
|
|
1567
1574
|
const probeCache = new Map();
|
|
1568
1575
|
const probePath = (host, routePath, tls) => {
|
|
1569
1576
|
const normalizedPath = `${routePath || '/'}`.startsWith('/') ? `${routePath || '/'}` : `/${routePath}`;
|
|
@@ -1573,18 +1580,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1573
1580
|
} catch {
|
|
1574
1581
|
return { path: normalizedPath, url: '', statuses: ['000'] };
|
|
1575
1582
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
// per hop, -i keeps full response headers available, and -s removes
|
|
1579
|
-
// only the progress meter. The body is discarded to keep a status
|
|
1580
|
-
// report bounded even when a host returns a large application page.
|
|
1581
|
-
const raw = shellExec(
|
|
1582
|
-
`curl -L -v -i -s --connect-timeout 3 --max-time 12 --max-redirs 10 ` +
|
|
1583
|
-
`-o /dev/null -w '\nUNDERPOST_CURL_FINAL=%{http_code}\n' ${shellArg(url)} 2>&1 || true`,
|
|
1584
|
-
{ stdout: true, silent: true, silentOnError: true, disableLog: true },
|
|
1585
|
-
);
|
|
1586
|
-
probeCache.set(url, curlStatusChainFactory(raw));
|
|
1587
|
-
}
|
|
1583
|
+
// Cache by URL so rows that share one never repeat network work.
|
|
1584
|
+
if (!probeCache.has(url)) probeCache.set(url, publicIngressProbeFactory(url));
|
|
1588
1585
|
return { path: normalizedPath, url, statuses: probeCache.get(url) };
|
|
1589
1586
|
};
|
|
1590
1587
|
|
|
@@ -1625,7 +1622,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1625
1622
|
|
|
1626
1623
|
// Padded on the raw values, coloured afterwards: an ANSI escape counts
|
|
1627
1624
|
// toward String.length and would skew every column right of it.
|
|
1628
|
-
const columns = ['HOST', 'PATH', 'KIND', 'ROUTE', 'TLS', 'HTTP3', 'CURRENT', 'OPPOSITE'];
|
|
1625
|
+
const columns = ['#', 'HOST', 'PATH', 'KIND', 'ROUTE', 'TLS', 'HTTP3', 'CURRENT', 'OPPOSITE'];
|
|
1629
1626
|
const deploymentStatus = (status, includeServing = false) => {
|
|
1630
1627
|
if (!status) return `unrouted - missing${includeServing ? ' not-serving' : ''}`;
|
|
1631
1628
|
return [
|
|
@@ -1635,10 +1632,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1635
1632
|
...(includeServing ? [status.serving ? 'serving' : 'not-serving'] : []),
|
|
1636
1633
|
].join(' ');
|
|
1637
1634
|
};
|
|
1638
|
-
const cellOf = (row) => {
|
|
1635
|
+
const cellOf = (row, index) => {
|
|
1639
1636
|
const facts = ingressFacts[row.host] || {};
|
|
1640
1637
|
const pathStatus = row.probes.map((probe) => `${probe.path} [${probe.statuses.join('→')}]`).join(' ');
|
|
1641
1638
|
return [
|
|
1639
|
+
`${index + 1}(${row.probes.length})`,
|
|
1642
1640
|
row.host,
|
|
1643
1641
|
pathStatus,
|
|
1644
1642
|
row.kind,
|
|
@@ -1693,7 +1691,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1693
1691
|
for (const cell of cells) console.log(line(cell, true));
|
|
1694
1692
|
};
|
|
1695
1693
|
for (const env of envs) {
|
|
1696
|
-
const
|
|
1694
|
+
const envRows = reportRows.filter((row) => row.env === env);
|
|
1695
|
+
const envCells = envRows.map((row, index) => cellOf(row, index));
|
|
1697
1696
|
if (envCells.length > 0) printTable(envCells, `[${env.toUpperCase()}]`);
|
|
1698
1697
|
}
|
|
1699
1698
|
console.log('');
|
|
@@ -1978,22 +1977,33 @@ EOF
|
|
|
1978
1977
|
|
|
1979
1978
|
/**
|
|
1980
1979
|
* @method instance
|
|
1981
|
-
* @
|
|
1980
|
+
* @description Deploys the custom instances a deploy declares in `conf.instances.json`.
|
|
1981
|
+
*
|
|
1982
|
+
* Every input is a flag: `--deploy-id`, `--instance-id`, `--replicas`, `--node-name`.
|
|
1983
|
+
* `--instance-id` naming a template id selects its whole variant family.
|
|
1984
|
+
* @param {string} path - Unused; every input is a flag.
|
|
1982
1985
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1983
1986
|
* @memberof UnderpostRun
|
|
1984
1987
|
*/
|
|
1985
1988
|
instance: async (path = '', options = DEFAULT_OPTION) => {
|
|
1986
|
-
const env = options
|
|
1989
|
+
const env = deployEnvFactory(options);
|
|
1987
1990
|
options = {
|
|
1988
1991
|
...options,
|
|
1989
1992
|
gatewayApi: gatewayApiEnabledFactory(options),
|
|
1990
1993
|
namespace: options.namespace || 'default',
|
|
1991
1994
|
};
|
|
1992
|
-
const baseCommand =
|
|
1995
|
+
const baseCommand = cli('underpost', { local: true });
|
|
1993
1996
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1994
|
-
|
|
1995
|
-
|
|
1997
|
+
const deployId = options.deployId;
|
|
1998
|
+
const id = options.instanceId;
|
|
1999
|
+
const replicas = options.replicas || 1;
|
|
2000
|
+
if (!deployId) throw new Error('The --deploy-id option is required by the instance runner');
|
|
2001
|
+
if (!id) throw new Error('The --instance-id option is required by the instance runner');
|
|
1996
2002
|
const confInstances = selectConfInstances(loadConfInstances(deployId), id);
|
|
2003
|
+
// Retarget `underpost-config` at this run's environment before any instance
|
|
2004
|
+
// Deployment is submitted: it is what `underpost host load` reads inside the pod,
|
|
2005
|
+
// and a stale one makes the container resolve the wrong NODE_ENV.
|
|
2006
|
+
if (!options.expose) Underpost.host.apply(domainContextFactory({ env, namespace: options.namespace }));
|
|
1997
2007
|
const { liveTrafficById, targetTrafficById, serving } = instanceTrafficPlanFactory({
|
|
1998
2008
|
instances: confInstances,
|
|
1999
2009
|
requestedTraffic: options.traffic,
|
|
@@ -2089,7 +2099,6 @@ EOF
|
|
|
2089
2099
|
const targetTraffic = targetTrafficById[instance.id];
|
|
2090
2100
|
const podId = `${_deployId}-${env}-${targetTraffic}`;
|
|
2091
2101
|
const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
|
|
2092
|
-
Underpost.deploy.configMap(env, options.namespace);
|
|
2093
2102
|
shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
|
|
2094
2103
|
shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
|
|
2095
2104
|
for (const _volume of _volumes)
|
|
@@ -2219,11 +2228,10 @@ EOF
|
|
|
2219
2228
|
|
|
2220
2229
|
/**
|
|
2221
2230
|
* @method deploy-key
|
|
2222
|
-
* @description Copies the deploy key for a specific user
|
|
2231
|
+
* @description Copies the cluster scoped deploy key for a specific user to a temporary location on the local machine.
|
|
2223
2232
|
* @param {string} path - The input value, identifier, or path for the operation (not used in this method).
|
|
2224
2233
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2225
2234
|
* @param {string} options.user - The user for which to copy the deploy key.
|
|
2226
|
-
* @param {string} options.deployId - The deployment identifier associated with the deploy key.
|
|
2227
2235
|
* @memberof UnderpostRun
|
|
2228
2236
|
*/
|
|
2229
2237
|
'deploy-key': (path, options = DEFAULT_OPTION) => {
|
|
@@ -2232,13 +2240,18 @@ EOF
|
|
|
2232
2240
|
shellExec(`rm -rf /home/dd/tmp/${prefix}_*`);
|
|
2233
2241
|
return;
|
|
2234
2242
|
}
|
|
2235
|
-
if (!options.user
|
|
2236
|
-
logger.error('
|
|
2243
|
+
if (!options.user) {
|
|
2244
|
+
logger.error('The --user option is required to copy the deploy key.');
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const sourcePath = `${Underpost.ssh.userKeyDir(options.user)}/id_rsa`;
|
|
2248
|
+
if (!fs.existsSync(sourcePath)) {
|
|
2249
|
+
logger.error(`No deploy key found at ${sourcePath}; run: node bin ssh --user ${options.user} --user-add`);
|
|
2237
2250
|
return;
|
|
2238
2251
|
}
|
|
2239
2252
|
const targetPath = `/home/dd/tmp/${prefix}_${s4()}${s4()}`;
|
|
2240
2253
|
fs.mkdirSync('/home/dd/tmp', { recursive: true });
|
|
2241
|
-
fs.copyFileSync(
|
|
2254
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
2242
2255
|
logger.info(`Copied deploy key to ${targetPath}`);
|
|
2243
2256
|
if (options.copy) pbcopy(targetPath);
|
|
2244
2257
|
},
|
|
@@ -2569,11 +2582,11 @@ EOF
|
|
|
2569
2582
|
// mode this build ran. The default/template instance owns the committed
|
|
2570
2583
|
// source files, so only its current-mode file is idempotently refreshed.
|
|
2571
2584
|
if (instance.templateId) {
|
|
2572
|
-
const instanceEnvDir =
|
|
2585
|
+
const instanceEnvDir = nodePath.dirname(instanceEnvFilePath(deployId, _id, env));
|
|
2573
2586
|
fs.mkdirpSync(instanceEnvDir);
|
|
2574
2587
|
const envsToWrite = isDefaultInstance ? [env] : ['development', 'production'];
|
|
2575
2588
|
for (const targetEnv of envsToWrite) {
|
|
2576
|
-
const templateEnvPath =
|
|
2589
|
+
const templateEnvPath = instanceEnvFilePath(deployId, instance.templateId, targetEnv);
|
|
2577
2590
|
if (!fs.existsSync(templateEnvPath))
|
|
2578
2591
|
throw new Error(`[instance-build-manifest] Missing canonical env file: ${templateEnvPath}`);
|
|
2579
2592
|
const baseEnv = dotenv.parse(fs.readFileSync(templateEnvPath, 'utf8'));
|
|
@@ -2585,7 +2598,7 @@ EOF
|
|
|
2585
2598
|
containerDeployId: `${_deployId}-${targetEnv}`,
|
|
2586
2599
|
builders: instanceEnvBuilder ? { [deployId]: instanceEnvBuilder } : {},
|
|
2587
2600
|
});
|
|
2588
|
-
writeEnv(
|
|
2601
|
+
writeEnv(instanceEnvFilePath(deployId, _id, targetEnv), builtEnv);
|
|
2589
2602
|
}
|
|
2590
2603
|
logger.info('[instance-build-manifest] Instance env written', {
|
|
2591
2604
|
dir: instanceEnvDir,
|
|
@@ -2667,7 +2680,6 @@ EOF
|
|
|
2667
2680
|
* @memberof UnderpostRun
|
|
2668
2681
|
*/
|
|
2669
2682
|
'host-update': async (path, options = DEFAULT_OPTION) => {
|
|
2670
|
-
// const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2671
2683
|
shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
|
|
2672
2684
|
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
2673
2685
|
},
|
|
@@ -2734,7 +2746,7 @@ EOF`);
|
|
|
2734
2746
|
* @memberof UnderpostRun
|
|
2735
2747
|
*/
|
|
2736
2748
|
'dd-container': async (path = '', options = DEFAULT_OPTION) => {
|
|
2737
|
-
const baseCommand =
|
|
2749
|
+
const baseCommand = cli('underpost', { local: true });
|
|
2738
2750
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
2739
2751
|
const currentImage = options.imageName
|
|
2740
2752
|
? options.imageName
|
|
@@ -2831,15 +2843,15 @@ EOF`);
|
|
|
2831
2843
|
* @memberof UnderpostRun
|
|
2832
2844
|
*/
|
|
2833
2845
|
'git-conf': (path = '', options = DEFAULT_OPTION) => {
|
|
2834
|
-
const defaultUsername = Underpost.
|
|
2835
|
-
const defaultEmail = Underpost.
|
|
2846
|
+
const defaultUsername = Underpost.host.store.get('GITHUB_USERNAME');
|
|
2847
|
+
const defaultEmail = Underpost.host.store.get('GITHUB_EMAIL');
|
|
2836
2848
|
const validPath = path && path.split(',').length;
|
|
2837
2849
|
const [username, email] = validPath ? path.split(',') : [defaultUsername, defaultEmail];
|
|
2838
2850
|
if (validPath) {
|
|
2839
|
-
Underpost.
|
|
2840
|
-
Underpost.
|
|
2841
|
-
Underpost.
|
|
2842
|
-
Underpost.
|
|
2851
|
+
Underpost.host.store.set('GITHUB_USERNAME', username);
|
|
2852
|
+
Underpost.host.store.set('GITHUB_EMAIL', email);
|
|
2853
|
+
Underpost.host.store.get('GITHUB_USERNAME');
|
|
2854
|
+
Underpost.host.store.get('GITHUB_EMAIL');
|
|
2843
2855
|
}
|
|
2844
2856
|
shellExec(
|
|
2845
2857
|
`git config --global credential.helper "" && ` +
|
|
@@ -2870,7 +2882,7 @@ EOF`);
|
|
|
2870
2882
|
|
|
2871
2883
|
/**
|
|
2872
2884
|
* @method promote
|
|
2873
|
-
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.
|
|
2885
|
+
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.routes` for 'dd', or a specific ID).
|
|
2874
2886
|
* When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
|
|
2875
2887
|
* TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
|
|
2876
2888
|
* (cert-manager Certificate resources) for each affected deployment.
|
|
@@ -2906,7 +2918,7 @@ EOF`);
|
|
|
2906
2918
|
};
|
|
2907
2919
|
|
|
2908
2920
|
if (inputDeployId === 'dd') {
|
|
2909
|
-
for (const deployId of
|
|
2921
|
+
for (const deployId of readDeployRoutes()) {
|
|
2910
2922
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, {
|
|
2911
2923
|
namespace: options.namespace,
|
|
2912
2924
|
env: inputEnv,
|
|
@@ -2932,44 +2944,6 @@ EOF`);
|
|
|
2932
2944
|
applyCerts(inputDeployId, targetTraffic);
|
|
2933
2945
|
}
|
|
2934
2946
|
},
|
|
2935
|
-
/**
|
|
2936
|
-
* @method metrics
|
|
2937
|
-
* @description Deploys Prometheus and Grafana for metrics monitoring, targeting the hosts defined in the deployment configuration files.
|
|
2938
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2939
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2940
|
-
* @memberof UnderpostRun
|
|
2941
|
-
*/
|
|
2942
|
-
metrics: async (path, options = DEFAULT_OPTION) => {
|
|
2943
|
-
if (path === 'server') {
|
|
2944
|
-
shellExec(
|
|
2945
|
-
`kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/high-availability-1.21+.yaml`,
|
|
2946
|
-
);
|
|
2947
|
-
await timer(2000);
|
|
2948
|
-
|
|
2949
|
-
shellExec(`kubectl patch deployment metrics-server -n kube-system \
|
|
2950
|
-
--type='json' \
|
|
2951
|
-
-p='[
|
|
2952
|
-
{
|
|
2953
|
-
"op":"add",
|
|
2954
|
-
"path":"/spec/template/spec/containers/0/args/-",
|
|
2955
|
-
"value":"--kubelet-insecure-tls"
|
|
2956
|
-
}
|
|
2957
|
-
]'`);
|
|
2958
|
-
shellExec(`kubectl scale deployment metrics-server \
|
|
2959
|
-
-n kube-system \
|
|
2960
|
-
--replicas=1`);
|
|
2961
|
-
|
|
2962
|
-
return;
|
|
2963
|
-
}
|
|
2964
|
-
const deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',');
|
|
2965
|
-
let hosts = [];
|
|
2966
|
-
for (const deployId of deployList) {
|
|
2967
|
-
const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
|
|
2968
|
-
hosts = hosts.concat(Object.keys(confServer));
|
|
2969
|
-
}
|
|
2970
|
-
shellExec(`node bin cluster --prom ${hosts.join(',')}`);
|
|
2971
|
-
shellExec(`node bin cluster --grafana`);
|
|
2972
|
-
},
|
|
2973
2947
|
/**
|
|
2974
2948
|
* @method cluster
|
|
2975
2949
|
* @description Deploys a full production/development ready Kubernetes cluster environment including MongoDB,
|
|
@@ -2982,21 +2956,24 @@ EOF`);
|
|
|
2982
2956
|
* references, and every host is written to `/etc/hosts` so the operator's browser reaches the PWA at
|
|
2983
2957
|
* `https://<host>` on the local machine.
|
|
2984
2958
|
*
|
|
2985
|
-
* Custom instances
|
|
2959
|
+
* Custom instances come from `--instance-id`. They are resolved per deploy against that deploy's
|
|
2986
2960
|
* own `conf.instances.json`, so an id only runs where its deploy declares it, and each one is deployed after its
|
|
2987
2961
|
* deploy's default workload is serving — an instance reads the parent's world configuration over the parent's
|
|
2988
2962
|
* gRPC ClusterIP at boot. Instance hosts share the deploy's environment: the same self-signed certificates and
|
|
2989
2963
|
* `/etc/hosts` pass in development, the same cert-manager issuance in production.
|
|
2990
|
-
*
|
|
2991
|
-
*
|
|
2992
|
-
*
|
|
2964
|
+
*
|
|
2965
|
+
* Every input is a flag: `--runtime-image` (default `express`), `--deploy-id` (comma-separated, or the `dd` meta
|
|
2966
|
+
* id; unset reads the route table) and `--instance-id` (comma-separated; a template id such as `mmo-server`
|
|
2967
|
+
* selects its whole variant family):
|
|
2968
|
+
* `node bin run cluster --runtime-image express --deploy-id dd-cyberia --instance-id mmo-server --dev`
|
|
2969
|
+
* @param {string} path - Unused; every input is a flag.
|
|
2993
2970
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2994
2971
|
* @memberof UnderpostRun
|
|
2995
2972
|
*/
|
|
2996
2973
|
cluster: async (path = '', options = DEFAULT_OPTION) => {
|
|
2997
2974
|
const { underpostRoot } = options;
|
|
2998
|
-
const env = options
|
|
2999
|
-
const baseCommand =
|
|
2975
|
+
const env = deployEnvFactory(options);
|
|
2976
|
+
const baseCommand = cli('underpost', { local: true });
|
|
3000
2977
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
3001
2978
|
const clusterType = clusterTypeFactory(options, 'kubeadm');
|
|
3002
2979
|
shellCd(`/home/dd/engine`);
|
|
@@ -3004,23 +2981,15 @@ EOF`);
|
|
|
3004
2981
|
await timer(5000);
|
|
3005
2982
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
3006
2983
|
await timer(5000);
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
? path.split(',')
|
|
3010
|
-
: [
|
|
3011
|
-
'express',
|
|
3012
|
-
fs.readFileSync(`${underpostRoot}/engine-private/deploy/dd.router`, 'utf8').replaceAll(',', '+'),
|
|
3013
|
-
'',
|
|
3014
|
-
];
|
|
2984
|
+
const runtimeImage = options.runtimeImage || 'express';
|
|
2985
|
+
const instanceListId = options.instanceId || '';
|
|
3015
2986
|
// shellExec(
|
|
3016
2987
|
// `${baseCommand} image${baseClusterCommand} --build ${
|
|
3017
2988
|
// runtimeImage ? ` --pull-base --path ${underpostRoot}/src/runtime/${runtimeImage}` : ''
|
|
3018
2989
|
// } --${clusterType}`,
|
|
3019
2990
|
// );
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
logger.warn('No deploy list provided');
|
|
3023
|
-
} else deployList = deployList.split('+');
|
|
2991
|
+
const deployList = options.deployId ? resolveDeployList(options.deployId) : readDeployRoutes();
|
|
2992
|
+
if (deployList.length === 0) logger.warn('No deploy list provided');
|
|
3024
2993
|
await timer(5000);
|
|
3025
2994
|
// --reset-mongodb wipes the retained hostPath volumes before the rollout.
|
|
3026
2995
|
// This workflow already tore the node down and re-imports every database
|
|
@@ -3097,10 +3066,13 @@ EOF`);
|
|
|
3097
3066
|
const hostListenResult = etcHostFactory(hosts);
|
|
3098
3067
|
logger.info(hostListenResult.renderHosts);
|
|
3099
3068
|
}
|
|
3100
|
-
const version = 'v3.3.
|
|
3069
|
+
const version = 'v3.3.73';
|
|
3101
3070
|
const instanceOptionsFactory = (deployId, instanceId) => ({
|
|
3102
3071
|
...options,
|
|
3103
3072
|
...clusterContextFactory(clusterType),
|
|
3073
|
+
deployId,
|
|
3074
|
+
instanceId,
|
|
3075
|
+
replicas: options.replicas || 1,
|
|
3104
3076
|
gatewayApi,
|
|
3105
3077
|
gatewayBootstrapComplete: true,
|
|
3106
3078
|
tls: true,
|
|
@@ -3147,17 +3119,16 @@ EOF`);
|
|
|
3147
3119
|
sudo rm -rf ./engine-private/, \
|
|
3148
3120
|
node bin clone underpostnet/engine-cyberia-private, \
|
|
3149
3121
|
sudo mv ./engine-cyberia-private ./engine-private, \
|
|
3150
|
-
node bin env
|
|
3151
|
-
node ./engine-private/itc-scripts/dd-cyberia-0.js, \
|
|
3122
|
+
node bin app load --env ${env} --args deploy-id=dd-cyberia, \
|
|
3152
3123
|
sudo chown -R dd:dd /home/dd/engine/src/client/public/cyberia, \
|
|
3153
|
-
node bin env
|
|
3124
|
+
node bin app load --env ${env} --args deploy-id=dd-cyberia, \
|
|
3154
3125
|
node bin client dd-cyberia ${env}, \
|
|
3155
3126
|
node bin start dd-cyberia ${env} --run'`
|
|
3156
3127
|
: '');
|
|
3157
3128
|
deployFlagsById[deployId] = deployFlags;
|
|
3158
3129
|
// SSR status and context documents belong to the ingress bootstrap, so
|
|
3159
3130
|
// build them on the host before any workload Deployment is submitted.
|
|
3160
|
-
shellExec(`${baseCommand} client ${deployId} ${env}`);
|
|
3131
|
+
shellExec(`${baseCommand} client ${deployId} --env ${env}`);
|
|
3161
3132
|
shellExec(`${baseCommand} deploy ${deployId} ${env} --build-manifest ${deployFlags}`);
|
|
3162
3133
|
// Seed the static tree before the routes exist, so every status page and
|
|
3163
3134
|
// intercepted context the manifests just pointed at resolves from the
|
|
@@ -3260,10 +3231,7 @@ EOF`);
|
|
|
3260
3231
|
// keep serving the custom fallback until the atomic promotion completes.
|
|
3261
3232
|
for (const instanceId of instancesByDeployId[deployId].ids) {
|
|
3262
3233
|
logger.info('[cluster] Deploying custom instance', { deployId, instanceId, env, clusterType });
|
|
3263
|
-
await UnderpostRun.RUNNERS.instance(
|
|
3264
|
-
`${deployId},${instanceId},${options.replicas || 1}`,
|
|
3265
|
-
instanceOptionsFactory(deployId, instanceId),
|
|
3266
|
-
);
|
|
3234
|
+
await UnderpostRun.RUNNERS.instance('', instanceOptionsFactory(deployId, instanceId));
|
|
3267
3235
|
}
|
|
3268
3236
|
}
|
|
3269
3237
|
logger.info('[cluster] Ingress stack deployed', {
|
|
@@ -3655,6 +3623,10 @@ EOF`);
|
|
|
3655
3623
|
/**
|
|
3656
3624
|
* @method deploy
|
|
3657
3625
|
* @description Deploys a specified service (identified by `path`) using blue/green strategy, monitors its status, and switches traffic upon readiness.
|
|
3626
|
+
*
|
|
3627
|
+
* The target colour is restarted when it is already deployed, and built and applied when it is not:
|
|
3628
|
+
* between cycles only the routed colour has a Deployment, so its counterpart being absent is the
|
|
3629
|
+
* normal state rather than a precondition.
|
|
3658
3630
|
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID to deploy).
|
|
3659
3631
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
3660
3632
|
* @memberof UnderpostRun
|
|
@@ -3662,19 +3634,57 @@ EOF`);
|
|
|
3662
3634
|
deploy: async (path, options = DEFAULT_OPTION) => {
|
|
3663
3635
|
const deployId = path;
|
|
3664
3636
|
const env = options.dev ? 'development' : 'production';
|
|
3637
|
+
const namespace = options.namespace || 'default';
|
|
3665
3638
|
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
3666
3639
|
if (!validVersion) throw new Error('Version mismatch');
|
|
3667
|
-
|
|
3668
|
-
const
|
|
3640
|
+
options = { ...options, gatewayApi: gatewayApiEnabledFactory(options) };
|
|
3641
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, {
|
|
3642
|
+
namespace,
|
|
3643
|
+
env,
|
|
3644
|
+
gatewayApi: options.gatewayApi,
|
|
3645
|
+
});
|
|
3646
|
+
const { targetTraffic, create } = redeployPlanFactory({
|
|
3647
|
+
liveTraffic: currentTraffic,
|
|
3648
|
+
hasDeployment: (colour) =>
|
|
3649
|
+
Underpost.deploy.deploymentExists({ deployment: `${deployId}-${env}-${colour}`, namespace }),
|
|
3650
|
+
});
|
|
3669
3651
|
const ignorePods = Underpost.kubectl
|
|
3670
|
-
.get(`${deployId}-${env}-${targetTraffic}`, 'pods',
|
|
3652
|
+
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', namespace)
|
|
3671
3653
|
.map((p) => p.NAME);
|
|
3672
3654
|
|
|
3673
|
-
|
|
3655
|
+
if (create) {
|
|
3656
|
+
// The target colour having no Deployment is the steady state of a
|
|
3657
|
+
// blue/green cycle, not a missing prerequisite: build that colour's
|
|
3658
|
+
// manifest and apply it, leaving the live colour routed until the
|
|
3659
|
+
// promotion below.
|
|
3660
|
+
const replicas = options.replicas || 1;
|
|
3661
|
+
const node =
|
|
3662
|
+
(currentTraffic
|
|
3663
|
+
? Underpost.deploy.deploymentNode({ deployment: `${deployId}-${env}-${currentTraffic}`, namespace })
|
|
3664
|
+
: '') || options.nodeName;
|
|
3665
|
+
logger.info('Target traffic colour is not deployed; creating it', {
|
|
3666
|
+
deployId,
|
|
3667
|
+
env,
|
|
3668
|
+
targetTraffic,
|
|
3669
|
+
node: node || '(unpinned)',
|
|
3670
|
+
});
|
|
3671
|
+
const deployFlags =
|
|
3672
|
+
` --versions ${targetTraffic} --replicas ${replicas} --namespace ${namespace}${node ? ` --node ${node}` : ''}` +
|
|
3673
|
+
`${options.image ? ` --image ${options.image}` : ''}` +
|
|
3674
|
+
`${Underpost.deploy.timeoutFlagsFactory(options)}${options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : ''}` +
|
|
3675
|
+
`${Underpost.deploy.gatewayApiFlagsFactory(options)}`;
|
|
3676
|
+
// `--build-manifest` returns before any cluster mutation, so the colour is
|
|
3677
|
+
// rendered first and applied by the second call. `--traffic` keeps the
|
|
3678
|
+
// generated traffic Service on the colour still serving.
|
|
3679
|
+
shellExec(
|
|
3680
|
+
`node bin deploy --build-manifest --info-router --traffic ${currentTraffic || targetTraffic}${deployFlags} ${deployId} ${env}`,
|
|
3681
|
+
);
|
|
3682
|
+
shellExec(`node bin deploy --disable-update-proxy${deployFlags} ${deployId} ${env}`);
|
|
3683
|
+
} else shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic} -n ${namespace}`);
|
|
3674
3684
|
|
|
3675
|
-
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods,
|
|
3685
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, namespace);
|
|
3676
3686
|
|
|
3677
|
-
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas,
|
|
3687
|
+
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas, namespace, options);
|
|
3678
3688
|
},
|
|
3679
3689
|
|
|
3680
3690
|
/**
|
|
@@ -3764,7 +3774,10 @@ EOF`);
|
|
|
3764
3774
|
}`;
|
|
3765
3775
|
shellExec(cmd, { async: true });
|
|
3766
3776
|
}
|
|
3767
|
-
|
|
3777
|
+
// A bare `return` here exited 0, so a runtime that latched `container-status=error` looked
|
|
3778
|
+
// like a clean start to everything upstream — the shell, and any CI job wrapping it.
|
|
3779
|
+
if ((await awaitDeployMonitor()) !== true)
|
|
3780
|
+
throw new Error(`[run dev] ${deployId} api runtime reported ${RUNTIME_STATUS.ERROR}; see the logs above`);
|
|
3768
3781
|
{
|
|
3769
3782
|
const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
3770
3783
|
|
|
@@ -3772,7 +3785,8 @@ EOF`);
|
|
|
3772
3785
|
async: true,
|
|
3773
3786
|
});
|
|
3774
3787
|
}
|
|
3775
|
-
if ((await awaitDeployMonitor()) !== true)
|
|
3788
|
+
if ((await awaitDeployMonitor()) !== true)
|
|
3789
|
+
throw new Error(`[run dev] ${deployId} client runtime reported ${RUNTIME_STATUS.ERROR}; see the logs above`);
|
|
3776
3790
|
shellExec(
|
|
3777
3791
|
`NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
|
|
3778
3792
|
);
|
|
@@ -3787,7 +3801,7 @@ EOF`);
|
|
|
3787
3801
|
*/
|
|
3788
3802
|
service: async (path = '', options = DEFAULT_OPTION) => {
|
|
3789
3803
|
const env = options.dev ? 'development' : 'production';
|
|
3790
|
-
const baseCommand =
|
|
3804
|
+
const baseCommand = cli('underpost', { local: true });
|
|
3791
3805
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
3792
3806
|
shellCd(`/home/dd/engine`);
|
|
3793
3807
|
let [deployId, serviceId, host, _path, replicas, image, node] = path.split(',');
|
|
@@ -3892,26 +3906,6 @@ EOF`);
|
|
|
3892
3906
|
logger.info(hostListenResult.renderHosts);
|
|
3893
3907
|
},
|
|
3894
3908
|
|
|
3895
|
-
/**
|
|
3896
|
-
* @method sh
|
|
3897
|
-
* @description Enables remote control for the Kitty terminal emulator.
|
|
3898
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
3899
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
3900
|
-
* @memberof UnderpostRun
|
|
3901
|
-
*/
|
|
3902
|
-
sh: async (path = '', options = DEFAULT_OPTION) => {
|
|
3903
|
-
let [operator, arg0, arg1] = path.split(',');
|
|
3904
|
-
if (operator == 'copy') {
|
|
3905
|
-
shellExec(
|
|
3906
|
-
`kitty @ get-text ${arg0 === 'all' ? '--match all' : '--self'} --extent all${
|
|
3907
|
-
arg1 === 'ansi' ? ' --ansi yes' : ''
|
|
3908
|
-
} | kitty +kitten clipboard`,
|
|
3909
|
-
);
|
|
3910
|
-
return;
|
|
3911
|
-
}
|
|
3912
|
-
shellExec(`kitty -o allow_remote_control=yes`);
|
|
3913
|
-
},
|
|
3914
|
-
|
|
3915
3909
|
/**
|
|
3916
3910
|
* @method log
|
|
3917
3911
|
* @description Searches and highlights keywords in a specified log file, optionally showing surrounding lines.
|
|
@@ -3970,28 +3964,44 @@ EOF`);
|
|
|
3970
3964
|
|
|
3971
3965
|
// Services
|
|
3972
3966
|
logger.info('Process info');
|
|
3973
|
-
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd
|
|
3967
|
+
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`, {
|
|
3968
|
+
silentOnError: true,
|
|
3969
|
+
});
|
|
3974
3970
|
logger.info('Command line');
|
|
3975
|
-
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo
|
|
3971
|
+
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`, {
|
|
3972
|
+
silentOnError: true,
|
|
3973
|
+
});
|
|
3976
3974
|
logger.info('Executable path');
|
|
3977
3975
|
shellExec(`sudo readlink -f /proc/${pid}/exe`);
|
|
3978
3976
|
logger.info('Working directory');
|
|
3979
|
-
shellExec(`sudo readlink -f /proc/${pid}/cwd
|
|
3977
|
+
shellExec(`sudo readlink -f /proc/${pid}/cwd`, {
|
|
3978
|
+
silentOnError: true,
|
|
3979
|
+
});
|
|
3980
3980
|
logger.info('Environment variables (first 200)');
|
|
3981
|
-
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200
|
|
3981
|
+
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`, {
|
|
3982
|
+
silentOnError: true,
|
|
3983
|
+
});
|
|
3982
3984
|
|
|
3983
3985
|
// Parent
|
|
3984
3986
|
logger.info('Parent process');
|
|
3985
|
-
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, {
|
|
3987
|
+
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, {
|
|
3988
|
+
stdout: true,
|
|
3989
|
+
silent: true,
|
|
3990
|
+
silentOnError: true,
|
|
3991
|
+
});
|
|
3986
3992
|
console.log(parentInfo);
|
|
3987
3993
|
const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
|
|
3988
3994
|
if (ppidMatch) {
|
|
3989
3995
|
const ppid = ppidMatch.trim().split(/\s+/)[1];
|
|
3990
3996
|
logger.info(`Parent PID: ${ppid}`);
|
|
3991
|
-
shellExec(`ps -fp ${ppid}
|
|
3997
|
+
shellExec(`ps -fp ${ppid}`, {
|
|
3998
|
+
silentOnError: true,
|
|
3999
|
+
});
|
|
3992
4000
|
}
|
|
3993
4001
|
logger.info('Process tree');
|
|
3994
|
-
shellExec(`pstree -s ${pid}
|
|
4002
|
+
shellExec(`pstree -s ${pid}`, {
|
|
4003
|
+
silentOnError: true,
|
|
4004
|
+
});
|
|
3995
4005
|
},
|
|
3996
4006
|
|
|
3997
4007
|
/**
|
|
@@ -4036,7 +4046,7 @@ EOF`);
|
|
|
4036
4046
|
'deploy-test': async (path, options = DEFAULT_OPTION) => {
|
|
4037
4047
|
// Note: use recomendation empty deploy cluster: node bin --dev cluster
|
|
4038
4048
|
const env = options.dev ? 'development' : 'production';
|
|
4039
|
-
const baseCommand =
|
|
4049
|
+
const baseCommand = cli('underpost', { local: true });
|
|
4040
4050
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
4041
4051
|
const inputs = path ? path.split(',') : [];
|
|
4042
4052
|
const deployId = inputs[0] ? inputs[0] : 'dd-test';
|
|
@@ -4045,10 +4055,12 @@ EOF`);
|
|
|
4045
4055
|
: [
|
|
4046
4056
|
`npm install -g npm@11.2.0`,
|
|
4047
4057
|
`npm install -g underpost`,
|
|
4048
|
-
`${baseCommand}
|
|
4058
|
+
`${baseCommand} host load`,
|
|
4049
4059
|
`${baseCommand} start --build --run ${deployId} ${env}`,
|
|
4050
4060
|
];
|
|
4051
|
-
shellExec(
|
|
4061
|
+
shellExec(
|
|
4062
|
+
`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none --deploy-id dd-test --cmd ${shellArgumentFactory(cmd)}`,
|
|
4063
|
+
);
|
|
4052
4064
|
},
|
|
4053
4065
|
|
|
4054
4066
|
/**
|
|
@@ -4179,26 +4191,7 @@ EOF`);
|
|
|
4179
4191
|
|
|
4180
4192
|
shellCd('/home/dd/engine');
|
|
4181
4193
|
},
|
|
4182
|
-
|
|
4183
|
-
* @method pull-rocky-image
|
|
4184
|
-
* @description Pulls the base `rockylinux:9` image from Docker Hub via Podman.
|
|
4185
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
4186
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4187
|
-
* @memberof UnderpostRun
|
|
4188
|
-
*/
|
|
4189
|
-
'pull-rocky-image': (path, options = DEFAULT_OPTION) => {
|
|
4190
|
-
shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
|
|
4191
|
-
},
|
|
4192
|
-
/**
|
|
4193
|
-
* @method rmi
|
|
4194
|
-
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
4195
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
4196
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4197
|
-
* @memberof UnderpostRun
|
|
4198
|
-
*/
|
|
4199
|
-
rmi: (path, options = DEFAULT_OPTION) => {
|
|
4200
|
-
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
4201
|
-
},
|
|
4194
|
+
|
|
4202
4195
|
/**
|
|
4203
4196
|
* @method kill
|
|
4204
4197
|
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
@@ -4244,333 +4237,6 @@ EOF`);
|
|
|
4244
4237
|
if (options.copy) pbcopy(password);
|
|
4245
4238
|
else console.log(password);
|
|
4246
4239
|
},
|
|
4247
|
-
/**
|
|
4248
|
-
* @method sops-setup
|
|
4249
|
-
* @description End-to-end SOPS/Age onboarding for a host: installs tooling, generates the Age
|
|
4250
|
-
* keypair and creation rules, pins the key path for non-interactive runs, encrypts the
|
|
4251
|
-
* requested Secrets into the Git-tracked store, then validates and applies them.
|
|
4252
|
-
*
|
|
4253
|
-
* Every step is idempotent and re-runnable. Notably it delegates key generation to
|
|
4254
|
-
* `secret sops --init` rather than calling `age-keygen` directly: a bare `age-keygen -o`
|
|
4255
|
-
* overwrites an existing key, which would orphan every manifest already encrypted to the
|
|
4256
|
-
* previous recipient with no way to recover them.
|
|
4257
|
-
*
|
|
4258
|
-
* On a host that pulled a store created elsewhere, the freshly generated key is not a recipient
|
|
4259
|
-
* of the inherited manifests. `init()` registers this host in the creation rules so what it
|
|
4260
|
-
* encrypts from here on stays readable, but existing manifests can only be re-keyed from a host
|
|
4261
|
-
* that still holds a decrypting key. That case is reported per secret and then raised by the
|
|
4262
|
-
* apply pre-flight with the available remedies, rather than surfacing as a sops decrypt error.
|
|
4263
|
-
*
|
|
4264
|
-
* Onboards the whole self-hosted data tier by default — PostgreSQL, MariaDB, and MongoDB
|
|
4265
|
-
* (`postgres-secret`, `mariadb-secret`, `mongodb-secret`, `mongodb-keyfile`). The MongoDB
|
|
4266
|
-
* keyfile is included because the StatefulSet mounts it for intra-replica-set auth and will
|
|
4267
|
-
* not start without it. Pass an explicit comma-separated list to narrow the set.
|
|
4268
|
-
*
|
|
4269
|
-
* Secret values are resolved per data key, in order:
|
|
4270
|
-
* 1. the origin seed file, when one exists (`engine-private/postgresql-password`) — this is
|
|
4271
|
-
* the real onboarding path, carrying the credential the cluster already runs on;
|
|
4272
|
-
* 2. `--args` as `key=value` pairs, for a value supplied by the operator;
|
|
4273
|
-
* 3. a freshly generated value: a base64 keyfile for `mongodb-keyfile`, `admin` for a
|
|
4274
|
-
* `username`, otherwise a 24-character secure password.
|
|
4275
|
-
*
|
|
4276
|
-
* Plaintext manifests are written by Node under `/dev/shm` at mode 600 and shredded by
|
|
4277
|
-
* `encrypt()`. They are never emitted through a shell heredoc, which would place the
|
|
4278
|
-
* credential in the command string and therefore in the process table and the command log.
|
|
4279
|
-
*
|
|
4280
|
-
* Usage:
|
|
4281
|
-
* underpost run sops-setup # postgres + mariadb + mongo
|
|
4282
|
-
* underpost run sops-setup mongodb-secret,mongodb-keyfile --namespace prod
|
|
4283
|
-
* underpost run sops-setup postgres-secret --args "password=s3cr3t"
|
|
4284
|
-
* underpost run sops-setup --dry-run # stop before mutating cluster
|
|
4285
|
-
* underpost run sops-setup --force # replace stored manifests
|
|
4286
|
-
* @param {string} path - Comma-separated Secret names to onboard. Defaults to the full data
|
|
4287
|
-
* tier: postgres-secret, mariadb-secret, mongodb-secret, mongodb-keyfile.
|
|
4288
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4289
|
-
* @param {string} options.namespace - Target namespace for the store and the apply (default: 'default').
|
|
4290
|
-
* @param {string} options.args - Comma-separated `key=value` overrides for Secret data keys.
|
|
4291
|
-
* @param {boolean} options.dryRun - Validate and server-dry-run only; never apply.
|
|
4292
|
-
* @param {boolean} options.force - Replace encrypted manifests that already exist.
|
|
4293
|
-
* @memberof UnderpostRun
|
|
4294
|
-
*/
|
|
4295
|
-
'sops-setup': (path = '', options = DEFAULT_OPTION) => {
|
|
4296
|
-
const namespace = options.namespace || 'default';
|
|
4297
|
-
const secretNames = (path || SOPS_SETUP_DEFAULT_SECRETS.join(','))
|
|
4298
|
-
.split(',')
|
|
4299
|
-
.map((name) => name.trim())
|
|
4300
|
-
.filter(Boolean);
|
|
4301
|
-
|
|
4302
|
-
// `--args key=value,key2=value2` overrides, applied to any secret that declares that key.
|
|
4303
|
-
const overrides = `${options.args || ''}`.split(',').reduce((acc, pair) => {
|
|
4304
|
-
const separator = pair.indexOf('=');
|
|
4305
|
-
if (separator > 0) acc[pair.slice(0, separator).trim()] = pair.slice(separator + 1).trim();
|
|
4306
|
-
return acc;
|
|
4307
|
-
}, {});
|
|
4308
|
-
|
|
4309
|
-
logger.info('sops-setup', { secretNames, namespace, dryRun: !!options.dryRun, force: !!options.force });
|
|
4310
|
-
|
|
4311
|
-
// 1. Host tooling, then keypair + creation rules. Both no-op when already present.
|
|
4312
|
-
Underpost.secret.sops.installTooling();
|
|
4313
|
-
Underpost.secret.sops.init();
|
|
4314
|
-
|
|
4315
|
-
// 2. Pin the resolved key path for non-interactive runs (systemd units, CronJobs, sudo).
|
|
4316
|
-
// Written with the concrete path rather than a guessed default, because `sudo` resets
|
|
4317
|
-
// HOME and a wrong guess surfaces later as an opaque decrypt failure.
|
|
4318
|
-
const keyFile = Underpost.secret.sops.keyFile();
|
|
4319
|
-
shellExec(
|
|
4320
|
-
`sudo tee /etc/profile.d/underpost-sops.sh >/dev/null <<'UNDERPOST_SOPS_ENV_EOF'
|
|
4321
|
-
export SOPS_AGE_KEY_FILE="\${SOPS_AGE_KEY_FILE:-${keyFile}}"
|
|
4322
|
-
UNDERPOST_SOPS_ENV_EOF`,
|
|
4323
|
-
);
|
|
4324
|
-
shellExec(`sudo chmod 644 /etc/profile.d/underpost-sops.sh`);
|
|
4325
|
-
|
|
4326
|
-
// 3. Build and encrypt each requested Secret.
|
|
4327
|
-
const stageDir = '/dev/shm/underpost-secrets';
|
|
4328
|
-
const held = Underpost.secret.sops.localRecipients();
|
|
4329
|
-
fs.ensureDirSync(stageDir);
|
|
4330
|
-
fs.chmodSync(stageDir, 0o700);
|
|
4331
|
-
try {
|
|
4332
|
-
for (const name of secretNames) {
|
|
4333
|
-
const stored = Underpost.secret.sops.has(name, namespace);
|
|
4334
|
-
if (stored && !options.force) {
|
|
4335
|
-
// A stored manifest this host cannot open is present but unusable here, so reporting it
|
|
4336
|
-
// as onboarded would send the operator on to an apply that is guaranteed to fail.
|
|
4337
|
-
if (Underpost.secret.sops.decryptable(Underpost.secret.sops.manifestPath(name, namespace), held))
|
|
4338
|
-
logger.info(`${name} is already onboarded in ns/${namespace}; skipping (use --force to replace)`);
|
|
4339
|
-
else
|
|
4340
|
-
logger.warn(
|
|
4341
|
-
`${name} is stored in ns/${namespace} but is sealed to an Age recipient this host does not hold; ` +
|
|
4342
|
-
`skipping. Adopt the store's key, re-key it from a host that holds one, or re-onboard from the ` +
|
|
4343
|
-
`origin seed files with --force.`,
|
|
4344
|
-
);
|
|
4345
|
-
continue;
|
|
4346
|
-
}
|
|
4347
|
-
|
|
4348
|
-
// Data keys come from the secret's origin seed contract, so an onboarded manifest
|
|
4349
|
-
// carries exactly the keys the workload's secretKeyRef already expects.
|
|
4350
|
-
const seedSources = Underpost.secret.sops.seedSources(name);
|
|
4351
|
-
const dataKeys = Object.keys(seedSources).length > 0 ? Object.keys(seedSources) : ['password'];
|
|
4352
|
-
const stringData = {};
|
|
4353
|
-
for (const key of dataKeys) {
|
|
4354
|
-
const seedPath = seedSources[key];
|
|
4355
|
-
if (seedPath && fs.existsSync(seedPath)) {
|
|
4356
|
-
stringData[key] = fs.readFileSync(seedPath, 'utf8').trim();
|
|
4357
|
-
logger.info(`${name}.${key} seeded from ${seedPath}`);
|
|
4358
|
-
} else if (overrides[key] !== undefined) {
|
|
4359
|
-
stringData[key] = overrides[key];
|
|
4360
|
-
logger.info(`${name}.${key} taken from --args`);
|
|
4361
|
-
} else {
|
|
4362
|
-
stringData[key] = generateSeedValue(key);
|
|
4363
|
-
// Replacing a stored manifest with a value nothing seeded means the credential the
|
|
4364
|
-
// running datastore still authenticates against is being thrown away.
|
|
4365
|
-
if (stored)
|
|
4366
|
-
logger.warn(
|
|
4367
|
-
`${name}.${key} generated while replacing the stored manifest — no seed file at ` +
|
|
4368
|
-
`${seedPath || '(unmapped)'} and no --args override. The running datastore keeps its old ` +
|
|
4369
|
-
`credential until this value is applied to it; pass --args "${key}=<value>" to keep the ` +
|
|
4370
|
-
`existing one.`,
|
|
4371
|
-
);
|
|
4372
|
-
else logger.info(`${name}.${key} generated`);
|
|
4373
|
-
}
|
|
4374
|
-
}
|
|
4375
|
-
|
|
4376
|
-
const stagePath = `${stageDir}/${name}.yaml`;
|
|
4377
|
-
fs.outputFileSync(
|
|
4378
|
-
stagePath,
|
|
4379
|
-
[
|
|
4380
|
-
'apiVersion: v1',
|
|
4381
|
-
'kind: Secret',
|
|
4382
|
-
'metadata:',
|
|
4383
|
-
` name: ${name}`,
|
|
4384
|
-
` namespace: ${namespace}`,
|
|
4385
|
-
' labels:',
|
|
4386
|
-
' app.kubernetes.io/managed-by: underpost',
|
|
4387
|
-
'type: Opaque',
|
|
4388
|
-
'stringData:',
|
|
4389
|
-
// Single-quoted YAML scalars with doubled internal quotes: values are generated or
|
|
4390
|
-
// operator-supplied and may contain characters YAML would otherwise interpret.
|
|
4391
|
-
...Object.entries(stringData).map(([key, value]) => ` ${key}: '${`${value}`.replace(/'/g, "''")}'`),
|
|
4392
|
-
'',
|
|
4393
|
-
].join('\n'),
|
|
4394
|
-
'utf8',
|
|
4395
|
-
);
|
|
4396
|
-
fs.chmodSync(stagePath, 0o600);
|
|
4397
|
-
// encrypt() stages, validates, moves into place, and shreds the plaintext source.
|
|
4398
|
-
Underpost.secret.sops.encrypt(stagePath, namespace, options);
|
|
4399
|
-
}
|
|
4400
|
-
} finally {
|
|
4401
|
-
// Defense in depth: encrypt() shreds each source, but a throw mid-loop must not leave a
|
|
4402
|
-
// plaintext manifest sitting in shared memory.
|
|
4403
|
-
fs.removeSync(stageDir);
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
|
-
Underpost.secret.sops.list();
|
|
4407
|
-
|
|
4408
|
-
// 4. Validate every manifest in the namespace, then apply unless this is a dry run.
|
|
4409
|
-
Underpost.secret.sops.apply(namespace, { dryRun: true });
|
|
4410
|
-
if (options.dryRun) return logger.info('--dry-run: validated only, cluster left unchanged');
|
|
4411
|
-
Underpost.secret.sops.apply(namespace);
|
|
4412
|
-
},
|
|
4413
|
-
/**
|
|
4414
|
-
* @method sops-status
|
|
4415
|
-
* @description Reports the live state of the SOPS/Age secret system: host tooling, the Age
|
|
4416
|
-
* key and its recipient, the committed creation rules, every stored manifest with whether the
|
|
4417
|
-
* local key can open it and whether the cluster still matches, and which managed Secrets are
|
|
4418
|
-
* onboarded versus still seeding from their origin path.
|
|
4419
|
-
*
|
|
4420
|
-
* Read-only and safe to run anywhere. Decryption happens only for the drift check, only for
|
|
4421
|
-
* manifests the local key is a recipient of, and only into `kubectl diff` with its output
|
|
4422
|
-
* discarded — no secret value is ever printed or written to disk.
|
|
4423
|
-
*
|
|
4424
|
-
* Usage:
|
|
4425
|
-
* underpost run sops-status # every managed key, ns default
|
|
4426
|
-
* underpost run sops-status mongo # partial match: both mongo keys
|
|
4427
|
-
* underpost run sops-status --namespace prod # every managed key in ns prod
|
|
4428
|
-
* @param {string} path - Comma-separated managed Secret keys to report on; empty reports all.
|
|
4429
|
-
* Matched as case-insensitive substrings (`mongo` selects mongodb-secret and mongodb-keyfile).
|
|
4430
|
-
* Filters both the stored-manifest listing and the coverage table.
|
|
4431
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4432
|
-
* @param {string} options.namespace - Namespace to inspect (DEFAULT_OPTION scheme, default 'default').
|
|
4433
|
-
* @memberof UnderpostRun
|
|
4434
|
-
*/
|
|
4435
|
-
'sops-status': (path = '', options = DEFAULT_OPTION) => {
|
|
4436
|
-
const sops = Underpost.secret.sops;
|
|
4437
|
-
// `--namespace` selects the namespace (DEFAULT_OPTION scheme); `path` narrows which managed
|
|
4438
|
-
// Secret keys to report on, so the two axes stay independent.
|
|
4439
|
-
const namespace = options.namespace || 'default';
|
|
4440
|
-
const manageSecretKeyFilter = path
|
|
4441
|
-
.split(',')
|
|
4442
|
-
.map((key) => key.trim().toLowerCase())
|
|
4443
|
-
.filter(Boolean);
|
|
4444
|
-
// Partial, case-insensitive substring match, so `mongo` reaches both `mongodb-secret` and
|
|
4445
|
-
// `mongodb-keyfile` without having to spell either out.
|
|
4446
|
-
const matchesKeyFilter = (name) =>
|
|
4447
|
-
manageSecretKeyFilter.length === 0 || manageSecretKeyFilter.some((key) => name.toLowerCase().includes(key));
|
|
4448
|
-
const mark = (ok) => (ok ? 'yes' : 'no');
|
|
4449
|
-
|
|
4450
|
-
// ── Tooling ────────────────────────────────────────────────────────────
|
|
4451
|
-
const version = (bin, flag) =>
|
|
4452
|
-
sops.hasBinary(bin)
|
|
4453
|
-
? shellExec(`${bin} ${flag} 2>/dev/null | head -1`, { stdout: true, silent: true, disableLog: true }).trim()
|
|
4454
|
-
: '(not installed)';
|
|
4455
|
-
logger.info(
|
|
4456
|
-
'[sops-status] Tooling\n' +
|
|
4457
|
-
` sops ${version('sops', '--version')}\n` +
|
|
4458
|
-
` age ${version('age', '--version')}\n` +
|
|
4459
|
-
` age-keygen ${sops.hasBinary('age-keygen') ? 'installed' : '(not installed)'}`,
|
|
4460
|
-
);
|
|
4461
|
-
|
|
4462
|
-
// ── Age key ────────────────────────────────────────────────────────────
|
|
4463
|
-
const keyFile = sops.keyFile();
|
|
4464
|
-
const keyExists = fs.existsSync(keyFile);
|
|
4465
|
-
// A key file may hold several identities — that is how a host joins a store it did not
|
|
4466
|
-
// create — so every check below works against the whole held set, not one recipient.
|
|
4467
|
-
const held = sops.localRecipients();
|
|
4468
|
-
const keyMode = keyExists ? (fs.statSync(keyFile).mode & 0o777).toString(8) : '';
|
|
4469
|
-
logger.info(
|
|
4470
|
-
'[sops-status] Age key\n' +
|
|
4471
|
-
` path ${keyFile}\n` +
|
|
4472
|
-
` present ${mark(keyExists)}${keyExists ? ` (mode ${keyMode}${keyMode === '600' || keyMode === '400' ? '' : ' — INSECURE, run chmod 600'})` : ''}\n` +
|
|
4473
|
-
` recipients ${held.join(', ') || (keyExists ? '(none — unreadable key file)' : '(none)')}` +
|
|
4474
|
-
(keyExists ? '' : `\n searched ${sops.keyFileCandidates().join(', ')}`),
|
|
4475
|
-
);
|
|
4476
|
-
|
|
4477
|
-
// ── Creation rules ─────────────────────────────────────────────────────
|
|
4478
|
-
const confPath = './engine-private/secrets/.sops.yaml';
|
|
4479
|
-
const ruleRecipients = sops.creationRecipients();
|
|
4480
|
-
logger.info(
|
|
4481
|
-
'[sops-status] Creation rules\n' +
|
|
4482
|
-
` config ${confPath} ${fs.existsSync(confPath) ? '' : '(missing — run: underpost secret sops --init)'}\n` +
|
|
4483
|
-
` recipients ${ruleRecipients.length > 0 ? ruleRecipients.join(', ') : '(none)'}\n` +
|
|
4484
|
-
` local key listed ${mark(held.some((recipient) => ruleRecipients.includes(recipient)))}`,
|
|
4485
|
-
);
|
|
4486
|
-
|
|
4487
|
-
// ── Stored manifests ───────────────────────────────────────────────────
|
|
4488
|
-
const manifests = sops.manifests(namespace).filter((manifest) => matchesKeyFilter(manifest.name));
|
|
4489
|
-
const onboarded = new Set();
|
|
4490
|
-
if (manifests.length === 0)
|
|
4491
|
-
logger.warn(
|
|
4492
|
-
`[sops-status] Store\n no encrypted manifests in ns/${namespace}` +
|
|
4493
|
-
(manageSecretKeyFilter.length > 0 ? ` matching ${manageSecretKeyFilter.join(', ')}` : ''),
|
|
4494
|
-
);
|
|
4495
|
-
else {
|
|
4496
|
-
const rows = manifests.map((manifest) => {
|
|
4497
|
-
onboarded.add(manifest.name);
|
|
4498
|
-
const recipients = sops.manifestRecipients(manifest.path);
|
|
4499
|
-
const decryptable = sops.decryptable(manifest.path, held);
|
|
4500
|
-
const live = shellExec(
|
|
4501
|
-
`kubectl get secret ${manifest.name} -n ${manifest.namespace} --ignore-not-found -o name 2>/dev/null || true`,
|
|
4502
|
-
{ stdout: true, silent: true, silentOnError: true, disableLog: true },
|
|
4503
|
-
).trim();
|
|
4504
|
-
// Drift is decided by kubectl's exit code; its stdout would contain the decrypted
|
|
4505
|
-
// values, so it is discarded rather than captured.
|
|
4506
|
-
let sync = 'n/a';
|
|
4507
|
-
if (live && decryptable) {
|
|
4508
|
-
const result = shellExec(
|
|
4509
|
-
`bash -c 'set -o pipefail; SOPS_AGE_KEY_FILE="${keyFile}" sops --decrypt "${manifest.path}" ` +
|
|
4510
|
-
`| kubectl diff -f - -n "${manifest.namespace}" >/dev/null 2>&1'`,
|
|
4511
|
-
{ silentOnError: true, disableLog: true, stdout: false },
|
|
4512
|
-
);
|
|
4513
|
-
sync = result.code === 0 ? 'in-sync' : result.code === 1 ? 'DRIFT' : 'error';
|
|
4514
|
-
} else if (!live) sync = 'not applied';
|
|
4515
|
-
else if (!decryptable) sync = 'no local key';
|
|
4516
|
-
return (
|
|
4517
|
-
` ${`${manifest.namespace}/${manifest.name}`.padEnd(34)} ` +
|
|
4518
|
-
`recipients=${String(recipients.length).padEnd(3)} ` +
|
|
4519
|
-
`decryptable=${mark(decryptable).padEnd(4)} ` +
|
|
4520
|
-
`live=${mark(!!live).padEnd(4)} ` +
|
|
4521
|
-
`${sync}`
|
|
4522
|
-
);
|
|
4523
|
-
});
|
|
4524
|
-
logger.info(`[sops-status] Store — ns/${namespace} (${manifests.length} manifest(s))\n` + rows.join('\n'));
|
|
4525
|
-
}
|
|
4526
|
-
|
|
4527
|
-
// ── Coverage ───────────────────────────────────────────────────────────
|
|
4528
|
-
const coverage = sops
|
|
4529
|
-
.managedSecrets()
|
|
4530
|
-
.filter(matchesKeyFilter)
|
|
4531
|
-
.map((name) => {
|
|
4532
|
-
const seeds = Object.values(sops.seedSources(name));
|
|
4533
|
-
const seedPresent = seeds.length > 0 && seeds.every((seed) => fs.existsSync(seed));
|
|
4534
|
-
const source = onboarded.has(name)
|
|
4535
|
-
? 'sops'
|
|
4536
|
-
: seedPresent
|
|
4537
|
-
? 'origin seed'
|
|
4538
|
-
: seeds.length
|
|
4539
|
-
? 'MISSING'
|
|
4540
|
-
: 'unmapped';
|
|
4541
|
-
return ` ${name.padEnd(24)} ${source.padEnd(12)} ${seeds.length ? `seed=${mark(seedPresent)}` : ''}`;
|
|
4542
|
-
});
|
|
4543
|
-
if (coverage.length === 0)
|
|
4544
|
-
logger.warn(
|
|
4545
|
-
`[sops-status] Coverage\n no managed Secret matches ${manageSecretKeyFilter.join(', ')}\n` +
|
|
4546
|
-
` known keys: ${sops.managedSecrets().join(', ')}`,
|
|
4547
|
-
);
|
|
4548
|
-
else
|
|
4549
|
-
logger.info('[sops-status] Coverage (which source each managed Secret deploys from)\n' + coverage.join('\n'));
|
|
4550
|
-
},
|
|
4551
|
-
/**
|
|
4552
|
-
* @method secret
|
|
4553
|
-
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
4554
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
4555
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4556
|
-
* @memberof UnderpostRun
|
|
4557
|
-
*/
|
|
4558
|
-
secret: (path, options = DEFAULT_OPTION) => {
|
|
4559
|
-
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
4560
|
-
Underpost.secret.underpost.createFromEnvFile(
|
|
4561
|
-
`/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${options.dev ? 'development' : 'production'}`,
|
|
4562
|
-
);
|
|
4563
|
-
},
|
|
4564
|
-
/**
|
|
4565
|
-
* @method underpost-config
|
|
4566
|
-
* @description Calls `Underpost.deploy.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
4567
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
4568
|
-
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
4569
|
-
* @memberof UnderpostRun
|
|
4570
|
-
*/
|
|
4571
|
-
'underpost-config': (path = '', options = DEFAULT_OPTION) => {
|
|
4572
|
-
Underpost.deploy.configMap(path ? path : 'production', options.namespace);
|
|
4573
|
-
},
|
|
4574
4240
|
/**
|
|
4575
4241
|
* @method gpu-env
|
|
4576
4242
|
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
@@ -4666,7 +4332,7 @@ UNDERPOST_SOPS_ENV_EOF`,
|
|
|
4666
4332
|
? 'Directory'
|
|
4667
4333
|
: 'File';
|
|
4668
4334
|
|
|
4669
|
-
const envs = Underpost.
|
|
4335
|
+
const envs = Underpost.host.store.list();
|
|
4670
4336
|
|
|
4671
4337
|
const cmd = `kubectl apply -f - <<'EOF'
|
|
4672
4338
|
apiVersion: ${apiVersion}
|
|
@@ -4743,7 +4409,7 @@ EOF`;
|
|
|
4743
4409
|
* @memberof UnderpostRun
|
|
4744
4410
|
*/
|
|
4745
4411
|
'push-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
4746
|
-
const baseCommand = '
|
|
4412
|
+
const baseCommand = cli('underpost', { local: true });
|
|
4747
4413
|
const env = options.dev ? 'development' : 'production';
|
|
4748
4414
|
const deployId = options.deployId || 'dd-default';
|
|
4749
4415
|
const pathParts = (path || '').split('.');
|
|
@@ -4774,7 +4440,7 @@ EOF`;
|
|
|
4774
4440
|
const storageFilePath = `engine-private/conf/${deployId}/storage.bundle.json`;
|
|
4775
4441
|
|
|
4776
4442
|
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
4777
|
-
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
4443
|
+
shellExec(`${baseCommand} client ${deployId} --env ${env} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
4778
4444
|
|
|
4779
4445
|
const pushBundleFiles = (host, routePath) => {
|
|
4780
4446
|
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
@@ -4831,7 +4497,7 @@ EOF`;
|
|
|
4831
4497
|
* @memberof UnderpostRun
|
|
4832
4498
|
*/
|
|
4833
4499
|
'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
4834
|
-
const baseCommand = '
|
|
4500
|
+
const baseCommand = cli('underpost', { local: true });
|
|
4835
4501
|
const env = options.dev ? 'development' : 'production';
|
|
4836
4502
|
const deployId = options.deployId || 'dd-default';
|
|
4837
4503
|
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
@@ -4935,15 +4601,30 @@ EOF`;
|
|
|
4935
4601
|
|
|
4936
4602
|
/**
|
|
4937
4603
|
* @method build-cluster-deployment-manifests
|
|
4938
|
-
* @description Builds deployment manifests for both production and development environments using `
|
|
4604
|
+
* @description Builds deployment manifests for both production and development environments using `underpost deploy --build-manifest`, syncing them, and setting replicas to 1 for the `dd` deployment. Runs under `HERMETIC_BUILD`, so it needs no reachable cluster.
|
|
4939
4605
|
* @param {string} path - Unused.
|
|
4940
4606
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
4941
4607
|
* @memberof UnderpostRun
|
|
4942
4608
|
*/
|
|
4943
|
-
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) =>
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4609
|
+
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) =>
|
|
4610
|
+
// Manifest generation writes files; it does not touch a cluster. Declaring that once
|
|
4611
|
+
// here is what lets the whole tree below run on a build box with no apiserver — the
|
|
4612
|
+
// profile rides into both child processes through the environment.
|
|
4613
|
+
withExecutionProfile('HERMETIC_BUILD', async () => {
|
|
4614
|
+
// Each deploy's package manifest is generated from the engine's before the cluster
|
|
4615
|
+
// manifests that ship it: a deploy id declares only its identity and whatever its
|
|
4616
|
+
// catalog adds, so a dependency the engine gained reaches every deploy from here.
|
|
4617
|
+
await syncDeployPackages();
|
|
4618
|
+
// `local` is load-bearing: this builds the manifests of the checkout it was
|
|
4619
|
+
// launched from, and a globally installed underpost is a different package that
|
|
4620
|
+
// reads different paths out of the same cwd.
|
|
4621
|
+
const underpost = cli('underpost', { local: true });
|
|
4622
|
+
const flags = '--build-manifest --sync --info-router --replicas 1';
|
|
4623
|
+
shellExec(`${underpost} deploy ${flags} dd development`);
|
|
4624
|
+
shellExec(`${underpost} deploy ${flags} dd production --cert`);
|
|
4625
|
+
// What the build just produced is what the project repository carries.
|
|
4626
|
+
if (deployRoutesExists()) Underpost.deploy.mirrorDefaultManifests();
|
|
4627
|
+
}),
|
|
4947
4628
|
|
|
4948
4629
|
/**
|
|
4949
4630
|
* @method monitor-ui
|
|
@@ -5083,7 +4764,7 @@ EOF`;
|
|
|
5083
4764
|
const result = await Underpost.run.CALL(runner, path, options);
|
|
5084
4765
|
return result;
|
|
5085
4766
|
} catch (error) {
|
|
5086
|
-
console.
|
|
4767
|
+
console.error(error);
|
|
5087
4768
|
logger.error(error);
|
|
5088
4769
|
process.exit(1);
|
|
5089
4770
|
}
|