@underpostnet/cyberia 3.3.0 → 3.3.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.github/workflows/coverall.cyberia.ci.yml +34 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +565 -2510
- package/CLI-HELP.md +306 -147
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +66 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +308 -91
- package/bin/deploy.js +18 -104
- package/bin/index.js +308 -91
- package/conf.js +91 -4
- package/deploy/cyberia-client/deploy.sh +47 -0
- package/deploy/cyberia-client/package.sh +18 -0
- package/deploy/cyberia-client/state.sh +14 -0
- package/deploy/cyberia-server/deploy.sh +47 -0
- package/deploy/cyberia-server/package.sh +18 -0
- package/deploy/cyberia-server/state.sh +14 -0
- package/deploy/dd-cyberia/deploy.sh +6 -26
- package/deploy/dd-cyberia/init.sh +35 -43
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +92 -170
- package/deploy/lib/github-actions-logging.sh +594 -0
- package/deploy/lib/host.sh +185 -0
- package/deploy/lib/state.sh +62 -0
- package/deploy/pwa-microservices-template/deploy.sh +12 -39
- package/deploy/release/deploy.sh +9 -39
- package/deployment.yaml +7 -2
- package/docker-compose.yml +7 -7
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +83 -10
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +1 -1
- package/src/api/cyberia-action/cyberia-action.service.js +2 -2
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +8 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +2 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +2 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +2 -2
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +0 -26
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +3 -3
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +391 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +184 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +153 -71
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs.js +3 -3
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +322 -142
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +558 -363
- package/src/cli/run.js +331 -650
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1111 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +258 -211
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -1
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +61 -46
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +38 -30
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +1 -1
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +53 -10
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +1 -1
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +1 -1
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +1 -1
- package/src/projects/cyberia/object-layer.js +1 -1
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +9 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
- package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +117 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +465 -0
- package/src/server/build/testing.js +528 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +3 -3
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +108 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +513 -422
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +1 -1
- package/src/server/{downloader.js → storage/downloader.js} +2 -2
- package/src/server/storage/repository.js +68 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +1 -1
- package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +609 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +334 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +979 -0
- package/test/unit/coverage-artifact.test.js +130 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +447 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/logger-redaction.test.js +112 -0
- package/test/unit/package.test.js +403 -0
- package/test/unit/prepare-host.test.js +299 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +193 -0
- package/vitest.config.js +58 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/environment.js +0 -98
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provides utilities for building, loading, and managing server configurations,
|
|
3
3
|
* deployment contexts, and service configurations (API, Client, WS).
|
|
4
|
-
* @module src/server/conf.js
|
|
4
|
+
* @module src/server/runtime/conf.js
|
|
5
5
|
* @namespace ServerConfBuilder
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -13,21 +13,30 @@ import {
|
|
|
13
13
|
getDirname,
|
|
14
14
|
newInstance,
|
|
15
15
|
orderAbc,
|
|
16
|
-
orderArrayFromAttrInt,
|
|
17
|
-
range,
|
|
18
16
|
timer,
|
|
19
|
-
} from '
|
|
17
|
+
} from '../../client/components/core/CommonJs.js';
|
|
20
18
|
import * as dir from 'path';
|
|
21
19
|
import net from 'net';
|
|
22
20
|
import crypto from 'crypto';
|
|
23
21
|
import colors from 'colors';
|
|
24
|
-
import { loggerFactory } from '
|
|
25
|
-
import { writeEnv } from './environment.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
22
|
+
import { loggerFactory } from '../ops/logger.js';
|
|
23
|
+
import { isOciRuntime, writeEnv } from './environment.js';
|
|
24
|
+
import { configRejectionFactory } from './config-scope.js';
|
|
25
|
+
import { shellArgumentFactory, shellExec } from './process.js';
|
|
26
|
+
import { UNDERPOST_GATEWAY, statusPageAssetPathFactory } from '../network/underpost-gateway.js';
|
|
27
|
+
import { COVERAGE_BUNDLE_DIRECTORY } from '../build/coverage.js';
|
|
28
|
+
import { DefaultConf } from '../../../conf.js';
|
|
29
29
|
import splitFile from 'split-file';
|
|
30
|
-
import
|
|
30
|
+
import { readDeployRoutes } from '../network/router.js';
|
|
31
|
+
import Underpost from '../../index.js';
|
|
32
|
+
import {
|
|
33
|
+
clearAwaitDeploy,
|
|
34
|
+
getRuntimeStatus,
|
|
35
|
+
isAwaitingDeploy,
|
|
36
|
+
latchAwaitDeploy,
|
|
37
|
+
RUNTIME_STATUS,
|
|
38
|
+
} from './runtime-status.js';
|
|
39
|
+
import { cronDeployIdResolve } from '../ops/cron.js';
|
|
31
40
|
|
|
32
41
|
colors.enable();
|
|
33
42
|
|
|
@@ -176,8 +185,8 @@ const getConfFolder = (deployId) => {
|
|
|
176
185
|
|
|
177
186
|
/**
|
|
178
187
|
* Resolves the full path to a specific configuration JSON file for a deploy ID.
|
|
179
|
-
* For `server` configs
|
|
180
|
-
*
|
|
188
|
+
* For `server` configs it prefers the `dev.<subConf>` variant when one exists: always for a
|
|
189
|
+
* sub-conf the caller named, and only in development for one inherited from `DEPLOY_SUB_CONF`.
|
|
181
190
|
*
|
|
182
191
|
* @method getConfFilePath
|
|
183
192
|
* @param {string} deployId - The deploy ID.
|
|
@@ -191,17 +200,28 @@ const getConfFolder = (deployId) => {
|
|
|
191
200
|
* // => './engine-private/conf/dd-myapp/conf.server.json'
|
|
192
201
|
*
|
|
193
202
|
* @example
|
|
194
|
-
* //
|
|
203
|
+
* // With an explicitly named subConf, in any environment:
|
|
195
204
|
* getConfFilePath('dd-myapp', 'server', 'local');
|
|
196
205
|
* // => './engine-private/conf/dd-myapp/conf.server.dev.local.json' (if it exists)
|
|
197
206
|
*/
|
|
198
207
|
const getConfFilePath = (deployId, confType, subConf = '') => {
|
|
199
208
|
const folder = getConfFolder(deployId);
|
|
200
|
-
|
|
201
|
-
|
|
209
|
+
const explicit = `${subConf ?? ''}`.trim();
|
|
210
|
+
// Without an explicit sub-conf, fall back to the env var loadConf() exported.
|
|
211
|
+
const effectiveSubConf = explicit || process.env.DEPLOY_SUB_CONF || '';
|
|
202
212
|
if (confType === 'server' && effectiveSubConf) {
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
// An explicitly named sub-conf is a request, not a hint: honor it whatever the ambient
|
|
214
|
+
// environment says. Only the env-var fallback stays gated on development, so a production
|
|
215
|
+
// deploy that merely carries DEPLOY_SUB_CONF is unaffected.
|
|
216
|
+
//
|
|
217
|
+
// The gate used to apply to both, and every `underpost` invocation loads the host
|
|
218
|
+
// configuration store with `override: true` — on a provisioned node that store carries
|
|
219
|
+
// NODE_ENV=production, so the gate was false for every CLI call and a named sub-conf was
|
|
220
|
+
// silently discarded in favour of the base conf.
|
|
221
|
+
if (explicit || process.env.NODE_ENV === 'development') {
|
|
222
|
+
const devConfPath = `${folder}/conf.${confType}.dev.${effectiveSubConf}.json`;
|
|
223
|
+
if (fs.existsSync(devConfPath)) return devConfPath;
|
|
224
|
+
}
|
|
205
225
|
}
|
|
206
226
|
return `${folder}/conf.${confType}.json`;
|
|
207
227
|
};
|
|
@@ -261,7 +281,7 @@ const Config = {
|
|
|
261
281
|
deployContext = process.env.DEPLOY_ID;
|
|
262
282
|
if (!subConf && process.argv[3] && typeof process.argv[3] === 'string') subConf = process.argv[3];
|
|
263
283
|
|
|
264
|
-
|
|
284
|
+
latchAwaitDeploy();
|
|
265
285
|
if (deployContext.startsWith('dd-')) loadConf(deployContext, subConf);
|
|
266
286
|
if (deployContext === 'proxy') await Config.buildProxy(deployList, subConf);
|
|
267
287
|
},
|
|
@@ -269,16 +289,15 @@ const Config = {
|
|
|
269
289
|
* @method deployIdFactory
|
|
270
290
|
* @description Creates a new deploy ID.
|
|
271
291
|
* @param {string} [deployId='dd-default']
|
|
272
|
-
* @param {object} [options={ subConf: ''
|
|
292
|
+
* @param {object} [options={ subConf: '' }] - The options.
|
|
273
293
|
* @memberof ServerConfBuilder
|
|
274
294
|
*/
|
|
275
|
-
deployIdFactory: function (deployId = DEFAULT_DEPLOY_ID, options = { subConf: ''
|
|
295
|
+
deployIdFactory: function (deployId = DEFAULT_DEPLOY_ID, options = { subConf: '' }) {
|
|
276
296
|
if (!deployId.startsWith('dd-')) deployId = `dd-${deployId}`;
|
|
277
297
|
|
|
278
298
|
logger.info('Build deployId', deployId);
|
|
279
299
|
|
|
280
300
|
const folder = `./engine-private/conf/${deployId}`;
|
|
281
|
-
const repoName = `engine-${deployId.split('dd-')[1]}`;
|
|
282
301
|
|
|
283
302
|
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
284
303
|
|
|
@@ -330,29 +349,6 @@ const Config = {
|
|
|
330
349
|
);
|
|
331
350
|
}
|
|
332
351
|
|
|
333
|
-
if (options.cluster === true) {
|
|
334
|
-
fs.writeFileSync(
|
|
335
|
-
`./.github/workflows/${repoName}.cd.yml`,
|
|
336
|
-
fs.readFileSync(`./.github/workflows/engine-test.cd.yml`, 'utf8').replaceAll('test', deployId.split('dd-')[1]),
|
|
337
|
-
'utf8',
|
|
338
|
-
);
|
|
339
|
-
fs.writeFileSync(
|
|
340
|
-
`./.github/workflows/${repoName}.ci.yml`,
|
|
341
|
-
fs.readFileSync(`./.github/workflows/engine-test.ci.yml`, 'utf8').replaceAll('test', deployId.split('dd-')[1]),
|
|
342
|
-
'utf8',
|
|
343
|
-
);
|
|
344
|
-
shellExec(`node bin new --default-conf --deploy-id ${deployId}`);
|
|
345
|
-
|
|
346
|
-
if (!fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
347
|
-
fs.writeFileSync(`./engine-private/deploy/dd.router`, deployId, 'utf8');
|
|
348
|
-
else
|
|
349
|
-
fs.writeFileSync(
|
|
350
|
-
`./engine-private/deploy/dd.router`,
|
|
351
|
-
fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim() + `,${deployId}`,
|
|
352
|
-
'utf8',
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
352
|
return { deployIdFolder: folder, deployId };
|
|
357
353
|
},
|
|
358
354
|
/**
|
|
@@ -412,17 +408,178 @@ const Config = {
|
|
|
412
408
|
* @param {string} [subConf=''] - The sub configuration.
|
|
413
409
|
* @memberof ServerConfBuilder
|
|
414
410
|
*/
|
|
411
|
+
/**
|
|
412
|
+
* @method deployEnvFilePath
|
|
413
|
+
* @description Resolves a deployment's env file for an environment.
|
|
414
|
+
*
|
|
415
|
+
* A named sub-configuration selects `.env.<env>.<subConf>` when that file exists and degrades to
|
|
416
|
+
* the plain `.env.<env>` when it does not. Single source of this precedence: {@link loadConf}
|
|
417
|
+
* materializes the working tree from it and {@link UnderpostApp} addresses it from the CLI, so
|
|
418
|
+
* both always agree on which file is in effect.
|
|
419
|
+
* @param {string} deployId - Deployment id.
|
|
420
|
+
* @param {string} [env='production'] - Environment selector.
|
|
421
|
+
* @param {string} [subConf] - Sub-configuration name.
|
|
422
|
+
* @returns {string} Path to the deployment env file.
|
|
423
|
+
* @memberof ServerConfBuilder
|
|
424
|
+
*/
|
|
425
|
+
const deployEnvFilePath = (deployId, env = 'production', subConf = '') => {
|
|
426
|
+
const base = `${getConfFolder(deployId)}/.env.${env || 'production'}`;
|
|
427
|
+
const name = `${subConf ?? ''}`.trim();
|
|
428
|
+
if (!name) return base;
|
|
429
|
+
const scoped = `${base}.${name}`;
|
|
430
|
+
return fs.existsSync(scoped) ? scoped : base;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* The environments a deployment's configuration actually carries.
|
|
435
|
+
*
|
|
436
|
+
* Read from the tree rather than assumed, because which environments exist is a property of the
|
|
437
|
+
* checkout: a workstation carries every one a developer runs, and a workload carries none at all —
|
|
438
|
+
* its environment is injected. A name is the segment after `.env.` and nothing further, so an
|
|
439
|
+
* overlay (`.env.production.oci`) and a sub-configuration (`.env.development.api`) are the
|
|
440
|
+
* variants of an environment, never environments of their own.
|
|
441
|
+
* @param {string} deployId - Deployment id.
|
|
442
|
+
* @returns {string[]} Environment names present, sorted.
|
|
443
|
+
* @memberof ServerConfBuilder
|
|
444
|
+
*/
|
|
445
|
+
const deployEnvNamesFactory = (deployId) => {
|
|
446
|
+
const folder = getConfFolder(deployId);
|
|
447
|
+
if (!fs.existsSync(folder)) return [];
|
|
448
|
+
return fs
|
|
449
|
+
.readdirSync(folder)
|
|
450
|
+
.map((entry) => `${entry}`.match(/^\.env\.([^.]+)$/)?.[1])
|
|
451
|
+
.filter(Boolean)
|
|
452
|
+
.sort();
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Resolves a custom instance's env file for an environment.
|
|
457
|
+
*
|
|
458
|
+
* An instance's runtime environment is its own file, not a sub-configuration of the parent
|
|
459
|
+
* deployment's: `instance-build-manifest` writes it, the instance container sources it at boot,
|
|
460
|
+
* and {@link UnderpostApp} addresses it from the CLI through `--args instance-id=`. Single
|
|
461
|
+
* source of that path, so the three can never disagree.
|
|
462
|
+
* @param {string} deployId - Parent deployment id.
|
|
463
|
+
* @param {string} instanceId - Instance id, as declared in `conf.instances.json`.
|
|
464
|
+
* @param {string} [env='production'] - Environment selector.
|
|
465
|
+
* @returns {string} Path to the instance env file.
|
|
466
|
+
* @memberof ServerConfBuilder
|
|
467
|
+
*/
|
|
468
|
+
const instanceEnvFilePath = (deployId, instanceId, env = 'production') =>
|
|
469
|
+
`${getConfFolder(deployId)}/instances/${instanceId}/env/${env || 'production'}.env`;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Suffix marking a deployment env file's OCI (container) runtime overlay.
|
|
473
|
+
* @constant {string}
|
|
474
|
+
* @memberof ServerConfBuilder
|
|
475
|
+
*/
|
|
476
|
+
const OCI_ENV_SUFFIX = '.oci';
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @method deployOciEnvFilePath
|
|
480
|
+
* @description Resolves a deployment's OCI runtime env overlay for an environment.
|
|
481
|
+
*
|
|
482
|
+
* The overlay is per environment, not per sub-configuration: it carries the values that only
|
|
483
|
+
* hold when the deployment runs as a container image (cluster-internal service names, in-cluster
|
|
484
|
+
* database endpoints), which are the same whichever host sub-configuration is in effect.
|
|
485
|
+
* @param {string} deployId - Deployment id.
|
|
486
|
+
* @param {string} [env='production'] - Environment selector.
|
|
487
|
+
* @returns {string} Path to the deployment's OCI env overlay.
|
|
488
|
+
* @memberof ServerConfBuilder
|
|
489
|
+
*/
|
|
490
|
+
const deployOciEnvFilePath = (deployId, env = 'production') =>
|
|
491
|
+
`${getConfFolder(deployId)}/.env.${env || 'production'}${OCI_ENV_SUFFIX}`;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* @method envLineKey
|
|
495
|
+
* @description The variable name a dotenv line declares, or `''` for blanks and comments.
|
|
496
|
+
* @param {string} line - One dotenv line.
|
|
497
|
+
* @returns {string} Declared key, or `''`.
|
|
498
|
+
* @memberof ServerConfBuilder
|
|
499
|
+
*/
|
|
500
|
+
const envLineKey = (line) => {
|
|
501
|
+
const trimmed = `${line}`.trimStart();
|
|
502
|
+
if (!trimmed || trimmed.startsWith('#')) return '';
|
|
503
|
+
const assign = trimmed.indexOf('=');
|
|
504
|
+
return assign === -1 ? '' : trimmed.slice(0, assign).trim();
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @method ociEnvContentFactory
|
|
509
|
+
* @description Applies an OCI overlay onto base env contents.
|
|
510
|
+
*
|
|
511
|
+
* Textual rather than parse-and-render: base lines the overlay does not redeclare survive
|
|
512
|
+
* verbatim — comments, ordering, and value quoting included — and the overlay is appended whole,
|
|
513
|
+
* so an overridden key appears exactly once with the container value.
|
|
514
|
+
* @param {string} baseContent - The deployment's `.env.<env>` contents.
|
|
515
|
+
* @param {string} overlayContent - The matching `.env.<env>.oci` contents.
|
|
516
|
+
* @returns {string} Overlay-applied env contents.
|
|
517
|
+
* @memberof ServerConfBuilder
|
|
518
|
+
*/
|
|
519
|
+
const ociEnvContentFactory = (baseContent, overlayContent) => {
|
|
520
|
+
const overlay = `${overlayContent ?? ''}`.trim();
|
|
521
|
+
if (!overlay) return baseContent;
|
|
522
|
+
const overridden = new Set(overlay.split('\n').map(envLineKey).filter(Boolean));
|
|
523
|
+
const kept = `${baseContent ?? ''}`
|
|
524
|
+
.split('\n')
|
|
525
|
+
.filter((line) => {
|
|
526
|
+
const key = envLineKey(line);
|
|
527
|
+
return !key || !overridden.has(key);
|
|
528
|
+
})
|
|
529
|
+
.join('\n')
|
|
530
|
+
.trimEnd();
|
|
531
|
+
return `${kept}${kept ? '\n' : ''}${overlay}\n`;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* @method deployEnvContentFactory
|
|
536
|
+
* @description Reads a deployment's env file for an environment, with its OCI overlay applied
|
|
537
|
+
* when one exists and the caller is resolving for a container runtime.
|
|
538
|
+
*
|
|
539
|
+
* Single source of the overlay precedence: {@link loadConf} materializes the working tree from
|
|
540
|
+
* it and {@link UnderpostApp} projects the cluster Secret from it, so the file a container reads
|
|
541
|
+
* and the Secret injected into it always carry the same values. Outside a container the base
|
|
542
|
+
* file is returned untouched, which is what keeps a developer host on its local endpoints.
|
|
543
|
+
* @param {string} deployId - Deployment id.
|
|
544
|
+
* @param {string} [env='production'] - Environment selector.
|
|
545
|
+
* @param {string} [subConf=''] - Sub-configuration name.
|
|
546
|
+
* @param {object} [options] - Resolution options.
|
|
547
|
+
* @param {boolean} [options.oci] - Force the overlay on or off; defaults to container detection.
|
|
548
|
+
* @returns {{source: string, overlay: string|null, content: string, values: Object<string,string>}}
|
|
549
|
+
* The base file used, the overlay applied (or `null`), and the resulting contents and values.
|
|
550
|
+
* @memberof ServerConfBuilder
|
|
551
|
+
*/
|
|
552
|
+
const deployEnvContentFactory = (deployId, env = 'production', subConf = '', options = {}) => {
|
|
553
|
+
const source = deployEnvFilePath(deployId, env, subConf);
|
|
554
|
+
const baseContent = fs.readFileSync(source, 'utf8');
|
|
555
|
+
const overlayPath = deployOciEnvFilePath(deployId, env);
|
|
556
|
+
const applyOverlay = (options.oci ?? isOciRuntime()) && fs.existsSync(overlayPath);
|
|
557
|
+
if (!applyOverlay) return { source, overlay: null, content: baseContent, values: dotenv.parse(baseContent) };
|
|
558
|
+
const content = ociEnvContentFactory(baseContent, fs.readFileSync(overlayPath, 'utf8'));
|
|
559
|
+
return { source, overlay: overlayPath, content, values: dotenv.parse(content) };
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @method cleanDeployEnvFiles
|
|
564
|
+
* @description Removes the working-tree env files {@link loadConf} materializes.
|
|
565
|
+
* Single source of the file list, shared with the app domain's `clean` action.
|
|
566
|
+
* @returns {Array<string>} The paths that were present and removed.
|
|
567
|
+
* @memberof ServerConfBuilder
|
|
568
|
+
*/
|
|
569
|
+
const cleanDeployEnvFiles = () => {
|
|
570
|
+
const targets = ['./.env', './.env.production', './.env.development', './.env.test'];
|
|
571
|
+
const present = targets.filter((target) => fs.existsSync(target));
|
|
572
|
+
for (const target of present) fs.removeSync(target);
|
|
573
|
+
return present;
|
|
574
|
+
};
|
|
575
|
+
|
|
415
576
|
const loadConf = (deployId = DEFAULT_DEPLOY_ID, subConf) => {
|
|
416
577
|
if (deployId === 'current') {
|
|
417
578
|
console.log(process.env.DEPLOY_ID);
|
|
418
579
|
return;
|
|
419
580
|
}
|
|
420
581
|
if (deployId === 'clean') {
|
|
421
|
-
|
|
422
|
-
fs.removeSync(`${path}/.env`);
|
|
423
|
-
fs.removeSync(`${path}/.env.production`);
|
|
424
|
-
fs.removeSync(`${path}/.env.development`);
|
|
425
|
-
fs.removeSync(`${path}/.env.test`);
|
|
582
|
+
cleanDeployEnvFiles();
|
|
426
583
|
return;
|
|
427
584
|
}
|
|
428
585
|
const folder = getConfFolder(deployId);
|
|
@@ -432,34 +589,58 @@ const loadConf = (deployId = DEFAULT_DEPLOY_ID, subConf) => {
|
|
|
432
589
|
if (subConf) process.env.DEPLOY_SUB_CONF = subConf;
|
|
433
590
|
|
|
434
591
|
for (const typeConf of Object.keys(Config.default)) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
592
|
+
const confPath = `${folder}/conf.${typeConf}.json`;
|
|
593
|
+
let srcConf;
|
|
594
|
+
if (fs.existsSync(confPath)) srcConf = fs.readFileSync(confPath, 'utf8');
|
|
595
|
+
else {
|
|
596
|
+
Config.default[typeConf] = newInstance(DefaultConf[typeConf]);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
// Same precedence as getConfFilePath, so loadConf and every later read agree on which file
|
|
600
|
+
// is in effect: a sub-conf named here was named by the caller, so it is not gated on the
|
|
601
|
+
// ambient environment.
|
|
602
|
+
if (typeConf === 'server' && subConf) {
|
|
603
|
+
const devConfPath = getConfFilePath(deployId, typeConf, subConf);
|
|
438
604
|
if (fs.existsSync(devConfPath)) srcConf = fs.readFileSync(devConfPath, 'utf8');
|
|
439
605
|
}
|
|
440
606
|
let parsed = JSON.parse(srcConf);
|
|
441
607
|
if (typeConf === 'server') parsed = loadReplicas(deployId, parsed);
|
|
442
608
|
Config.default[typeConf] = parsed;
|
|
443
609
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
610
|
+
// Each materialized copy carries its own OCI overlay, so a container that later switches
|
|
611
|
+
// NODE_ENV still reads container values rather than the host endpoints of the base file.
|
|
612
|
+
const available = deployEnvNamesFactory(deployId);
|
|
613
|
+
for (const name of available)
|
|
614
|
+
fs.writeFileSync(`./.env.${name}`, deployEnvContentFactory(deployId, name).content, 'utf8');
|
|
615
|
+
|
|
616
|
+
const envName = process.env.NODE_ENV || 'development';
|
|
617
|
+
// A tree with no environment of its own is a workload: its configuration is injected, and
|
|
618
|
+
// materializing is neither possible nor wanted. A tree that carries environments but not this
|
|
619
|
+
// one is a mistake worth naming — falling through would run the deployment against whichever
|
|
620
|
+
// environment happened to be loaded.
|
|
621
|
+
if (available.length > 0) {
|
|
622
|
+
if (!available.includes(envName))
|
|
623
|
+
throw new Error(
|
|
624
|
+
configRejectionFactory({
|
|
625
|
+
domain: 'app',
|
|
626
|
+
deployId,
|
|
627
|
+
env: envName,
|
|
628
|
+
key: 'NODE_ENV',
|
|
629
|
+
reason: `no environment source; ${getConfFolder(deployId)} carries ${available.join(', ')}`,
|
|
630
|
+
}),
|
|
631
|
+
);
|
|
632
|
+
const { content, values } = deployEnvContentFactory(deployId, envName, subConf);
|
|
633
|
+
fs.writeFileSync(`./.env`, content, 'utf8');
|
|
634
|
+
process.env = { ...process.env, ...values, NODE_ENV: envName };
|
|
635
|
+
} else process.env.NODE_ENV = envName;
|
|
459
636
|
const originPackageJson = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
|
|
460
637
|
const packageJson = JSON.parse(fs.readFileSync(`${folder}/package.json`, 'utf8'));
|
|
461
638
|
originPackageJson.scripts.start = packageJson.scripts.start;
|
|
462
639
|
packageJson.scripts = originPackageJson.scripts;
|
|
640
|
+
// `bin` is engine identity, not deploy identity: it is what `npm link` publishes as the
|
|
641
|
+
// global `underpost` command. Deploy manifests do not carry one, so without this the
|
|
642
|
+
// checkout would stop being linkable the moment its environment was loaded.
|
|
643
|
+
if (originPackageJson.bin) packageJson.bin = originPackageJson.bin;
|
|
463
644
|
fs.writeFileSync(`./package.json`, JSON.stringify(packageJson, null, 4), 'utf8');
|
|
464
645
|
return { folder, deployId };
|
|
465
646
|
};
|
|
@@ -830,247 +1011,6 @@ const cloneSrcComponents = async ({ toOptions, fromOptions }) => {
|
|
|
830
1011
|
}
|
|
831
1012
|
};
|
|
832
1013
|
|
|
833
|
-
/**
|
|
834
|
-
* @method buildProxyRouter
|
|
835
|
-
* @description Builds the proxy router.
|
|
836
|
-
* @memberof ServerConfBuilder
|
|
837
|
-
*/
|
|
838
|
-
const buildProxyRouter = () => {
|
|
839
|
-
const confServer = newInstance(Config.default.server);
|
|
840
|
-
let currentPort = parseInt(process.env.PORT) + 1;
|
|
841
|
-
const proxyRouter = {};
|
|
842
|
-
for (const host of Object.keys(confServer)) {
|
|
843
|
-
for (const path of Object.keys(confServer[host])) {
|
|
844
|
-
if (confServer[host][path].singleReplica) continue;
|
|
845
|
-
|
|
846
|
-
if (isDevProxyContext()) confServer[host][path].proxy = [isTlsDevProxy() ? 443 : 80];
|
|
847
|
-
|
|
848
|
-
confServer[host][path].port = newInstance(currentPort);
|
|
849
|
-
for (const port of confServer[host][path].proxy) {
|
|
850
|
-
if (!(port in proxyRouter)) proxyRouter[port] = {};
|
|
851
|
-
proxyRouter[port][`${host}${path}`] = {
|
|
852
|
-
// target: `http://${host}:${confServer[host][path].port}${path}`,
|
|
853
|
-
target: `http://localhost:${confServer[host][path].port}`,
|
|
854
|
-
// target: `http://127.0.0.1:${confServer[host][path].port}`,
|
|
855
|
-
proxy: confServer[host][path].proxy,
|
|
856
|
-
redirect: confServer[host][path].redirect,
|
|
857
|
-
host,
|
|
858
|
-
path,
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
currentPort++;
|
|
862
|
-
if (confServer[host][path].peer) {
|
|
863
|
-
const peerPath = path === '/' ? `/peer` : `${path}/peer`;
|
|
864
|
-
confServer[host][peerPath] = newInstance(confServer[host][path]);
|
|
865
|
-
confServer[host][peerPath].port = newInstance(currentPort);
|
|
866
|
-
for (const port of confServer[host][path].proxy) {
|
|
867
|
-
if (!(port in proxyRouter)) proxyRouter[port] = {};
|
|
868
|
-
proxyRouter[port][`${host}${peerPath}`] = {
|
|
869
|
-
// target: `http://${host}:${confServer[host][peerPath].port}${peerPath}`,
|
|
870
|
-
target: `http://localhost:${confServer[host][peerPath].port}`,
|
|
871
|
-
// target: `http://127.0.0.1:${confServer[host][peerPath].port}`,
|
|
872
|
-
proxy: confServer[host][peerPath].proxy,
|
|
873
|
-
host,
|
|
874
|
-
path: peerPath,
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
currentPort++;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
return proxyRouter;
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* @method pathPortAssignmentFactory
|
|
887
|
-
* @description Creates the path port assignment.
|
|
888
|
-
* @param {string} deployId - The deploy ID.
|
|
889
|
-
* @param {object} router - The router.
|
|
890
|
-
* @param {object} confServer - The server configuration.
|
|
891
|
-
* @memberof ServerConfBuilder
|
|
892
|
-
*/
|
|
893
|
-
const pathPortAssignmentFactory = async (deployId, router, confServer) => {
|
|
894
|
-
const pathPortAssignmentData = {};
|
|
895
|
-
for (const host of Object.keys(confServer)) {
|
|
896
|
-
const pathPortAssignment = [];
|
|
897
|
-
for (const path of Object.keys(confServer[host])) {
|
|
898
|
-
const { peer } = confServer[host][path];
|
|
899
|
-
if (!router[`${host}${path === '/' ? '' : path}`]) continue;
|
|
900
|
-
const port = parseInt(router[`${host}${path === '/' ? '' : path}`].split(':')[2]);
|
|
901
|
-
// logger.info('', { host, port, path });
|
|
902
|
-
pathPortAssignment.push({
|
|
903
|
-
port,
|
|
904
|
-
path,
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
if (peer) {
|
|
908
|
-
// logger.info('', { host, port: port + 1, path: '/peer' });
|
|
909
|
-
pathPortAssignment.push({
|
|
910
|
-
port: port + 1,
|
|
911
|
-
path: `${path === '/' ? '' : path}/peer`,
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
pathPortAssignmentData[host] = pathPortAssignment;
|
|
916
|
-
}
|
|
917
|
-
if (fs.existsSync(`./engine-private/replica`)) {
|
|
918
|
-
const singleReplicas = await fs.readdir(`./engine-private/replica`);
|
|
919
|
-
for (let replica of singleReplicas) {
|
|
920
|
-
if (replica.startsWith(deployId)) {
|
|
921
|
-
const replicaServerConf = loadConfServerJson(`./engine-private/replica/${replica}/conf.server.json`);
|
|
922
|
-
for (const host of Object.keys(replicaServerConf)) {
|
|
923
|
-
const pathPortAssignment = [];
|
|
924
|
-
for (const path of Object.keys(replicaServerConf[host])) {
|
|
925
|
-
const { peer } = replicaServerConf[host][path];
|
|
926
|
-
if (!router[`${host}${path === '/' ? '' : path}`]) continue;
|
|
927
|
-
const port = parseInt(router[`${host}${path === '/' ? '' : path}`].split(':')[2]);
|
|
928
|
-
// logger.info('', { host, port, path });
|
|
929
|
-
pathPortAssignment.push({
|
|
930
|
-
port,
|
|
931
|
-
path,
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
if (peer) {
|
|
935
|
-
// logger.info('', { host, port: port + 1, path: '/peer' });
|
|
936
|
-
pathPortAssignment.push({
|
|
937
|
-
port: port + 1,
|
|
938
|
-
path: `${path === '/' ? '' : path}/peer`,
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
pathPortAssignmentData[host] = pathPortAssignmentData[host].concat(pathPortAssignment);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
return pathPortAssignmentData;
|
|
948
|
-
};
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* @method deployRangePortFactory
|
|
952
|
-
* @description Creates the deploy range port factory.
|
|
953
|
-
* @param {object} router - The router.
|
|
954
|
-
* @returns {object} - The deploy range port factory.
|
|
955
|
-
* @memberof ServerConfBuilder
|
|
956
|
-
*/
|
|
957
|
-
const deployRangePortFactory = (router) => {
|
|
958
|
-
const ports = Object.values(router).map((p) => parseInt(p.split(':')[2]));
|
|
959
|
-
const fromPort = Math.min(...ports);
|
|
960
|
-
const toPort = Math.max(...ports);
|
|
961
|
-
return { ports, fromPort, toPort };
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* @method buildKindPorts
|
|
966
|
-
* @description Builds the kind ports.
|
|
967
|
-
* @param {number} from - The from port.
|
|
968
|
-
* @param {number} to - The to port.
|
|
969
|
-
* @returns {string} - The kind ports.
|
|
970
|
-
* @memberof ServerConfBuilder
|
|
971
|
-
*/
|
|
972
|
-
const buildKindPorts = (from, to) =>
|
|
973
|
-
range(parseInt(from), parseInt(to))
|
|
974
|
-
.map(
|
|
975
|
-
(port) => ` - name: 'tcp-${port}'
|
|
976
|
-
protocol: TCP
|
|
977
|
-
port: ${port}
|
|
978
|
-
targetPort: ${port}
|
|
979
|
-
- name: 'udp-${port}'
|
|
980
|
-
protocol: UDP
|
|
981
|
-
port: ${port}
|
|
982
|
-
targetPort: ${port}
|
|
983
|
-
`,
|
|
984
|
-
)
|
|
985
|
-
.join('\n');
|
|
986
|
-
|
|
987
|
-
/**
|
|
988
|
-
* @method buildPortProxyRouter
|
|
989
|
-
* @description Builds the port proxy router.
|
|
990
|
-
* @param {object} options - The options.
|
|
991
|
-
* @param {number} [options.port=4000] - The port.
|
|
992
|
-
* @param {object} options.proxyRouter - The proxy router.
|
|
993
|
-
* @param {object} [options.hosts] - The hosts.
|
|
994
|
-
* @param {boolean} [options.orderByPathLength=false] - Whether to order by path length.
|
|
995
|
-
* @param {boolean} [options.devProxyContext=false] - Whether to use dev proxy context.
|
|
996
|
-
* @returns {object} - The port proxy router.
|
|
997
|
-
* @memberof ServerConfBuilder
|
|
998
|
-
*/
|
|
999
|
-
const buildPortProxyRouter = (
|
|
1000
|
-
options = { port: 4000, proxyRouter, hosts, orderByPathLength: false, devProxyContext: false },
|
|
1001
|
-
) => {
|
|
1002
|
-
let { port, proxyRouter, hosts, orderByPathLength } = options;
|
|
1003
|
-
hosts = hosts || proxyRouter[port] || {};
|
|
1004
|
-
|
|
1005
|
-
const router = {};
|
|
1006
|
-
// build router
|
|
1007
|
-
Object.keys(hosts).map((hostKey) => {
|
|
1008
|
-
let { host, path, target, proxy, peer } = hosts[hostKey];
|
|
1009
|
-
|
|
1010
|
-
if (!proxy.includes(port)) {
|
|
1011
|
-
logger.warn('Proxy port not set on conf', { port, host, path, proxy, target });
|
|
1012
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1013
|
-
logger.warn('Omitting host', { host, path, target });
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
// ${process.env.NODE_ENV === 'development' && !isDevProxyContext() ? `:${port}` : ''}
|
|
1018
|
-
const absoluteHost = [80, 443].includes(port)
|
|
1019
|
-
? `${host}${path === '/' ? '' : path}`
|
|
1020
|
-
: `${host}:${port}${path === '/' ? '' : path}`;
|
|
1021
|
-
|
|
1022
|
-
if (absoluteHost in router)
|
|
1023
|
-
logger.warn('Overwrite: Absolute host already exists on router', { absoluteHost, target });
|
|
1024
|
-
|
|
1025
|
-
if (options.devProxyContext === true) {
|
|
1026
|
-
const appDevPort = parseInt(target.split(':')[2]) - process.env.DEV_PROXY_PORT_OFFSET;
|
|
1027
|
-
router[absoluteHost] = `http://localhost:${appDevPort}`;
|
|
1028
|
-
} else router[absoluteHost] = target;
|
|
1029
|
-
}); // order router
|
|
1030
|
-
|
|
1031
|
-
if (Object.keys(router).length === 0) return router;
|
|
1032
|
-
|
|
1033
|
-
const devApiConfPath = `./engine-private/conf/${process.argv[3]}/conf.server.dev.${process.argv[4]}-dev-api.json`;
|
|
1034
|
-
if (options.devProxyContext === true && process.env.NODE_ENV === 'development' && fs.existsSync(devApiConfPath)) {
|
|
1035
|
-
const confDevApiServer = JSON.parse(fs.readFileSync(devApiConfPath, 'utf8'));
|
|
1036
|
-
let devApiHosts = [];
|
|
1037
|
-
let origins = [];
|
|
1038
|
-
for (const _host of Object.keys(confDevApiServer))
|
|
1039
|
-
for (const _path of Object.keys(confDevApiServer[_host])) {
|
|
1040
|
-
if (confDevApiServer[_host][_path].origins && confDevApiServer[_host][_path].origins.length) {
|
|
1041
|
-
origins.push(...confDevApiServer[_host][_path].origins);
|
|
1042
|
-
if (_path !== 'peer' && devApiHosts.length === 0)
|
|
1043
|
-
devApiHosts.push(
|
|
1044
|
-
`${_host}${[80, 443].includes(port) && isDevProxyContext() ? '' : `:${port}`}${_path == '/' ? '' : _path}`,
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
origins = Array.from(new Set(origins));
|
|
1049
|
-
console.log({
|
|
1050
|
-
origins,
|
|
1051
|
-
devApiHosts,
|
|
1052
|
-
});
|
|
1053
|
-
for (const devApiHost of devApiHosts) {
|
|
1054
|
-
if (devApiHost in router) {
|
|
1055
|
-
const target = router[devApiHost];
|
|
1056
|
-
delete router[devApiHost];
|
|
1057
|
-
router[`${devApiHost}/${process.env.BASE_API}`] = target;
|
|
1058
|
-
router[`${devApiHost}/socket.io`] = target;
|
|
1059
|
-
for (const origin of origins) router[devApiHost] = origin;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
if (orderByPathLength === true) {
|
|
1065
|
-
const reOrderRouter = {};
|
|
1066
|
-
for (const absoluteHostKey of orderArrayFromAttrInt(Object.keys(router), 'length'))
|
|
1067
|
-
reOrderRouter[absoluteHostKey] = router[absoluteHostKey];
|
|
1068
|
-
return reOrderRouter;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
return router;
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
1014
|
/**
|
|
1075
1015
|
* @method buildReplicaId
|
|
1076
1016
|
* @description Builds the replica ID.
|
|
@@ -1098,17 +1038,7 @@ const getDataDeploy = async (
|
|
|
1098
1038
|
disableSyncEnvPort: false,
|
|
1099
1039
|
},
|
|
1100
1040
|
) => {
|
|
1101
|
-
let dataDeploy =
|
|
1102
|
-
.readFileSync(`./engine-private/deploy/dd.router`, 'utf8')
|
|
1103
|
-
.split(',')
|
|
1104
|
-
.map((deployId) => deployId.trim())
|
|
1105
|
-
.filter((deployId) => deployId);
|
|
1106
|
-
|
|
1107
|
-
dataDeploy = dataDeploy.map((deployId) => {
|
|
1108
|
-
return {
|
|
1109
|
-
deployId,
|
|
1110
|
-
};
|
|
1111
|
-
});
|
|
1041
|
+
let dataDeploy = readDeployRoutes().map((deployId) => ({ deployId }));
|
|
1112
1042
|
|
|
1113
1043
|
if (options && options.buildSingleReplica && fs.existsSync(`./engine-private/replica`))
|
|
1114
1044
|
fs.removeSync(`./engine-private/replica`);
|
|
@@ -1125,7 +1055,7 @@ const getDataDeploy = async (
|
|
|
1125
1055
|
for (const path of Object.keys(serverConf[host])) {
|
|
1126
1056
|
if (!isReplicaDeploy && serverConf[host][path].replicas && serverConf[host][path].singleReplica) {
|
|
1127
1057
|
if (options && options.buildSingleReplica)
|
|
1128
|
-
await Underpost.
|
|
1058
|
+
await Underpost.client.callback(deployObj.deployId, '', host, path, {
|
|
1129
1059
|
singleReplica: true,
|
|
1130
1060
|
});
|
|
1131
1061
|
replicaDataDeploy = replicaDataDeploy.concat(
|
|
@@ -1143,7 +1073,7 @@ const getDataDeploy = async (
|
|
|
1143
1073
|
}
|
|
1144
1074
|
|
|
1145
1075
|
if (!options.disableSyncEnvPort && options.buildSingleReplica)
|
|
1146
|
-
await Underpost.
|
|
1076
|
+
await Underpost.client.callback(undefined, '', '', '', { syncEnvPort: true });
|
|
1147
1077
|
|
|
1148
1078
|
logger.info('Deployments configured', buildDataDeploy);
|
|
1149
1079
|
|
|
@@ -1168,7 +1098,7 @@ const validateTemplatePath = (absolutePath = '') => {
|
|
|
1168
1098
|
const clients = DefaultConf.client.default.services;
|
|
1169
1099
|
|
|
1170
1100
|
if (
|
|
1171
|
-
absolutePath.match('src/api') &&
|
|
1101
|
+
absolutePath.match('src/api/') &&
|
|
1172
1102
|
!absolutePath.match('src/api/types.js') &&
|
|
1173
1103
|
!confServer.apis.find((p) => absolutePath.match(`src/api/${p}/`))
|
|
1174
1104
|
) {
|
|
@@ -1243,11 +1173,11 @@ const validateTemplatePath = (absolutePath = '') => {
|
|
|
1243
1173
|
* @memberof ServerConfBuilder
|
|
1244
1174
|
*/
|
|
1245
1175
|
const awaitDeployMonitor = async (isFinal = false, deltaMs = 1000, callback = false) => {
|
|
1246
|
-
if (!callback)
|
|
1176
|
+
if (!callback) latchAwaitDeploy();
|
|
1247
1177
|
if (isFinal) logger.info('Final deployment running (no replica)');
|
|
1248
1178
|
await timer(deltaMs);
|
|
1249
|
-
if (
|
|
1250
|
-
if (
|
|
1179
|
+
if (getRuntimeStatus() === RUNTIME_STATUS.ERROR) return false;
|
|
1180
|
+
if (isAwaitingDeploy()) return await awaitDeployMonitor(false, deltaMs, true);
|
|
1251
1181
|
return true;
|
|
1252
1182
|
};
|
|
1253
1183
|
|
|
@@ -1564,35 +1494,49 @@ const getInstanceContext = async (options = { deployId, singleReplica, replicas,
|
|
|
1564
1494
|
* @param {string} options.host - The host.
|
|
1565
1495
|
* @param {string} options.path - The path.
|
|
1566
1496
|
* @param {string} options.origin - The origin.
|
|
1567
|
-
* @returns {
|
|
1497
|
+
* @returns {Promise<string|undefined>} The derived sub-conf id (`<subConf>-dev-api`), or
|
|
1498
|
+
* undefined when no client origin was given and nothing was derived.
|
|
1568
1499
|
* @memberof ServerConfBuilder
|
|
1569
1500
|
*/
|
|
1570
1501
|
const buildApiConf = async (options = { deployId: '', subConf: '', host: '', path: '', origin: '' }) => {
|
|
1571
1502
|
let { deployId, subConf, host, path, origin } = options;
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
if (process.argv[4]) host = process.argv[4].trim();
|
|
1575
|
-
if (process.argv[5]) path = process.argv[5].trim();
|
|
1503
|
+
// The client origin is what selects this mode, so it is resolved before
|
|
1504
|
+
// anything else: `src/api.js` also runs without any of these positionals.
|
|
1576
1505
|
if (process.argv[6])
|
|
1577
1506
|
origin = `${process.env.NODE_ENV === 'production' ? 'https' : 'http'}://${process.argv[6].trim()}`;
|
|
1578
|
-
|
|
1579
|
-
if (!
|
|
1507
|
+
if (!origin) return undefined;
|
|
1508
|
+
if (!deployId) deployId = process.argv[2]?.trim();
|
|
1509
|
+
if (!subConf) subConf = process.argv[3]?.trim();
|
|
1510
|
+
if (process.argv[4]) host = process.argv[4].trim();
|
|
1511
|
+
if (process.argv[5]) path = process.argv[5].trim();
|
|
1512
|
+
if (!deployId || !subConf || !host || !path) {
|
|
1513
|
+
logger.warn('Skipping api conf; a client origin needs deploy-id, sub-conf, host and path', {
|
|
1514
|
+
deployId,
|
|
1515
|
+
subConf,
|
|
1516
|
+
host,
|
|
1517
|
+
path,
|
|
1518
|
+
origin,
|
|
1519
|
+
});
|
|
1520
|
+
return undefined;
|
|
1521
|
+
}
|
|
1580
1522
|
const confServer = JSON.parse(
|
|
1581
1523
|
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`, 'utf8'),
|
|
1582
1524
|
);
|
|
1583
1525
|
const envObj = dotenv.parse(
|
|
1584
1526
|
fs.readFileSync(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}`, 'utf8'),
|
|
1585
1527
|
);
|
|
1586
|
-
if (host
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1528
|
+
if (!confServer[host]?.[path])
|
|
1529
|
+
throw new Error(`No ${host}${path} instance in conf.server.dev.${subConf}.json for ${deployId}`);
|
|
1530
|
+
confServer[host][path].origins = [origin];
|
|
1531
|
+
logger.info('Build api conf', { host, path, origin });
|
|
1532
|
+
const devApiSubConf = `${subConf}-dev-api`;
|
|
1533
|
+
writeEnv(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${devApiSubConf}`, envObj);
|
|
1591
1534
|
fs.writeFileSync(
|
|
1592
|
-
`./engine-private/conf/${deployId}/conf.server.dev.${
|
|
1535
|
+
`./engine-private/conf/${deployId}/conf.server.dev.${devApiSubConf}.json`,
|
|
1593
1536
|
JSON.stringify(confServer, null, 4),
|
|
1594
1537
|
'utf8',
|
|
1595
1538
|
);
|
|
1539
|
+
return devApiSubConf;
|
|
1596
1540
|
};
|
|
1597
1541
|
|
|
1598
1542
|
/**
|
|
@@ -1616,12 +1560,19 @@ const buildClientStaticConf = async (
|
|
|
1616
1560
|
if (!subConf) subConf = process.argv[3].trim();
|
|
1617
1561
|
if (!host) host = process.argv[4].trim();
|
|
1618
1562
|
if (!path) path = process.argv[5].trim();
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
)
|
|
1563
|
+
// The API server derives these two files, and it derives them only when it is
|
|
1564
|
+
// given the client origin. Their absence means that step has not run, which is
|
|
1565
|
+
// a setup order to state rather than an ENOENT to decode.
|
|
1566
|
+
const confPath = `./engine-private/conf/${deployId}/conf.server.dev.${subConf}-dev-api.json`;
|
|
1567
|
+
const envPath = `./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-api`;
|
|
1568
|
+
for (const source of [confPath, envPath])
|
|
1569
|
+
if (!fs.existsSync(source))
|
|
1570
|
+
throw new Error(
|
|
1571
|
+
`Missing ${source}. Start the API server first, with the client origin as its last argument: ` +
|
|
1572
|
+
`npm run dev:api ${deployId} ${subConf} ${host} ${path} <client-host:port>`,
|
|
1573
|
+
);
|
|
1574
|
+
const confServer = JSON.parse(fs.readFileSync(confPath, 'utf8'));
|
|
1575
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
1625
1576
|
envObj.PORT = parseInt(envObj.PORT);
|
|
1626
1577
|
const apiBaseHost = devProxy
|
|
1627
1578
|
? devProxyHostFactory({ host, tls: isTlsDevProxy() })
|
|
@@ -1640,16 +1591,6 @@ const buildClientStaticConf = async (
|
|
|
1640
1591
|
);
|
|
1641
1592
|
};
|
|
1642
1593
|
|
|
1643
|
-
/**
|
|
1644
|
-
* @method isDeployRunnerContext
|
|
1645
|
-
* @description Checks if the deploy runner context is valid.
|
|
1646
|
-
* @param {string} path - The path.
|
|
1647
|
-
* @param {object} options - The options.
|
|
1648
|
-
* @returns {boolean} - The deploy runner context.
|
|
1649
|
-
* @memberof ServerConfBuilder
|
|
1650
|
-
*/
|
|
1651
|
-
const isDeployRunnerContext = (path, options) => !options.build && path && path !== 'template-deploy';
|
|
1652
|
-
|
|
1653
1594
|
/**
|
|
1654
1595
|
* @method isDevProxyContext
|
|
1655
1596
|
* @description Checks if the dev proxy context is valid.
|
|
@@ -1860,7 +1801,7 @@ const dispatchBuildInstanceEnv = ({
|
|
|
1860
1801
|
const loadProjectInstanceEnvBuilder = async (deployId) => {
|
|
1861
1802
|
const match = /^dd-([a-z0-9][a-z0-9-]*)$/.exec(`${deployId || ''}`);
|
|
1862
1803
|
if (!match) return null;
|
|
1863
|
-
const moduleUrl = new URL(
|
|
1804
|
+
const moduleUrl = new URL(`../../projects/${match[1]}/instance-data.js`, import.meta.url);
|
|
1864
1805
|
if (!fs.existsSync(moduleUrl)) return null;
|
|
1865
1806
|
const projectModule = await import(moduleUrl.href);
|
|
1866
1807
|
if (projectModule.buildInstanceEnv === undefined) return null;
|
|
@@ -2382,6 +2323,56 @@ const curlStatusChainFactory = (raw = '') => {
|
|
|
2382
2323
|
return chain;
|
|
2383
2324
|
};
|
|
2384
2325
|
|
|
2326
|
+
/**
|
|
2327
|
+
* @method publicIngressProbeFactory
|
|
2328
|
+
* @description Probes one public URL the way the traffic report does.
|
|
2329
|
+
*
|
|
2330
|
+
* `-L` follows the real redirect chain, `-v` supplies one response line per hop,
|
|
2331
|
+
* `-i` keeps full response headers available, and `-s` removes only the progress
|
|
2332
|
+
* meter. The body is discarded so a report stays bounded even when a host
|
|
2333
|
+
* returns a large application page. One implementation, because the traffic
|
|
2334
|
+
* table and the ingress event have to agree on what "reachable" means.
|
|
2335
|
+
* @param {string} url - Absolute URL to probe.
|
|
2336
|
+
* @returns {Array<string>} Ordered response codes; `['000']` when nothing answered.
|
|
2337
|
+
* @memberof ServerConfBuilder
|
|
2338
|
+
*/
|
|
2339
|
+
const publicIngressProbeFactory = (url) => {
|
|
2340
|
+
if (!url) return ['000'];
|
|
2341
|
+
const argument = shellArgumentFactory(url);
|
|
2342
|
+
return curlStatusChainFactory(
|
|
2343
|
+
shellExec(
|
|
2344
|
+
`curl -L -v -i -s --connect-timeout 3 --max-time 12 --max-redirs 10 ` +
|
|
2345
|
+
`-o /dev/null -w '\nUNDERPOST_CURL_FINAL=%{http_code}\n' ${argument} 2>&1 || true`,
|
|
2346
|
+
{ stdout: true, silent: true, silentOnError: true, disableLog: true },
|
|
2347
|
+
),
|
|
2348
|
+
);
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* @method publicIngressUrlsFactory
|
|
2353
|
+
* @description Every public URL a deploy list publishes, from the same conf the
|
|
2354
|
+
* traffic report reads.
|
|
2355
|
+
* @param {string[]} [deployList] - Deploy ids.
|
|
2356
|
+
* @param {string} [env='production'] - Environment whose routes are published.
|
|
2357
|
+
* @returns {Array<{host: string, path: string, url: string}>} Distinct routable URLs.
|
|
2358
|
+
* @memberof ServerConfBuilder
|
|
2359
|
+
*/
|
|
2360
|
+
const publicIngressUrlsFactory = (deployList = [], env = 'production') => {
|
|
2361
|
+
const urls = new Map();
|
|
2362
|
+
for (const deployId of deployList)
|
|
2363
|
+
for (const entry of deployTrafficEntriesFactory({ deployId, env }))
|
|
2364
|
+
for (const routePath of `${entry.path || '/'}`.split(' ').filter(Boolean)) {
|
|
2365
|
+
const path = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
2366
|
+
try {
|
|
2367
|
+
const url = new URL(path, `https://${entry.host}`).href;
|
|
2368
|
+
if (!urls.has(url)) urls.set(url, { host: entry.host, path, url });
|
|
2369
|
+
} catch {
|
|
2370
|
+
/* a host the conf cannot turn into a URL is not routable */
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return [...urls.values()];
|
|
2374
|
+
};
|
|
2375
|
+
|
|
2385
2376
|
/**
|
|
2386
2377
|
* @method trafficTableRowsFactory
|
|
2387
2378
|
* @description Resolves the live colour of each routable deployment, optionally
|
|
@@ -2464,6 +2455,25 @@ const hostRenderInstancesFactory = ({ declared = [], preserved = [], isDeployed
|
|
|
2464
2455
|
const isTrafficServingFactory = ({ liveTraffic = '', hasReadyEndpoints = () => false }) =>
|
|
2465
2456
|
!!liveTraffic && hasReadyEndpoints(liveTraffic);
|
|
2466
2457
|
|
|
2458
|
+
/**
|
|
2459
|
+
* @method redeployPlanFactory
|
|
2460
|
+
* @description The colour a redeploy targets, and how that colour is brought up.
|
|
2461
|
+
*
|
|
2462
|
+
* The inactive colour has no Deployment for most of a blue/green cycle: `run sync`
|
|
2463
|
+
* deletes it before publishing the maintenance route, and a promote leaves only
|
|
2464
|
+
* the colour it routed to. So a `rollout restart` of the target is a NotFound
|
|
2465
|
+
* error on exactly the state blue/green is meant to be in, and its absence has to
|
|
2466
|
+
* select building that colour instead of failing the deploy.
|
|
2467
|
+
* @param {string} [liveTraffic] - Colour currently routed, or empty when none is.
|
|
2468
|
+
* @param {Function} hasDeployment - `(colour) => boolean`.
|
|
2469
|
+
* @returns {{targetTraffic: string, create: boolean}} Colour to deploy, and whether it must be created rather than restarted.
|
|
2470
|
+
* @memberof ServerConfBuilder
|
|
2471
|
+
*/
|
|
2472
|
+
const redeployPlanFactory = ({ liveTraffic = '', hasDeployment = () => false }) => {
|
|
2473
|
+
const targetTraffic = nextTrafficFactory(liveTraffic);
|
|
2474
|
+
return { targetTraffic, create: !hasDeployment(targetTraffic) };
|
|
2475
|
+
};
|
|
2476
|
+
|
|
2467
2477
|
/**
|
|
2468
2478
|
* @method instanceTrafficPlanFactory
|
|
2469
2479
|
* @description Resolves, for each instance, the colour routed now and the colour
|
|
@@ -2931,39 +2941,14 @@ ${renderHosts}`,
|
|
|
2931
2941
|
return { renderHosts, changed: true };
|
|
2932
2942
|
};
|
|
2933
2943
|
|
|
2934
|
-
/**
|
|
2935
|
-
* Resolves the concrete deploy ids a build or conf-sync run should iterate over.
|
|
2936
|
-
*
|
|
2937
|
-
* The meta deploy id `dd` fans out to the comma separated ids declared in
|
|
2938
|
-
* `engine-private/deploy/dd.router`; when that file is absent (e.g. the private
|
|
2939
|
-
* repository is not checked out) it falls back to {@link ServerConfBuilder.DEFAULT_DEPLOY_ID}.
|
|
2940
|
-
* Any other value is parsed as a comma separated list.
|
|
2941
|
-
* Entries are trimmed and empties dropped.
|
|
2942
|
-
*
|
|
2943
|
-
* @method resolveDeployList
|
|
2944
|
-
* @param {string} deployId - A deploy id, a comma separated list, or the `dd` meta id.
|
|
2945
|
-
* @returns {string[]} Ordered list of concrete deploy ids.
|
|
2946
|
-
* @memberof ServerConfBuilder
|
|
2947
|
-
*/
|
|
2948
|
-
const resolveDeployList = (deployId) =>
|
|
2949
|
-
(deployId === 'dd'
|
|
2950
|
-
? fs.existsSync('./engine-private/deploy/dd.router')
|
|
2951
|
-
? fs.readFileSync('./engine-private/deploy/dd.router', 'utf8')
|
|
2952
|
-
: DEFAULT_DEPLOY_ID
|
|
2953
|
-
: deployId
|
|
2954
|
-
)
|
|
2955
|
-
.split(',')
|
|
2956
|
-
.map((id) => id.trim())
|
|
2957
|
-
.filter(Boolean);
|
|
2958
|
-
|
|
2959
2944
|
/**
|
|
2960
2945
|
* Syncs a single deploy id's private configuration into its dedicated
|
|
2961
2946
|
* `engine-<suffix>-private` repository and pushes the result.
|
|
2962
2947
|
*
|
|
2963
2948
|
* Idempotent and safe to rerun: the private repo is cloned when missing or reset to a clean
|
|
2964
|
-
* checkout when present, then the deploy id's `conf` folder
|
|
2965
|
-
* `
|
|
2966
|
-
* commit/push step is a no-op when nothing changed (`silentOnError`).
|
|
2949
|
+
* checkout when present, then the deploy id's `conf` folder (its `.env.<env>.oci` runtime
|
|
2950
|
+
* overlays included), matching `replica` entries, and any caller-supplied `extraPaths` payloads
|
|
2951
|
+
* are mirrored. The commit/push step is a no-op when nothing changed (`silentOnError`).
|
|
2967
2952
|
*
|
|
2968
2953
|
* @method syncPrivateConf
|
|
2969
2954
|
* @param {string} deployId - A concrete deploy id (e.g. `dd-cyberia`), not the `dd` meta id.
|
|
@@ -2973,10 +2958,10 @@ const resolveDeployList = (deployId) =>
|
|
|
2973
2958
|
* @memberof ServerConfBuilder
|
|
2974
2959
|
*/
|
|
2975
2960
|
const syncPrivateConf = (deployId, extraPaths = []) => {
|
|
2976
|
-
const
|
|
2977
|
-
const privateRepoName = `engine-${suffix}-private`;
|
|
2961
|
+
const privateRepoName = Underpost.repo.privateRepoFactory(deployId);
|
|
2978
2962
|
const privateGitUri = `${process.env.GITHUB_USERNAME}/${privateRepoName}`;
|
|
2979
2963
|
const privateRepoPath = `../${privateRepoName}`;
|
|
2964
|
+
const cronDeployId = cronDeployIdResolve();
|
|
2980
2965
|
|
|
2981
2966
|
if (!fs.existsSync(privateRepoPath)) {
|
|
2982
2967
|
shellExec(`cd .. && underpost clone ${privateGitUri}`, { silent: true });
|
|
@@ -2991,14 +2976,16 @@ const syncPrivateConf = (deployId, extraPaths = []) => {
|
|
|
2991
2976
|
fs.removeSync(confDest);
|
|
2992
2977
|
fs.mkdirSync(confDest, { recursive: true });
|
|
2993
2978
|
fs.copySync(`./engine-private/conf/${deployId}`, confDest);
|
|
2994
|
-
|
|
2979
|
+
// A checkout that declares no cron deploy has no `conf/<cron-id>` to mirror. Without this
|
|
2980
|
+
// the path resolves to `conf/null` and the whole sync fails over an absent optional.
|
|
2981
|
+
if (cronDeployId) fs.copySync(`./engine-private/conf/${cronDeployId}`, `${privateRepoPath}/conf/${cronDeployId}`);
|
|
2982
|
+
fs.copySync(`./engine-private/deploy`, `${privateRepoPath}/deploy`);
|
|
2995
2983
|
fs.removeSync(`${privateRepoPath}/replica`);
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
for (const entry of fs.readdirSync(
|
|
3000
|
-
if (entry.match(deployId)) fs.copySync(`${
|
|
3001
|
-
}
|
|
2984
|
+
fs.removeSync(`${privateRepoPath}/logs`);
|
|
2985
|
+
const replicaSrcDir = `./engine-private/replica`;
|
|
2986
|
+
if (fs.existsSync(replicaSrcDir))
|
|
2987
|
+
for (const entry of fs.readdirSync(replicaSrcDir))
|
|
2988
|
+
if (entry.match(deployId)) fs.copySync(`${replicaSrcDir}/${entry}`, `${privateRepoPath}/replica/${entry}`);
|
|
3002
2989
|
|
|
3003
2990
|
for (const extraPath of extraPaths) fs.copySync(`./engine-private/${extraPath}`, `${privateRepoPath}/${extraPath}`);
|
|
3004
2991
|
|
|
@@ -3031,31 +3018,111 @@ const syncDeployIdSources = (sourceMoves = []) => {
|
|
|
3031
3018
|
return true;
|
|
3032
3019
|
};
|
|
3033
3020
|
|
|
3021
|
+
const TEMPLATE_REPOSITORY_NAME = 'pwa-microservices-template';
|
|
3022
|
+
|
|
3023
|
+
/**
|
|
3024
|
+
* @method gitOriginRepositoryName
|
|
3025
|
+
* @description Repository name a checkout's `origin` points at, without the `.git`
|
|
3026
|
+
* suffix, or `null` when the path is not a work tree or declares no origin.
|
|
3027
|
+
* @param {string} repoPath - Local repository path.
|
|
3028
|
+
* @returns {string|null}
|
|
3029
|
+
* @memberof ServerConfBuilder
|
|
3030
|
+
*/
|
|
3031
|
+
const gitOriginRepositoryName = (repoPath) => {
|
|
3032
|
+
if (!fs.existsSync(`${repoPath}/.git`)) return null;
|
|
3033
|
+
const url = shellExec(`cd ${repoPath} && git config --get remote.origin.url`, {
|
|
3034
|
+
stdout: true,
|
|
3035
|
+
silent: true,
|
|
3036
|
+
disableLog: true,
|
|
3037
|
+
silentOnError: true,
|
|
3038
|
+
});
|
|
3039
|
+
const name = typeof url === 'string' ? url.trim().split('/').pop() : '';
|
|
3040
|
+
return name ? name.replace(/\.git$/, '') : null;
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
/**
|
|
3044
|
+
* @method ensureTemplateCheckout
|
|
3045
|
+
* @description Guarantees `toPath` is a checkout of the template repository, cloning it
|
|
3046
|
+
* when it is missing or belongs to another repository.
|
|
3047
|
+
*
|
|
3048
|
+
* Publishing steps swap the checkout's `.git` for a product repository's history, so an
|
|
3049
|
+
* assembly that trusted whatever `.git` it found would read another product's identity
|
|
3050
|
+
* back into the base template `package.json`.
|
|
3051
|
+
*
|
|
3052
|
+
* @param {object} params
|
|
3053
|
+
* @param {string} params.toPath - Template checkout path.
|
|
3054
|
+
* @param {string} [params.githubUsername] - Owner of the template repository.
|
|
3055
|
+
* @param {boolean} [params.noClone=false] - Fail instead of cloning a missing/foreign checkout.
|
|
3056
|
+
* @returns {boolean} `true` when the checkout was restored.
|
|
3057
|
+
* @memberof ServerConfBuilder
|
|
3058
|
+
*/
|
|
3059
|
+
const ensureTemplateCheckout = ({ toPath, githubUsername, noClone = false }) => {
|
|
3060
|
+
const owner = githubUsername || 'underpostnet';
|
|
3061
|
+
const repository = `${owner}/${TEMPLATE_REPOSITORY_NAME}`;
|
|
3062
|
+
if (gitOriginRepositoryName(toPath) === TEMPLATE_REPOSITORY_NAME) return false;
|
|
3063
|
+
if (noClone) throw new Error(`buildTemplate: ${toPath} is not a ${repository} checkout (--no-clone)`);
|
|
3064
|
+
logger.warn('Restoring template checkout', { toPath, repository });
|
|
3065
|
+
|
|
3066
|
+
// Swap in the clone only once it is complete, so a failed one leaves the checkout as is.
|
|
3067
|
+
const templatePath = dir.resolve(toPath);
|
|
3068
|
+
const stagePath = fs.mkdtempSync(`${dir.dirname(templatePath)}/.${TEMPLATE_REPOSITORY_NAME}-`);
|
|
3069
|
+
try {
|
|
3070
|
+
shellExec(`cd ${stagePath} && node ${dir.resolve('./bin')} clone ${repository}`);
|
|
3071
|
+
const clonePath = `${stagePath}/${TEMPLATE_REPOSITORY_NAME}`;
|
|
3072
|
+
if (gitOriginRepositoryName(clonePath) !== TEMPLATE_REPOSITORY_NAME)
|
|
3073
|
+
throw new Error(`buildTemplate: could not clone ${repository}`);
|
|
3074
|
+
fs.removeSync(templatePath);
|
|
3075
|
+
fs.moveSync(clonePath, templatePath);
|
|
3076
|
+
} finally {
|
|
3077
|
+
fs.removeSync(stagePath);
|
|
3078
|
+
}
|
|
3079
|
+
return true;
|
|
3080
|
+
};
|
|
3081
|
+
|
|
3082
|
+
/**
|
|
3083
|
+
* @method pruneTemplateWorkTree
|
|
3084
|
+
* @description Empties the template checkout of everything a build reconstructs, so a
|
|
3085
|
+
* source renamed or deleted in the engine — or a client bundle belonging to another
|
|
3086
|
+
* deploy id — cannot survive from a previous run into the assembled output.
|
|
3087
|
+
* @param {string} toPath - Template checkout path.
|
|
3088
|
+
* @param {string[]} preservedEntries - Top level entries to keep (git history, installed modules).
|
|
3089
|
+
* @returns {void}
|
|
3090
|
+
* @memberof ServerConfBuilder
|
|
3091
|
+
*/
|
|
3092
|
+
const pruneTemplateWorkTree = (toPath, preservedEntries = []) => {
|
|
3093
|
+
for (const entry of fs.readdirSync(toPath)) {
|
|
3094
|
+
if (preservedEntries.includes(entry)) continue;
|
|
3095
|
+
fs.removeSync(`${toPath}/${entry}`);
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3034
3099
|
/**
|
|
3035
3100
|
* Rebuilds the standalone `pwa-microservices-template` from scratch out of the current
|
|
3036
3101
|
* engine source tree.
|
|
3037
3102
|
*
|
|
3038
|
-
* Clones the template repo next to the engine when
|
|
3039
|
-
*
|
|
3103
|
+
* Clones the template repo next to the engine when the checkout is missing or belongs to
|
|
3104
|
+
* another repository, empties it so the result cannot inherit anything from a previous run,
|
|
3105
|
+
* then syncs every engine-tracked file the template is allowed to carry
|
|
3040
3106
|
* ({@link validateTemplatePath}), strips engine-only + product modules, restores the template's
|
|
3041
3107
|
* own CI workflows + guest services, and rewrites `package.json` / `package-lock.json` / `README`
|
|
3042
3108
|
* so the result is a standalone, installable project. Throws on failure; callers own exit codes.
|
|
3043
3109
|
*
|
|
3044
|
-
* Product catalogs are read dynamically ({@link module:src/server/catalog} `loadProductCatalogs`),
|
|
3110
|
+
* Product catalogs are read dynamically ({@link module:src/server/build/catalog.js} `loadProductCatalogs`),
|
|
3045
3111
|
* so this stays decoupled from — and survives removal of — any product module.
|
|
3046
3112
|
*
|
|
3047
3113
|
* @method buildTemplate
|
|
3048
3114
|
* @param {object} [options]
|
|
3049
3115
|
* @param {string} [options.srcPath='./'] - Engine source root to sync from.
|
|
3050
3116
|
* @param {string} [options.toPath='../pwa-microservices-template'] - Template output path.
|
|
3117
|
+
* @param {boolean} [options.noClone=false] - Fail instead of cloning when the checkout is missing or foreign.
|
|
3051
3118
|
* @returns {Promise<void>}
|
|
3052
3119
|
* @memberof ServerConfBuilder
|
|
3053
3120
|
*/
|
|
3054
|
-
const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template' } = {}) => {
|
|
3121
|
+
const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-template', noClone = false } = {}) => {
|
|
3055
3122
|
const walk = (await import('ignore-walk')).default;
|
|
3056
|
-
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
3057
|
-
await import('
|
|
3058
|
-
const { loadProductCatalogs } = await import('
|
|
3123
|
+
const { TEMPLATE_RESTORE_PATHS, TEMPLATE_PRESERVED_ENTRIES, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION } =
|
|
3124
|
+
await import('../../projects/underpost/catalog-underpost.js');
|
|
3125
|
+
const { loadProductCatalogs } = await import('../build/catalog.js');
|
|
3059
3126
|
const githubUsername = process.env.GITHUB_USERNAME;
|
|
3060
3127
|
|
|
3061
3128
|
logger.info('Build template', { srcPath, toPath });
|
|
@@ -3068,10 +3135,9 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
|
|
|
3068
3135
|
)
|
|
3069
3136
|
).filter((p) => !p.startsWith('.git'));
|
|
3070
3137
|
|
|
3071
|
-
|
|
3072
|
-
shellExec(`cd .. && node engine/bin clone ${githubUsername}/pwa-microservices-template`);
|
|
3073
|
-
|
|
3138
|
+
ensureTemplateCheckout({ toPath, githubUsername, noClone });
|
|
3074
3139
|
shellExec(`cd ${toPath} && git config core.filemode false`);
|
|
3140
|
+
pruneTemplateWorkTree(toPath, TEMPLATE_PRESERVED_ENTRIES);
|
|
3075
3141
|
|
|
3076
3142
|
for (const copyPath of sourceFiles) {
|
|
3077
3143
|
if (copyPath === 'NaN') continue;
|
|
@@ -3180,7 +3246,8 @@ git add .`);
|
|
|
3180
3246
|
* `engine-<idPart>`). A pod started with `underpost start --build --private-test-repo`
|
|
3181
3247
|
* clones this repo, so work-in-progress engine source can be tested end to end
|
|
3182
3248
|
* without touching the production source. Mirrors {@link updatePrivateTemplateRepo}
|
|
3183
|
-
* but per-deploy-id and against the test repo
|
|
3249
|
+
* but per-deploy-id and against the test repo, publishing from its own work tree
|
|
3250
|
+
* (`<template parent>/engine-test-<idPart>`) so the template checkout keeps its identity.
|
|
3184
3251
|
*
|
|
3185
3252
|
* Assumes the deploy id template has already been assembled at the template path
|
|
3186
3253
|
* (run `node bin/build <deployId>` first, or use `node bin/build <deployId> --update-private`).
|
|
@@ -3190,39 +3257,53 @@ git add .`);
|
|
|
3190
3257
|
*/
|
|
3191
3258
|
const updatePrivateEngineTestRepo = async (deployId) => {
|
|
3192
3259
|
const username = process.env.GITHUB_USERNAME || 'underpostnet';
|
|
3193
|
-
const repoName =
|
|
3260
|
+
const repoName = Underpost.repo.engineRepoFactory(deployId, { test: true });
|
|
3194
3261
|
const templatePath = '/home/dd/pwa-microservices-template';
|
|
3195
3262
|
if (!fs.existsSync(templatePath))
|
|
3196
3263
|
throw new Error(`updatePrivateEngineTestRepo: assemble the template first (node bin/build ${deployId})`);
|
|
3197
3264
|
|
|
3198
|
-
// Detach the assembled working tree from any engine-build git history.
|
|
3199
|
-
shellExec(`sudo rm -rf ${templatePath}/.git`);
|
|
3200
|
-
|
|
3201
3265
|
// Adopt the test repo's existing history when present (so the push is a delta);
|
|
3202
3266
|
// otherwise publish a fresh history on first push.
|
|
3203
|
-
|
|
3267
|
+
const parentPath = dir.dirname(templatePath);
|
|
3268
|
+
shellExec(`cd ${parentPath} && sudo rm -rf ./${repoName}.git && underpost clone --bare ${username}/${repoName}`, {
|
|
3204
3269
|
silent: true,
|
|
3205
3270
|
disableLog: true,
|
|
3206
3271
|
silentOnError: true,
|
|
3207
3272
|
});
|
|
3208
|
-
|
|
3273
|
+
|
|
3274
|
+
// Publishing from a work tree of its own leaves the template checkout its own git
|
|
3275
|
+
// history, and copying that tree from 0 keeps a stale file out of the published one.
|
|
3276
|
+
const publishPath = `${parentPath}/${repoName}`;
|
|
3277
|
+
shellExec(`sudo rm -rf ${publishPath}`);
|
|
3278
|
+
fs.copySync(templatePath, publishPath, {
|
|
3279
|
+
filter: (src) => {
|
|
3280
|
+
const entries = dir.relative(templatePath, src).split(dir.sep);
|
|
3281
|
+
return !entries.includes('.git') && !entries.includes('node_modules');
|
|
3282
|
+
},
|
|
3283
|
+
});
|
|
3284
|
+
if (fs.existsSync(`${parentPath}/${repoName}.git`)) shellExec(`mv ${parentPath}/${repoName}.git ${publishPath}/.git`);
|
|
3209
3285
|
|
|
3210
3286
|
// `git init` converts the moved bare repo into a normal work-tree repo (bare
|
|
3211
3287
|
// clones have no work tree, so `git add` would fail), and bootstraps a fresh
|
|
3212
3288
|
// repo on first publish. Idempotent — mirrors updatePrivateTemplateRepo.
|
|
3213
|
-
shellExec(`cd ${
|
|
3289
|
+
shellExec(`cd ${publishPath}
|
|
3214
3290
|
git init
|
|
3215
3291
|
git config user.name '${username}'
|
|
3216
3292
|
git config user.email 'development@underpost.net'
|
|
3217
|
-
git add .`);
|
|
3293
|
+
git add -A .`);
|
|
3218
3294
|
|
|
3219
|
-
|
|
3295
|
+
// The bundled coverage report is a build output the artifact has to carry: force-added so a
|
|
3296
|
+
// tree-level ignore rule can never silently drop it from the published source.
|
|
3297
|
+
if (fs.existsSync(`${publishPath}/${COVERAGE_BUNDLE_DIRECTORY}`))
|
|
3298
|
+
shellExec(`cd ${publishPath} && git add -A -f ${COVERAGE_BUNDLE_DIRECTORY}`);
|
|
3299
|
+
|
|
3300
|
+
const hasChanges = shellExec(`node bin cmt ${publishPath} --has-changes`, {
|
|
3220
3301
|
stdout: true,
|
|
3221
3302
|
silent: true,
|
|
3222
3303
|
disableLog: true,
|
|
3223
3304
|
}).trim();
|
|
3224
3305
|
if (hasChanges === '1')
|
|
3225
|
-
shellExec(`cd ${
|
|
3306
|
+
shellExec(`cd ${publishPath} && git commit -m 'Update ${repoName}' && underpost push . ${username}/${repoName}`);
|
|
3226
3307
|
else logger.info('No changes to publish', { repoName });
|
|
3227
3308
|
};
|
|
3228
3309
|
|
|
@@ -3243,12 +3324,15 @@ git add .`);
|
|
|
3243
3324
|
* instance runs — `/etc/hosts` is written in one pass for every host the
|
|
3244
3325
|
* gateway will serve.
|
|
3245
3326
|
* @param {Array<string>} deployList - Deploy ids being brought up.
|
|
3246
|
-
* @param {string} [instanceList] -
|
|
3327
|
+
* @param {string} [instanceList] - Comma-separated instance/template ids, as `--instance-id` takes them.
|
|
3247
3328
|
* @returns {{ byDeployId: Object<string,{ids: Array<string>, hosts: Array<string>}>, unmatched: Array<string> }}
|
|
3248
3329
|
* Per-deploy selection, and the requested ids no deploy declares.
|
|
3249
3330
|
*/
|
|
3250
3331
|
const clusterInstancesFactory = (deployList = [], instanceList = '') => {
|
|
3251
|
-
const requested = `${instanceList || ''}
|
|
3332
|
+
const requested = `${instanceList || ''}`
|
|
3333
|
+
.split(',')
|
|
3334
|
+
.map((id) => id.trim())
|
|
3335
|
+
.filter(Boolean);
|
|
3252
3336
|
const byDeployId = Object.fromEntries(
|
|
3253
3337
|
deployList.map((deployId) => {
|
|
3254
3338
|
const confPath = `./engine-private/conf/${deployId}/conf.instances.json`;
|
|
@@ -3273,6 +3357,10 @@ const clusterInstancesFactory = (deployList = [], instanceList = '') => {
|
|
|
3273
3357
|
};
|
|
3274
3358
|
|
|
3275
3359
|
export {
|
|
3360
|
+
cleanDeployEnvFiles,
|
|
3361
|
+
deployEnvFilePath,
|
|
3362
|
+
deployEnvNamesFactory,
|
|
3363
|
+
instanceEnvFilePath,
|
|
3276
3364
|
Config,
|
|
3277
3365
|
loadConf,
|
|
3278
3366
|
loadConfInstances,
|
|
@@ -3292,25 +3380,19 @@ export {
|
|
|
3292
3380
|
addWsConf,
|
|
3293
3381
|
buildWsSrc,
|
|
3294
3382
|
cloneSrcComponents,
|
|
3295
|
-
buildProxyRouter,
|
|
3296
3383
|
getDataDeploy,
|
|
3297
3384
|
validateTemplatePath,
|
|
3298
3385
|
buildReplicaId,
|
|
3299
3386
|
mergeFile,
|
|
3300
3387
|
getPathsSSR,
|
|
3301
|
-
buildKindPorts,
|
|
3302
|
-
buildPortProxyRouter,
|
|
3303
3388
|
splitFileFactory,
|
|
3304
3389
|
generateSecurePassword,
|
|
3305
3390
|
resolveReplicaCount,
|
|
3306
|
-
pathPortAssignmentFactory,
|
|
3307
|
-
deployRangePortFactory,
|
|
3308
3391
|
awaitDeployMonitor,
|
|
3309
3392
|
buildCliDoc,
|
|
3310
3393
|
getInstanceContext,
|
|
3311
3394
|
buildApiConf,
|
|
3312
3395
|
buildClientStaticConf,
|
|
3313
|
-
isDeployRunnerContext,
|
|
3314
3396
|
isDevProxyContext,
|
|
3315
3397
|
devProxyHostFactory,
|
|
3316
3398
|
isTlsDevProxy,
|
|
@@ -3320,6 +3402,10 @@ export {
|
|
|
3320
3402
|
loadConfServerJson,
|
|
3321
3403
|
getConfFolder,
|
|
3322
3404
|
getConfFilePath,
|
|
3405
|
+
deployEnvContentFactory,
|
|
3406
|
+
deployOciEnvFilePath,
|
|
3407
|
+
ociEnvContentFactory,
|
|
3408
|
+
OCI_ENV_SUFFIX,
|
|
3323
3409
|
readConfJson,
|
|
3324
3410
|
DEFAULT_DEPLOY_ID,
|
|
3325
3411
|
clusterContextFactory,
|
|
@@ -3344,10 +3430,13 @@ export {
|
|
|
3344
3430
|
trafficProbePathsFactory,
|
|
3345
3431
|
hostIngressFactsFactory,
|
|
3346
3432
|
curlStatusChainFactory,
|
|
3433
|
+
publicIngressProbeFactory,
|
|
3434
|
+
publicIngressUrlsFactory,
|
|
3347
3435
|
hostRenderInstancesFactory,
|
|
3348
3436
|
instanceTrafficPlanFactory,
|
|
3349
3437
|
isTrafficServingFactory,
|
|
3350
3438
|
nextTrafficFactory,
|
|
3439
|
+
redeployPlanFactory,
|
|
3351
3440
|
schedulableNodeFactory,
|
|
3352
3441
|
stopPlanFactory,
|
|
3353
3442
|
trafficFromRoutingInfoFactory,
|
|
@@ -3355,9 +3444,11 @@ export {
|
|
|
3355
3444
|
resolveEnvScoped,
|
|
3356
3445
|
sortInstancesByPath,
|
|
3357
3446
|
waitForPort,
|
|
3358
|
-
resolveDeployList,
|
|
3359
3447
|
syncPrivateConf,
|
|
3360
3448
|
syncDeployIdSources,
|
|
3449
|
+
gitOriginRepositoryName,
|
|
3450
|
+
ensureTemplateCheckout,
|
|
3451
|
+
pruneTemplateWorkTree,
|
|
3361
3452
|
buildTemplate,
|
|
3362
3453
|
updatePrivateTemplateRepo,
|
|
3363
3454
|
updatePrivateEngineTestRepo,
|