@underpostnet/cyberia 3.3.0 → 3.3.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +8 -4
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +3 -19
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/downloader.js +0 -73
- package/src/server/environment.js +0 -98
package/src/cli/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,41 +24,46 @@ import {
|
|
|
22
24
|
generateSecurePassword,
|
|
23
25
|
instanceHttpRouteRulesFactory,
|
|
24
26
|
instanceInterceptStatusesFactory,
|
|
27
|
+
instanceEnvFilePath,
|
|
25
28
|
instancePortFactory,
|
|
26
29
|
instanceProxyRoutesFactory,
|
|
27
30
|
instanceStatusPageEntriesFactory,
|
|
28
|
-
|
|
31
|
+
instanceProjectPathFactory,
|
|
29
32
|
loadConfInstances,
|
|
30
33
|
loadProjectInstanceEnvBuilder,
|
|
31
34
|
loadConfServerJson,
|
|
32
|
-
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 { clientBundleHostFilter, pullClientBundle, pushClientBundle } from '../client-builder/client-bundle.js';
|
|
53
|
+
import { cronDeployIdResolve } from '../server/ops/cron.js';
|
|
54
|
+
import { deployEnvFactory, getNpmRootPath, writeEnv } from '../server/runtime/environment.js';
|
|
55
|
+
import { actionInitLog, loggerFactory } from '../server/ops/logger.js';
|
|
52
56
|
|
|
53
57
|
import fs from 'fs-extra';
|
|
54
58
|
import { range, s4, setPad, timer } from '../client/components/core/CommonJs.js';
|
|
55
59
|
|
|
56
60
|
import os from 'os';
|
|
61
|
+
import nodePath from 'node:path';
|
|
62
|
+
import { domainContextFactory } from './domains.js';
|
|
57
63
|
import Underpost from '../index.js';
|
|
58
64
|
import dotenv from 'dotenv';
|
|
59
65
|
import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
|
|
66
|
+
import { MongoExpress } from '../db/mongo/MongoExpress.js';
|
|
60
67
|
import {
|
|
61
68
|
UNDERPOST_GATEWAY,
|
|
62
69
|
assertStaticAssets,
|
|
@@ -69,7 +76,7 @@ import {
|
|
|
69
76
|
readHostInstanceRegistry,
|
|
70
77
|
writeHostInstanceRegistry,
|
|
71
78
|
writeHostServerConf,
|
|
72
|
-
} from '../server/underpost-gateway.js';
|
|
79
|
+
} from '../server/network/underpost-gateway.js';
|
|
73
80
|
const logger = loggerFactory(import.meta);
|
|
74
81
|
|
|
75
82
|
/**
|
|
@@ -89,6 +96,9 @@ const logger = loggerFactory(import.meta);
|
|
|
89
96
|
* @property {string} volumeHostPath - The host path for the volume.
|
|
90
97
|
* @property {string} volumeMountPath - The mount path for the volume.
|
|
91
98
|
* @property {string} imageName - The name of the image to run.
|
|
99
|
+
* @property {string} runtimeImage - `src/runtime/<name>` image family the cluster runner brings up (`cluster`).
|
|
100
|
+
* @property {string} image - Container image the deployment pulls and runs (`sync`).
|
|
101
|
+
* @property {string} versions - Comma-separated blue/green deployment versions (`sync`).
|
|
92
102
|
* @property {string} containerName - The name of the container to run.
|
|
93
103
|
* @property {string} namespace - The namespace to run in.
|
|
94
104
|
* @property {string} timeoutResponse - The response timeout duration.
|
|
@@ -132,11 +142,13 @@ const logger = loggerFactory(import.meta);
|
|
|
132
142
|
* @property {boolean} kind - Whether to run in kind mode.
|
|
133
143
|
* @property {boolean} k3s - Whether to run in k3s mode.
|
|
134
144
|
* @property {string} hosts - The hosts to use.
|
|
145
|
+
* @property {string} split - Zip part size in MB for push-bundle, or 'none' to upload a single zip.
|
|
135
146
|
* @property {string} deployId - The deployment ID.
|
|
136
147
|
* @property {string} instanceId - The instance ID.
|
|
137
148
|
* @property {string} user - The user to run as.
|
|
138
149
|
* @property {string} group - The group to use.
|
|
139
150
|
* @property {string} pid - The process ID.
|
|
151
|
+
* @property {string} repoEnginePrivate - Private configuration repository the pull runner checks out, overriding the pairing derived from the engine source.
|
|
140
152
|
* @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
|
|
141
153
|
* @property {string} monitorStatus - The monitor status option.
|
|
142
154
|
* @property {string} monitorStatusKindType - The monitor status kind type option.
|
|
@@ -183,6 +195,9 @@ const DEFAULT_OPTION = {
|
|
|
183
195
|
volumeHostPath: '',
|
|
184
196
|
volumeMountPath: '',
|
|
185
197
|
imageName: '',
|
|
198
|
+
image: '',
|
|
199
|
+
runtimeImage: '',
|
|
200
|
+
versions: '',
|
|
186
201
|
containerName: '',
|
|
187
202
|
namespace: 'default',
|
|
188
203
|
timeoutResponse: '',
|
|
@@ -226,11 +241,13 @@ const DEFAULT_OPTION = {
|
|
|
226
241
|
kind: false,
|
|
227
242
|
k3s: false,
|
|
228
243
|
hosts: '',
|
|
244
|
+
split: '',
|
|
229
245
|
deployId: '',
|
|
230
246
|
instanceId: '',
|
|
231
247
|
user: '',
|
|
232
248
|
group: '',
|
|
233
249
|
pid: '',
|
|
250
|
+
repoEnginePrivate: '',
|
|
234
251
|
disablePrivateConfUpdate: false,
|
|
235
252
|
monitorStatus: '',
|
|
236
253
|
monitorStatusKindType: '',
|
|
@@ -271,33 +288,6 @@ const DEFAULT_OPTION = {
|
|
|
271
288
|
* @memberof UnderpostRun
|
|
272
289
|
*/
|
|
273
290
|
|
|
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
291
|
class UnderpostRun {
|
|
302
292
|
/**
|
|
303
293
|
* @static
|
|
@@ -326,13 +316,12 @@ class UnderpostRun {
|
|
|
326
316
|
throw new Error(`Invalid Kubernetes node name: ${options.nodeName}`);
|
|
327
317
|
const env = options.dev ? 'development' : 'production';
|
|
328
318
|
const requestedDeploys = `${path || options.deployId || ''}`.trim();
|
|
329
|
-
const routerPath = './engine-private/deploy/dd.router';
|
|
330
319
|
const confRoot = './engine-private/conf';
|
|
331
320
|
const deployIds = [
|
|
332
321
|
...new Set(
|
|
333
322
|
requestedDeploys
|
|
334
323
|
? resolveDeployList(requestedDeploys)
|
|
335
|
-
:
|
|
324
|
+
: deployRoutesExists()
|
|
336
325
|
? resolveDeployList('dd')
|
|
337
326
|
: fs.existsSync(confRoot)
|
|
338
327
|
? fs
|
|
@@ -488,7 +477,7 @@ class UnderpostRun {
|
|
|
488
477
|
* @memberof UnderpostRun
|
|
489
478
|
*/
|
|
490
479
|
'dev-cluster': (path, options = DEFAULT_OPTION) => {
|
|
491
|
-
const baseCommand =
|
|
480
|
+
const baseCommand = cli('underpost', { local: true });
|
|
492
481
|
const mongoHosts = ['mongodb-0.mongodb-service'];
|
|
493
482
|
let primaryMongoHost = 'mongodb-0.mongodb-service';
|
|
494
483
|
const clusterType = clusterTypeFactory(options);
|
|
@@ -826,52 +815,21 @@ class UnderpostRun {
|
|
|
826
815
|
return results;
|
|
827
816
|
},
|
|
828
817
|
|
|
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
818
|
/**
|
|
859
819
|
* @method cluster-build
|
|
860
|
-
* @description
|
|
861
|
-
*
|
|
820
|
+
* @description Resets the workspace and regenerates every routed deploy's default
|
|
821
|
+
* configuration, optionally committing the result to both repositories.
|
|
822
|
+
*
|
|
823
|
+
* Single-replica materialization is not done here: `deploy --sync` builds those folders
|
|
824
|
+
* as part of the deploy itself (`getDataDeploy({ buildSingleReplica: true })`).
|
|
825
|
+
* @param {string} path - `cmt` to commit the regenerated configuration; otherwise unused.
|
|
862
826
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
863
827
|
* @memberof UnderpostRun
|
|
864
828
|
*/
|
|
865
829
|
'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
830
|
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()}`);
|
|
831
|
+
shellExec(`node bin app clean`);
|
|
832
|
+
for (const deployId of readDeployRoutes()) shellExec(`node bin new --default-conf --deploy-id ${deployId}`);
|
|
875
833
|
if (path === 'cmt') {
|
|
876
834
|
shellExec(`git add . && underpost cmt . build cluster-build`);
|
|
877
835
|
shellExec(`cd engine-private && git add . && underpost cmt . build cluster-build`);
|
|
@@ -887,14 +845,14 @@ class UnderpostRun {
|
|
|
887
845
|
* @memberof UnderpostRun
|
|
888
846
|
*/
|
|
889
847
|
'template-deploy': (path = '', options = DEFAULT_OPTION) => {
|
|
890
|
-
const baseCommand =
|
|
848
|
+
const baseCommand = cli('underpost', { local: true });
|
|
891
849
|
shellExec(`npm run security:secrets`);
|
|
892
850
|
const reportPath = './gitleaks-report.json';
|
|
893
851
|
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
894
852
|
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
895
853
|
return;
|
|
896
854
|
}
|
|
897
|
-
|
|
855
|
+
Underpost.repo.fastForwardEnginePair(baseCommand);
|
|
898
856
|
shellExec(`${baseCommand} run shared-dir`);
|
|
899
857
|
|
|
900
858
|
// Capture the sanitized message from the last N commits (--from-n-commit, default 1) for
|
|
@@ -964,14 +922,14 @@ class UnderpostRun {
|
|
|
964
922
|
* @memberof UnderpostRun
|
|
965
923
|
*/
|
|
966
924
|
'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
|
|
967
|
-
const baseCommand =
|
|
925
|
+
const baseCommand = cli('underpost', { local: true });
|
|
968
926
|
shellExec(`npm run security:secrets`);
|
|
969
927
|
const reportPath = './gitleaks-report.json';
|
|
970
928
|
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
971
929
|
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
972
930
|
return;
|
|
973
931
|
}
|
|
974
|
-
|
|
932
|
+
Underpost.repo.fastForwardEnginePair(baseCommand);
|
|
975
933
|
shellExec(`${baseCommand} run shared-dir`);
|
|
976
934
|
|
|
977
935
|
// Capture the sanitized message from the last N commits (--from-n-commit, default 1).
|
|
@@ -1015,47 +973,51 @@ class UnderpostRun {
|
|
|
1015
973
|
},
|
|
1016
974
|
/**
|
|
1017
975
|
* @method pull
|
|
1018
|
-
* @description
|
|
1019
|
-
*
|
|
976
|
+
* @description Brings the node's engine checkout and its private configuration to the tip of
|
|
977
|
+
* their repositories, at `/home/dd/engine` and `/home/dd/engine/engine-private`.
|
|
978
|
+
*
|
|
979
|
+
* With no argument this is the monorepo pair, `engine` and `engine-private`. Naming a source
|
|
980
|
+
* repository deploys that source instead, and its private conf repository is derived from the
|
|
981
|
+
* conf id they share rather than passed separately — `underpostnet/engine-test-lampp` pairs
|
|
982
|
+
* with `underpostnet/engine-lampp-private`, as does `underpostnet/engine-lampp`.
|
|
983
|
+
* `--repo-engine-private` overrides that derivation for a conf repository the pairing cannot
|
|
984
|
+
* name, and is the flag `deploy/lib/host.sh` forwards `ENGINE_SRC_PRIVATE_REPO` through.
|
|
985
|
+
*
|
|
986
|
+
* Both checkouts are replaced rather than merged, by the same `--switch-repo` route the fleet
|
|
987
|
+
* sync takes — so a node moves onto a test source repo and back without being reprovisioned,
|
|
988
|
+
* and one that has drifted onto commits of its own is brought back instead of refused.
|
|
989
|
+
*
|
|
990
|
+
* The replacement checkout is installed before this returns: it is a different commit than
|
|
991
|
+
* the one the caller started from, and nothing that follows can run against uninstalled
|
|
992
|
+
* dependencies.
|
|
993
|
+
* @param {string} path - Engine source repository, as `owner/repo` or a clone URL. Defaults to
|
|
994
|
+
* the `GITHUB_USERNAME` monorepo.
|
|
1020
995
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1021
996
|
* @memberof UnderpostRun
|
|
1022
997
|
*/
|
|
1023
|
-
pull: (path, options = DEFAULT_OPTION) => {
|
|
998
|
+
pull: (path = '', options = DEFAULT_OPTION) => {
|
|
1024
999
|
// shellExec is fail-fast by default — any non-zero exit throws and
|
|
1025
1000
|
// 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
|
-
}
|
|
1001
|
+
const { engine: source, enginePrivate: privateRepo } = Underpost.repo.enginePairFactory({
|
|
1002
|
+
engine: path,
|
|
1003
|
+
enginePrivate: options.repoEnginePrivate,
|
|
1004
|
+
});
|
|
1005
|
+
logger.info('Pulling engine source', { source, privateRepo });
|
|
1006
|
+
|
|
1007
|
+
const engineRoot = `/home/dd/engine`;
|
|
1008
|
+
// Cleaning is the in-process API rather than a shell round trip through the
|
|
1009
|
+
// global `underpost`: that bin is linked at the very checkout being replaced,
|
|
1010
|
+
// so a tree left without its dependencies made the pre-clean — and with it the
|
|
1011
|
+
// whole pull — exit non-zero on exactly the node that needed replacing.
|
|
1012
|
+
if (fs.existsSync(engineRoot)) Underpost.repo.clean({ paths: [engineRoot, `${engineRoot}/engine-private`] });
|
|
1013
|
+
Underpost.repo.syncCheckout({ path: engineRoot, repo: source });
|
|
1014
|
+
Underpost.repo.syncCheckout({ path: `${engineRoot}/engine-private`, repo: privateRepo });
|
|
1015
|
+
// The checkout that lands here is a different commit than the one this process
|
|
1016
|
+
// started from, and its package.json is only a description until it is installed.
|
|
1017
|
+
// Everything after this — the linked global bin included — runs from this tree.
|
|
1018
|
+
shellExec(`cd ${engineRoot} && npm install`);
|
|
1058
1019
|
},
|
|
1020
|
+
|
|
1059
1021
|
/**
|
|
1060
1022
|
* @method ssh-deploy
|
|
1061
1023
|
* @description Dispatches the corresponding CD workflow for SSH-based deployment, replacing empty commits with workflow_dispatch.
|
|
@@ -1125,36 +1087,35 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1125
1087
|
},
|
|
1126
1088
|
/**
|
|
1127
1089
|
* @method sync
|
|
1128
|
-
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`)
|
|
1090
|
+
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`).
|
|
1091
|
+
*
|
|
1092
|
+
* Every input is a flag: `--deploy-id`, `--replicas`, `--versions`, `--image`, `--node-name`.
|
|
1129
1093
|
*
|
|
1130
1094
|
* Forwards `--image-pull-policy <policy>` to the underlying `deploy --build-manifest` invocation when `options.imagePullPolicy` is set,
|
|
1131
1095
|
* which then plumbs through `buildManifest` and `deploymentYamlPartsFactory` to override the container `imagePullPolicy` in the generated
|
|
1132
1096
|
* `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
|
-
*
|
|
1097
|
+
* `node bin run sync --deploy-id dd-core --kubeadm --image-pull-policy Always`
|
|
1098
|
+
*
|
|
1099
|
+
* Runs in one of two modes. Given `--deploy-id`, it deploys. Passed `--build`, or no
|
|
1100
|
+
* `--deploy-id` at all, it renders the manifests and the router table and stops before
|
|
1101
|
+
* touching the cluster:
|
|
1102
|
+
* `node bin run --dev --build sync --deploy-id dd-default`
|
|
1103
|
+
* @param {string} [path] - Unused; every input is a flag.
|
|
1135
1104
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1136
1105
|
* @memberof UnderpostRun
|
|
1137
1106
|
*/
|
|
1138
1107
|
sync: async (path, options = DEFAULT_OPTION) => {
|
|
1139
|
-
|
|
1140
|
-
const env = options.dev ? 'development' : 'production';
|
|
1108
|
+
const env = deployEnvFactory(options);
|
|
1141
1109
|
options = { ...options, gatewayApi: gatewayApiEnabledFactory(options) };
|
|
1142
|
-
const baseCommand = '
|
|
1110
|
+
const baseCommand = cli('underpost', { local: true });
|
|
1143
1111
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1144
1112
|
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];
|
|
1113
|
+
const deploying = !options.build && !!options.deployId;
|
|
1114
|
+
const deployId = options.deployId || 'dd-default';
|
|
1115
|
+
const replicas = options.replicas || 1;
|
|
1116
|
+
const image = options.image || '';
|
|
1117
|
+
const node = options.nodeName || (!options.kubeadm && !options.k3s ? 'kind-control-plane' : os.hostname());
|
|
1118
|
+
let versions = options.versions || '';
|
|
1158
1119
|
shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
|
|
1159
1120
|
|
|
1160
1121
|
if (image && !image.startsWith('localhost'))
|
|
@@ -1165,7 +1126,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1165
1126
|
k3s: options.k3s,
|
|
1166
1127
|
});
|
|
1167
1128
|
|
|
1168
|
-
if (
|
|
1129
|
+
if (deploying) {
|
|
1169
1130
|
if (!options.disablePrivateConfUpdate) {
|
|
1170
1131
|
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
1171
1132
|
if (!validVersion) throw new Error('Version mismatch');
|
|
@@ -1183,7 +1144,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1183
1144
|
}
|
|
1184
1145
|
}
|
|
1185
1146
|
|
|
1186
|
-
const currentTraffic =
|
|
1147
|
+
const currentTraffic = deploying
|
|
1187
1148
|
? Underpost.deploy.getCurrentTraffic(deployId, {
|
|
1188
1149
|
namespace: options.namespace,
|
|
1189
1150
|
env,
|
|
@@ -1207,14 +1168,12 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1207
1168
|
});
|
|
1208
1169
|
|
|
1209
1170
|
const ignorePods =
|
|
1210
|
-
|
|
1171
|
+
deploying && targetTraffic
|
|
1211
1172
|
? Underpost.kubectl.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace).map((p) => p.NAME)
|
|
1212
1173
|
: [];
|
|
1213
1174
|
|
|
1214
1175
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
1215
|
-
const cmdString = options.cmd
|
|
1216
|
-
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
1217
|
-
: '';
|
|
1176
|
+
const cmdString = options.cmd ? ` --cmd ${shellArgumentFactory(options.cmd)}` : '';
|
|
1218
1177
|
const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
|
|
1219
1178
|
|
|
1220
1179
|
const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
|
|
@@ -1223,11 +1182,22 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1223
1182
|
const sshKeyPathFlag = options.sshKeyPath ? ` --ssh-key-path ${options.sshKeyPath}` : '';
|
|
1224
1183
|
const gatewayApiFlags = Underpost.deploy.gatewayApiFlagsFactory(options);
|
|
1225
1184
|
|
|
1185
|
+
// Retarget `underpost-config` at this sync's environment before anything is
|
|
1186
|
+
// deployed. `underpost host load` reads it back inside the
|
|
1187
|
+
// pod, so a stale Secret makes the container resolve the wrong NODE_ENV.
|
|
1188
|
+
if (deploying) {
|
|
1189
|
+
Underpost.host.apply(domainContextFactory({ env, namespace: options.namespace }));
|
|
1190
|
+
Underpost.app.apply(
|
|
1191
|
+
domainContextFactory({ env, namespace: options.namespace, args: { 'deploy-id': deployId } }),
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1226
1195
|
// A direct sync owns the same gateway-first contract as the full cluster
|
|
1227
1196
|
// runner. Build the host-side SSR documents before generating routes unless
|
|
1228
|
-
// the caller explicitly selected a pre-built bundle workflow.
|
|
1229
|
-
|
|
1230
|
-
|
|
1197
|
+
// the caller explicitly selected a pre-built bundle workflow. `--env` is
|
|
1198
|
+
// explicit: the SSR pass must render for the target environment, never for
|
|
1199
|
+
// whatever NODE_ENV the calling shell happens to carry.
|
|
1200
|
+
if (deploying && !options.skipFullBuild) shellExec(`${baseCommand} client ${deployId} --env ${env} --ssr`);
|
|
1231
1201
|
|
|
1232
1202
|
shellExec(
|
|
1233
1203
|
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
@@ -1237,7 +1207,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1237
1207
|
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag}${imagePullPolicyFlag}${sshKeyPathFlag}${gatewayApiFlags} ${deployId} ${env}`,
|
|
1238
1208
|
);
|
|
1239
1209
|
|
|
1240
|
-
if (
|
|
1210
|
+
if (deploying) {
|
|
1241
1211
|
if (options.gatewayApi) {
|
|
1242
1212
|
const namespace = options.namespace || 'default';
|
|
1243
1213
|
const gatewayRoot = Underpost.deploy.underpostGatewayRootFactory(options);
|
|
@@ -1308,6 +1278,48 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1308
1278
|
);
|
|
1309
1279
|
},
|
|
1310
1280
|
|
|
1281
|
+
/**
|
|
1282
|
+
* @method net-tables
|
|
1283
|
+
* @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`.
|
|
1284
|
+
* @param {string} path - The input value, identifier, or path for the operation (unused).
|
|
1285
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow (unused).
|
|
1286
|
+
* @memberof UnderpostRun
|
|
1287
|
+
*/
|
|
1288
|
+
'net-tables': (path, options = DEFAULT_OPTION) => {
|
|
1289
|
+
logger.info(`INTERFACES & LINKS`);
|
|
1290
|
+
shellExec(`(echo "INTERFACE STATE MAC FLAGS"; ip -br link show) | column -t`);
|
|
1291
|
+
logger.info(`IP ADDRESSES`);
|
|
1292
|
+
shellExec(`(echo "INTERFACE STATE IPV4 IPV6"; ip -br addr show) | column -t`);
|
|
1293
|
+
logger.info('KERNEL ROUTING TABLE');
|
|
1294
|
+
shellExec(`(echo "DESTINATION GATEWAY INTERFACE PROTO SCOPE METRIC"; \
|
|
1295
|
+
ip route show | awk '{
|
|
1296
|
+
dst = $1; gw = "on-link"; dev = "-"; proto = "-"; scope = "-"; metric = "-";
|
|
1297
|
+
for (i=1; i<=NF; i++) {
|
|
1298
|
+
if ($i == "via") gw = $(i+1);
|
|
1299
|
+
if ($i == "dev") dev = $(i+1);
|
|
1300
|
+
if ($i == "proto") proto = $(i+1);
|
|
1301
|
+
if ($i == "scope") scope = $(i+1);
|
|
1302
|
+
if ($i == "metric") metric = $(i+1);
|
|
1303
|
+
}
|
|
1304
|
+
print dst, gw, dev, proto, scope, metric;
|
|
1305
|
+
}') | column -t`);
|
|
1306
|
+
logger.info(`ACTIVE NAT FLOWS`);
|
|
1307
|
+
shellExec(`(echo "CHAIN TYPE TARGET_IP_PORT PACKETS BYTES"; \
|
|
1308
|
+
sudo nft list table ip nat | awk '
|
|
1309
|
+
/chain (PREROUTING|POSTROUTING)/ {chain=$2}
|
|
1310
|
+
/dnat to|masquerade|snat to/ && !/packets 0/ {
|
|
1311
|
+
type = ($0 ~ "dnat") ? "DNAT" : (($0 ~ "masquerade") ? "MASQUERADE" : "SNAT");
|
|
1312
|
+
target = "N/A";
|
|
1313
|
+
pkts = "0"; bytes = "0";
|
|
1314
|
+
for(i=1; i<=NF; i++) {
|
|
1315
|
+
if ($i == "packets") pkts = $(i+1);
|
|
1316
|
+
if ($i == "bytes") bytes = $(i+1);
|
|
1317
|
+
if ($i == "to") target = $(i+1);
|
|
1318
|
+
}
|
|
1319
|
+
print chain, type, target, pkts, bytes;
|
|
1320
|
+
}') | column -t`);
|
|
1321
|
+
},
|
|
1322
|
+
|
|
1311
1323
|
/**
|
|
1312
1324
|
* @method stop
|
|
1313
1325
|
* @description Deletes colour-suffixed Deployments and their Services, leaving routing untouched.
|
|
@@ -1419,8 +1431,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1419
1431
|
? options.timezone
|
|
1420
1432
|
: path
|
|
1421
1433
|
? path
|
|
1422
|
-
: Underpost.
|
|
1423
|
-
? Underpost.
|
|
1434
|
+
: Underpost.host.store.get('TIME_ZONE', undefined, { disableLog: true })
|
|
1435
|
+
? Underpost.host.store.get('TIME_ZONE')
|
|
1424
1436
|
: process.env.TIME_ZONE
|
|
1425
1437
|
? process.env.TIME_ZONE
|
|
1426
1438
|
: 'America/New_York';
|
|
@@ -1563,7 +1575,6 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1563
1575
|
// Probe the exact public URL represented by each host/path row. PWA rows
|
|
1564
1576
|
// can carry several configured paths in one cell, while instance rows
|
|
1565
1577
|
// carry one; cache by URL so shared rows never repeat network work.
|
|
1566
|
-
const shellArg = (value) => `'${`${value}`.replaceAll("'", "'\\''")}'`;
|
|
1567
1578
|
const probeCache = new Map();
|
|
1568
1579
|
const probePath = (host, routePath, tls) => {
|
|
1569
1580
|
const normalizedPath = `${routePath || '/'}`.startsWith('/') ? `${routePath || '/'}` : `/${routePath}`;
|
|
@@ -1573,18 +1584,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1573
1584
|
} catch {
|
|
1574
1585
|
return { path: normalizedPath, url: '', statuses: ['000'] };
|
|
1575
1586
|
}
|
|
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
|
-
}
|
|
1587
|
+
// Cache by URL so rows that share one never repeat network work.
|
|
1588
|
+
if (!probeCache.has(url)) probeCache.set(url, publicIngressProbeFactory(url));
|
|
1588
1589
|
return { path: normalizedPath, url, statuses: probeCache.get(url) };
|
|
1589
1590
|
};
|
|
1590
1591
|
|
|
@@ -1625,7 +1626,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1625
1626
|
|
|
1626
1627
|
// Padded on the raw values, coloured afterwards: an ANSI escape counts
|
|
1627
1628
|
// toward String.length and would skew every column right of it.
|
|
1628
|
-
const columns = ['HOST', 'PATH', 'KIND', 'ROUTE', 'TLS', 'HTTP3', 'CURRENT', 'OPPOSITE'];
|
|
1629
|
+
const columns = ['#', 'HOST', 'PATH', 'KIND', 'ROUTE', 'TLS', 'HTTP3', 'CURRENT', 'OPPOSITE'];
|
|
1629
1630
|
const deploymentStatus = (status, includeServing = false) => {
|
|
1630
1631
|
if (!status) return `unrouted - missing${includeServing ? ' not-serving' : ''}`;
|
|
1631
1632
|
return [
|
|
@@ -1635,10 +1636,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1635
1636
|
...(includeServing ? [status.serving ? 'serving' : 'not-serving'] : []),
|
|
1636
1637
|
].join(' ');
|
|
1637
1638
|
};
|
|
1638
|
-
const cellOf = (row) => {
|
|
1639
|
+
const cellOf = (row, index) => {
|
|
1639
1640
|
const facts = ingressFacts[row.host] || {};
|
|
1640
1641
|
const pathStatus = row.probes.map((probe) => `${probe.path} [${probe.statuses.join('→')}]`).join(' ');
|
|
1641
1642
|
return [
|
|
1643
|
+
`${index + 1}(${row.probes.length})`,
|
|
1642
1644
|
row.host,
|
|
1643
1645
|
pathStatus,
|
|
1644
1646
|
row.kind,
|
|
@@ -1693,7 +1695,8 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1693
1695
|
for (const cell of cells) console.log(line(cell, true));
|
|
1694
1696
|
};
|
|
1695
1697
|
for (const env of envs) {
|
|
1696
|
-
const
|
|
1698
|
+
const envRows = reportRows.filter((row) => row.env === env);
|
|
1699
|
+
const envCells = envRows.map((row, index) => cellOf(row, index));
|
|
1697
1700
|
if (envCells.length > 0) printTable(envCells, `[${env.toUpperCase()}]`);
|
|
1698
1701
|
}
|
|
1699
1702
|
console.log('');
|
|
@@ -1978,22 +1981,33 @@ EOF
|
|
|
1978
1981
|
|
|
1979
1982
|
/**
|
|
1980
1983
|
* @method instance
|
|
1981
|
-
* @
|
|
1984
|
+
* @description Deploys the custom instances a deploy declares in `conf.instances.json`.
|
|
1985
|
+
*
|
|
1986
|
+
* Every input is a flag: `--deploy-id`, `--instance-id`, `--replicas`, `--node-name`.
|
|
1987
|
+
* `--instance-id` naming a template id selects its whole variant family.
|
|
1988
|
+
* @param {string} path - Unused; every input is a flag.
|
|
1982
1989
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1983
1990
|
* @memberof UnderpostRun
|
|
1984
1991
|
*/
|
|
1985
1992
|
instance: async (path = '', options = DEFAULT_OPTION) => {
|
|
1986
|
-
const env = options
|
|
1993
|
+
const env = deployEnvFactory(options);
|
|
1987
1994
|
options = {
|
|
1988
1995
|
...options,
|
|
1989
1996
|
gatewayApi: gatewayApiEnabledFactory(options),
|
|
1990
1997
|
namespace: options.namespace || 'default',
|
|
1991
1998
|
};
|
|
1992
|
-
const baseCommand =
|
|
1999
|
+
const baseCommand = cli('underpost', { local: true });
|
|
1993
2000
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1994
|
-
|
|
1995
|
-
|
|
2001
|
+
const deployId = options.deployId;
|
|
2002
|
+
const id = options.instanceId;
|
|
2003
|
+
const replicas = options.replicas || 1;
|
|
2004
|
+
if (!deployId) throw new Error('The --deploy-id option is required by the instance runner');
|
|
2005
|
+
if (!id) throw new Error('The --instance-id option is required by the instance runner');
|
|
1996
2006
|
const confInstances = selectConfInstances(loadConfInstances(deployId), id);
|
|
2007
|
+
// Retarget `underpost-config` at this run's environment before any instance
|
|
2008
|
+
// Deployment is submitted: it is what `underpost host load` reads inside the pod,
|
|
2009
|
+
// and a stale one makes the container resolve the wrong NODE_ENV.
|
|
2010
|
+
if (!options.expose) Underpost.host.apply(domainContextFactory({ env, namespace: options.namespace }));
|
|
1997
2011
|
const { liveTrafficById, targetTrafficById, serving } = instanceTrafficPlanFactory({
|
|
1998
2012
|
instances: confInstances,
|
|
1999
2013
|
requestedTraffic: options.traffic,
|
|
@@ -2089,7 +2103,6 @@ EOF
|
|
|
2089
2103
|
const targetTraffic = targetTrafficById[instance.id];
|
|
2090
2104
|
const podId = `${_deployId}-${env}-${targetTraffic}`;
|
|
2091
2105
|
const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
|
|
2092
|
-
Underpost.deploy.configMap(env, options.namespace);
|
|
2093
2106
|
shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
|
|
2094
2107
|
shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
|
|
2095
2108
|
for (const _volume of _volumes)
|
|
@@ -2219,11 +2232,10 @@ EOF
|
|
|
2219
2232
|
|
|
2220
2233
|
/**
|
|
2221
2234
|
* @method deploy-key
|
|
2222
|
-
* @description Copies the deploy key for a specific user
|
|
2235
|
+
* @description Copies the cluster scoped deploy key for a specific user to a temporary location on the local machine.
|
|
2223
2236
|
* @param {string} path - The input value, identifier, or path for the operation (not used in this method).
|
|
2224
2237
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2225
2238
|
* @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
2239
|
* @memberof UnderpostRun
|
|
2228
2240
|
*/
|
|
2229
2241
|
'deploy-key': (path, options = DEFAULT_OPTION) => {
|
|
@@ -2232,13 +2244,18 @@ EOF
|
|
|
2232
2244
|
shellExec(`rm -rf /home/dd/tmp/${prefix}_*`);
|
|
2233
2245
|
return;
|
|
2234
2246
|
}
|
|
2235
|
-
if (!options.user
|
|
2236
|
-
logger.error('
|
|
2247
|
+
if (!options.user) {
|
|
2248
|
+
logger.error('The --user option is required to copy the deploy key.');
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const sourcePath = `${Underpost.ssh.userKeyDir(options.user)}/id_rsa`;
|
|
2252
|
+
if (!fs.existsSync(sourcePath)) {
|
|
2253
|
+
logger.error(`No deploy key found at ${sourcePath}; run: node bin ssh --user ${options.user} --user-add`);
|
|
2237
2254
|
return;
|
|
2238
2255
|
}
|
|
2239
2256
|
const targetPath = `/home/dd/tmp/${prefix}_${s4()}${s4()}`;
|
|
2240
2257
|
fs.mkdirSync('/home/dd/tmp', { recursive: true });
|
|
2241
|
-
fs.copyFileSync(
|
|
2258
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
2242
2259
|
logger.info(`Copied deploy key to ${targetPath}`);
|
|
2243
2260
|
if (options.copy) pbcopy(targetPath);
|
|
2244
2261
|
},
|
|
@@ -2254,19 +2271,25 @@ EOF
|
|
|
2254
2271
|
* If `--build` is supplied the image is built from the project Dockerfile and loaded into the
|
|
2255
2272
|
* cluster before the manifest is written (kind by default; `--kubeadm` / `--k3s` override).
|
|
2256
2273
|
*
|
|
2257
|
-
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2262
|
-
*
|
|
2274
|
+
* The target is named by `--deploy-id` and `--instance-id`, like every other instance runner.
|
|
2275
|
+
* Where the artifacts land is not an argument: an instance's project directory is derived from
|
|
2276
|
+
* the conf entry itself (see {@link instanceProjectPathFactory}), so the caller cannot pair an
|
|
2277
|
+
* instance with someone else's checkout. Artifacts are written to
|
|
2278
|
+
* `<project>/manifests/deployments/<instance>-<env>/`, and in production the pair is also
|
|
2279
|
+
* copied to `<project>/Dockerfile` and `<project>/deployment.yaml`.
|
|
2280
|
+
*
|
|
2281
|
+
* @param {string} path - Unused; the target comes from `--deploy-id` and `--instance-id`.
|
|
2263
2282
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2264
2283
|
* @memberof UnderpostRun
|
|
2265
2284
|
*/
|
|
2266
2285
|
'instance-build-manifest': async (path, options = DEFAULT_OPTION) => {
|
|
2267
2286
|
const env = options.dev ? 'development' : 'production';
|
|
2268
|
-
|
|
2269
|
-
const
|
|
2287
|
+
const deployId = options.deployId;
|
|
2288
|
+
const id = options.instanceId;
|
|
2289
|
+
if (!deployId || !id) {
|
|
2290
|
+
logger.error('[instance-build-manifest] --deploy-id and --instance-id are required');
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2270
2293
|
|
|
2271
2294
|
const confInstances = loadConfInstances(deployId);
|
|
2272
2295
|
// Targeting a template id builds every world in the family. The fan-out
|
|
@@ -2284,20 +2307,23 @@ EOF
|
|
|
2284
2307
|
}
|
|
2285
2308
|
if (!options.instanceOnly && (selected.length > 1 || selected[0].id !== id)) {
|
|
2286
2309
|
for (const instance of selected)
|
|
2287
|
-
await UnderpostRun.RUNNERS['instance-build-manifest'](
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2310
|
+
await UnderpostRun.RUNNERS['instance-build-manifest']('', {
|
|
2311
|
+
...options,
|
|
2312
|
+
instanceId: instance.id,
|
|
2313
|
+
instanceOnly: true,
|
|
2314
|
+
});
|
|
2291
2315
|
return;
|
|
2292
2316
|
}
|
|
2293
2317
|
|
|
2318
|
+
const instance = selected[0];
|
|
2319
|
+
// The conf names the project: its repository, else its runtime, else its id. A world and the
|
|
2320
|
+
// checkout it publishes to cannot disagree, because only one of them is stated.
|
|
2321
|
+
const rootPath = instanceProjectPathFactory(instance);
|
|
2294
2322
|
const envManifestPath = `${rootPath}/manifests/deployments/${id}-${env}`;
|
|
2295
2323
|
const outputPath = `${envManifestPath}/deployment.yaml`;
|
|
2296
2324
|
const dockerfileManifestPath = `${envManifestPath}/Dockerfile`;
|
|
2297
2325
|
|
|
2298
2326
|
fs.mkdirpSync(envManifestPath);
|
|
2299
|
-
|
|
2300
|
-
const instance = selected[0];
|
|
2301
2327
|
const isDefaultInstance = instance.id === instance.templateId || !instance.templateId;
|
|
2302
2328
|
const instanceEnvBuilder = await loadProjectInstanceEnvBuilder(deployId);
|
|
2303
2329
|
|
|
@@ -2344,14 +2370,15 @@ EOF
|
|
|
2344
2370
|
const _fromPort = instancePortFactory({ instance, env });
|
|
2345
2371
|
const _toPort = instancePortFactory({ instance, env, container: true });
|
|
2346
2372
|
|
|
2347
|
-
// Build image from
|
|
2348
|
-
|
|
2373
|
+
// Build the image from the project's own Dockerfile and load it into the cluster when
|
|
2374
|
+
// --build is set.
|
|
2375
|
+
if (options.build) {
|
|
2349
2376
|
const isKind = !options.kubeadm && !options.k3s;
|
|
2350
2377
|
Underpost.image.build({
|
|
2351
|
-
path:
|
|
2378
|
+
path: rootPath,
|
|
2352
2379
|
imageName: _image,
|
|
2353
2380
|
podmanSave: true,
|
|
2354
|
-
imageOutPath:
|
|
2381
|
+
imageOutPath: rootPath,
|
|
2355
2382
|
kind: isKind,
|
|
2356
2383
|
kubeadm: !!options.kubeadm,
|
|
2357
2384
|
k3s: !!options.k3s,
|
|
@@ -2471,12 +2498,11 @@ EOF
|
|
|
2471
2498
|
// a rewrite never points at a document that cannot exist. The check is a
|
|
2472
2499
|
// read: placing the document into the gateway volume is `deploy
|
|
2473
2500
|
// --sync-static`'s job at apply time, and a build must not mutate the host.
|
|
2474
|
-
//
|
|
2475
|
-
//
|
|
2476
|
-
const statusPageEntries = instanceStatusPageEntriesFactory({
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
}).filter((entry) => fs.existsSync(entry.sourcePath));
|
|
2501
|
+
// No project override: this runner and the sync now derive the root from the same instance
|
|
2502
|
+
// by the same rule, so passing one here could only make them disagree.
|
|
2503
|
+
const statusPageEntries = instanceStatusPageEntriesFactory({ instances: [instance] }).filter((entry) =>
|
|
2504
|
+
fs.existsSync(entry.sourcePath),
|
|
2505
|
+
);
|
|
2480
2506
|
|
|
2481
2507
|
// httproute.yaml — this instance's own routes, including the status routes
|
|
2482
2508
|
// that reach the static utility instead of this workload. No Gateway is
|
|
@@ -2569,11 +2595,11 @@ EOF
|
|
|
2569
2595
|
// mode this build ran. The default/template instance owns the committed
|
|
2570
2596
|
// source files, so only its current-mode file is idempotently refreshed.
|
|
2571
2597
|
if (instance.templateId) {
|
|
2572
|
-
const instanceEnvDir =
|
|
2598
|
+
const instanceEnvDir = nodePath.dirname(instanceEnvFilePath(deployId, _id, env));
|
|
2573
2599
|
fs.mkdirpSync(instanceEnvDir);
|
|
2574
2600
|
const envsToWrite = isDefaultInstance ? [env] : ['development', 'production'];
|
|
2575
2601
|
for (const targetEnv of envsToWrite) {
|
|
2576
|
-
const templateEnvPath =
|
|
2602
|
+
const templateEnvPath = instanceEnvFilePath(deployId, instance.templateId, targetEnv);
|
|
2577
2603
|
if (!fs.existsSync(templateEnvPath))
|
|
2578
2604
|
throw new Error(`[instance-build-manifest] Missing canonical env file: ${templateEnvPath}`);
|
|
2579
2605
|
const baseEnv = dotenv.parse(fs.readFileSync(templateEnvPath, 'utf8'));
|
|
@@ -2585,7 +2611,7 @@ EOF
|
|
|
2585
2611
|
containerDeployId: `${_deployId}-${targetEnv}`,
|
|
2586
2612
|
builders: instanceEnvBuilder ? { [deployId]: instanceEnvBuilder } : {},
|
|
2587
2613
|
});
|
|
2588
|
-
writeEnv(
|
|
2614
|
+
writeEnv(instanceEnvFilePath(deployId, _id, targetEnv), builtEnv);
|
|
2589
2615
|
}
|
|
2590
2616
|
logger.info('[instance-build-manifest] Instance env written', {
|
|
2591
2617
|
dir: instanceEnvDir,
|
|
@@ -2667,7 +2693,6 @@ EOF
|
|
|
2667
2693
|
* @memberof UnderpostRun
|
|
2668
2694
|
*/
|
|
2669
2695
|
'host-update': async (path, options = DEFAULT_OPTION) => {
|
|
2670
|
-
// const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2671
2696
|
shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
|
|
2672
2697
|
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
2673
2698
|
},
|
|
@@ -2734,7 +2759,7 @@ EOF`);
|
|
|
2734
2759
|
* @memberof UnderpostRun
|
|
2735
2760
|
*/
|
|
2736
2761
|
'dd-container': async (path = '', options = DEFAULT_OPTION) => {
|
|
2737
|
-
const baseCommand =
|
|
2762
|
+
const baseCommand = cli('underpost', { local: true });
|
|
2738
2763
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
2739
2764
|
const currentImage = options.imageName
|
|
2740
2765
|
? options.imageName
|
|
@@ -2831,15 +2856,15 @@ EOF`);
|
|
|
2831
2856
|
* @memberof UnderpostRun
|
|
2832
2857
|
*/
|
|
2833
2858
|
'git-conf': (path = '', options = DEFAULT_OPTION) => {
|
|
2834
|
-
const defaultUsername = Underpost.
|
|
2835
|
-
const defaultEmail = Underpost.
|
|
2859
|
+
const defaultUsername = Underpost.host.store.get('GITHUB_USERNAME');
|
|
2860
|
+
const defaultEmail = Underpost.host.store.get('GITHUB_EMAIL');
|
|
2836
2861
|
const validPath = path && path.split(',').length;
|
|
2837
2862
|
const [username, email] = validPath ? path.split(',') : [defaultUsername, defaultEmail];
|
|
2838
2863
|
if (validPath) {
|
|
2839
|
-
Underpost.
|
|
2840
|
-
Underpost.
|
|
2841
|
-
Underpost.
|
|
2842
|
-
Underpost.
|
|
2864
|
+
Underpost.host.store.set('GITHUB_USERNAME', username);
|
|
2865
|
+
Underpost.host.store.set('GITHUB_EMAIL', email);
|
|
2866
|
+
Underpost.host.store.get('GITHUB_USERNAME');
|
|
2867
|
+
Underpost.host.store.get('GITHUB_EMAIL');
|
|
2843
2868
|
}
|
|
2844
2869
|
shellExec(
|
|
2845
2870
|
`git config --global credential.helper "" && ` +
|
|
@@ -2870,7 +2895,7 @@ EOF`);
|
|
|
2870
2895
|
|
|
2871
2896
|
/**
|
|
2872
2897
|
* @method promote
|
|
2873
|
-
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.
|
|
2898
|
+
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.routes` for 'dd', or a specific ID).
|
|
2874
2899
|
* When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
|
|
2875
2900
|
* TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
|
|
2876
2901
|
* (cert-manager Certificate resources) for each affected deployment.
|
|
@@ -2906,7 +2931,7 @@ EOF`);
|
|
|
2906
2931
|
};
|
|
2907
2932
|
|
|
2908
2933
|
if (inputDeployId === 'dd') {
|
|
2909
|
-
for (const deployId of
|
|
2934
|
+
for (const deployId of readDeployRoutes()) {
|
|
2910
2935
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, {
|
|
2911
2936
|
namespace: options.namespace,
|
|
2912
2937
|
env: inputEnv,
|
|
@@ -2932,44 +2957,6 @@ EOF`);
|
|
|
2932
2957
|
applyCerts(inputDeployId, targetTraffic);
|
|
2933
2958
|
}
|
|
2934
2959
|
},
|
|
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
2960
|
/**
|
|
2974
2961
|
* @method cluster
|
|
2975
2962
|
* @description Deploys a full production/development ready Kubernetes cluster environment including MongoDB,
|
|
@@ -2982,21 +2969,24 @@ EOF`);
|
|
|
2982
2969
|
* references, and every host is written to `/etc/hosts` so the operator's browser reaches the PWA at
|
|
2983
2970
|
* `https://<host>` on the local machine.
|
|
2984
2971
|
*
|
|
2985
|
-
* Custom instances
|
|
2972
|
+
* Custom instances come from `--instance-id`. They are resolved per deploy against that deploy's
|
|
2986
2973
|
* own `conf.instances.json`, so an id only runs where its deploy declares it, and each one is deployed after its
|
|
2987
2974
|
* deploy's default workload is serving — an instance reads the parent's world configuration over the parent's
|
|
2988
2975
|
* gRPC ClusterIP at boot. Instance hosts share the deploy's environment: the same self-signed certificates and
|
|
2989
2976
|
* `/etc/hosts` pass in development, the same cert-manager issuance in production.
|
|
2990
|
-
*
|
|
2991
|
-
*
|
|
2992
|
-
*
|
|
2977
|
+
*
|
|
2978
|
+
* Every input is a flag: `--runtime-image` (default `express`), `--deploy-id` (comma-separated, or the `dd` meta
|
|
2979
|
+
* id; unset reads the route table) and `--instance-id` (comma-separated; a template id such as `mmo-server`
|
|
2980
|
+
* selects its whole variant family):
|
|
2981
|
+
* `node bin run cluster --runtime-image express --deploy-id dd-cyberia --instance-id mmo-server --dev`
|
|
2982
|
+
* @param {string} path - Unused; every input is a flag.
|
|
2993
2983
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2994
2984
|
* @memberof UnderpostRun
|
|
2995
2985
|
*/
|
|
2996
2986
|
cluster: async (path = '', options = DEFAULT_OPTION) => {
|
|
2997
2987
|
const { underpostRoot } = options;
|
|
2998
|
-
const env = options
|
|
2999
|
-
const baseCommand =
|
|
2988
|
+
const env = deployEnvFactory(options);
|
|
2989
|
+
const baseCommand = cli('underpost', { local: true });
|
|
3000
2990
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
3001
2991
|
const clusterType = clusterTypeFactory(options, 'kubeadm');
|
|
3002
2992
|
shellCd(`/home/dd/engine`);
|
|
@@ -3004,23 +2994,15 @@ EOF`);
|
|
|
3004
2994
|
await timer(5000);
|
|
3005
2995
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
3006
2996
|
await timer(5000);
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
? path.split(',')
|
|
3010
|
-
: [
|
|
3011
|
-
'express',
|
|
3012
|
-
fs.readFileSync(`${underpostRoot}/engine-private/deploy/dd.router`, 'utf8').replaceAll(',', '+'),
|
|
3013
|
-
'',
|
|
3014
|
-
];
|
|
2997
|
+
const runtimeImage = options.runtimeImage || 'express';
|
|
2998
|
+
const instanceListId = options.instanceId || '';
|
|
3015
2999
|
// shellExec(
|
|
3016
3000
|
// `${baseCommand} image${baseClusterCommand} --build ${
|
|
3017
3001
|
// runtimeImage ? ` --pull-base --path ${underpostRoot}/src/runtime/${runtimeImage}` : ''
|
|
3018
3002
|
// } --${clusterType}`,
|
|
3019
3003
|
// );
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
logger.warn('No deploy list provided');
|
|
3023
|
-
} else deployList = deployList.split('+');
|
|
3004
|
+
const deployList = options.deployId ? resolveDeployList(options.deployId) : readDeployRoutes();
|
|
3005
|
+
if (deployList.length === 0) logger.warn('No deploy list provided');
|
|
3024
3006
|
await timer(5000);
|
|
3025
3007
|
// --reset-mongodb wipes the retained hostPath volumes before the rollout.
|
|
3026
3008
|
// This workflow already tore the node down and re-imports every database
|
|
@@ -3097,10 +3079,13 @@ EOF`);
|
|
|
3097
3079
|
const hostListenResult = etcHostFactory(hosts);
|
|
3098
3080
|
logger.info(hostListenResult.renderHosts);
|
|
3099
3081
|
}
|
|
3100
|
-
const version = 'v3.3.
|
|
3082
|
+
const version = 'v3.3.76';
|
|
3101
3083
|
const instanceOptionsFactory = (deployId, instanceId) => ({
|
|
3102
3084
|
...options,
|
|
3103
3085
|
...clusterContextFactory(clusterType),
|
|
3086
|
+
deployId,
|
|
3087
|
+
instanceId,
|
|
3088
|
+
replicas: options.replicas || 1,
|
|
3104
3089
|
gatewayApi,
|
|
3105
3090
|
gatewayBootstrapComplete: true,
|
|
3106
3091
|
tls: true,
|
|
@@ -3147,17 +3132,16 @@ EOF`);
|
|
|
3147
3132
|
sudo rm -rf ./engine-private/, \
|
|
3148
3133
|
node bin clone underpostnet/engine-cyberia-private, \
|
|
3149
3134
|
sudo mv ./engine-cyberia-private ./engine-private, \
|
|
3150
|
-
node bin env
|
|
3151
|
-
node ./engine-private/itc-scripts/dd-cyberia-0.js, \
|
|
3135
|
+
node bin app load --env ${env} --args deploy-id=dd-cyberia, \
|
|
3152
3136
|
sudo chown -R dd:dd /home/dd/engine/src/client/public/cyberia, \
|
|
3153
|
-
node bin env
|
|
3137
|
+
node bin app load --env ${env} --args deploy-id=dd-cyberia, \
|
|
3154
3138
|
node bin client dd-cyberia ${env}, \
|
|
3155
3139
|
node bin start dd-cyberia ${env} --run'`
|
|
3156
3140
|
: '');
|
|
3157
3141
|
deployFlagsById[deployId] = deployFlags;
|
|
3158
3142
|
// SSR status and context documents belong to the ingress bootstrap, so
|
|
3159
3143
|
// build them on the host before any workload Deployment is submitted.
|
|
3160
|
-
shellExec(`${baseCommand} client ${deployId} ${env}`);
|
|
3144
|
+
shellExec(`${baseCommand} client ${deployId} --env ${env}`);
|
|
3161
3145
|
shellExec(`${baseCommand} deploy ${deployId} ${env} --build-manifest ${deployFlags}`);
|
|
3162
3146
|
// Seed the static tree before the routes exist, so every status page and
|
|
3163
3147
|
// intercepted context the manifests just pointed at resolves from the
|
|
@@ -3260,10 +3244,7 @@ EOF`);
|
|
|
3260
3244
|
// keep serving the custom fallback until the atomic promotion completes.
|
|
3261
3245
|
for (const instanceId of instancesByDeployId[deployId].ids) {
|
|
3262
3246
|
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
|
-
);
|
|
3247
|
+
await UnderpostRun.RUNNERS.instance('', instanceOptionsFactory(deployId, instanceId));
|
|
3267
3248
|
}
|
|
3268
3249
|
}
|
|
3269
3250
|
logger.info('[cluster] Ingress stack deployed', {
|
|
@@ -3655,6 +3636,10 @@ EOF`);
|
|
|
3655
3636
|
/**
|
|
3656
3637
|
* @method deploy
|
|
3657
3638
|
* @description Deploys a specified service (identified by `path`) using blue/green strategy, monitors its status, and switches traffic upon readiness.
|
|
3639
|
+
*
|
|
3640
|
+
* The target colour is restarted when it is already deployed, and built and applied when it is not:
|
|
3641
|
+
* between cycles only the routed colour has a Deployment, so its counterpart being absent is the
|
|
3642
|
+
* normal state rather than a precondition.
|
|
3658
3643
|
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID to deploy).
|
|
3659
3644
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
3660
3645
|
* @memberof UnderpostRun
|
|
@@ -3662,19 +3647,57 @@ EOF`);
|
|
|
3662
3647
|
deploy: async (path, options = DEFAULT_OPTION) => {
|
|
3663
3648
|
const deployId = path;
|
|
3664
3649
|
const env = options.dev ? 'development' : 'production';
|
|
3650
|
+
const namespace = options.namespace || 'default';
|
|
3665
3651
|
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
3666
3652
|
if (!validVersion) throw new Error('Version mismatch');
|
|
3667
|
-
|
|
3668
|
-
const
|
|
3653
|
+
options = { ...options, gatewayApi: gatewayApiEnabledFactory(options) };
|
|
3654
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, {
|
|
3655
|
+
namespace,
|
|
3656
|
+
env,
|
|
3657
|
+
gatewayApi: options.gatewayApi,
|
|
3658
|
+
});
|
|
3659
|
+
const { targetTraffic, create } = redeployPlanFactory({
|
|
3660
|
+
liveTraffic: currentTraffic,
|
|
3661
|
+
hasDeployment: (colour) =>
|
|
3662
|
+
Underpost.deploy.deploymentExists({ deployment: `${deployId}-${env}-${colour}`, namespace }),
|
|
3663
|
+
});
|
|
3669
3664
|
const ignorePods = Underpost.kubectl
|
|
3670
|
-
.get(`${deployId}-${env}-${targetTraffic}`, 'pods',
|
|
3665
|
+
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', namespace)
|
|
3671
3666
|
.map((p) => p.NAME);
|
|
3672
3667
|
|
|
3673
|
-
|
|
3668
|
+
if (create) {
|
|
3669
|
+
// The target colour having no Deployment is the steady state of a
|
|
3670
|
+
// blue/green cycle, not a missing prerequisite: build that colour's
|
|
3671
|
+
// manifest and apply it, leaving the live colour routed until the
|
|
3672
|
+
// promotion below.
|
|
3673
|
+
const replicas = options.replicas || 1;
|
|
3674
|
+
const node =
|
|
3675
|
+
(currentTraffic
|
|
3676
|
+
? Underpost.deploy.deploymentNode({ deployment: `${deployId}-${env}-${currentTraffic}`, namespace })
|
|
3677
|
+
: '') || options.nodeName;
|
|
3678
|
+
logger.info('Target traffic colour is not deployed; creating it', {
|
|
3679
|
+
deployId,
|
|
3680
|
+
env,
|
|
3681
|
+
targetTraffic,
|
|
3682
|
+
node: node || '(unpinned)',
|
|
3683
|
+
});
|
|
3684
|
+
const deployFlags =
|
|
3685
|
+
` --versions ${targetTraffic} --replicas ${replicas} --namespace ${namespace}${node ? ` --node ${node}` : ''}` +
|
|
3686
|
+
`${options.image ? ` --image ${options.image}` : ''}` +
|
|
3687
|
+
`${Underpost.deploy.timeoutFlagsFactory(options)}${options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : ''}` +
|
|
3688
|
+
`${Underpost.deploy.gatewayApiFlagsFactory(options)}`;
|
|
3689
|
+
// `--build-manifest` returns before any cluster mutation, so the colour is
|
|
3690
|
+
// rendered first and applied by the second call. `--traffic` keeps the
|
|
3691
|
+
// generated traffic Service on the colour still serving.
|
|
3692
|
+
shellExec(
|
|
3693
|
+
`node bin deploy --build-manifest --info-router --traffic ${currentTraffic || targetTraffic}${deployFlags} ${deployId} ${env}`,
|
|
3694
|
+
);
|
|
3695
|
+
shellExec(`node bin deploy --disable-update-proxy${deployFlags} ${deployId} ${env}`);
|
|
3696
|
+
} else shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic} -n ${namespace}`);
|
|
3674
3697
|
|
|
3675
|
-
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods,
|
|
3698
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, namespace);
|
|
3676
3699
|
|
|
3677
|
-
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas,
|
|
3700
|
+
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas, namespace, options);
|
|
3678
3701
|
},
|
|
3679
3702
|
|
|
3680
3703
|
/**
|
|
@@ -3764,7 +3787,10 @@ EOF`);
|
|
|
3764
3787
|
}`;
|
|
3765
3788
|
shellExec(cmd, { async: true });
|
|
3766
3789
|
}
|
|
3767
|
-
|
|
3790
|
+
// A bare `return` here exited 0, so a runtime that latched `container-status=error` looked
|
|
3791
|
+
// like a clean start to everything upstream — the shell, and any CI job wrapping it.
|
|
3792
|
+
if ((await awaitDeployMonitor()) !== true)
|
|
3793
|
+
throw new Error(`[run dev] ${deployId} api runtime reported ${RUNTIME_STATUS.ERROR}; see the logs above`);
|
|
3768
3794
|
{
|
|
3769
3795
|
const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
3770
3796
|
|
|
@@ -3772,7 +3798,8 @@ EOF`);
|
|
|
3772
3798
|
async: true,
|
|
3773
3799
|
});
|
|
3774
3800
|
}
|
|
3775
|
-
if ((await awaitDeployMonitor()) !== true)
|
|
3801
|
+
if ((await awaitDeployMonitor()) !== true)
|
|
3802
|
+
throw new Error(`[run dev] ${deployId} client runtime reported ${RUNTIME_STATUS.ERROR}; see the logs above`);
|
|
3776
3803
|
shellExec(
|
|
3777
3804
|
`NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
|
|
3778
3805
|
);
|
|
@@ -3787,7 +3814,7 @@ EOF`);
|
|
|
3787
3814
|
*/
|
|
3788
3815
|
service: async (path = '', options = DEFAULT_OPTION) => {
|
|
3789
3816
|
const env = options.dev ? 'development' : 'production';
|
|
3790
|
-
const baseCommand =
|
|
3817
|
+
const baseCommand = cli('underpost', { local: true });
|
|
3791
3818
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
3792
3819
|
shellCd(`/home/dd/engine`);
|
|
3793
3820
|
let [deployId, serviceId, host, _path, replicas, image, node] = path.split(',');
|
|
@@ -3836,9 +3863,9 @@ EOF`);
|
|
|
3836
3863
|
);
|
|
3837
3864
|
switch (serviceId) {
|
|
3838
3865
|
case 'mongo-express-service': {
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3866
|
+
// One deploy path for the client, shared with `cluster --mongo-express`: the secret,
|
|
3867
|
+
// the auth-mode overlay selection and the readiness wait live there.
|
|
3868
|
+
await MongoExpress.deploy({ namespace: options.namespace, options });
|
|
3842
3869
|
podToMonitor = 'mongo-express';
|
|
3843
3870
|
break;
|
|
3844
3871
|
}
|
|
@@ -3892,26 +3919,6 @@ EOF`);
|
|
|
3892
3919
|
logger.info(hostListenResult.renderHosts);
|
|
3893
3920
|
},
|
|
3894
3921
|
|
|
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
3922
|
/**
|
|
3916
3923
|
* @method log
|
|
3917
3924
|
* @description Searches and highlights keywords in a specified log file, optionally showing surrounding lines.
|
|
@@ -3970,28 +3977,44 @@ EOF`);
|
|
|
3970
3977
|
|
|
3971
3978
|
// Services
|
|
3972
3979
|
logger.info('Process info');
|
|
3973
|
-
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd
|
|
3980
|
+
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`, {
|
|
3981
|
+
silentOnError: true,
|
|
3982
|
+
});
|
|
3974
3983
|
logger.info('Command line');
|
|
3975
|
-
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo
|
|
3984
|
+
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`, {
|
|
3985
|
+
silentOnError: true,
|
|
3986
|
+
});
|
|
3976
3987
|
logger.info('Executable path');
|
|
3977
3988
|
shellExec(`sudo readlink -f /proc/${pid}/exe`);
|
|
3978
3989
|
logger.info('Working directory');
|
|
3979
|
-
shellExec(`sudo readlink -f /proc/${pid}/cwd
|
|
3990
|
+
shellExec(`sudo readlink -f /proc/${pid}/cwd`, {
|
|
3991
|
+
silentOnError: true,
|
|
3992
|
+
});
|
|
3980
3993
|
logger.info('Environment variables (first 200)');
|
|
3981
|
-
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200
|
|
3994
|
+
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`, {
|
|
3995
|
+
silentOnError: true,
|
|
3996
|
+
});
|
|
3982
3997
|
|
|
3983
3998
|
// Parent
|
|
3984
3999
|
logger.info('Parent process');
|
|
3985
|
-
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, {
|
|
4000
|
+
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, {
|
|
4001
|
+
stdout: true,
|
|
4002
|
+
silent: true,
|
|
4003
|
+
silentOnError: true,
|
|
4004
|
+
});
|
|
3986
4005
|
console.log(parentInfo);
|
|
3987
4006
|
const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
|
|
3988
4007
|
if (ppidMatch) {
|
|
3989
4008
|
const ppid = ppidMatch.trim().split(/\s+/)[1];
|
|
3990
4009
|
logger.info(`Parent PID: ${ppid}`);
|
|
3991
|
-
shellExec(`ps -fp ${ppid}
|
|
4010
|
+
shellExec(`ps -fp ${ppid}`, {
|
|
4011
|
+
silentOnError: true,
|
|
4012
|
+
});
|
|
3992
4013
|
}
|
|
3993
4014
|
logger.info('Process tree');
|
|
3994
|
-
shellExec(`pstree -s ${pid}
|
|
4015
|
+
shellExec(`pstree -s ${pid}`, {
|
|
4016
|
+
silentOnError: true,
|
|
4017
|
+
});
|
|
3995
4018
|
},
|
|
3996
4019
|
|
|
3997
4020
|
/**
|
|
@@ -4036,7 +4059,7 @@ EOF`);
|
|
|
4036
4059
|
'deploy-test': async (path, options = DEFAULT_OPTION) => {
|
|
4037
4060
|
// Note: use recomendation empty deploy cluster: node bin --dev cluster
|
|
4038
4061
|
const env = options.dev ? 'development' : 'production';
|
|
4039
|
-
const baseCommand =
|
|
4062
|
+
const baseCommand = cli('underpost', { local: true });
|
|
4040
4063
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
4041
4064
|
const inputs = path ? path.split(',') : [];
|
|
4042
4065
|
const deployId = inputs[0] ? inputs[0] : 'dd-test';
|
|
@@ -4045,10 +4068,12 @@ EOF`);
|
|
|
4045
4068
|
: [
|
|
4046
4069
|
`npm install -g npm@11.2.0`,
|
|
4047
4070
|
`npm install -g underpost`,
|
|
4048
|
-
`${baseCommand}
|
|
4071
|
+
`${baseCommand} host load`,
|
|
4049
4072
|
`${baseCommand} start --build --run ${deployId} ${env}`,
|
|
4050
4073
|
];
|
|
4051
|
-
shellExec(
|
|
4074
|
+
shellExec(
|
|
4075
|
+
`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none --deploy-id dd-test --cmd ${shellArgumentFactory(cmd)}`,
|
|
4076
|
+
);
|
|
4052
4077
|
},
|
|
4053
4078
|
|
|
4054
4079
|
/**
|
|
@@ -4179,26 +4204,7 @@ EOF`);
|
|
|
4179
4204
|
|
|
4180
4205
|
shellCd('/home/dd/engine');
|
|
4181
4206
|
},
|
|
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
|
-
},
|
|
4207
|
+
|
|
4202
4208
|
/**
|
|
4203
4209
|
* @method kill
|
|
4204
4210
|
* @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 +4250,6 @@ EOF`);
|
|
|
4244
4250
|
if (options.copy) pbcopy(password);
|
|
4245
4251
|
else console.log(password);
|
|
4246
4252
|
},
|
|
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
4253
|
/**
|
|
4575
4254
|
* @method gpu-env
|
|
4576
4255
|
* @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 +4345,7 @@ UNDERPOST_SOPS_ENV_EOF`,
|
|
|
4666
4345
|
? 'Directory'
|
|
4667
4346
|
: 'File';
|
|
4668
4347
|
|
|
4669
|
-
const envs = Underpost.
|
|
4348
|
+
const envs = Underpost.host.store.list();
|
|
4670
4349
|
|
|
4671
4350
|
const cmd = `kubectl apply -f - <<'EOF'
|
|
4672
4351
|
apiVersion: ${apiVersion}
|
|
@@ -4732,186 +4411,47 @@ EOF`;
|
|
|
4732
4411
|
|
|
4733
4412
|
/**
|
|
4734
4413
|
* @method push-bundle
|
|
4735
|
-
* @description Builds the
|
|
4736
|
-
* Steps: set env, build
|
|
4737
|
-
*
|
|
4738
|
-
*
|
|
4739
|
-
*
|
|
4414
|
+
* @description Builds the deployment's client bundles and uploads them to file storage.
|
|
4415
|
+
* Steps: set env, build and split the zips, then upload each resolved route's artifacts
|
|
4416
|
+
* under the deployment's `storage.bundle.json`. Routes the client build skips — redirects,
|
|
4417
|
+
* rebuild-disabled routes, and `singleReplica` routes that carry their own deploy id —
|
|
4418
|
+
* produce no bundle and are not pushed.
|
|
4419
|
+
* @param {string} path - Optional comma-separated host filter (e.g. 'a.com' or 'a.com,b.com').
|
|
4420
|
+
* Empty selects every host in `engine-private/conf/<deployId>/conf.server.json`.
|
|
4740
4421
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
4741
|
-
* @param {string} [options.deployId] -
|
|
4422
|
+
* @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
|
|
4423
|
+
* @param {string} [options.split] - Zip part size in MB, or 'none' to upload one zip. Defaults to 8.
|
|
4742
4424
|
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
4743
4425
|
* @memberof UnderpostRun
|
|
4744
4426
|
*/
|
|
4745
|
-
'push-bundle': (path = '', options = DEFAULT_OPTION) =>
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
let splitFlag = '--split 8';
|
|
4754
|
-
if (splitOption) {
|
|
4755
|
-
if (splitOption === 'none-split') {
|
|
4756
|
-
splitFlag = '';
|
|
4757
|
-
} else {
|
|
4758
|
-
const splitMb = Number(splitOption);
|
|
4759
|
-
if (Number.isFinite(splitMb) && splitMb > 0) {
|
|
4760
|
-
splitFlag = `--split ${splitMb}`;
|
|
4761
|
-
} else {
|
|
4762
|
-
logger.warn('push-bundle: invalid split option, using default split 8', {
|
|
4763
|
-
path,
|
|
4764
|
-
splitOption,
|
|
4765
|
-
});
|
|
4766
|
-
}
|
|
4767
|
-
}
|
|
4768
|
-
}
|
|
4769
|
-
|
|
4770
|
-
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
4771
|
-
const confServer = fs.existsSync(confServerPath)
|
|
4772
|
-
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
4773
|
-
: {};
|
|
4774
|
-
const storageFilePath = `engine-private/conf/${deployId}/storage.bundle.json`;
|
|
4775
|
-
|
|
4776
|
-
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
4777
|
-
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
4778
|
-
|
|
4779
|
-
const pushBundleFiles = (host, routePath) => {
|
|
4780
|
-
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
4781
|
-
const buildDir = `./${fsPath}`;
|
|
4782
|
-
if (!fs.existsSync(buildDir)) return;
|
|
4783
|
-
const partFiles = fs
|
|
4784
|
-
.readdirSync(buildDir)
|
|
4785
|
-
.filter(
|
|
4786
|
-
(name) =>
|
|
4787
|
-
name.startsWith(`${buildId}.zip.part`) ||
|
|
4788
|
-
name.startsWith(`${buildId}.zip-part`) ||
|
|
4789
|
-
name === `${buildId}.zip`,
|
|
4790
|
-
)
|
|
4791
|
-
.map((name) => `${fsPath}/${name}`);
|
|
4792
|
-
if (partFiles.length === 0) {
|
|
4793
|
-
logger.warn(`push-bundle: no bundle files found for '${host}${routePath}'`, { buildId });
|
|
4794
|
-
return;
|
|
4795
|
-
}
|
|
4796
|
-
for (const partFile of partFiles) {
|
|
4797
|
-
shellExec(
|
|
4798
|
-
`${baseCommand} fs ${partFile} --deploy-id ${deployId} --storage-file-path ${storageFilePath} --force`,
|
|
4799
|
-
);
|
|
4800
|
-
}
|
|
4801
|
-
};
|
|
4802
|
-
|
|
4803
|
-
for (const host of Object.keys(confServer)) {
|
|
4804
|
-
for (const routePath of Object.keys(confServer[host])) {
|
|
4805
|
-
const routeConf = confServer[host][routePath] || {};
|
|
4806
|
-
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
4807
|
-
if (routeConf.singleReplica) {
|
|
4808
|
-
if (routeConf.replicas) {
|
|
4809
|
-
for (const replica of routeConf.replicas) {
|
|
4810
|
-
pushBundleFiles(host, replica);
|
|
4811
|
-
}
|
|
4812
|
-
}
|
|
4813
|
-
continue;
|
|
4814
|
-
}
|
|
4815
|
-
pushBundleFiles(host, routePath);
|
|
4816
|
-
}
|
|
4817
|
-
}
|
|
4818
|
-
},
|
|
4427
|
+
'push-bundle': (path = '', options = DEFAULT_OPTION) =>
|
|
4428
|
+
pushClientBundle({
|
|
4429
|
+
deployId: options.deployId || 'dd-default',
|
|
4430
|
+
hosts: clientBundleHostFilter(path),
|
|
4431
|
+
split: options.split,
|
|
4432
|
+
dev: options.dev,
|
|
4433
|
+
}),
|
|
4819
4434
|
|
|
4820
4435
|
/**
|
|
4821
4436
|
* @method pull-bundle
|
|
4822
|
-
* @description Downloads
|
|
4823
|
-
*
|
|
4824
|
-
*
|
|
4825
|
-
*
|
|
4826
|
-
*
|
|
4827
|
-
*
|
|
4437
|
+
* @description Downloads the deployment's client bundles from file storage and installs them
|
|
4438
|
+
* into the served public directory.
|
|
4439
|
+
* Steps: set env, download the parts, merge and extract each resolved route's zip, then
|
|
4440
|
+
* replace `public/<host>[<route>]` with the extracted tree. Route selection matches
|
|
4441
|
+
* push-bundle, so the two directions always address the same set of bundles.
|
|
4442
|
+
* @param {string} path - Optional comma-separated host filter (e.g. 'a.com' or 'a.com,b.com').
|
|
4443
|
+
* Empty selects every host in `engine-private/conf/<deployId>/conf.server.json`.
|
|
4828
4444
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
4829
4445
|
* @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
|
|
4830
4446
|
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
4831
4447
|
* @memberof UnderpostRun
|
|
4832
4448
|
*/
|
|
4833
|
-
'pull-bundle': (path = '', options = DEFAULT_OPTION) =>
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
4840
|
-
: {};
|
|
4841
|
-
const hostsArg = path
|
|
4842
|
-
? path
|
|
4843
|
-
.split(',')
|
|
4844
|
-
.map((h) => h.trim())
|
|
4845
|
-
.filter(Boolean)
|
|
4846
|
-
: Object.keys(confServer);
|
|
4847
|
-
|
|
4848
|
-
if (hostsArg.length === 0) {
|
|
4849
|
-
logger.error('pull-bundle: no hosts resolved', { deployId, path, confServerPath });
|
|
4850
|
-
return;
|
|
4851
|
-
}
|
|
4852
|
-
|
|
4853
|
-
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
4854
|
-
if (!fs.existsSync('./build')) fs.mkdirSync('./build', { recursive: true });
|
|
4855
|
-
shellExec(
|
|
4856
|
-
`${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
|
|
4857
|
-
);
|
|
4858
|
-
|
|
4859
|
-
const pullBundleRoute = (host, routePath) => {
|
|
4860
|
-
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
4861
|
-
const zipPath = `build/${buildId}.zip`;
|
|
4862
|
-
const buildDir = './build';
|
|
4863
|
-
const hasZip = fs.existsSync(zipPath);
|
|
4864
|
-
const hasParts =
|
|
4865
|
-
fs.existsSync(buildDir) &&
|
|
4866
|
-
fs
|
|
4867
|
-
.readdirSync(buildDir)
|
|
4868
|
-
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
4869
|
-
|
|
4870
|
-
if (!hasZip && !hasParts) {
|
|
4871
|
-
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
4872
|
-
return;
|
|
4873
|
-
}
|
|
4874
|
-
|
|
4875
|
-
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
4876
|
-
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
4877
|
-
shellExec(`sudo rm -rf ${zipPath}`);
|
|
4878
|
-
|
|
4879
|
-
if (fs.existsSync(buildDir)) {
|
|
4880
|
-
fs.readdirSync(buildDir)
|
|
4881
|
-
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
4882
|
-
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
4883
|
-
}
|
|
4884
|
-
|
|
4885
|
-
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
4886
|
-
if (!fs.existsSync(extractedDir)) {
|
|
4887
|
-
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
4888
|
-
return;
|
|
4889
|
-
}
|
|
4890
|
-
|
|
4891
|
-
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
4892
|
-
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
4893
|
-
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
4894
|
-
fs.copySync(`${extractedDir}`, `${publicDestPath}`);
|
|
4895
|
-
};
|
|
4896
|
-
|
|
4897
|
-
for (const host of hostsArg) {
|
|
4898
|
-
const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
|
|
4899
|
-
|
|
4900
|
-
for (const routePath of routePaths) {
|
|
4901
|
-
const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
|
|
4902
|
-
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
4903
|
-
if (routeConf.singleReplica) {
|
|
4904
|
-
if (routeConf.replicas) {
|
|
4905
|
-
for (const replica of routeConf.replicas) {
|
|
4906
|
-
pullBundleRoute(host, replica);
|
|
4907
|
-
}
|
|
4908
|
-
}
|
|
4909
|
-
continue;
|
|
4910
|
-
}
|
|
4911
|
-
pullBundleRoute(host, routePath);
|
|
4912
|
-
}
|
|
4913
|
-
}
|
|
4914
|
-
},
|
|
4449
|
+
'pull-bundle': async (path = '', options = DEFAULT_OPTION) =>
|
|
4450
|
+
await pullClientBundle({
|
|
4451
|
+
deployId: options.deployId || 'dd-default',
|
|
4452
|
+
hosts: clientBundleHostFilter(path),
|
|
4453
|
+
dev: options.dev,
|
|
4454
|
+
}),
|
|
4915
4455
|
|
|
4916
4456
|
/**
|
|
4917
4457
|
* @method kubeadm-wireguard
|
|
@@ -4935,15 +4475,30 @@ EOF`;
|
|
|
4935
4475
|
|
|
4936
4476
|
/**
|
|
4937
4477
|
* @method build-cluster-deployment-manifests
|
|
4938
|
-
* @description Builds deployment manifests for both production and development environments using `
|
|
4478
|
+
* @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
4479
|
* @param {string} path - Unused.
|
|
4940
4480
|
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
4941
4481
|
* @memberof UnderpostRun
|
|
4942
4482
|
*/
|
|
4943
|
-
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) =>
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4483
|
+
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) =>
|
|
4484
|
+
// Manifest generation writes files; it does not touch a cluster. Declaring that once
|
|
4485
|
+
// here is what lets the whole tree below run on a build box with no apiserver — the
|
|
4486
|
+
// profile rides into both child processes through the environment.
|
|
4487
|
+
withExecutionProfile('HERMETIC_BUILD', async () => {
|
|
4488
|
+
// Each deploy's package manifest is generated from the engine's before the cluster
|
|
4489
|
+
// manifests that ship it: a deploy id declares only its identity and whatever its
|
|
4490
|
+
// catalog adds, so a dependency the engine gained reaches every deploy from here.
|
|
4491
|
+
await syncDeployPackages();
|
|
4492
|
+
// `local` is load-bearing: this builds the manifests of the checkout it was
|
|
4493
|
+
// launched from, and a globally installed underpost is a different package that
|
|
4494
|
+
// reads different paths out of the same cwd.
|
|
4495
|
+
const underpost = cli('underpost', { local: true });
|
|
4496
|
+
const flags = '--build-manifest --sync --info-router --replicas 1';
|
|
4497
|
+
shellExec(`${underpost} deploy ${flags} dd development`);
|
|
4498
|
+
shellExec(`${underpost} deploy ${flags} dd production --cert`);
|
|
4499
|
+
// What the build just produced is what the project repository carries.
|
|
4500
|
+
if (deployRoutesExists()) Underpost.deploy.mirrorDefaultManifests();
|
|
4501
|
+
}),
|
|
4947
4502
|
|
|
4948
4503
|
/**
|
|
4949
4504
|
* @method monitor-ui
|
|
@@ -5083,7 +4638,7 @@ EOF`;
|
|
|
5083
4638
|
const result = await Underpost.run.CALL(runner, path, options);
|
|
5084
4639
|
return result;
|
|
5085
4640
|
} catch (error) {
|
|
5086
|
-
console.
|
|
4641
|
+
console.error(error);
|
|
5087
4642
|
logger.error(error);
|
|
5088
4643
|
process.exit(1);
|
|
5089
4644
|
}
|