@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/runtime/wp/Wp.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import fs from 'fs-extra';
|
|
15
15
|
import path from 'path';
|
|
16
|
-
import { shellExec } from '../../server/process.js';
|
|
17
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
16
|
+
import { shellExec } from '../../server/runtime/process.js';
|
|
17
|
+
import { loggerFactory } from '../../server/ops/logger.js';
|
|
18
18
|
import { Lampp } from '../lampp/Lampp.js';
|
|
19
19
|
import Underpost from '../../index.js';
|
|
20
20
|
|
|
@@ -141,14 +141,7 @@ class WpService {
|
|
|
141
141
|
({ freshInstall } = WpService.provisionFresh({ host, siteRoot: wpDir, db, wp, subDir }));
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
// Mark the directory as safe before git operations — the site root is owned
|
|
146
|
-
// by daemon:daemon (Apache) and git 2.35+ refuses to run in directories owned
|
|
147
|
-
// by a different user unless explicitly declared safe.
|
|
148
|
-
if (repository) {
|
|
149
|
-
shellExec(`git config --global --add safe.directory "${wpDir}"`);
|
|
150
|
-
Underpost.repo.initLocalRepo({ path: wpDir, origin: repository });
|
|
151
|
-
}
|
|
144
|
+
if (repository) Underpost.repo.initLocalRepo({ path: wpDir, origin: repository });
|
|
152
145
|
|
|
153
146
|
// Write a root .htaccess that rewrites / → /subDir/ when running in subdirectory mode
|
|
154
147
|
if (subDir) {
|
|
@@ -187,9 +180,9 @@ class WpService {
|
|
|
187
180
|
}
|
|
188
181
|
|
|
189
182
|
/**
|
|
190
|
-
* Clone mode —
|
|
191
|
-
* verifies `wp-config.php` exists.
|
|
192
|
-
*
|
|
183
|
+
* Clone mode — materializes `siteRoot` from `repository` via
|
|
184
|
+
* {@link UnderpostRepository.provisionSiteRoot}, then verifies `wp-config.php` exists.
|
|
185
|
+
* An unreachable remote or a checkout without `wp-config.php` falls back to a fresh install.
|
|
193
186
|
* @param {object} opts
|
|
194
187
|
* @param {string} opts.host - Virtual-host name (for logging).
|
|
195
188
|
* @param {string} opts.siteRoot - Absolute path where the site should live.
|
|
@@ -197,37 +190,11 @@ class WpService {
|
|
|
197
190
|
* @param {object|null} [opts.db] - MariaDB config used as fallback for fresh install.
|
|
198
191
|
*/
|
|
199
192
|
static provisionClone({ host, siteRoot, repository, db, wp, subDir = '' }) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Step 0 — verify the remote repository is reachable; fall back to fresh install if not.
|
|
205
|
-
// repository.js isRemoteRepo handles token injection internally.
|
|
206
|
-
const repoAccessible = Underpost.repo.isRemoteRepo(repository);
|
|
207
|
-
logger.info(`${host}: remote accessible = ${repoAccessible} (${repository})`);
|
|
208
|
-
if (!repoAccessible) {
|
|
209
|
-
logger.warn(`${host}: remote repository not accessible (${repository}) — running fresh install`);
|
|
210
|
-
return WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
|
|
211
|
-
}
|
|
193
|
+
const { accessible } = Underpost.repo.provisionSiteRoot({ host, siteRoot, repository });
|
|
194
|
+
if (!accessible) return WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
|
|
212
195
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
logger.info(`${host}: cloning ${repository} → ${siteRoot}`);
|
|
216
|
-
const cloneUrl = Underpost.repo.resolveAuthUrl(repository);
|
|
217
|
-
const tmp = `${siteRoot}.tmp`;
|
|
218
|
-
if (fs.existsSync(tmp)) shellExec(`sudo rm -rf "${tmp}"`);
|
|
219
|
-
shellExec(`git clone "${cloneUrl}" "${tmp}"`);
|
|
220
|
-
shellExec(`sudo mv "${tmp}" "${siteRoot}"`);
|
|
221
|
-
shellExec(`sudo chmod -R 755 "${siteRoot}"`);
|
|
222
|
-
shellExec(`sudo chown -R daemon:daemon "${siteRoot}"`);
|
|
223
|
-
} else {
|
|
224
|
-
logger.info(`${host}: repo already present at ${siteRoot}`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Step 2 — verify wp-config.php; if missing, wipe and do a fresh install.
|
|
228
|
-
// initLocalRepo is NOT called here — createApp always calls it after provisionClone
|
|
229
|
-
// returns (whether clone or fallback path) so we avoid a double-init and ensure
|
|
230
|
-
// safe.directory is declared before git runs.
|
|
196
|
+
// A checkout without wp-config.php is not a WordPress site: discard it and install fresh.
|
|
197
|
+
// initLocalRepo is left to createApp so both provisioning paths converge on one call.
|
|
231
198
|
if (!fs.existsSync(path.join(siteRoot, 'wp-config.php'))) {
|
|
232
199
|
logger.warn(`${host}: wp-config.php not found — wiping site root and running fresh install`);
|
|
233
200
|
shellExec(`sudo rm -rf "${siteRoot}"`);
|
|
@@ -515,10 +482,9 @@ Thumbs.db
|
|
|
515
482
|
const repoName = repository.split('/').pop().split('.')[0];
|
|
516
483
|
|
|
517
484
|
logger.info(`${host}: persisting site to repository`);
|
|
518
|
-
shellExec(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
);
|
|
485
|
+
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, {
|
|
486
|
+
silentOnError: true,
|
|
487
|
+
});
|
|
522
488
|
shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repoName} -f`);
|
|
523
489
|
logger.info(`${host}: initial commit pushed to ${githubOrg}/${repoName}`);
|
|
524
490
|
}
|
|
@@ -630,7 +596,9 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
|
|
|
630
596
|
|
|
631
597
|
// MariaDB export is handled by the shared db.js backup flow — no duplicate dump here.
|
|
632
598
|
if (fs.existsSync(path.join(siteRoot, '.git'))) {
|
|
633
|
-
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, {
|
|
599
|
+
shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, {
|
|
600
|
+
silentOnError: true,
|
|
601
|
+
});
|
|
634
602
|
shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repository.split('/').pop().split('.')[0]}`);
|
|
635
603
|
logger.info(`backup: git push done for ${siteRoot}`);
|
|
636
604
|
} else {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* deploy id resolve to the empty catalog — nothing else breaks.
|
|
9
9
|
*
|
|
10
10
|
* Each product catalog default-exports the uniform shape documented in
|
|
11
|
-
* {@link module:src/
|
|
11
|
+
* {@link module:src/projects/cyberia/catalog-cyberia.js}.
|
|
12
12
|
*
|
|
13
|
-
* @module src/server/catalog.js
|
|
13
|
+
* @module src/server/build/catalog.js
|
|
14
14
|
* @namespace Catalog
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -18,7 +18,13 @@ import fs from 'fs-extra';
|
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Empty product catalog returned for deploy ids without a dedicated module.
|
|
23
|
+
*
|
|
24
|
+
* The `package*` fields are the deploy's package manifest contract, read by
|
|
25
|
+
* {@link module:src/server/build/package.js}: a deploy id with no catalog gets the engine
|
|
26
|
+
* manifest under its own identity, and one with a catalog adds exactly what it declares here.
|
|
27
|
+
*/
|
|
22
28
|
const EMPTY_CATALOG = {
|
|
23
29
|
sourceMoves: [],
|
|
24
30
|
privateConfPaths: [],
|
|
@@ -28,6 +34,10 @@ const EMPTY_CATALOG = {
|
|
|
28
34
|
copies: [],
|
|
29
35
|
keywords: [],
|
|
30
36
|
description: '',
|
|
37
|
+
packageName: '',
|
|
38
|
+
packageBin: {},
|
|
39
|
+
packageDependencies: {},
|
|
40
|
+
packageScripts: {},
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
/**
|
|
@@ -44,7 +54,7 @@ const loadDeployCatalog = async (deployId) => {
|
|
|
44
54
|
const suffix = (deployId ?? '').split('dd-')[1];
|
|
45
55
|
if (!suffix) return EMPTY_CATALOG;
|
|
46
56
|
if (fs.existsSync(`./src/projects/${suffix}/catalog-${suffix}.js`)) {
|
|
47
|
-
const mod = await import(
|
|
57
|
+
const mod = await import(`../../projects/${suffix}/catalog-${suffix}.js`);
|
|
48
58
|
return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
|
|
49
59
|
}
|
|
50
60
|
return EMPTY_CATALOG;
|
|
@@ -63,7 +73,7 @@ const loadProductCatalogs = async () => {
|
|
|
63
73
|
const catalogs = [];
|
|
64
74
|
for (const file of await fs.readdir('./src/projects')) {
|
|
65
75
|
if (file === 'underpost') continue;
|
|
66
|
-
const mod = await import(
|
|
76
|
+
const mod = await import(`../../projects/${file}/catalog-${file}.js`);
|
|
67
77
|
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
68
78
|
}
|
|
69
79
|
return catalogs;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coverage HTML report as a build artifact: where a run leaves it, where an assembled
|
|
3
|
+
* deploy artifact carries it, and what is published when nothing was carried.
|
|
4
|
+
*
|
|
5
|
+
* The report is produced by the test stage (`npm run test:coverage`) and bundled into the
|
|
6
|
+
* deploy artifact at {@link COVERAGE_BUNDLE_DIRECTORY}. A workload container only ever
|
|
7
|
+
* copies it: running a test runner inside a pod costs minutes of the build phase, needs the
|
|
8
|
+
* dev dependency tree, and — because a suite's expected non-zero exits latch the runtime
|
|
9
|
+
* status contract — publishes a false `error` to the deployment monitor.
|
|
10
|
+
*
|
|
11
|
+
* @module src/server/build/coverage.js
|
|
12
|
+
* @namespace UnderpostCoverage
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs-extra';
|
|
16
|
+
import { UNDERPOST_TESTING } from './testing.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @constant COVERAGE_BUNDLE_DIRECTORY
|
|
20
|
+
* @description Path, relative to a source tree root, where an assembled deploy artifact
|
|
21
|
+
* carries the report. Deliberately not under `coverage/`, which every engine `.gitignore`
|
|
22
|
+
* excludes, so the bundled artifact is publishable source rather than a local run's output.
|
|
23
|
+
* @memberof UnderpostCoverage
|
|
24
|
+
*/
|
|
25
|
+
const COVERAGE_BUNDLE_DIRECTORY = 'docs/coverage';
|
|
26
|
+
|
|
27
|
+
const normalizeRoot = (root) => `${root ?? ''}`.replace(/\/+$/, '');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @method coverageReportCandidates
|
|
31
|
+
* @description Every place a coverage HTML report can live in a source tree, most local
|
|
32
|
+
* first: the reporters a run writes, then the bundled artifact a deploy artifact carries.
|
|
33
|
+
* @param {string} root - Source tree root.
|
|
34
|
+
* @returns {string[]} Candidate report directories.
|
|
35
|
+
* @memberof UnderpostCoverage
|
|
36
|
+
*/
|
|
37
|
+
const coverageReportCandidates = (root) => {
|
|
38
|
+
const base = normalizeRoot(root);
|
|
39
|
+
if (!base) return [];
|
|
40
|
+
const coverage = `${base}/${UNDERPOST_TESTING.coverageDirectory}`;
|
|
41
|
+
return [`${coverage}/html`, `${coverage}/lcov-report`, coverage, `${base}/${COVERAGE_BUNDLE_DIRECTORY}`];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @method resolveCoverageReportPath
|
|
46
|
+
* @description First candidate that actually carries an HTML index. A directory holding
|
|
47
|
+
* only `lcov.info` is not a report and is not published.
|
|
48
|
+
* @param {string} root - Source tree root.
|
|
49
|
+
* @returns {string|undefined} Report directory, or undefined when none is available.
|
|
50
|
+
* @memberof UnderpostCoverage
|
|
51
|
+
*/
|
|
52
|
+
const resolveCoverageReportPath = (root) =>
|
|
53
|
+
coverageReportCandidates(root).find((candidate) => fs.existsSync(`${candidate}/index.html`));
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @method bundleCoverageReport
|
|
57
|
+
* @description Copies the report of `fromRoot` into `toRoot`'s bundle directory, so the
|
|
58
|
+
* assembled artifact carries it into every container started from that source.
|
|
59
|
+
* @param {string} fromRoot - Tree the report was generated in.
|
|
60
|
+
* @param {string} toRoot - Assembled artifact root.
|
|
61
|
+
* @returns {{bundled: boolean, from?: string, to: string}} What was bundled, and where.
|
|
62
|
+
* @memberof UnderpostCoverage
|
|
63
|
+
*/
|
|
64
|
+
const bundleCoverageReport = (fromRoot, toRoot) => {
|
|
65
|
+
const to = `${normalizeRoot(toRoot)}/${COVERAGE_BUNDLE_DIRECTORY}`;
|
|
66
|
+
const from = resolveCoverageReportPath(fromRoot);
|
|
67
|
+
if (!from) return { bundled: false, to };
|
|
68
|
+
fs.emptyDirSync(to);
|
|
69
|
+
fs.copySync(from, to);
|
|
70
|
+
return { bundled: true, from, to };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @method coverageUnavailablePage
|
|
75
|
+
* @description The page served where a report was expected but none was bundled. A static
|
|
76
|
+
* document, never an invitation to generate one at runtime.
|
|
77
|
+
* @param {object} [options]
|
|
78
|
+
* @param {string} [options.command] - Command that produces the report.
|
|
79
|
+
* @returns {string} HTML document.
|
|
80
|
+
* @memberof UnderpostCoverage
|
|
81
|
+
*/
|
|
82
|
+
const coverageUnavailablePage = ({ command = 'npm run test:coverage' } = {}) => `<!doctype html>
|
|
83
|
+
<html lang="en">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="utf-8" />
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
87
|
+
<meta name="robots" content="noindex" />
|
|
88
|
+
<title>Coverage report unavailable</title>
|
|
89
|
+
<style>
|
|
90
|
+
:root { color-scheme: light dark; }
|
|
91
|
+
body { margin: 0; display: grid; place-items: center; min-height: 100vh;
|
|
92
|
+
font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
|
|
93
|
+
main { max-width: 34rem; padding: 2rem; }
|
|
94
|
+
h1 { font-size: 1.25rem; margin: 0 0 .75rem; }
|
|
95
|
+
p { margin: 0 0 .75rem; opacity: .8; }
|
|
96
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; }
|
|
97
|
+
</style>
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
<main>
|
|
101
|
+
<h1>Coverage report unavailable</h1>
|
|
102
|
+
<p>This build carried no coverage report. The report is generated by the test stage and
|
|
103
|
+
bundled into the deploy artifact under <code>${COVERAGE_BUNDLE_DIRECTORY}</code>.</p>
|
|
104
|
+
<p>Run <code>${command}</code> in the build environment, then assemble and publish the
|
|
105
|
+
artifact again.</p>
|
|
106
|
+
</main>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
COVERAGE_BUNDLE_DIRECTORY,
|
|
113
|
+
bundleCoverageReport,
|
|
114
|
+
coverageReportCandidates,
|
|
115
|
+
coverageUnavailablePage,
|
|
116
|
+
resolveCoverageReportPath,
|
|
117
|
+
};
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module is pure with respect to the environment: it classifies, resolves and
|
|
3
|
+
* decides, but never executes. Enforcement belongs to `src/server/runtime/process.js`,
|
|
4
|
+
* which owns the subprocess boundary.
|
|
5
|
+
*
|
|
6
|
+
* @module src/server/build/execution.js
|
|
7
|
+
* @namespace UnderpostExecution
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import nodePath from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const packageRoot = nodePath.resolve(nodePath.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @constant EXECUTION_PROFILE_ENV_KEY
|
|
18
|
+
* @description Environment key the active profile rides on into child processes. Reading it
|
|
19
|
+
* back is what makes a nested `underpost` invocation stay inside its parent's profile.
|
|
20
|
+
* @memberof UnderpostExecution
|
|
21
|
+
*/
|
|
22
|
+
const EXECUTION_PROFILE_ENV_KEY = 'UNDERPOST_EXECUTION_PROFILE';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @constant CAPABILITIES
|
|
26
|
+
* @description The side-effect classes a command can exercise, ordered least to most
|
|
27
|
+
* privileged. The order is load-bearing: a compound command takes the capability of its
|
|
28
|
+
* most privileged segment, so `kubectl create … | kubectl apply -f -` is a cluster write
|
|
29
|
+
* rather than a cluster read.
|
|
30
|
+
* @memberof UnderpostExecution
|
|
31
|
+
*/
|
|
32
|
+
const CAPABILITIES = ['fs', 'git', 'net', 'cluster:read', 'host:write', 'cluster:write'];
|
|
33
|
+
|
|
34
|
+
const capabilityRank = (capability) => CAPABILITIES.indexOf(capability);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @constant EXECUTION_PROFILES
|
|
38
|
+
* @description The profile table. `permits` is the closed set of capabilities a run may
|
|
39
|
+
* exercise; everything else is elided.
|
|
40
|
+
*
|
|
41
|
+
* `reads: 'neutral'` is what lets a denied read stay invisible to its caller: a blocked
|
|
42
|
+
* `cluster:read` returns the empty result that 200+ `silentOnError` call sites already
|
|
43
|
+
* treat as "not present", so no caller needs to learn a new failure mode.
|
|
44
|
+
* @memberof UnderpostExecution
|
|
45
|
+
*/
|
|
46
|
+
const EXECUTION_PROFILES = {
|
|
47
|
+
// Full access. The default, and the only profile that may mutate a cluster or a host.
|
|
48
|
+
LIVE_CLUSTER: {
|
|
49
|
+
name: 'LIVE_CLUSTER',
|
|
50
|
+
permits: [...CAPABILITIES],
|
|
51
|
+
reads: 'live',
|
|
52
|
+
description: 'Full access: cluster and host mutation permitted.',
|
|
53
|
+
},
|
|
54
|
+
// Manifest and artifact generation. Filesystem and git are build outputs, not
|
|
55
|
+
// environment mutation, so they stay permitted — a build that regenerates manifests
|
|
56
|
+
// and commits them is doing its job.
|
|
57
|
+
HERMETIC_BUILD: {
|
|
58
|
+
name: 'HERMETIC_BUILD',
|
|
59
|
+
permits: ['fs', 'git'],
|
|
60
|
+
reads: 'neutral',
|
|
61
|
+
description: 'Build outputs only: no cluster, host or network side effects.',
|
|
62
|
+
},
|
|
63
|
+
// Nothing executes. Every effect is reported as the intent it would have carried out.
|
|
64
|
+
OFFLINE_DRY_RUN: {
|
|
65
|
+
name: 'OFFLINE_DRY_RUN',
|
|
66
|
+
permits: [],
|
|
67
|
+
reads: 'neutral',
|
|
68
|
+
description: 'Nothing executes; every command is reported as intent.',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const DEFAULT_PROFILE = 'LIVE_CLUSTER';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @constant PROFILE_ALIAS_FLAGS
|
|
76
|
+
* @description Legacy bypass flags mapped onto the profile that subsumes them, so an
|
|
77
|
+
* operator who already knows the old flag lands in the right profile without learning a
|
|
78
|
+
* new one. The flags stay on the CLI surface — they are published entrypoints.
|
|
79
|
+
*
|
|
80
|
+
* Each alias is guarded by `when`, because a bypass flag is only equivalent to a profile
|
|
81
|
+
* in the mode it was written for. `--disable-update-underpost-config` means "skip one
|
|
82
|
+
* projection step" during a live deploy, but means "this run cannot reach a cluster at
|
|
83
|
+
* all" during a manifest build; aliasing it unconditionally would silently turn a real
|
|
84
|
+
* deploy into a no-op. Flags that are step toggles in every mode are deliberately absent:
|
|
85
|
+
* they keep their own guards, which the profile gate sits underneath rather than replaces.
|
|
86
|
+
* @memberof UnderpostExecution
|
|
87
|
+
*/
|
|
88
|
+
const PROFILE_ALIAS_FLAGS = {
|
|
89
|
+
disableUpdateUnderpostConfig: {
|
|
90
|
+
profile: 'HERMETIC_BUILD',
|
|
91
|
+
when: (options) => options?.buildManifest === true,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Verbs that only read cluster state. Everything else kubectl accepts is treated as a
|
|
96
|
+
// write: an unrecognised verb must not be assumed harmless.
|
|
97
|
+
const KUBECTL_READ_VERBS = new Set([
|
|
98
|
+
'get',
|
|
99
|
+
'describe',
|
|
100
|
+
'logs',
|
|
101
|
+
'top',
|
|
102
|
+
'explain',
|
|
103
|
+
'api-resources',
|
|
104
|
+
'api-versions',
|
|
105
|
+
'version',
|
|
106
|
+
'cluster-info',
|
|
107
|
+
'config',
|
|
108
|
+
'auth',
|
|
109
|
+
'diff',
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// kubectl flags that consume the token after them, which would otherwise be mistaken
|
|
113
|
+
// for the verb (`kubectl -n default get pods`).
|
|
114
|
+
const KUBECTL_VALUE_FLAGS = new Set(['-n', '--namespace', '--context', '--kubeconfig', '--server', '--user']);
|
|
115
|
+
|
|
116
|
+
const HOST_WRITE_BINARIES = new Set([
|
|
117
|
+
'systemctl',
|
|
118
|
+
'journalctl',
|
|
119
|
+
'dnf',
|
|
120
|
+
'yum',
|
|
121
|
+
'apt',
|
|
122
|
+
'apt-get',
|
|
123
|
+
'rpm',
|
|
124
|
+
'firewall-cmd',
|
|
125
|
+
'setenforce',
|
|
126
|
+
'semanage',
|
|
127
|
+
'restorecon',
|
|
128
|
+
'chcon',
|
|
129
|
+
'mount',
|
|
130
|
+
'umount',
|
|
131
|
+
'mkfs',
|
|
132
|
+
'parted',
|
|
133
|
+
'lvcreate',
|
|
134
|
+
'modprobe',
|
|
135
|
+
'sysctl',
|
|
136
|
+
'useradd',
|
|
137
|
+
'groupadd',
|
|
138
|
+
'usermod',
|
|
139
|
+
'lxc',
|
|
140
|
+
'lxd',
|
|
141
|
+
'virsh',
|
|
142
|
+
'virt-install',
|
|
143
|
+
'docker',
|
|
144
|
+
'podman',
|
|
145
|
+
'crictl',
|
|
146
|
+
'kind',
|
|
147
|
+
'kubeadm',
|
|
148
|
+
'k3s',
|
|
149
|
+
'wg',
|
|
150
|
+
'wg-quick',
|
|
151
|
+
'ip',
|
|
152
|
+
'iptables',
|
|
153
|
+
'nft',
|
|
154
|
+
'haproxy',
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
// `gh` reaches a remote service and writes to it (`gh secret set`, `gh release create`), so it
|
|
158
|
+
// is a network effect rather than local filesystem work — an unclassified binary would default
|
|
159
|
+
// to `fs` and run under HERMETIC_BUILD.
|
|
160
|
+
const NET_BINARIES = new Set(['curl', 'wget', 'ssh', 'scp', 'sftp', 'rsync', 'nc', 'ping', 'dig', 'nslookup', 'gh']);
|
|
161
|
+
|
|
162
|
+
const CLUSTER_BINARIES = new Set(['kubectl', 'helm']);
|
|
163
|
+
|
|
164
|
+
// Binaries that re-enter the engine. They are classified as local process work because
|
|
165
|
+
// the child resolves its own profile from the environment — gating them here would stop
|
|
166
|
+
// a hermetic build from reaching the very stages it exists to run.
|
|
167
|
+
const NESTED_CLI_BINARIES = new Set(['node', 'npm', 'npx', 'underpost']);
|
|
168
|
+
|
|
169
|
+
const stripSudo = (tokens) => {
|
|
170
|
+
if (tokens[0] !== 'sudo') return { tokens, escalated: false };
|
|
171
|
+
let index = 1;
|
|
172
|
+
// `sudo -u dd -E cmd` — step over sudo's own flags and their values.
|
|
173
|
+
while (index < tokens.length && tokens[index].startsWith('-')) {
|
|
174
|
+
const consumesValue = tokens[index] === '-u' || tokens[index] === '--user' || tokens[index] === '-g';
|
|
175
|
+
index += consumesValue ? 2 : 1;
|
|
176
|
+
}
|
|
177
|
+
return { tokens: tokens.slice(index), escalated: true };
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const kubectlVerb = (tokens) => {
|
|
181
|
+
for (let index = 1; index < tokens.length; index++) {
|
|
182
|
+
const token = tokens[index];
|
|
183
|
+
if (KUBECTL_VALUE_FLAGS.has(token)) {
|
|
184
|
+
index++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (token.startsWith('-')) continue;
|
|
188
|
+
return token;
|
|
189
|
+
}
|
|
190
|
+
return '';
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const classifySegment = (segment) => {
|
|
194
|
+
const tokens = `${segment ?? ''}`.trim().split(/\s+/).filter(Boolean);
|
|
195
|
+
if (tokens.length === 0) return 'fs';
|
|
196
|
+
|
|
197
|
+
const { tokens: bare, escalated } = stripSudo(tokens);
|
|
198
|
+
if (bare.length === 0) return escalated ? 'host:write' : 'fs';
|
|
199
|
+
|
|
200
|
+
// Leading `VAR=value` assignments are not the command.
|
|
201
|
+
let start = 0;
|
|
202
|
+
while (start < bare.length && /^[A-Z_][A-Z0-9_]*=/i.test(bare[start])) start++;
|
|
203
|
+
const binary = nodePath.basename(bare[start] ?? '');
|
|
204
|
+
const rest = bare.slice(start);
|
|
205
|
+
|
|
206
|
+
if (CLUSTER_BINARIES.has(binary)) {
|
|
207
|
+
if (binary === 'helm') {
|
|
208
|
+
const verb = rest[1] ?? '';
|
|
209
|
+
return verb === 'list' || verb === 'get' || verb === 'status' ? 'cluster:read' : 'cluster:write';
|
|
210
|
+
}
|
|
211
|
+
return KUBECTL_READ_VERBS.has(kubectlVerb(rest)) ? 'cluster:read' : 'cluster:write';
|
|
212
|
+
}
|
|
213
|
+
if (NESTED_CLI_BINARIES.has(binary)) return 'fs';
|
|
214
|
+
if (binary === 'git') return 'git';
|
|
215
|
+
if (HOST_WRITE_BINARIES.has(binary)) return 'host:write';
|
|
216
|
+
if (NET_BINARIES.has(binary)) return 'net';
|
|
217
|
+
|
|
218
|
+
// An escalated command we do not otherwise recognise is a host mutation by definition.
|
|
219
|
+
return escalated ? 'host:write' : 'fs';
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Classifies the side effect a command exercises.
|
|
224
|
+
*
|
|
225
|
+
* Compound commands resolve to their most privileged segment, so a pipeline cannot
|
|
226
|
+
* launder a write through a read.
|
|
227
|
+
* @param {string} cmd - The command string.
|
|
228
|
+
* @returns {string} One of {@link CAPABILITIES}.
|
|
229
|
+
* @memberof UnderpostExecution
|
|
230
|
+
*/
|
|
231
|
+
const classifyCommand = (cmd = '') => {
|
|
232
|
+
const text = `${cmd ?? ''}`;
|
|
233
|
+
if (!text.trim()) return 'fs';
|
|
234
|
+
// Heredoc bodies are data, not commands: `kubectl apply -f - <<'EOF' … EOF` must be
|
|
235
|
+
// classified on its first line alone, or YAML content would be parsed as a pipeline.
|
|
236
|
+
const [head] = text.split(/<<-?\s*'?[A-Za-z_]+'?/);
|
|
237
|
+
const segments = `${head}`.split(/&&|\|\||[;|]/);
|
|
238
|
+
let capability = 'fs';
|
|
239
|
+
for (const segment of segments) {
|
|
240
|
+
const segmentCapability = classifySegment(segment);
|
|
241
|
+
if (capabilityRank(segmentCapability) > capabilityRank(capability)) capability = segmentCapability;
|
|
242
|
+
}
|
|
243
|
+
return capability;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Resolves a profile by name, falling back to the default rather than throwing on an
|
|
248
|
+
* unknown value — an unreadable environment variable must not brick every command.
|
|
249
|
+
* @param {string} [name] - Profile name.
|
|
250
|
+
* @returns {object} The profile record.
|
|
251
|
+
* @memberof UnderpostExecution
|
|
252
|
+
*/
|
|
253
|
+
const executionProfileFactory = (name = '') => {
|
|
254
|
+
const key = `${name ?? ''}`.trim().toUpperCase().replace(/-/g, '_');
|
|
255
|
+
return EXECUTION_PROFILES[key] ?? EXECUTION_PROFILES[DEFAULT_PROFILE];
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Maps a CLI options object onto the profile it selects.
|
|
260
|
+
*
|
|
261
|
+
* An explicit `--profile` wins. Otherwise a legacy bypass flag selects the profile that
|
|
262
|
+
* subsumes it, but only in the mode where the two actually mean the same thing.
|
|
263
|
+
* @param {object} [options] - Commander options.
|
|
264
|
+
* @returns {string} Profile name.
|
|
265
|
+
* @memberof UnderpostExecution
|
|
266
|
+
*/
|
|
267
|
+
const profileFromOptionsFactory = (options = {}) => {
|
|
268
|
+
const explicit = `${options?.profile ?? ''}`.trim();
|
|
269
|
+
if (explicit) return executionProfileFactory(explicit).name;
|
|
270
|
+
for (const [flag, alias] of Object.entries(PROFILE_ALIAS_FLAGS))
|
|
271
|
+
if (options?.[flag] === true && alias.when(options)) return alias.profile;
|
|
272
|
+
return '';
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The profile in effect for this process.
|
|
277
|
+
* @returns {object} The active profile record.
|
|
278
|
+
* @memberof UnderpostExecution
|
|
279
|
+
*/
|
|
280
|
+
const activeExecutionProfile = () => executionProfileFactory(process.env[EXECUTION_PROFILE_ENV_KEY]);
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Installs a profile as the process-wide default.
|
|
284
|
+
*
|
|
285
|
+
* Writing the environment variable is the propagation mechanism: children inherit the
|
|
286
|
+
* environment, so a nested `underpost` invocation resolves the same profile with no
|
|
287
|
+
* command-string threading.
|
|
288
|
+
* @param {string} name - Profile name.
|
|
289
|
+
* @returns {object} The profile now in effect.
|
|
290
|
+
* @memberof UnderpostExecution
|
|
291
|
+
*/
|
|
292
|
+
const setExecutionProfile = (name) => {
|
|
293
|
+
const profile = executionProfileFactory(name);
|
|
294
|
+
process.env[EXECUTION_PROFILE_ENV_KEY] = profile.name;
|
|
295
|
+
return profile;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Runs `fn` under `name`, restoring the previous profile afterwards.
|
|
300
|
+
*
|
|
301
|
+
* Restores through a `finally` and awaits a thenable result, so a throwing or async
|
|
302
|
+
* stage cannot leak a hermetic profile into the rest of the process.
|
|
303
|
+
* @param {string} name - Profile to run under.
|
|
304
|
+
* @param {Function} fn - Work to run.
|
|
305
|
+
* @returns {*} Whatever `fn` returns.
|
|
306
|
+
* @memberof UnderpostExecution
|
|
307
|
+
*/
|
|
308
|
+
const withExecutionProfile = (name, fn) => {
|
|
309
|
+
const previous = process.env[EXECUTION_PROFILE_ENV_KEY];
|
|
310
|
+
const restore = () => {
|
|
311
|
+
if (previous === undefined) delete process.env[EXECUTION_PROFILE_ENV_KEY];
|
|
312
|
+
else process.env[EXECUTION_PROFILE_ENV_KEY] = previous;
|
|
313
|
+
};
|
|
314
|
+
setExecutionProfile(name);
|
|
315
|
+
let result;
|
|
316
|
+
try {
|
|
317
|
+
result = fn();
|
|
318
|
+
} catch (error) {
|
|
319
|
+
restore();
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
if (result && typeof result.then === 'function') return result.finally(restore);
|
|
323
|
+
restore();
|
|
324
|
+
return result;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Decides whether a command may run under the active profile.
|
|
329
|
+
*
|
|
330
|
+
* Returns a decision rather than acting on one; `shellExec` owns the consequence.
|
|
331
|
+
* @param {string} cmd - The command string.
|
|
332
|
+
* @param {object} [context] - Optional overrides.
|
|
333
|
+
* @param {string} [context.profile] - Profile name, defaulting to the active one.
|
|
334
|
+
* @returns {{permitted: boolean, capability: string, profile: string, neutral: boolean}} Decision.
|
|
335
|
+
* @memberof UnderpostExecution
|
|
336
|
+
*/
|
|
337
|
+
const executionDecisionFactory = (cmd, { profile } = {}) => {
|
|
338
|
+
const record = profile ? executionProfileFactory(profile) : activeExecutionProfile();
|
|
339
|
+
const capability = classifyCommand(cmd);
|
|
340
|
+
return {
|
|
341
|
+
permitted: record.permits.includes(capability),
|
|
342
|
+
capability,
|
|
343
|
+
profile: record.name,
|
|
344
|
+
neutral: record.reads === 'neutral',
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const localUnderpostCli = `node ${packageRoot}/bin/index.js`;
|
|
349
|
+
let underpostCliCache;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Resolves how to invoke a CLI from a subprocess.
|
|
353
|
+
*
|
|
354
|
+
* Two different questions hide behind "run underpost", and conflating them runs the
|
|
355
|
+
* wrong codebase:
|
|
356
|
+
*
|
|
357
|
+
* - `local: true` — re-enter the package this process is running from, resolved through
|
|
358
|
+
* `import.meta.url`. From a checkout that is the checkout; from a global install it is
|
|
359
|
+
* that same global install, so a deployed node is unaffected. Every multi-stage runner
|
|
360
|
+
* wants this: a stage that re-enters `underpost` must run the code its parent runs, or
|
|
361
|
+
* the two disagree about layout. A stale global install reading credential seeds from a
|
|
362
|
+
* directory this version moved is what that skew looks like in practice.
|
|
363
|
+
* - default — whichever CLI this machine is meant to use: the globally linked binary
|
|
364
|
+
* when one exists, otherwise this checkout by absolute path, so a build box without a
|
|
365
|
+
* global install still works and callers that `cd` elsewhere keep resolving the same
|
|
366
|
+
* CLI.
|
|
367
|
+
*
|
|
368
|
+
* This replaces the `options.dev ? 'node bin' : 'underpost'` branch copied across 14 call
|
|
369
|
+
* sites, none of which agreed on what happened when neither resolved.
|
|
370
|
+
* @param {string} [name] - CLI to resolve.
|
|
371
|
+
* @param {object} [options] - Resolution options.
|
|
372
|
+
* @param {boolean} [options.local] - Re-enter this checkout instead of a global install.
|
|
373
|
+
* @returns {string} Command prefix to invoke it with.
|
|
374
|
+
* @memberof UnderpostExecution
|
|
375
|
+
*/
|
|
376
|
+
const cli = (name = 'underpost', { local = false } = {}) => {
|
|
377
|
+
if (name !== 'underpost') return name;
|
|
378
|
+
if (local) return localUnderpostCli;
|
|
379
|
+
if (underpostCliCache) return underpostCliCache;
|
|
380
|
+
try {
|
|
381
|
+
execFileSync('sh', ['-c', 'command -v underpost'], { stdio: 'ignore' });
|
|
382
|
+
underpostCliCache = 'underpost';
|
|
383
|
+
} catch {
|
|
384
|
+
underpostCliCache = localUnderpostCli;
|
|
385
|
+
}
|
|
386
|
+
return underpostCliCache;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export {
|
|
390
|
+
activeExecutionProfile,
|
|
391
|
+
CAPABILITIES,
|
|
392
|
+
classifyCommand,
|
|
393
|
+
cli,
|
|
394
|
+
EXECUTION_PROFILE_ENV_KEY,
|
|
395
|
+
EXECUTION_PROFILES,
|
|
396
|
+
executionDecisionFactory,
|
|
397
|
+
executionProfileFactory,
|
|
398
|
+
PROFILE_ALIAS_FLAGS,
|
|
399
|
+
profileFromOptionsFactory,
|
|
400
|
+
setExecutionProfile,
|
|
401
|
+
withExecutionProfile,
|
|
402
|
+
};
|