@underpostnet/cyberia 3.3.0 → 3.3.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.github/workflows/coverall.cyberia.ci.yml +34 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +565 -2510
- package/CLI-HELP.md +306 -147
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +66 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +308 -91
- package/bin/deploy.js +18 -104
- package/bin/index.js +308 -91
- package/conf.js +91 -4
- package/deploy/cyberia-client/deploy.sh +47 -0
- package/deploy/cyberia-client/package.sh +18 -0
- package/deploy/cyberia-client/state.sh +14 -0
- package/deploy/cyberia-server/deploy.sh +47 -0
- package/deploy/cyberia-server/package.sh +18 -0
- package/deploy/cyberia-server/state.sh +14 -0
- package/deploy/dd-cyberia/deploy.sh +6 -26
- package/deploy/dd-cyberia/init.sh +35 -43
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +92 -170
- package/deploy/lib/github-actions-logging.sh +594 -0
- package/deploy/lib/host.sh +185 -0
- package/deploy/lib/state.sh +62 -0
- package/deploy/pwa-microservices-template/deploy.sh +12 -39
- package/deploy/release/deploy.sh +9 -39
- package/deployment.yaml +7 -2
- package/docker-compose.yml +7 -7
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +83 -10
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +1 -1
- package/src/api/cyberia-action/cyberia-action.service.js +2 -2
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +8 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +2 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +2 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +2 -2
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +0 -26
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +3 -3
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +391 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +184 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +153 -71
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs.js +3 -3
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +322 -142
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +558 -363
- package/src/cli/run.js +331 -650
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1111 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +258 -211
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -1
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +61 -46
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +38 -30
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +1 -1
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +53 -10
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +1 -1
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +1 -1
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +1 -1
- package/src/projects/cyberia/object-layer.js +1 -1
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +9 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
- package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +117 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +465 -0
- package/src/server/build/testing.js +528 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +3 -3
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +108 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +513 -422
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +1 -1
- package/src/server/{downloader.js → storage/downloader.js} +2 -2
- package/src/server/storage/repository.js +68 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +1 -1
- package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +609 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +334 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +979 -0
- package/test/unit/coverage-artifact.test.js +130 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +447 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/logger-redaction.test.js +112 -0
- package/test/unit/package.test.js +403 -0
- package/test/unit/prepare-host.test.js +299 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +193 -0
- package/vitest.config.js +58 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/environment.js +0 -98
package/src/cli/repository.js
CHANGED
|
@@ -6,23 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import dotenv from 'dotenv';
|
|
8
8
|
import { commitData } from '../client/components/core/CommonJs.js';
|
|
9
|
-
import { pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
10
|
-
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
9
|
+
import { pbcopy, shellArgumentFactory, shellCd, shellExec } from '../server/runtime/process.js';
|
|
10
|
+
import { actionInitLog, loggerFactory, redactSensitiveText } from '../server/ops/logger.js';
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
getConfFilePath,
|
|
18
|
-
loadReplicas,
|
|
19
|
-
loadConfServerJson,
|
|
20
|
-
getDataDeploy,
|
|
21
|
-
buildReplicaId,
|
|
22
|
-
readConfInstances,
|
|
23
|
-
} from '../server/conf.js';
|
|
24
|
-
import { getNpmRootPath, writeEnv } from '../server/environment.js';
|
|
25
|
-
import { buildClient, unzipClientBuild, mergeClientBuildZip } from '../client-builder/client-build.js';
|
|
13
|
+
import { Config, readConfJson, readConfInstances } from '../server/runtime/conf.js';
|
|
14
|
+
import { readDeployRoutes, registerDeployRoute } from '../server/network/router.js';
|
|
15
|
+
import { getUnderpostRootPath } from '../server/runtime/environment.js';
|
|
16
|
+
import { githubCommitUrlFactory, repositoryIdentityFactory } from '../server/storage/repository.js';
|
|
26
17
|
import { DefaultConf } from '../../conf.js';
|
|
27
18
|
import Underpost from '../index.js';
|
|
28
19
|
|
|
@@ -50,18 +41,25 @@ class UnderpostRepository {
|
|
|
50
41
|
clone(gitUri = `${process.env.GITHUB_USERNAME}/pwa-microservices-template`, options = { bare: false, g8: false }) {
|
|
51
42
|
const gExtension = options.g8 === true ? '.g8' : '.git';
|
|
52
43
|
const repoName = gitUri.split('/').pop();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
44
|
+
const auth = Underpost.repo.gitAuthFactory(`https://github.com/${gitUri}${gExtension}`);
|
|
45
|
+
// A bare clone lands in `<repo>.git`: clearing `<repo>` instead would both leave the
|
|
46
|
+
// real target in place and delete an unrelated work tree that happens to share the name.
|
|
47
|
+
const clonePath = `./${repoName}${options?.bare === true ? gExtension : ''}`;
|
|
48
|
+
if (fs.existsSync(clonePath)) fs.removeSync(clonePath);
|
|
49
|
+
shellExec(`git clone ${options?.bare === true ? ` --bare ` : ''}"${auth.url}"`, {
|
|
50
|
+
disableLog: true,
|
|
51
|
+
env: auth.env,
|
|
52
|
+
});
|
|
62
53
|
},
|
|
63
54
|
/**
|
|
64
55
|
* Pulls updates from a GitHub repository.
|
|
56
|
+
*
|
|
57
|
+
* `--ff-only` because the reconcile strategy must not come from the host: without it git
|
|
58
|
+
* refuses a divergent pull outright on a node that configured neither `pull.rebase` nor
|
|
59
|
+
* `pull.ff`, and silently merges or rebases on one that did — the same command producing a
|
|
60
|
+
* different history per machine. Advancing to the remote is what every caller wants; a
|
|
61
|
+
* checkout that has drifted onto commits of its own is replaced through
|
|
62
|
+
* {@link UnderpostRepository.switchRemote}, not reconciled here.
|
|
65
63
|
* @param {string} [repoPath='./'] - The local path to the repository.
|
|
66
64
|
* @param {string} [gitUri=`${process.env.GITHUB_USERNAME}/pwa-microservices-template`] - The URI of the GitHub repository.
|
|
67
65
|
* @param {object} [options={ g8: false }] - Pulling options.
|
|
@@ -74,14 +72,11 @@ class UnderpostRepository {
|
|
|
74
72
|
options = { g8: false },
|
|
75
73
|
) {
|
|
76
74
|
const gExtension = options.g8 === true ? '.g8' : '.git';
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
disableLog: true,
|
|
83
|
-
},
|
|
84
|
-
);
|
|
75
|
+
const auth = Underpost.repo.gitAuthFactory(`https://github.com/${gitUri}${gExtension}`);
|
|
76
|
+
shellExec(`cd ${repoPath} && git pull --ff-only "${auth.url}"`, {
|
|
77
|
+
disableLog: true,
|
|
78
|
+
env: auth.env,
|
|
79
|
+
});
|
|
85
80
|
},
|
|
86
81
|
/**
|
|
87
82
|
* Creates a Git commit with a conventional commit message.
|
|
@@ -103,7 +98,7 @@ class UnderpostRepository {
|
|
|
103
98
|
* @param {string} [options.hashes=''] - If provided with diff option, shows the diff between two hashes.
|
|
104
99
|
* @param {string} [options.extension=''] - If provided with diff option, filters the diff by this file extension.
|
|
105
100
|
* @param {boolean|string} [options.changelog=undefined] - If true, prints the changelog since the last CI integration commit (starting with 'ci(package-pwa-microservices-'). If a number string, prints the changelog of the last N commits split by version sections. Only considers commits starting with '[<tag>]'.
|
|
106
|
-
* @param {boolean} [options.changelogBuild=false] - If true, scrapes
|
|
101
|
+
* @param {boolean} [options.changelogBuild=false] - If true, scrapes git history and builds a CHANGELOG.md from the five latest versions. Commits containing 'New release v:' are used as version section titles. Only commits starting with '[<tag>]' are included as entries.
|
|
107
102
|
* @param {string} [options.changelogMinVersion=''] - If set, overrides the default minimum version limit (2.85.0) for --changelog-build.
|
|
108
103
|
* @param {boolean} [options.changelogNoHash=false] - If true, omits commit hashes from the changelog entries.
|
|
109
104
|
* @param {boolean} [options.remoteUrl=false] - If true, prints the current git remote URL (origin) in plain text and returns.
|
|
@@ -226,6 +221,19 @@ class UnderpostRepository {
|
|
|
226
221
|
return;
|
|
227
222
|
}
|
|
228
223
|
|
|
224
|
+
if (options.propagateMsg) {
|
|
225
|
+
// Chain hop: carry the payload the previous workflow sent instead of regenerating it, so
|
|
226
|
+
// every repository downstream of the dispatch commits the same entries.
|
|
227
|
+
const propagated = Underpost.repo.resolvePropagationMessage(process.env.PROPAGATED_MESSAGE);
|
|
228
|
+
if (propagated) {
|
|
229
|
+
console.log(propagated);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Nothing arrived — fall through to this repository's own changelog for the last N commits.
|
|
233
|
+
options.changelogMsg = true;
|
|
234
|
+
options.changelogNoHash = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
229
237
|
if (options.changelog !== undefined || options.changelogBuild || options.changelogMsg !== undefined) {
|
|
230
238
|
const releaseMatch = 'New release v:';
|
|
231
239
|
// Helper: parse [<tag>] commits into grouped sections
|
|
@@ -279,9 +287,7 @@ class UnderpostRepository {
|
|
|
279
287
|
.filter((c) => c.hash);
|
|
280
288
|
};
|
|
281
289
|
|
|
282
|
-
const
|
|
283
|
-
const commitUrl = (shortHash, fullHash) =>
|
|
284
|
-
`[${shortHash}](https://github.com/${githubUser}/engine/commit/${fullHash})`;
|
|
290
|
+
const commitUrl = (shortHash, hash) => githubCommitUrlFactory({ shortHash, hash });
|
|
285
291
|
|
|
286
292
|
// Helper: extract version from commit message containing 'New release v:'
|
|
287
293
|
const extractVersion = (message) => {
|
|
@@ -331,17 +337,19 @@ class UnderpostRepository {
|
|
|
331
337
|
const changelogMinVersion = options.changelogMinVersion || '2.97.1';
|
|
332
338
|
|
|
333
339
|
if (options.changelogBuild) {
|
|
334
|
-
// --changelog-build:
|
|
340
|
+
// --changelog-build: retain the current changes and the five latest version sections.
|
|
335
341
|
const allCommits = fetchHistory();
|
|
336
342
|
const sections = buildVersionSections(allCommits);
|
|
337
343
|
|
|
338
|
-
//
|
|
344
|
+
// Stop at either the fifth release section or the configured minimum-version boundary.
|
|
339
345
|
const limitedSections = [];
|
|
346
|
+
let versionCount = 0;
|
|
340
347
|
for (const section of sections) {
|
|
341
348
|
limitedSections.push(section);
|
|
342
349
|
if (section.title) {
|
|
350
|
+
versionCount++;
|
|
343
351
|
const versionStr = section.title.replace(releaseMatch, '').trim();
|
|
344
|
-
if (versionStr === changelogMinVersion) break;
|
|
352
|
+
if (versionCount === 5 || versionStr === changelogMinVersion) break;
|
|
345
353
|
}
|
|
346
354
|
}
|
|
347
355
|
|
|
@@ -441,7 +449,10 @@ class UnderpostRepository {
|
|
|
441
449
|
} ${message ? message : commitData[commitType].description}`;
|
|
442
450
|
if (options.copy) return pbcopy(_message);
|
|
443
451
|
shellExec(
|
|
444
|
-
`cd ${repoPath} && git commit ${options?.empty ? `--allow-empty ` : ''}${
|
|
452
|
+
`cd ${repoPath} && git commit ${options?.empty ? `--allow-empty ` : ''}${
|
|
453
|
+
// A propagated payload is multi-line and may hold `$`: quoting keeps every entry intact.
|
|
454
|
+
options.edit ? `--amend --no-edit ` : `-m ${shellArgumentFactory(_message)}`
|
|
455
|
+
}`,
|
|
445
456
|
);
|
|
446
457
|
},
|
|
447
458
|
|
|
@@ -474,14 +485,11 @@ class UnderpostRepository {
|
|
|
474
485
|
options = { f: false, g8: false },
|
|
475
486
|
) {
|
|
476
487
|
const gExtension = options.g8 === true ? '.g8' : '.git';
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
disableLog: true,
|
|
483
|
-
},
|
|
484
|
-
);
|
|
488
|
+
const auth = Underpost.repo.gitAuthFactory(`https://github.com/${gitUri}${gExtension}`);
|
|
489
|
+
shellExec(`cd ${repoPath} && git push "${auth.url}"${options?.f === true ? ' --force' : ''}`, {
|
|
490
|
+
disableLog: true,
|
|
491
|
+
env: auth.env,
|
|
492
|
+
});
|
|
485
493
|
logger.info(
|
|
486
494
|
'commit url',
|
|
487
495
|
`http://github.com/${gitUri}${gExtension === '.g8' ? '.g8' : ''}/commit/${shellExec(
|
|
@@ -493,13 +501,96 @@ class UnderpostRepository {
|
|
|
493
501
|
);
|
|
494
502
|
},
|
|
495
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Initializes the base cluster deploy folder `engine-private/deploy` from `./conf.js` (DefaultConf).
|
|
506
|
+
*
|
|
507
|
+
* Writes the minimum set required by the cluster runtime:
|
|
508
|
+
* `conf.event.json`, `conf.users.json`, `conf.wireguard.json`, `dd.cron`, `dd.routes`, `id_rsa`, `id_rsa.pub`.
|
|
509
|
+
*
|
|
510
|
+
* When a deploy ID is explicitly provided, it also generates the deploy CI/CD workflows and its default conf.
|
|
511
|
+
*
|
|
512
|
+
* Idempotent: existing files are never overwritten, so it is safe to rerun over a provisioned cluster.
|
|
513
|
+
* @param {string} [deployId=''] - Deploy ID registered in the route table and used as default cron deploy ID (defaults to `dd-default`, `dd-` prefix is normalized).
|
|
514
|
+
* @returns {{ deployPath: string, created: string[] }} The deploy folder and the files created by this run.
|
|
515
|
+
* @memberof UnderpostRepository
|
|
516
|
+
*/
|
|
517
|
+
clusterDeployFactory(deployId = '') {
|
|
518
|
+
const deployPath = './engine-private/deploy';
|
|
519
|
+
const created = [];
|
|
520
|
+
|
|
521
|
+
const hasDeployId = `${deployId || ''}`.trim().length > 0;
|
|
522
|
+
deployId = `${deployId || ''}`.trim() || 'dd-default';
|
|
523
|
+
if (!deployId.startsWith('dd-')) deployId = `dd-${deployId}`;
|
|
524
|
+
|
|
525
|
+
fs.mkdirSync(deployPath, { recursive: true });
|
|
526
|
+
|
|
527
|
+
const confFiles = {
|
|
528
|
+
'conf.event.json': DefaultConf.event,
|
|
529
|
+
'conf.users.json': DefaultConf.users,
|
|
530
|
+
'conf.wireguard.json': DefaultConf.wireguard,
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
for (const [fileName, conf] of Object.entries(confFiles)) {
|
|
534
|
+
const confPath = `${deployPath}/${fileName}`;
|
|
535
|
+
if (fs.existsSync(confPath)) continue;
|
|
536
|
+
fs.writeFileSync(confPath, JSON.stringify(conf, null, 4), 'utf8');
|
|
537
|
+
created.push(confPath);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const cronPath = `${deployPath}/dd.cron`;
|
|
541
|
+
if (!fs.existsSync(cronPath)) {
|
|
542
|
+
fs.writeFileSync(cronPath, deployId, 'utf8');
|
|
543
|
+
created.push(cronPath);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const routesPath = `${deployPath}/dd.routes`;
|
|
547
|
+
const routesExists = fs.existsSync(routesPath);
|
|
548
|
+
registerDeployRoute(deployId);
|
|
549
|
+
if (!routesExists) created.push(routesPath);
|
|
550
|
+
|
|
551
|
+
const keyPath = `${deployPath}/id_rsa`;
|
|
552
|
+
const pubKeyPath = `${deployPath}/id_rsa.pub`;
|
|
553
|
+
if (!fs.existsSync(keyPath)) {
|
|
554
|
+
shellExec(`ssh-keygen -t ed25519 -f ${keyPath} -N "" -q -C "root@${deployId}"`, { disableLog: true });
|
|
555
|
+
fs.chmodSync(keyPath, 0o600);
|
|
556
|
+
created.push(keyPath);
|
|
557
|
+
created.push(pubKeyPath);
|
|
558
|
+
} else if (!fs.existsSync(pubKeyPath)) {
|
|
559
|
+
shellExec(`ssh-keygen -y -f ${keyPath} -P "" > ${pubKeyPath}`, { disableLog: true });
|
|
560
|
+
created.push(pubKeyPath);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (hasDeployId) {
|
|
564
|
+
const repoName = `engine-${deployId.split('dd-')[1]}`;
|
|
565
|
+
fs.writeFileSync(
|
|
566
|
+
`./.github/workflows/${repoName}.cd.yml`,
|
|
567
|
+
fs
|
|
568
|
+
.readFileSync(`./.github/workflows/engine-test.cd.yml`, 'utf8')
|
|
569
|
+
.replaceAll('test', deployId.split('dd-')[1]),
|
|
570
|
+
'utf8',
|
|
571
|
+
);
|
|
572
|
+
fs.writeFileSync(
|
|
573
|
+
`./.github/workflows/${repoName}.ci.yml`,
|
|
574
|
+
fs
|
|
575
|
+
.readFileSync(`./.github/workflows/engine-test.ci.yml`, 'utf8')
|
|
576
|
+
.replaceAll('test', deployId.split('dd-')[1]),
|
|
577
|
+
'utf8',
|
|
578
|
+
);
|
|
579
|
+
shellExec(`node bin new --default-conf --deploy-id ${deployId}`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
logger.info('Cluster deploy base', { deployPath, created });
|
|
583
|
+
|
|
584
|
+
return { deployPath, created };
|
|
585
|
+
},
|
|
586
|
+
|
|
496
587
|
/**
|
|
497
588
|
* Initializes a new Underpost repository, optionally setting up a deploy ID or sub-configuration.
|
|
498
589
|
* @param {string} [projectName=''] - The name of the project to create.
|
|
499
590
|
* @param {object} [options] - Initialization options.
|
|
500
591
|
* @param {string} [options.deployId=''] - The deployment ID to set up.
|
|
501
592
|
* @param {string} [options.subConf=''] - The sub-configuration to create.
|
|
502
|
-
* @param {boolean} [options.cluster=false] - If true,
|
|
593
|
+
* @param {boolean} [options.cluster=false] - If true, initializes only the base cluster deploy folder (`engine-private/deploy`) from `./conf.js` and returns; no project or deploy ID scaffolding runs.
|
|
503
594
|
* @param {boolean} [options.dev=false] - If true, uses development settings.
|
|
504
595
|
* @param {boolean} [options.buildRepos=false] - If true, creates the deployment repositories (engine-*, engine-*-private, engine-*-cron-backups).
|
|
505
596
|
* @param {boolean} [options.purge=false] - If true, removes the deploy ID conf and all related repositories (requires deployId).
|
|
@@ -534,6 +625,12 @@ class UnderpostRepository {
|
|
|
534
625
|
await logger.setUpInfo();
|
|
535
626
|
actionInitLog();
|
|
536
627
|
|
|
628
|
+
// Handle cluster deploy base operation (standalone: never scaffolds a project)
|
|
629
|
+
if (options.cluster === true) {
|
|
630
|
+
UnderpostRepository.API.clusterDeployFactory(options.deployId || projectName);
|
|
631
|
+
return resolve(true);
|
|
632
|
+
}
|
|
633
|
+
|
|
537
634
|
// Handle cleanTemplate operation
|
|
538
635
|
if (options.cleanTemplate) {
|
|
539
636
|
logger.info('Cleaning build directory');
|
|
@@ -630,8 +727,6 @@ class UnderpostRepository {
|
|
|
630
727
|
];
|
|
631
728
|
|
|
632
729
|
const username = process.env.GITHUB_USERNAME;
|
|
633
|
-
const token = process.env.GITHUB_TOKEN;
|
|
634
|
-
|
|
635
730
|
if (!username) {
|
|
636
731
|
logger.error('GITHUB_USERNAME environment variable not set');
|
|
637
732
|
return reject(false);
|
|
@@ -643,7 +738,7 @@ class UnderpostRepository {
|
|
|
643
738
|
logger.info(`Created repository directory: ${repo.path}`);
|
|
644
739
|
}
|
|
645
740
|
|
|
646
|
-
const remoteUrl = `https
|
|
741
|
+
const remoteUrl = `https://github.com/${username}/${repo.name}.git`;
|
|
647
742
|
UnderpostRepository.API.initLocalRepo({ path: repo.path, origin: remoteUrl });
|
|
648
743
|
logger.info(`Initialized git repository with remote: ${repo.name}`);
|
|
649
744
|
}
|
|
@@ -674,257 +769,7 @@ class UnderpostRepository {
|
|
|
674
769
|
}
|
|
675
770
|
return resolve(true);
|
|
676
771
|
} catch (error) {
|
|
677
|
-
console.
|
|
678
|
-
logger.error(error, error.stack);
|
|
679
|
-
return reject(false);
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
},
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Builds client assets, single replicas, and/or syncs environment ports.
|
|
686
|
-
* @param {string} [deployId='dd-default'] - The deployment ID.
|
|
687
|
-
* @param {string} [subConf=''] - The sub-configuration for the build.
|
|
688
|
-
* @param {string} [host=''] - Comma-separated hosts to filter the build.
|
|
689
|
-
* @param {string} [path=''] - Comma-separated paths to filter the build.
|
|
690
|
-
* @param {object} [options] - Build options.
|
|
691
|
-
* @param {boolean} [options.syncEnvPort=false] - If true, syncs environment port assignments across all deploy IDs.
|
|
692
|
-
* @param {boolean} [options.singleReplica=false] - If true, builds single replica folders instead of full client.
|
|
693
|
-
* @param {boolean} [options.buildZip=false] - If true, creates zip files of the builds.
|
|
694
|
-
* @param {string|number} [options.split=''] - Optional ZIP part size in MB. When set with buildZip, writes split parts.
|
|
695
|
-
* @param {string} [options.unzip=''] - Optional build ZIP prefix to extract from ./build.
|
|
696
|
-
* @param {string} [options.mergeZip=''] - Optional build prefix to merge split ZIP parts into a single ZIP.
|
|
697
|
-
* @param {boolean} [options.liteBuild=false] - If true, skips full build (default is full build).
|
|
698
|
-
* @param {boolean} [options.iconsBuild=false] - If true, builds icons.
|
|
699
|
-
* @returns {Promise<boolean>} A promise that resolves when the build is complete.
|
|
700
|
-
* @memberof UnderpostRepository
|
|
701
|
-
*/
|
|
702
|
-
client(
|
|
703
|
-
deployId = 'dd-default',
|
|
704
|
-
subConf = '',
|
|
705
|
-
host = '',
|
|
706
|
-
path = '',
|
|
707
|
-
options = {
|
|
708
|
-
syncEnvPort: false,
|
|
709
|
-
singleReplica: false,
|
|
710
|
-
buildZip: false,
|
|
711
|
-
split: '',
|
|
712
|
-
unzip: '',
|
|
713
|
-
mergeZip: '',
|
|
714
|
-
liteBuild: false,
|
|
715
|
-
iconsBuild: false,
|
|
716
|
-
},
|
|
717
|
-
) {
|
|
718
|
-
return new Promise(async (resolve, reject) => {
|
|
719
|
-
try {
|
|
720
|
-
if (options.mergeZip) {
|
|
721
|
-
mergeClientBuildZip({
|
|
722
|
-
buildPrefix: options.mergeZip,
|
|
723
|
-
logger,
|
|
724
|
-
});
|
|
725
|
-
return resolve(true);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (options.unzip) {
|
|
729
|
-
unzipClientBuild({
|
|
730
|
-
buildPrefix: options.unzip,
|
|
731
|
-
logger,
|
|
732
|
-
});
|
|
733
|
-
return resolve(true);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Handle singleReplica operation (must run before syncEnvPort to ensure replica dirs exist)
|
|
737
|
-
if (options.singleReplica) {
|
|
738
|
-
const replicaPath = path;
|
|
739
|
-
if (!deployId || !host || !replicaPath) {
|
|
740
|
-
logger.error('client --single-replica requires deploy-id, host, and path arguments');
|
|
741
|
-
return reject(false);
|
|
742
|
-
}
|
|
743
|
-
const serverConf = loadReplicas(
|
|
744
|
-
deployId,
|
|
745
|
-
loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
if (serverConf[host][replicaPath].replicas) {
|
|
749
|
-
{
|
|
750
|
-
let replicaIndex = -1;
|
|
751
|
-
for (const replica of serverConf[host][replicaPath].replicas) {
|
|
752
|
-
replicaIndex++;
|
|
753
|
-
const replicaDeployId = `${deployId}-${serverConf[host][replicaPath].replicas[replicaIndex].slice(1)}`;
|
|
754
|
-
await fs.copy(`./engine-private/conf/${deployId}`, `./engine-private/replica/${replicaDeployId}`);
|
|
755
|
-
fs.writeFileSync(
|
|
756
|
-
`./engine-private/replica/${replicaDeployId}/package.json`,
|
|
757
|
-
fs
|
|
758
|
-
.readFileSync(`./engine-private/replica/${replicaDeployId}/package.json`, 'utf8')
|
|
759
|
-
.replaceAll(`${deployId}`, `${replicaDeployId}`),
|
|
760
|
-
'utf8',
|
|
761
|
-
);
|
|
762
|
-
const replicaFolder = `./engine-private/replica/${replicaDeployId}`;
|
|
763
|
-
for (const envFile of ['.env.production', '.env.development', '.env.test']) {
|
|
764
|
-
const envFilePath = `${replicaFolder}/${envFile}`;
|
|
765
|
-
if (fs.existsSync(envFilePath)) {
|
|
766
|
-
fs.writeFileSync(
|
|
767
|
-
envFilePath,
|
|
768
|
-
fs
|
|
769
|
-
.readFileSync(envFilePath, 'utf8')
|
|
770
|
-
.replaceAll(`DEPLOY_ID=${deployId}`, `DEPLOY_ID=${replicaDeployId}`),
|
|
771
|
-
'utf8',
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
{
|
|
778
|
-
let replicaIndex = -1;
|
|
779
|
-
for (const replica of serverConf[host][replicaPath].replicas) {
|
|
780
|
-
replicaIndex++;
|
|
781
|
-
const replicaDeployId = `${deployId}-${serverConf[host][replicaPath].replicas[replicaIndex].slice(1)}`;
|
|
782
|
-
let replicaServerConf = JSON.parse(
|
|
783
|
-
fs.readFileSync(`./engine-private/replica/${replicaDeployId}/conf.server.json`, 'utf8'),
|
|
784
|
-
);
|
|
785
|
-
|
|
786
|
-
const singleReplicaConf = replicaServerConf[host][replicaPath];
|
|
787
|
-
singleReplicaConf.replicas = undefined;
|
|
788
|
-
singleReplicaConf.singleReplica = undefined;
|
|
789
|
-
|
|
790
|
-
replicaServerConf = {};
|
|
791
|
-
replicaServerConf[host] = {};
|
|
792
|
-
replicaServerConf[host][replica] = singleReplicaConf;
|
|
793
|
-
|
|
794
|
-
fs.writeFileSync(
|
|
795
|
-
`./engine-private/replica/${replicaDeployId}/conf.server.json`,
|
|
796
|
-
JSON.stringify(replicaServerConf, null, 4),
|
|
797
|
-
'utf8',
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
if (!options.syncEnvPort) return resolve(true);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Handle syncEnvPort operation
|
|
806
|
-
if (options.syncEnvPort) {
|
|
807
|
-
const dataDeploy = await getDataDeploy({ disableSyncEnvPort: true });
|
|
808
|
-
const dataEnv = [
|
|
809
|
-
{ env: 'production', port: 3000 },
|
|
810
|
-
{ env: 'development', port: 4000 },
|
|
811
|
-
{ env: 'test', port: 5000 },
|
|
812
|
-
];
|
|
813
|
-
let portOffset = 0;
|
|
814
|
-
const singleReplicaPortOffsets = {};
|
|
815
|
-
for (const deployIdObj of dataDeploy) {
|
|
816
|
-
const { deployId } = deployIdObj;
|
|
817
|
-
const baseConfPath = fs.existsSync(`./engine-private/replica/${deployId}`)
|
|
818
|
-
? `./engine-private/replica`
|
|
819
|
-
: `./engine-private/conf`;
|
|
820
|
-
|
|
821
|
-
const effectivePortOffset =
|
|
822
|
-
singleReplicaPortOffsets[deployId] !== undefined ? singleReplicaPortOffsets[deployId] : portOffset;
|
|
823
|
-
|
|
824
|
-
let skipDeploy = false;
|
|
825
|
-
for (const envInstanceObj of dataEnv) {
|
|
826
|
-
const envPath = `${baseConfPath}/${deployId}/.env.${envInstanceObj.env}`;
|
|
827
|
-
if (!fs.existsSync(envPath)) {
|
|
828
|
-
logger.warn(`Skipping ${deployId}: ${envPath} not found`);
|
|
829
|
-
skipDeploy = true;
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
833
|
-
envObj.PORT = `${envInstanceObj.port + effectivePortOffset}`;
|
|
834
|
-
writeEnv(envPath, envObj);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (skipDeploy) continue;
|
|
838
|
-
if (singleReplicaPortOffsets[deployId] !== undefined) continue;
|
|
839
|
-
|
|
840
|
-
const serverConf = loadReplicas(
|
|
841
|
-
deployId,
|
|
842
|
-
loadConfServerJson(`${baseConfPath}/${deployId}/conf.server.json`),
|
|
843
|
-
);
|
|
844
|
-
for (const host of Object.keys(serverConf)) {
|
|
845
|
-
let deferredSingleReplicaSlots = [];
|
|
846
|
-
for (const path of Object.keys(serverConf[host])) {
|
|
847
|
-
if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
|
|
848
|
-
deferredSingleReplicaSlots.push({
|
|
849
|
-
replicas: serverConf[host][path].replicas,
|
|
850
|
-
peer: !!serverConf[host][path].peer,
|
|
851
|
-
});
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
portOffset++;
|
|
855
|
-
if (serverConf[host][path].peer) portOffset++;
|
|
856
|
-
}
|
|
857
|
-
for (const slot of deferredSingleReplicaSlots) {
|
|
858
|
-
for (const replica of slot.replicas) {
|
|
859
|
-
const replicaDeployId = buildReplicaId({ deployId, replica });
|
|
860
|
-
singleReplicaPortOffsets[replicaDeployId] = portOffset;
|
|
861
|
-
portOffset++;
|
|
862
|
-
if (slot.peer) portOffset++;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
return resolve(true);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Handle buildFullClient operation (default)
|
|
871
|
-
{
|
|
872
|
-
const { deployId: resolvedDeployId } = loadConf(deployId, subConf ?? '');
|
|
873
|
-
|
|
874
|
-
let argHost = host ? host.split(',') : [];
|
|
875
|
-
let argPath = path ? path.split(',') : [];
|
|
876
|
-
let deployIdSingleReplicas = [];
|
|
877
|
-
let singleReplicaHosts = [];
|
|
878
|
-
const isReplicaContext = resolvedDeployId
|
|
879
|
-
? fs.existsSync(`./engine-private/replica/${resolvedDeployId}`)
|
|
880
|
-
: false;
|
|
881
|
-
const serverConf = resolvedDeployId
|
|
882
|
-
? readConfJson(resolvedDeployId, 'server', { loadReplicas: true })
|
|
883
|
-
: Config.default.server;
|
|
884
|
-
const confFilePath = resolvedDeployId ? getConfFilePath(resolvedDeployId, 'server') : null;
|
|
885
|
-
const originalConfBackup = confFilePath ? fs.readFileSync(confFilePath, 'utf8') : null;
|
|
886
|
-
for (const host of Object.keys(serverConf)) {
|
|
887
|
-
for (const path of Object.keys(serverConf[host])) {
|
|
888
|
-
if (argHost.length && argPath.length && (!argHost.includes(host) || !argPath.includes(path))) {
|
|
889
|
-
delete serverConf[host][path];
|
|
890
|
-
} else {
|
|
891
|
-
if (!isReplicaContext && serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
|
|
892
|
-
singleReplicaHosts.push({ host, path });
|
|
893
|
-
deployIdSingleReplicas = deployIdSingleReplicas.concat(
|
|
894
|
-
serverConf[host][path].replicas.map((replica) =>
|
|
895
|
-
buildReplicaId({ deployId: resolvedDeployId, replica }),
|
|
896
|
-
),
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
await buildClient({
|
|
903
|
-
deployId: resolvedDeployId,
|
|
904
|
-
buildZip: options.buildZip || false,
|
|
905
|
-
split: options.split || '',
|
|
906
|
-
fullBuild: options.liteBuild ? false : true,
|
|
907
|
-
iconsBuild: options.iconsBuild || false,
|
|
908
|
-
ssrOnly: options.ssr || false,
|
|
909
|
-
});
|
|
910
|
-
for (const replicaDeployId of deployIdSingleReplicas) {
|
|
911
|
-
if (!fs.existsSync(`./engine-private/replica/${replicaDeployId}`)) {
|
|
912
|
-
logger.warn('Skip replica client build: replica folder not found', { replicaDeployId });
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
await Underpost.repo.client(replicaDeployId, '', '', '', {
|
|
916
|
-
buildZip: options.buildZip || false,
|
|
917
|
-
split: options.split || '',
|
|
918
|
-
liteBuild: options.liteBuild || false,
|
|
919
|
-
iconsBuild: options.iconsBuild || false,
|
|
920
|
-
ssr: options.ssr || false,
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return resolve(true);
|
|
925
|
-
}
|
|
926
|
-
} catch (error) {
|
|
927
|
-
console.log(error);
|
|
772
|
+
console.error(error);
|
|
928
773
|
logger.error(error, error.stack);
|
|
929
774
|
return reject(false);
|
|
930
775
|
}
|
|
@@ -973,7 +818,7 @@ class UnderpostRepository {
|
|
|
973
818
|
*/
|
|
974
819
|
privateConfUpdate(deployId) {
|
|
975
820
|
shellCd(`/home/dd/engine`);
|
|
976
|
-
const privateRepoName =
|
|
821
|
+
const privateRepoName = Underpost.repo.privateRepoFactory(deployId);
|
|
977
822
|
const privateRepoPath = `../${privateRepoName}`;
|
|
978
823
|
if (fs.existsSync(privateRepoPath)) fs.removeSync(privateRepoPath);
|
|
979
824
|
shellExec(`cd .. && underpost clone ${process.env.GITHUB_USERNAME}/${privateRepoName}`);
|
|
@@ -1274,23 +1119,32 @@ Prevent build private config repo.`,
|
|
|
1274
1119
|
/**
|
|
1275
1120
|
* Resolves the default branch for a remote GitHub repository by querying
|
|
1276
1121
|
* `git ls-remote --symref` and extracting the HEAD ref target.
|
|
1277
|
-
* Falls back to `main` when detection fails.
|
|
1278
1122
|
* @param {string} repo - The GitHub repository (e.g., "owner/repo").
|
|
1279
1123
|
* @returns {string} The default branch name (e.g. "main" or "master").
|
|
1280
1124
|
* @memberof UnderpostRepository
|
|
1281
1125
|
*/
|
|
1282
1126
|
getDefaultBranch(repo) {
|
|
1283
|
-
if (!repo)
|
|
1284
|
-
const
|
|
1285
|
-
const raw = shellExec(
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1127
|
+
if (!repo) throw new Error('[repo] getDefaultBranch requires a repository reference');
|
|
1128
|
+
const auth = Underpost.repo.gitAuthFactory(repo);
|
|
1129
|
+
const raw = shellExec(`git ls-remote --symref "${auth.url}" HEAD 2>&1`, {
|
|
1130
|
+
stdout: true,
|
|
1131
|
+
silent: true,
|
|
1132
|
+
disableLog: true,
|
|
1133
|
+
silentOnError: true,
|
|
1134
|
+
env: auth.env,
|
|
1135
|
+
});
|
|
1289
1136
|
// --symref emits a line like: ref: refs/heads/main HEAD
|
|
1290
1137
|
const match = typeof raw === 'string' ? raw.match(/^ref:\s*refs\/heads\/(\S+)\tHEAD$/m) : null;
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1138
|
+
// Guessing here is worse than failing: `main` against a `master` remote
|
|
1139
|
+
// fetches a ref that does not exist, and the caller has already pointed
|
|
1140
|
+
// origin at the new URL by the time git says so.
|
|
1141
|
+
if (!match)
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
`[repo] cannot read the default branch of '${repo}'; it may be empty, unreachable, ` +
|
|
1144
|
+
`or need credentials: ${redactSensitiveText(`${raw || ''}`.trim().split('\n').pop() || 'no ref reported')}`,
|
|
1145
|
+
);
|
|
1146
|
+
logger.info('getDefaultBranch', { repo, branch: match[1] });
|
|
1147
|
+
return match[1];
|
|
1294
1148
|
},
|
|
1295
1149
|
|
|
1296
1150
|
/**
|
|
@@ -1323,7 +1177,7 @@ Prevent build private config repo.`,
|
|
|
1323
1177
|
} else {
|
|
1324
1178
|
let token = process.env.GITHUB_TOKEN;
|
|
1325
1179
|
if (!token) {
|
|
1326
|
-
const envPath = `${
|
|
1180
|
+
const envPath = `${getUnderpostRootPath()}/.env`;
|
|
1327
1181
|
if (fs.existsSync(envPath) && fs.statSync(envPath).isFile()) {
|
|
1328
1182
|
const envVars = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
1329
1183
|
token = envVars.GITHUB_TOKEN;
|
|
@@ -1348,46 +1202,220 @@ Prevent build private config repo.`,
|
|
|
1348
1202
|
},
|
|
1349
1203
|
|
|
1350
1204
|
/**
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
* @param {string} url -
|
|
1354
|
-
* @returns {string}
|
|
1205
|
+
* Normalizes a repository reference to a clone URL, expanding the short
|
|
1206
|
+
* `owner/repo` form against GitHub.
|
|
1207
|
+
* @param {string} url - Repository URL or `owner/repo` short form.
|
|
1208
|
+
* @returns {string} Clone URL, unchanged when one was already given.
|
|
1209
|
+
* @memberof UnderpostRepository
|
|
1210
|
+
*/
|
|
1211
|
+
repoUrlFactory(url = '') {
|
|
1212
|
+
const value = `${url || ''}`.trim();
|
|
1213
|
+
if (!value) return value;
|
|
1214
|
+
const remote = value.startsWith('http://') || value.startsWith('https://') || value.startsWith('git@');
|
|
1215
|
+
return remote ? value : `https://github.com/${value}`;
|
|
1216
|
+
},
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Reduces a repository reference to its `owner/repo` slug, the form the
|
|
1220
|
+
* clone and pull commands take.
|
|
1221
|
+
* @param {string} url - Repository URL or `owner/repo` short form.
|
|
1222
|
+
* @returns {string} Slug.
|
|
1223
|
+
* @throws {Error} When no owner and repository can be read from the reference.
|
|
1224
|
+
* @memberof UnderpostRepository
|
|
1225
|
+
*/
|
|
1226
|
+
repoSlugFactory(url = '') {
|
|
1227
|
+
const path = Underpost.repo
|
|
1228
|
+
.repoUrlFactory(url)
|
|
1229
|
+
.replace(/^git@[^:]+:/, '')
|
|
1230
|
+
.replace(/^https?:\/\/[^/]+\//, '')
|
|
1231
|
+
.replace(/\.git$/, '')
|
|
1232
|
+
.replace(/\/+$/, '');
|
|
1233
|
+
const [owner, repo] = path.split('/');
|
|
1234
|
+
if (!owner || !repo) throw new Error(`[repo] '${url}' is not an owner/repo reference or a clone URL`);
|
|
1235
|
+
return `${owner}/${repo}`;
|
|
1236
|
+
},
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Reduces any deploy-scoped reference to the conf id its repositories share.
|
|
1240
|
+
*
|
|
1241
|
+
* Single source of that parsing: deploy ids (`dd-lampp`), source repos (`engine-lampp`),
|
|
1242
|
+
* test source repos (`engine-test-lampp`) and private conf repos (`engine-lampp-private`)
|
|
1243
|
+
* all name the same deployment, and the monorepo (`engine` / `engine-private`) names none.
|
|
1244
|
+
* @param {string} reference - Deploy id, repository name, `owner/repo` slug, or clone URL.
|
|
1245
|
+
* @returns {string} Conf id, or `''` for the monorepo.
|
|
1246
|
+
* @memberof UnderpostRepository
|
|
1247
|
+
*/
|
|
1248
|
+
confIdFactory(reference = '') {
|
|
1249
|
+
const name = `${reference || ''}`
|
|
1250
|
+
.trim()
|
|
1251
|
+
.split('/')
|
|
1252
|
+
.pop()
|
|
1253
|
+
.replace(/\.git$/, '');
|
|
1254
|
+
const confId = name
|
|
1255
|
+
.replace(/^dd-/, '')
|
|
1256
|
+
.replace(/^engine-test-/, '')
|
|
1257
|
+
.replace(/^engine-/, '')
|
|
1258
|
+
.replace(/-private$/, '');
|
|
1259
|
+
return confId === 'engine' || confId === 'private' ? '' : confId;
|
|
1260
|
+
},
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Names the engine source repository a reference deploys from.
|
|
1264
|
+
* @param {string} reference - Deploy id or repository reference.
|
|
1265
|
+
* @param {object} [options] - Naming options.
|
|
1266
|
+
* @param {boolean} [options.test=false] - Name the private test source repo instead.
|
|
1267
|
+
* @returns {string} Repository name, without owner.
|
|
1268
|
+
* @memberof UnderpostRepository
|
|
1269
|
+
*/
|
|
1270
|
+
engineRepoFactory(reference = '', options = { test: false }) {
|
|
1271
|
+
const confId = Underpost.repo.confIdFactory(reference);
|
|
1272
|
+
if (!confId) return 'engine';
|
|
1273
|
+
return options?.test === true ? `engine-test-${confId}` : `engine-${confId}`;
|
|
1274
|
+
},
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Names the private configuration repository a reference's conf lives in.
|
|
1278
|
+
* @param {string} reference - Deploy id or repository reference.
|
|
1279
|
+
* @returns {string} Repository name, without owner.
|
|
1280
|
+
* @memberof UnderpostRepository
|
|
1281
|
+
*/
|
|
1282
|
+
privateRepoFactory(reference = '') {
|
|
1283
|
+
const confId = Underpost.repo.confIdFactory(reference);
|
|
1284
|
+
return confId ? `engine-${confId}-private` : 'engine-private';
|
|
1285
|
+
},
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Names the canonical GitHub package repository a reference's engine mirrors into.
|
|
1289
|
+
*
|
|
1290
|
+
* The monorepo has none — `.github/workflows/ghpkg.ci.yml` builds `engine-ghpkg-<conf_id>`
|
|
1291
|
+
* per deploy, and the template lineage mirrors into `pwa-microservices-template-ghpkg`
|
|
1292
|
+
* instead — so an unnamed deploy resolves to `''` rather than an invented name.
|
|
1293
|
+
* @param {string} reference - Deploy id or repository reference.
|
|
1294
|
+
* @returns {string} Repository name without owner, or `''` for the monorepo.
|
|
1295
|
+
* @memberof UnderpostRepository
|
|
1296
|
+
*/
|
|
1297
|
+
ghpkgRepoFactory(reference = '') {
|
|
1298
|
+
const confId = Underpost.repo.confIdFactory(reference);
|
|
1299
|
+
return confId ? `engine-ghpkg-${confId}` : '';
|
|
1300
|
+
},
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Every distinct repository a deploy's instances are built from, as declared by
|
|
1304
|
+
* `metadata.repository` in its `conf.instances.json`.
|
|
1305
|
+
*
|
|
1306
|
+
* An instance is a separate product with its own repository and its own workflows —
|
|
1307
|
+
* `dd-cyberia` builds from `cyberia-server` and `cyberia-client` — so anything acting on
|
|
1308
|
+
* "the repositories of a deploy" has to include them or it covers only half the deploy.
|
|
1309
|
+
* The path is left to {@link readConfInstances} rather than rebuilt here.
|
|
1310
|
+
* @param {string} deployId - Deploy id (e.g. `dd-cyberia`).
|
|
1311
|
+
* @returns {Array<string>} Deduplicated `owner/repo` slugs, empty when the deploy declares none.
|
|
1355
1312
|
* @memberof UnderpostRepository
|
|
1356
1313
|
*/
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1314
|
+
instanceRepos(deployId = '') {
|
|
1315
|
+
const id = `${deployId || ''}`.trim();
|
|
1316
|
+
if (!id) return [];
|
|
1317
|
+
try {
|
|
1318
|
+
return [
|
|
1319
|
+
...new Set(
|
|
1320
|
+
readConfInstances(id)
|
|
1321
|
+
.map((entry) => entry?.metadata?.repository)
|
|
1322
|
+
.filter(Boolean),
|
|
1323
|
+
),
|
|
1324
|
+
];
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
// A deploy that declares no instances has no file, which is not a fault; anything else is.
|
|
1327
|
+
if (error.code !== 'ENOENT')
|
|
1328
|
+
logger.warn(`[repo] unreadable conf.instances.json for ${id}`, { error: error.message });
|
|
1329
|
+
return [];
|
|
1363
1330
|
}
|
|
1331
|
+
},
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Resolves the repository pair a node checks out: the engine source and the private
|
|
1335
|
+
* configuration that belongs to it.
|
|
1336
|
+
*
|
|
1337
|
+
* One source of that pairing, because two callers compose it — `run pull` on the node
|
|
1338
|
+
* itself and `edge --sync` composing the same pull for a fleet — and a node whose engine
|
|
1339
|
+
* and conf came from two different resolutions reads a conf that does not describe it.
|
|
1340
|
+
* The private repository is derived from the conf id the two share and takes the engine's
|
|
1341
|
+
* owner, so naming only the source can never split the pair: `underpostnet/engine-test-lampp`
|
|
1342
|
+
* pairs with `underpostnet/engine-lampp-private`, as does `underpostnet/engine-lampp`.
|
|
1343
|
+
* Naming `enginePrivate` overrides that derivation with its own `owner/repo` or clone URL.
|
|
1344
|
+
* @param {object} [options] - Pair options.
|
|
1345
|
+
* @param {string} [options.engine] - Engine source, as `owner/repo` or a clone URL.
|
|
1346
|
+
* @param {string} [options.enginePrivate] - Private configuration repository, overriding the derivation.
|
|
1347
|
+
* @param {string} [options.account] - Owner the monorepo defaults to; `GITHUB_USERNAME` otherwise.
|
|
1348
|
+
* @returns {{engine: string, enginePrivate: string}} Both slugs.
|
|
1349
|
+
* @memberof UnderpostRepository
|
|
1350
|
+
*/
|
|
1351
|
+
enginePairFactory({ engine = '', enginePrivate = '', account = '' } = {}) {
|
|
1352
|
+
const owner = `${account || ''}`.trim() || process.env.GITHUB_USERNAME || 'underpostnet';
|
|
1353
|
+
const source = Underpost.repo.repoSlugFactory(`${engine || ''}`.trim() || `${owner}/engine`);
|
|
1354
|
+
const conf = `${enginePrivate || ''}`.trim();
|
|
1355
|
+
return {
|
|
1356
|
+
engine: source,
|
|
1357
|
+
enginePrivate: conf
|
|
1358
|
+
? Underpost.repo.repoSlugFactory(conf)
|
|
1359
|
+
: `${source.split('/')[0]}/${Underpost.repo.privateRepoFactory(source)}`,
|
|
1360
|
+
};
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1363
|
+
/**
|
|
1364
|
+
* Fast-forwards the engine and engine-private checkouts ahead of a template deploy.
|
|
1365
|
+
*
|
|
1366
|
+
* The deploy runners publish local commits, so they must not take the `run pull` route: that one
|
|
1367
|
+
* is for provisioning a node and force-replaces both trees at the remote tip, discarding the very
|
|
1368
|
+
* commits being published. `underpost pull` is `git pull --ff-only`, so a diverged checkout stops
|
|
1369
|
+
* the deploy instead of losing history.
|
|
1370
|
+
* @param {string} baseCommand - Resolved `underpost` CLI invocation.
|
|
1371
|
+
* @returns {void}
|
|
1372
|
+
*/
|
|
1373
|
+
fastForwardEnginePair(baseCommand) {
|
|
1374
|
+
shellExec(`${baseCommand} pull . ${process.env.GITHUB_USERNAME}/engine`);
|
|
1375
|
+
shellExec(`${baseCommand} pull ./engine-private ${process.env.GITHUB_USERNAME}/engine-private`);
|
|
1376
|
+
},
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Keeps GitHub credentials in the child environment instead of command arguments or remotes.
|
|
1380
|
+
* @param {string} url - Repository URL or owner/repo reference.
|
|
1381
|
+
* @returns {{url: string, env: NodeJS.ProcessEnv}} Token-free URL and Git child environment.
|
|
1382
|
+
* @memberof UnderpostRepository
|
|
1383
|
+
*/
|
|
1384
|
+
gitAuthFactory(url) {
|
|
1385
|
+
const normalized = Underpost.repo.repoUrlFactory(url).replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/i, '$1');
|
|
1386
|
+
const env = {
|
|
1387
|
+
...process.env,
|
|
1388
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
1389
|
+
GIT_CONFIG_COUNT: '1',
|
|
1390
|
+
GIT_CONFIG_KEY_0: 'credential.helper',
|
|
1391
|
+
GIT_CONFIG_VALUE_0: '',
|
|
1392
|
+
};
|
|
1364
1393
|
if (process.env.GITHUB_TOKEN && normalized.startsWith('https://github.com/')) {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1394
|
+
env.GIT_CONFIG_COUNT = '2';
|
|
1395
|
+
env.GIT_CONFIG_KEY_1 = 'http.https://github.com/.extraheader';
|
|
1396
|
+
env.GIT_CONFIG_VALUE_1 = `AUTHORIZATION: basic ${Buffer.from(
|
|
1397
|
+
`x-access-token:${process.env.GITHUB_TOKEN}`,
|
|
1398
|
+
).toString('base64')}`;
|
|
1369
1399
|
}
|
|
1370
|
-
return normalized;
|
|
1400
|
+
return { url: normalized, env };
|
|
1371
1401
|
},
|
|
1372
1402
|
/**
|
|
1373
1403
|
* Checks whether a remote Git repository URL is reachable.
|
|
1374
1404
|
* Uses `silentOnError` so a non-reachable remote returns false instead of throwing.
|
|
1375
|
-
*
|
|
1405
|
+
* Passes `GITHUB_TOKEN` to Git through its child environment when available.
|
|
1376
1406
|
* @param {string} url - Full HTTPS clone URL to test (e.g. "https://github.com/org/repo.git").
|
|
1377
1407
|
* @returns {boolean} `true` when the remote responded with at least one ref hash.
|
|
1378
1408
|
* @memberof UnderpostRepository
|
|
1379
1409
|
*/
|
|
1380
1410
|
isRemoteRepo(url) {
|
|
1381
1411
|
if (!url) return false;
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
// `-c credential.helper=` disables any host-configured credential helper so its stderr
|
|
1385
|
-
// warnings don't pollute the ls-remote output; the token is already embedded in authUrl.
|
|
1386
|
-
const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git -c credential.helper= ls-remote "${authUrl}" HEAD 2>&1`, {
|
|
1412
|
+
const auth = Underpost.repo.gitAuthFactory(url);
|
|
1413
|
+
const raw = shellExec(`git ls-remote "${auth.url}" HEAD 2>&1`, {
|
|
1387
1414
|
stdout: true,
|
|
1388
1415
|
silent: true,
|
|
1389
1416
|
disableLog: true,
|
|
1390
1417
|
silentOnError: true,
|
|
1418
|
+
env: auth.env,
|
|
1391
1419
|
});
|
|
1392
1420
|
const refLine = typeof raw === 'string' ? (raw.match(/^[0-9a-f]{40}\t.*$/m) || [])[0] : undefined;
|
|
1393
1421
|
const accessible = !!refLine;
|
|
@@ -1420,7 +1448,7 @@ Prevent build private config repo.`,
|
|
|
1420
1448
|
* Sequence (idempotent, re-runnable):
|
|
1421
1449
|
* 1. Normalize the URL (`owner/repo` → full GitHub HTTPS) and set/add the
|
|
1422
1450
|
* remote, storing the token-free URL so no secret leaks into `.git/config`.
|
|
1423
|
-
* 2. Force-fetch the target branch
|
|
1451
|
+
* 2. Force-fetch the target branch with credentials isolated in the child environment.
|
|
1424
1452
|
* 3. Reset the working tree to the fetched tip and check out the target
|
|
1425
1453
|
* branch, overwriting any current tracked content.
|
|
1426
1454
|
*
|
|
@@ -1438,23 +1466,58 @@ Prevent build private config repo.`,
|
|
|
1438
1466
|
if (!url) throw new Error('switchRemote requires a target remote url');
|
|
1439
1467
|
if (!fs.existsSync(`${repoPath}/.git`)) throw new Error(`switchRemote: not a git repository: ${repoPath}`);
|
|
1440
1468
|
const targetBranch = branch || Underpost.repo.getDefaultBranch(url);
|
|
1441
|
-
|
|
1442
|
-
let normalized = url;
|
|
1443
|
-
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('git@')) {
|
|
1444
|
-
normalized = `https://github.com/${url}`;
|
|
1445
|
-
}
|
|
1446
|
-
const authUrl = Underpost.repo.resolveAuthUrl(url);
|
|
1469
|
+
const auth = Underpost.repo.gitAuthFactory(url);
|
|
1447
1470
|
const current = Underpost.repo.getRemoteUrl({ path: repoPath, remote });
|
|
1448
|
-
if (!current) shellExec(`cd "${repoPath}" && git remote add ${remote} "${
|
|
1449
|
-
else shellExec(`cd "${repoPath}" && git remote set-url ${remote} "${
|
|
1450
|
-
logger.info('switchRemote', { path: repoPath, remote, branch: targetBranch, url:
|
|
1451
|
-
shellExec(`cd "${repoPath}" &&
|
|
1471
|
+
if (!current) shellExec(`cd "${repoPath}" && git remote add ${remote} "${auth.url}"`);
|
|
1472
|
+
else shellExec(`cd "${repoPath}" && git remote set-url ${remote} "${auth.url}"`);
|
|
1473
|
+
logger.info('switchRemote', { path: repoPath, remote, branch: targetBranch, url: auth.url });
|
|
1474
|
+
shellExec(`cd "${repoPath}" && git fetch --force "${auth.url}" ${targetBranch}`, { env: auth.env });
|
|
1452
1475
|
// reset --hard first clears the worktree so the checkout cannot be blocked
|
|
1453
1476
|
// by conflicting local changes; -B points the target branch at the fetched tip.
|
|
1454
1477
|
shellExec(`cd "${repoPath}" && git reset --hard FETCH_HEAD`);
|
|
1455
1478
|
shellExec(`cd "${repoPath}" && git checkout -B ${targetBranch} FETCH_HEAD`);
|
|
1456
1479
|
},
|
|
1457
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* Brings a local checkout to the requested repository at its tip, whatever state it is in.
|
|
1483
|
+
*
|
|
1484
|
+
* Clones when the path is absent, and otherwise replaces the checkout through
|
|
1485
|
+
* {@link UnderpostRepository.switchRemote} — the same operation `underpost cmt --switch-repo`
|
|
1486
|
+
* runs, so a node reached by `run pull` and a node reached by `edge --sync` end on the same
|
|
1487
|
+
* commit by the same route.
|
|
1488
|
+
*
|
|
1489
|
+
* One operation covers both the retarget and the same-repository case, because they only
|
|
1490
|
+
* look different: an `--ff-only` pull refuses a checkout that has drifted onto commits of
|
|
1491
|
+
* its own, which is exactly the node that most needs bringing back. A deploy checkout is a
|
|
1492
|
+
* projection of its remote, never a place work is authored, so it is replaced rather than
|
|
1493
|
+
* reconciled.
|
|
1494
|
+
*
|
|
1495
|
+
* The clone lands under the repository's own name and is renamed into place, because the
|
|
1496
|
+
* checkout path is fixed by the deployment layout while the repository name is not.
|
|
1497
|
+
*
|
|
1498
|
+
* @param {object} opts
|
|
1499
|
+
* @param {string} opts.path - Absolute checkout path.
|
|
1500
|
+
* @param {string} opts.repo - Repository `owner/repo` slug or clone URL.
|
|
1501
|
+
* @param {string} [opts.branch] - Branch to land on; the remote's default branch otherwise.
|
|
1502
|
+
* @returns {'cloned'|'switched'} What the call had to do.
|
|
1503
|
+
* @memberof UnderpostRepository
|
|
1504
|
+
*/
|
|
1505
|
+
syncCheckout({ path: checkoutPath, repo, branch = '' }) {
|
|
1506
|
+
const slug = Underpost.repo.repoSlugFactory(repo);
|
|
1507
|
+
const repoName = slug.split('/')[1];
|
|
1508
|
+
const parent = path.dirname(checkoutPath);
|
|
1509
|
+
|
|
1510
|
+
if (!fs.existsSync(checkoutPath)) {
|
|
1511
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
1512
|
+
shellExec(`cd ${parent} && underpost clone ${slug}`, { silent: true });
|
|
1513
|
+
if (`${parent}/${repoName}` !== checkoutPath) shellExec(`sudo mv ${parent}/${repoName} ${checkoutPath}`);
|
|
1514
|
+
return 'cloned';
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
Underpost.repo.switchRemote({ url: slug, path: checkoutPath, branch });
|
|
1518
|
+
return 'switched';
|
|
1519
|
+
},
|
|
1520
|
+
|
|
1458
1521
|
/**
|
|
1459
1522
|
* Returns metadata about unpushed commits in a git repository.
|
|
1460
1523
|
* Fetches from origin, then counts commits ahead of the remote branch.
|
|
@@ -1519,6 +1582,41 @@ Prevent build private config repo.`,
|
|
|
1519
1582
|
// callers fall back to their own generic default.
|
|
1520
1583
|
return sanitized === 'No changelog entries found.' ? '' : sanitized;
|
|
1521
1584
|
},
|
|
1585
|
+
/**
|
|
1586
|
+
* Resolves the changelog payload a workflow received from the previous link of the
|
|
1587
|
+
* propagation chain.
|
|
1588
|
+
*
|
|
1589
|
+
* Every hop commits the payload with `cmt`, which wraps it as
|
|
1590
|
+
* `type(scope): <emoji> <first entry>` — the newest entry rides on the subject line, so a
|
|
1591
|
+
* downstream hop that drops the whole subject line loses it. This strips only the wrapper
|
|
1592
|
+
* and keeps every entry, on its own line, in order.
|
|
1593
|
+
*
|
|
1594
|
+
* @param {string} rawMessage - The message as received (a commit message or a dispatch input).
|
|
1595
|
+
* @returns {string} The propagated entries, or an empty string when there is nothing to carry.
|
|
1596
|
+
* @memberof UnderpostRepository
|
|
1597
|
+
*/
|
|
1598
|
+
resolvePropagationMessage(rawMessage) {
|
|
1599
|
+
if (!rawMessage) return '';
|
|
1600
|
+
const types = Object.keys(commitData).join('|');
|
|
1601
|
+
const emojis = Object.values(commitData)
|
|
1602
|
+
.map(({ emoji }) => emoji)
|
|
1603
|
+
.filter(Boolean);
|
|
1604
|
+
const lines = `${rawMessage}`
|
|
1605
|
+
.replace(new RegExp(`^(${types})(\\([^)]*\\))?:\\s*`), '')
|
|
1606
|
+
.split('\n')
|
|
1607
|
+
.map((line) => line.trim())
|
|
1608
|
+
.filter(Boolean);
|
|
1609
|
+
if (lines[0]) {
|
|
1610
|
+
for (const emoji of emojis)
|
|
1611
|
+
if (lines[0].startsWith(emoji)) {
|
|
1612
|
+
lines[0] = lines[0].slice(emoji.length).trim();
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
if (!lines[0]) lines.shift();
|
|
1616
|
+
}
|
|
1617
|
+
return lines.join('\n');
|
|
1618
|
+
},
|
|
1619
|
+
|
|
1522
1620
|
/**
|
|
1523
1621
|
* Initializes a git repository at the given path and configures user identity
|
|
1524
1622
|
* from environment variables (`GITHUB_USERNAME` / `GITHUB_EMAIL`).
|
|
@@ -1530,9 +1628,13 @@ Prevent build private config repo.`,
|
|
|
1530
1628
|
* @memberof UnderpostRepository
|
|
1531
1629
|
*/
|
|
1532
1630
|
initLocalRepo({ path: repoPath, origin }) {
|
|
1533
|
-
const gitUsername =
|
|
1631
|
+
const gitUsername = repositoryIdentityFactory().owner;
|
|
1534
1632
|
const gitEmail = process.env.GITHUB_EMAIL || `development@underpost.net`;
|
|
1535
1633
|
|
|
1634
|
+
// Runtime document roots are chowned to the web server user, and git refuses to operate on
|
|
1635
|
+
// a tree owned by someone else until it is declared safe.
|
|
1636
|
+
Underpost.repo.declareSafeDirectory(repoPath);
|
|
1637
|
+
|
|
1536
1638
|
if (!fs.existsSync(`${repoPath}/.git`)) {
|
|
1537
1639
|
shellExec(`mkdir -p "${repoPath}" && git init "${repoPath}"`);
|
|
1538
1640
|
}
|
|
@@ -1598,6 +1700,18 @@ Prevent build private config repo.`,
|
|
|
1598
1700
|
case 'commit':
|
|
1599
1701
|
if (fs.existsSync(repoPath)) {
|
|
1600
1702
|
shellExec(`cd ${repoPath} && git add .`);
|
|
1703
|
+
// A backup run that produced no new bytes is a normal outcome, not a
|
|
1704
|
+
// failure: committing anyway exits non-zero and buries real errors.
|
|
1705
|
+
const pending = shellExec(`cd ${repoPath} && git status --porcelain`, {
|
|
1706
|
+
stdout: true,
|
|
1707
|
+
silent: true,
|
|
1708
|
+
disableLog: true,
|
|
1709
|
+
silentOnError: true,
|
|
1710
|
+
});
|
|
1711
|
+
if (!`${pending || ''}`.trim()) {
|
|
1712
|
+
logger.info(`No changes to commit: ${repoName}`, { message });
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1601
1715
|
shellExec(`underpost cmt ${repoPath} backup '' '${message}'`);
|
|
1602
1716
|
logger.info(`Committed to repository: ${repoName}`, { message });
|
|
1603
1717
|
}
|
|
@@ -1622,6 +1736,91 @@ Prevent build private config repo.`,
|
|
|
1622
1736
|
}
|
|
1623
1737
|
},
|
|
1624
1738
|
|
|
1739
|
+
/**
|
|
1740
|
+
* Declares a path safe for git, skipping the write when it is already declared so repeated
|
|
1741
|
+
* provisioning does not append duplicate global config entries.
|
|
1742
|
+
* @param {string} repoPath - Absolute path to declare.
|
|
1743
|
+
* @returns {void}
|
|
1744
|
+
* @memberof UnderpostRepository
|
|
1745
|
+
*/
|
|
1746
|
+
declareSafeDirectory(repoPath) {
|
|
1747
|
+
const declared = shellExec(`git config --global --get-all safe.directory`, {
|
|
1748
|
+
stdout: true,
|
|
1749
|
+
silent: true,
|
|
1750
|
+
disableLog: true,
|
|
1751
|
+
silentOnError: true,
|
|
1752
|
+
});
|
|
1753
|
+
if (`${declared ?? ''}`.split('\n').some((entry) => entry.trim() === repoPath)) return;
|
|
1754
|
+
shellExec(`git config --global --add safe.directory "${repoPath}"`, { silent: true });
|
|
1755
|
+
},
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* Materializes a runtime's document root from the repository its conf route declares.
|
|
1759
|
+
*
|
|
1760
|
+
* Shared by every repository-backed runtime — {@link WpService} for WordPress site roots and
|
|
1761
|
+
* {@link LamppService} for static Apache document roots — so one deployment can serve many
|
|
1762
|
+
* repositories on the same host through a single provisioning contract.
|
|
1763
|
+
*
|
|
1764
|
+
* Idempotent: an existing directory is left alone, and the clone lands on a temporary path
|
|
1765
|
+
* first so an interrupted run never leaves a partial tree at the document root.
|
|
1766
|
+
*
|
|
1767
|
+
* @param {object} opts
|
|
1768
|
+
* @param {string} opts.host - Virtual-host name, for logging.
|
|
1769
|
+
* @param {string} opts.siteRoot - Absolute path the checkout must occupy.
|
|
1770
|
+
* @param {string} opts.repository - Repository URL or `owner/repo` short form.
|
|
1771
|
+
* @param {string} [opts.owner='daemon:daemon'] - Filesystem owner of the served tree.
|
|
1772
|
+
* @returns {{accessible: boolean, cloned: boolean}} Whether the remote answered, and whether
|
|
1773
|
+
* this call created the checkout.
|
|
1774
|
+
* @memberof UnderpostRepository
|
|
1775
|
+
*/
|
|
1776
|
+
provisionSiteRoot({ host, siteRoot, repository, owner = 'daemon:daemon' }) {
|
|
1777
|
+
// An `env:` reference to a variable the process never received resolves to an empty string,
|
|
1778
|
+
// so a conf fault and an unreachable remote arrive here as the same falsy value. Reported
|
|
1779
|
+
// together they read as a GitHub problem and send the search to the wrong place.
|
|
1780
|
+
const reference = `${repository ?? ''}`.trim();
|
|
1781
|
+
if (!reference) {
|
|
1782
|
+
logger.error(
|
|
1783
|
+
`${host}: no repository resolved for ${siteRoot} — the conf route's reference is empty, ` +
|
|
1784
|
+
`so its 'env:' variable is missing from this process's environment`,
|
|
1785
|
+
);
|
|
1786
|
+
return { accessible: false, cloned: false };
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (!process.env.GITHUB_TOKEN && Underpost.repo.repoUrlFactory(reference).startsWith('https://github.com/'))
|
|
1790
|
+
logger.warn(`${host}: GITHUB_TOKEN not set — git operations will fail for private repositories`);
|
|
1791
|
+
|
|
1792
|
+
if (!Underpost.repo.isRemoteRepo(reference)) {
|
|
1793
|
+
logger.warn(`${host}: remote repository not accessible (${reference})`);
|
|
1794
|
+
return { accessible: false, cloned: false };
|
|
1795
|
+
}
|
|
1796
|
+
// Provisioned means a checkout is there, not merely that the path is. The runtime image
|
|
1797
|
+
// ships the document-root directories, so an existence test calls every fresh pod already
|
|
1798
|
+
// provisioned, skips the clone, and leaves Apache serving 403 from a directory nothing
|
|
1799
|
+
// populates. `.git` is the only mark that the declared repository actually landed here.
|
|
1800
|
+
if (fs.existsSync(`${siteRoot}/.git`)) {
|
|
1801
|
+
logger.info(`${host}: repo already present at ${siteRoot}`);
|
|
1802
|
+
return { accessible: true, cloned: false };
|
|
1803
|
+
}
|
|
1804
|
+
const populateInPlace = fs.existsSync(siteRoot);
|
|
1805
|
+
if (populateInPlace) logger.warn(`${host}: ${siteRoot} holds no checkout; provisioning ${reference} into it`);
|
|
1806
|
+
|
|
1807
|
+
logger.info(`${host}: cloning ${reference} → ${siteRoot}`);
|
|
1808
|
+
const tmp = `${siteRoot}.tmp`;
|
|
1809
|
+
if (fs.existsSync(tmp)) shellExec(`sudo rm -rf "${tmp}"`);
|
|
1810
|
+
fs.mkdirSync(path.dirname(siteRoot), { recursive: true });
|
|
1811
|
+
const auth = Underpost.repo.gitAuthFactory(reference);
|
|
1812
|
+
shellExec(`git clone "${auth.url}" "${tmp}"`, { env: auth.env });
|
|
1813
|
+
// Copied into a directory that already exists rather than renamed over it: that directory
|
|
1814
|
+
// can be image-provided or a mount point, and a rename onto one fails where a copy works.
|
|
1815
|
+
if (populateInPlace) {
|
|
1816
|
+
shellExec(`sudo cp -a "${tmp}/." "${siteRoot}/"`);
|
|
1817
|
+
shellExec(`sudo rm -rf "${tmp}"`);
|
|
1818
|
+
} else shellExec(`sudo mv "${tmp}" "${siteRoot}"`);
|
|
1819
|
+
shellExec(`sudo chmod -R 755 "${siteRoot}"`);
|
|
1820
|
+
shellExec(`sudo chown -R ${owner} "${siteRoot}"`);
|
|
1821
|
+
return { accessible: true, cloned: true };
|
|
1822
|
+
},
|
|
1823
|
+
|
|
1625
1824
|
/**
|
|
1626
1825
|
* Resolves the in-pod site-root directory where a conf route's repository lives.
|
|
1627
1826
|
*
|
|
@@ -1701,7 +1900,7 @@ Prevent build private config repo.`,
|
|
|
1701
1900
|
backupPodRepositories({ deployId, namespace = 'default', env = 'production' }) {
|
|
1702
1901
|
const confServer = readConfJson(deployId, 'server', { resolve: true });
|
|
1703
1902
|
const githubToken = process.env.GITHUB_TOKEN || '';
|
|
1704
|
-
const githubUsername =
|
|
1903
|
+
const githubUsername = repositoryIdentityFactory().owner;
|
|
1705
1904
|
|
|
1706
1905
|
if (!githubToken) {
|
|
1707
1906
|
logger.warn('backupPodRepositories: GITHUB_TOKEN not available — git push will fail');
|
|
@@ -1760,7 +1959,7 @@ Prevent build private config repo.`,
|
|
|
1760
1959
|
`git config --global --add safe.directory '${siteRoot}' 2>/dev/null || true`,
|
|
1761
1960
|
`cd '${siteRoot}' && git add -A && git commit -m 'backup $(date -u +%Y-%m-%dT%H:%M:%SZ)' || true`,
|
|
1762
1961
|
`cd '${siteRoot}' && underpost push . ${githubUsername}/${repoName}`,
|
|
1763
|
-
`cd /home/dd/engine && node bin
|
|
1962
|
+
`cd /home/dd/engine && node bin host clean --force`,
|
|
1764
1963
|
].join(' && ');
|
|
1765
1964
|
|
|
1766
1965
|
try {
|
|
@@ -1805,7 +2004,7 @@ Prevent build private config repo.`,
|
|
|
1805
2004
|
throw new Error('privateEngineRepoFactory: GITHUB_USERNAME not set');
|
|
1806
2005
|
}
|
|
1807
2006
|
|
|
1808
|
-
const repoName =
|
|
2007
|
+
const repoName = Underpost.repo.privateRepoFactory(effectiveDeployId);
|
|
1809
2008
|
logger.info(`engine-private missing — cloning ${username}/${repoName}`);
|
|
1810
2009
|
shellExec(`underpost clone ${username}/${repoName}`);
|
|
1811
2010
|
if (!fs.existsSync(`./${repoName}`)) {
|
|
@@ -1832,11 +2031,11 @@ Prevent build private config repo.`,
|
|
|
1832
2031
|
|
|
1833
2032
|
/**
|
|
1834
2033
|
* Resolves the GitHub repository for a given instance runtime by scanning
|
|
1835
|
-
* every `conf.instances.json` listed in `./engine-private/deploy/dd.
|
|
2034
|
+
* every `conf.instances.json` listed in `./engine-private/deploy/dd.routes`.
|
|
1836
2035
|
*
|
|
1837
2036
|
* Resolution order:
|
|
1838
2037
|
* 1. If `runtime` is falsy, returns `${GITHUB_USERNAME}/engine`.
|
|
1839
|
-
* 2. Iterates each deploy ID found in `dd.
|
|
2038
|
+
* 2. Iterates each deploy ID found in `dd.routes` and looks for an instance
|
|
1840
2039
|
* whose `runtime` field matches the supplied value.
|
|
1841
2040
|
* 3. When a match is found, returns `instance.metadata.repository`.
|
|
1842
2041
|
* 4. Falls back to `${GITHUB_USERNAME}/engine` when no match is found.
|
|
@@ -1853,14 +2052,7 @@ Prevent build private config repo.`,
|
|
|
1853
2052
|
// (docker-image.<runtime>.dev.ci.yml) but resolves to the same instance
|
|
1854
2053
|
// repo as its production counterpart, so strip it before matching.
|
|
1855
2054
|
runtime = runtime.replace(/\.dev$/, '');
|
|
1856
|
-
const
|
|
1857
|
-
const deployIds = fs.existsSync(ddRouter)
|
|
1858
|
-
? fs
|
|
1859
|
-
.readFileSync(ddRouter, 'utf8')
|
|
1860
|
-
.split(',')
|
|
1861
|
-
.map((s) => s.trim())
|
|
1862
|
-
.filter(Boolean)
|
|
1863
|
-
: [];
|
|
2055
|
+
const deployIds = readDeployRoutes();
|
|
1864
2056
|
for (const deployId of deployIds) {
|
|
1865
2057
|
const confPath = `./engine-private/conf/${deployId}/conf.instances.json`;
|
|
1866
2058
|
if (!fs.existsSync(confPath)) continue;
|
|
@@ -1928,8 +2120,11 @@ Prevent build private config repo.`,
|
|
|
1928
2120
|
logger.info('[sparseCheckoutDirectory] path already present, skipping', localPath);
|
|
1929
2121
|
return false;
|
|
1930
2122
|
}
|
|
1931
|
-
const
|
|
1932
|
-
shellExec(`git clone --depth 1 --no-checkout ${
|
|
2123
|
+
const auth = Underpost.repo.gitAuthFactory(`https://github.com/${repoOwner}/${repoName}.git`);
|
|
2124
|
+
shellExec(`git clone --depth 1 --no-checkout "${auth.url}" ${targetDir}`, {
|
|
2125
|
+
disableLog: true,
|
|
2126
|
+
env: auth.env,
|
|
2127
|
+
});
|
|
1933
2128
|
shellExec(`cd ${targetDir} && git sparse-checkout set ${subPath} && git checkout`, { disableLog: true });
|
|
1934
2129
|
logger.info('[sparseCheckoutDirectory] sparse checkout complete', localPath);
|
|
1935
2130
|
return true;
|