@underpostnet/cyberia 3.3.0 → 3.3.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +8 -4
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +3 -19
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/downloader.js +0 -73
- package/src/server/environment.js +0 -98
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { expect } from 'chai';
|
|
4
4
|
import http from 'node:http';
|
|
5
5
|
import net from 'node:net';
|
|
6
|
+
import Underpost from '../../../../src/index.js';
|
|
6
7
|
import {
|
|
7
8
|
FORWARD_PROXY,
|
|
8
9
|
fetchViaForwardProxy,
|
|
@@ -19,32 +20,46 @@ import {
|
|
|
19
20
|
forwardProxyTargetFactory,
|
|
20
21
|
forwardProxyTunnelTargetFactory,
|
|
21
22
|
forwardProxyUnitFactory,
|
|
22
|
-
} from '
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
} from '../../../../src/server/network/forward-proxy.js';
|
|
24
|
+
import { redactSensitiveText } from '../../../../src/server/ops/logger.js';
|
|
25
|
+
import { ShellExecError, shellArgumentFactory } from '../../../../src/server/runtime/process.js';
|
|
26
|
+
import { probeGroupsFactory, prometheusConfFactory } from '../../../../src/server/ops/monitoring.js';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import { Dns } from '../../../../src/server/network/dns.js';
|
|
29
|
+
import { homeDirectoryPathFactory } from '../../../../src/server/ops/systemd.js';
|
|
30
|
+
import UnderpostWireguard, {
|
|
31
|
+
ENGINE_SYNC_STEPS,
|
|
25
32
|
UNDERPOST_EDGE,
|
|
33
|
+
localPeerFactory,
|
|
26
34
|
allowedIpsConflictsFactory,
|
|
35
|
+
assertEdgeIdentity,
|
|
27
36
|
backendNameFactory,
|
|
28
37
|
defaultPeerFactory,
|
|
29
38
|
deployListFactory,
|
|
39
|
+
edgeContextFactory,
|
|
30
40
|
edgeRouteTableFactory,
|
|
31
|
-
|
|
41
|
+
endpointHostFactory,
|
|
32
42
|
firewallCommandsFactory,
|
|
33
43
|
haproxyConfFactory,
|
|
34
44
|
haproxyMapsFactory,
|
|
35
45
|
hostProxyEntriesFactory,
|
|
46
|
+
hubFactory,
|
|
36
47
|
instanceProxyEntriesFactory,
|
|
37
48
|
mergeRouteTablesFactory,
|
|
49
|
+
nodeIdentityFactory,
|
|
50
|
+
nodeNameCandidatesFactory,
|
|
38
51
|
peerFactory,
|
|
39
52
|
quicForwardCommandsFactory,
|
|
40
53
|
redirectHostFactory,
|
|
54
|
+
topologyFactory,
|
|
41
55
|
tunnelAddressFactory,
|
|
42
56
|
tunnelNetworkCidrFactory,
|
|
57
|
+
unregisteredPeersFactory,
|
|
43
58
|
wireguardClientConfFactory,
|
|
44
|
-
|
|
59
|
+
wireguardHealthFactory,
|
|
45
60
|
wireguardServerConfFactory,
|
|
46
61
|
wireguardStatusFactory,
|
|
47
|
-
} from '
|
|
62
|
+
} from '../../../../src/cli/wireguard.js';
|
|
48
63
|
|
|
49
64
|
// The conf shape the PRD names: two published sites, each fronted by a bare
|
|
50
65
|
// domain that only redirects to its `www` host.
|
|
@@ -94,6 +109,10 @@ const PEERS = [
|
|
|
94
109
|
{ id: 'homelab-b', address: '10.0.0.3', publicKey: 'BBB=', hosts: ['www.nexodev.org'], instances: ['mmo-server'] },
|
|
95
110
|
];
|
|
96
111
|
|
|
112
|
+
// Emitted only when this checkout ships the script it names, so the case needs a tree that
|
|
113
|
+
// carries the deploy: the base template restores no deploy id of its own.
|
|
114
|
+
const shipsCyberiaPackageScript = fs.existsSync('./deploy/dd-cyberia/package.sh');
|
|
115
|
+
|
|
97
116
|
describe('edge hub routing', () => {
|
|
98
117
|
describe('hostProxyEntriesFactory', () => {
|
|
99
118
|
it('extracts every domain that declares proxy ports, with its redirect target', () => {
|
|
@@ -289,7 +308,7 @@ describe('edge hub routing', () => {
|
|
|
289
308
|
});
|
|
290
309
|
|
|
291
310
|
describe('deployListFactory', () => {
|
|
292
|
-
it('expands the dd meta id through dd.
|
|
311
|
+
it('expands the dd meta id through dd.routes', () => {
|
|
293
312
|
const list = deployListFactory('dd');
|
|
294
313
|
expect(list.length).to.be.above(0);
|
|
295
314
|
for (const id of list) expect(id.startsWith('dd-')).to.equal(true);
|
|
@@ -535,15 +554,6 @@ describe('edge hub routing', () => {
|
|
|
535
554
|
);
|
|
536
555
|
expect(client).to.include('PostDown = iptables -D FORWARD -o %i -d 10.0.0.0/24 -j ACCEPT');
|
|
537
556
|
});
|
|
538
|
-
|
|
539
|
-
it('recovers repeatable client settings from an installed config without reading a private key', () => {
|
|
540
|
-
expect(wireguardClientSettingsFactory(client)).to.deep.equal({
|
|
541
|
-
address: '10.0.0.2/32',
|
|
542
|
-
hubPublicKey: 'HUB=',
|
|
543
|
-
endpoint: 'vps.example.com:51820',
|
|
544
|
-
cidr: '10.0.0.0/24',
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
557
|
});
|
|
548
558
|
|
|
549
559
|
describe('quicForwardCommandsFactory', () => {
|
|
@@ -582,41 +592,41 @@ describe('edge hub routing', () => {
|
|
|
582
592
|
|
|
583
593
|
describe('firewallCommandsFactory', () => {
|
|
584
594
|
it('opens both transports and the tunnel port on the hub', () => {
|
|
585
|
-
const commands = firewallCommandsFactory({ role: '
|
|
595
|
+
const commands = firewallCommandsFactory({ role: 'hub' }).join('\n');
|
|
586
596
|
for (const port of ['80/tcp', '443/tcp', '443/udp', '51820/udp']) expect(commands).to.include(port);
|
|
587
597
|
});
|
|
588
598
|
|
|
589
599
|
// A spoke dials out; it publishes nothing. What it does need is for the
|
|
590
600
|
// tunnel interface to sit in a zone that permits the forwarded traffic.
|
|
591
601
|
it('opens nothing on a spoke beyond trusting the tunnel interface', () => {
|
|
592
|
-
const commands = firewallCommandsFactory({ role: '
|
|
602
|
+
const commands = firewallCommandsFactory({ role: 'control' }).join('\n');
|
|
593
603
|
expect(commands).to.include('--zone=trusted --add-interface=wg0');
|
|
594
604
|
expect(commands).to.not.include('--add-port');
|
|
595
605
|
});
|
|
596
606
|
|
|
597
607
|
it('is a no-op where firewalld is not running', () => {
|
|
598
|
-
for (const command of firewallCommandsFactory({ role: '
|
|
608
|
+
for (const command of firewallCommandsFactory({ role: 'hub' }))
|
|
599
609
|
expect(command).to.include('systemctl is-active --quiet firewalld');
|
|
600
610
|
});
|
|
601
611
|
|
|
602
612
|
// A reset that leaves the ports open has not returned the host to zero, and
|
|
603
613
|
// the only way the two directions cannot drift is sharing one rule list.
|
|
604
614
|
it('withdraws exactly the rules it opens, for either role', () => {
|
|
605
|
-
const opened = firewallCommandsFactory({ role: '
|
|
606
|
-
const withdrawn = firewallCommandsFactory({ role: '
|
|
615
|
+
const opened = firewallCommandsFactory({ role: 'hub', listenPort: 51821 });
|
|
616
|
+
const withdrawn = firewallCommandsFactory({ role: 'hub', listenPort: 51821, remove: true });
|
|
607
617
|
expect(withdrawn).to.have.lengthOf(opened.length);
|
|
608
618
|
expect(withdrawn.join('\n')).to.equal(opened.join('\n').replace(/--add-/g, '--remove-'));
|
|
609
|
-
expect(firewallCommandsFactory({ role: '
|
|
619
|
+
expect(firewallCommandsFactory({ role: 'worker', remove: true }).join('\n')).to.include(
|
|
610
620
|
'--zone=trusted --remove-interface=wg0',
|
|
611
621
|
);
|
|
612
622
|
});
|
|
613
623
|
});
|
|
614
624
|
|
|
615
625
|
describe('wireguardStatusFactory', () => {
|
|
616
|
-
// One row per spoke carries both halves of its state: what
|
|
626
|
+
// One row per spoke carries both halves of its state: what topology
|
|
617
627
|
// binds to it, and what the live interface reports — so there is no second
|
|
618
628
|
// command to list peers.
|
|
619
|
-
it('folds link state onto
|
|
629
|
+
it('folds link state onto topology bindings', () => {
|
|
620
630
|
const rows = wireguardStatusFactory({
|
|
621
631
|
peers: PEERS,
|
|
622
632
|
latestHandshakes: 'AAA=\t1000\nBBB=\t0',
|
|
@@ -628,6 +638,8 @@ describe('edge hub routing', () => {
|
|
|
628
638
|
{
|
|
629
639
|
id: 'homelab-a',
|
|
630
640
|
address: '10.0.0.2',
|
|
641
|
+
managementHost: '',
|
|
642
|
+
publicKey: 'AAA=',
|
|
631
643
|
allowedIPs: ['10.0.0.2/32', '192.168.10.0/24'],
|
|
632
644
|
hosts: ['www.dogmadual.com'],
|
|
633
645
|
instances: [],
|
|
@@ -641,6 +653,8 @@ describe('edge hub routing', () => {
|
|
|
641
653
|
{
|
|
642
654
|
id: 'homelab-b',
|
|
643
655
|
address: '10.0.0.3',
|
|
656
|
+
managementHost: '',
|
|
657
|
+
publicKey: 'BBB=',
|
|
644
658
|
allowedIPs: ['10.0.0.3/32'],
|
|
645
659
|
hosts: ['www.nexodev.org'],
|
|
646
660
|
instances: ['mmo-server'],
|
|
@@ -664,8 +678,8 @@ describe('edge hub routing', () => {
|
|
|
664
678
|
});
|
|
665
679
|
});
|
|
666
680
|
|
|
667
|
-
describe('
|
|
668
|
-
it('fills the defaults
|
|
681
|
+
describe('topology normalization', () => {
|
|
682
|
+
it('fills the defaults hand-written topology may omit', () => {
|
|
669
683
|
const peer = peerFactory({ id: 'homelab-a', address: '10.0.0.2' });
|
|
670
684
|
expect(peer.allowedIPs).to.deep.equal(['10.0.0.2/32']);
|
|
671
685
|
expect(peer.hosts).to.deep.equal([]);
|
|
@@ -673,13 +687,14 @@ describe('edge hub routing', () => {
|
|
|
673
687
|
expect(peer.default).to.equal(false);
|
|
674
688
|
});
|
|
675
689
|
|
|
676
|
-
// The three routing bindings are the whole
|
|
690
|
+
// The three routing bindings are the whole topology surface; anything else
|
|
677
691
|
// a hand-edited file carries is not a binding the edge honours.
|
|
678
692
|
it('keeps only the three routing bindings on a peer', () => {
|
|
679
693
|
const peer = peerFactory({ id: 'a', address: '10.0.0.2', clients: ['legacy'], keepalive: 15 });
|
|
680
694
|
expect(Object.keys(peer)).to.deep.equal([
|
|
681
695
|
'id',
|
|
682
696
|
'address',
|
|
697
|
+
'managementHost',
|
|
683
698
|
'publicKey',
|
|
684
699
|
'allowedIPs',
|
|
685
700
|
'hosts',
|
|
@@ -694,26 +709,101 @@ describe('edge hub routing', () => {
|
|
|
694
709
|
]);
|
|
695
710
|
});
|
|
696
711
|
|
|
697
|
-
it('drops
|
|
698
|
-
const
|
|
699
|
-
expect(
|
|
712
|
+
it('drops topology peers with no id', () => {
|
|
713
|
+
const hub = hubFactory({ peers: [{ address: '10.0.0.2' }, { id: 'ok', address: '10.0.0.3' }] });
|
|
714
|
+
expect(hub.peers.map((peer) => peer.id)).to.deep.equal(['ok']);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('keeps machine identity fields out of deployment topology', () => {
|
|
718
|
+
const topology = topologyFactory({
|
|
719
|
+
'64.176.25.136': {
|
|
720
|
+
role: 'hub',
|
|
721
|
+
nodeName: 'vultr',
|
|
722
|
+
peerId: 'wrong',
|
|
723
|
+
peers: [],
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
expect(topology['64.176.25.136']).to.not.have.keys('role', 'nodeName', 'peerId');
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('derives a node runtime context without storing host identity in topology', () => {
|
|
730
|
+
const context = edgeContextFactory({
|
|
731
|
+
topology: {
|
|
732
|
+
'64.176.25.136': {
|
|
733
|
+
publicKey: 'HUB=',
|
|
734
|
+
peers: [{ id: 'homelab-a', address: '10.0.0.2', publicKey: 'SPOKE=' }],
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
identity: { nodeName: 'localhost.localdomain', role: 'control', hubHost: '64.176.25.136', peerId: 'homelab-a' },
|
|
738
|
+
});
|
|
739
|
+
expect(context.role).to.equal('control');
|
|
740
|
+
expect(context.peerId).to.equal('homelab-a');
|
|
741
|
+
expect(context.endpoint).to.equal('64.176.25.136:51820');
|
|
742
|
+
expect(context.hubPublicKey).to.equal('HUB=');
|
|
700
743
|
});
|
|
701
744
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
expect(
|
|
709
|
-
expect(
|
|
745
|
+
it('keeps management routing separate from tunnel routing', () => {
|
|
746
|
+
const peer = peerFactory({
|
|
747
|
+
id: 'worker-a',
|
|
748
|
+
address: '10.0.0.3',
|
|
749
|
+
managementHost: '192.168.1.191',
|
|
750
|
+
});
|
|
751
|
+
expect(peer.managementHost).to.equal('192.168.1.191');
|
|
752
|
+
expect(peer.allowedIPs).to.deep.equal(['10.0.0.3/32']);
|
|
710
753
|
});
|
|
711
754
|
|
|
712
|
-
it('
|
|
713
|
-
|
|
714
|
-
expect(
|
|
715
|
-
expect(
|
|
716
|
-
|
|
755
|
+
it('extracts the public host from IPv4, DNS, and IPv6 endpoints', () => {
|
|
756
|
+
expect(endpointHostFactory('64.176.25.136:51820')).to.equal('64.176.25.136');
|
|
757
|
+
expect(endpointHostFactory('edge.example.com:51820')).to.equal('edge.example.com');
|
|
758
|
+
expect(endpointHostFactory('[2001:db8::1]:51820')).to.equal('2001:db8::1');
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('requires a fresh hub handshake off the hub', () => {
|
|
762
|
+
const context = {
|
|
763
|
+
role: 'control',
|
|
764
|
+
peerId: 'homelab-a',
|
|
765
|
+
address: '10.0.0.2',
|
|
766
|
+
hubPublicKey: 'HUB=',
|
|
767
|
+
};
|
|
768
|
+
expect(
|
|
769
|
+
wireguardHealthFactory({ context, active: 'active', latestHandshakes: 'HUB=\t1000', now: 1030 }).ok,
|
|
770
|
+
).to.equal(true);
|
|
771
|
+
expect(
|
|
772
|
+
wireguardHealthFactory({ context, active: 'active', latestHandshakes: 'HUB=\t1000', now: 1200 }).ok,
|
|
773
|
+
).to.equal(false);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('requires the default routing peer to reconnect on the hub', () => {
|
|
777
|
+
const context = {
|
|
778
|
+
role: 'hub',
|
|
779
|
+
peers: [
|
|
780
|
+
{ id: 'homelab-a', address: '10.0.0.2', publicKey: 'DEFAULT=', default: true },
|
|
781
|
+
{ id: 'worker-a', address: '10.0.0.3', publicKey: 'WORKER=' },
|
|
782
|
+
],
|
|
783
|
+
};
|
|
784
|
+
const disconnected = wireguardHealthFactory({ context, active: 'active', latestHandshakes: '', now: 1030 });
|
|
785
|
+
expect(disconnected).to.include({ ok: false, peerId: 'homelab-a', handshakeAgeSeconds: null });
|
|
786
|
+
const connected = wireguardHealthFactory({
|
|
787
|
+
context,
|
|
788
|
+
active: 'active',
|
|
789
|
+
latestHandshakes: 'DEFAULT=\t1000',
|
|
790
|
+
now: 1030,
|
|
791
|
+
});
|
|
792
|
+
expect(connected).to.include({ ok: true, peerId: 'homelab-a', handshakeAgeSeconds: 30 });
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('refuses remediation on a different role or peer identity', () => {
|
|
796
|
+
const state = { role: 'control', peerId: 'homelab-a' };
|
|
797
|
+
expect(() => assertEdgeIdentity(state, { expectedRole: 'hub' })).to.throw("expected role 'hub'");
|
|
798
|
+
expect(() => assertEdgeIdentity(state, { expectedRole: 'control', expectedId: 'worker-a' })).to.throw(
|
|
799
|
+
"expected peer 'worker-a'",
|
|
800
|
+
);
|
|
801
|
+
expect(assertEdgeIdentity(state, { expectedRole: 'control', expectedId: 'homelab-a' })).to.deep.equal(state);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('normalizes only supported node roles', () => {
|
|
805
|
+
expect(nodeIdentityFactory({ role: 'control' }).role).to.equal('control');
|
|
806
|
+
expect(nodeIdentityFactory({ role: 'invalid' }).role).to.equal('');
|
|
717
807
|
});
|
|
718
808
|
});
|
|
719
809
|
});
|
|
@@ -841,16 +931,16 @@ describe('edge hub forward proxy', () => {
|
|
|
841
931
|
// The listener binds the tunnel address alone, and the rule narrows the port
|
|
842
932
|
// to the tunnel CIDR on top of that — the port is reachable from nowhere else.
|
|
843
933
|
it('admits the proxy port from the tunnel only, on the hub only', () => {
|
|
844
|
-
const hub = firewallCommandsFactory({ role: '
|
|
934
|
+
const hub = firewallCommandsFactory({ role: 'hub' }).join('\n');
|
|
845
935
|
expect(hub).to.include(
|
|
846
936
|
`--add-rich-rule="rule family=ipv4 source address=${UNDERPOST_EDGE.tunnelCidr} port port=${FORWARD_PROXY.port} protocol=tcp accept"`,
|
|
847
937
|
);
|
|
848
938
|
expect(hub).to.not.include(`--add-port=${FORWARD_PROXY.port}`);
|
|
849
|
-
expect(firewallCommandsFactory({ role: '
|
|
939
|
+
expect(firewallCommandsFactory({ role: 'control' }).join('\n')).to.not.include('rich-rule');
|
|
850
940
|
});
|
|
851
941
|
|
|
852
942
|
it('admits a custom recorded tunnel subnet instead of the default', () => {
|
|
853
|
-
expect(firewallCommandsFactory({ role: '
|
|
943
|
+
expect(firewallCommandsFactory({ role: 'hub', tunnelCidr: '10.23.0.0/24' }).join('\n')).to.include(
|
|
854
944
|
`source address=10.23.0.0/24 port port=${FORWARD_PROXY.port} protocol=tcp accept`,
|
|
855
945
|
);
|
|
856
946
|
expect(tunnelNetworkCidrFactory('10.23.7.9/20')).to.equal('10.23.0.0/20');
|
|
@@ -1010,7 +1100,7 @@ describe('edge hub forward proxy', () => {
|
|
|
1010
1100
|
let proxy;
|
|
1011
1101
|
let proxyConfig;
|
|
1012
1102
|
|
|
1013
|
-
|
|
1103
|
+
beforeAll(async () => {
|
|
1014
1104
|
origin = await listen(
|
|
1015
1105
|
http.createServer((req, res) => {
|
|
1016
1106
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
@@ -1026,7 +1116,7 @@ describe('edge hub forward proxy', () => {
|
|
|
1026
1116
|
proxyConfig = { host: '127.0.0.1', port: port(proxy), apiKey: API_KEY };
|
|
1027
1117
|
});
|
|
1028
1118
|
|
|
1029
|
-
|
|
1119
|
+
afterAll(() => {
|
|
1030
1120
|
for (const server of servers) server.close();
|
|
1031
1121
|
});
|
|
1032
1122
|
|
|
@@ -1128,9 +1218,9 @@ describe('edge hub forward proxy', () => {
|
|
|
1128
1218
|
const SPOKE = [peerFactory({ id: 'homelab-a', address: '10.0.0.2', publicKey: 'AAA=', default: true })];
|
|
1129
1219
|
|
|
1130
1220
|
it('is closed by default, so no edge exposes SSH it was not told to', () => {
|
|
1131
|
-
expect(
|
|
1132
|
-
expect(
|
|
1133
|
-
expect(
|
|
1221
|
+
expect(hubFactory({}).sshForwardPort).to.equal(0);
|
|
1222
|
+
expect(hubFactory({ sshForwardPort: 'nonsense' }).sshForwardPort).to.equal(0);
|
|
1223
|
+
expect(hubFactory({ sshForwardPort: 2222 }).sshForwardPort).to.equal(2222);
|
|
1134
1224
|
expect(haproxyConfFactory({ peers: SPOKE, defaultPeerId: 'homelab-a' })).to.not.include('fe_ssh');
|
|
1135
1225
|
});
|
|
1136
1226
|
|
|
@@ -1163,15 +1253,519 @@ describe('edge hub forward proxy', () => {
|
|
|
1163
1253
|
});
|
|
1164
1254
|
|
|
1165
1255
|
it('opens the port publicly, and withdraws it on teardown', () => {
|
|
1166
|
-
const opened = firewallCommandsFactory({ role: '
|
|
1256
|
+
const opened = firewallCommandsFactory({ role: 'hub', sshForwardPort: 2222 }).join('\n');
|
|
1167
1257
|
expect(opened).to.include('--add-port=2222/tcp');
|
|
1168
|
-
const closed = firewallCommandsFactory({ role: '
|
|
1258
|
+
const closed = firewallCommandsFactory({ role: 'hub', sshForwardPort: 2222, remove: true }).join('\n');
|
|
1169
1259
|
expect(closed).to.include('--remove-port=2222/tcp');
|
|
1170
1260
|
});
|
|
1171
1261
|
|
|
1172
1262
|
it('opens no SSH port when none is configured, and never on a spoke', () => {
|
|
1173
|
-
expect(firewallCommandsFactory({ role: '
|
|
1174
|
-
expect(firewallCommandsFactory({ role: '
|
|
1263
|
+
expect(firewallCommandsFactory({ role: 'hub' }).join('\n')).to.not.include('2222');
|
|
1264
|
+
expect(firewallCommandsFactory({ role: 'worker', sshForwardPort: 2222 }).join('\n')).to.not.include('2222');
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
// A spoke is identified by the key it presents, not by the name topology
|
|
1269
|
+
// gives it. Reporting the name without the key is what turns "the hub has the
|
|
1270
|
+
// wrong key for this machine" into an unanswerable question.
|
|
1271
|
+
describe('peer identity in --status', () => {
|
|
1272
|
+
it('reports the public key alongside the topology name', () => {
|
|
1273
|
+
const [row] = wireguardStatusFactory({
|
|
1274
|
+
peers: [{ id: 'homelab-a', address: '10.0.0.2', publicKey: 'w1M+9VHZ=' }],
|
|
1275
|
+
latestHandshakes: 'w1M+9VHZ=\t1000',
|
|
1276
|
+
now: 1030,
|
|
1277
|
+
});
|
|
1278
|
+
expect(row.publicKey).to.equal('w1M+9VHZ=');
|
|
1279
|
+
expect(row.id).to.equal('homelab-a');
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('carries the key through the off-box view too', () => {
|
|
1283
|
+
const hub = hubFactory({ peers: [{ id: 'a', address: '10.0.0.2', publicKey: 'KEY=' }] });
|
|
1284
|
+
expect(hub.peers[0].publicKey).to.equal('KEY=');
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// `wg set` adds a peer and never removes the one it supersedes, so a corrected
|
|
1289
|
+
// --peer-add leaves the previous identity on the wire holding its old
|
|
1290
|
+
// handshake — invisible to every view that is keyed by topology.
|
|
1291
|
+
describe('unregisteredPeersFactory', () => {
|
|
1292
|
+
const PEER = { id: 'homelab-a', address: '10.0.0.2', publicKey: 'CURRENT=' };
|
|
1293
|
+
|
|
1294
|
+
it('reports an identity on the wire that topology does not name', () => {
|
|
1295
|
+
expect(
|
|
1296
|
+
unregisteredPeersFactory({
|
|
1297
|
+
peers: [PEER],
|
|
1298
|
+
latestHandshakes: 'CURRENT=\t900\nSUPERSEDED=\t500',
|
|
1299
|
+
now: 1000,
|
|
1300
|
+
}),
|
|
1301
|
+
).to.deep.equal([{ publicKey: 'SUPERSEDED=', handshakeAgeSeconds: 500 }]);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
it('reports a leftover that never handshaked as an unknown age, not as fresh', () => {
|
|
1305
|
+
const [row] = unregisteredPeersFactory({ peers: [PEER], latestHandshakes: 'GHOST=\t0', now: 1000 });
|
|
1306
|
+
expect(row).to.deep.equal({ publicKey: 'GHOST=', handshakeAgeSeconds: null });
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
it('is quiet when the wire matches topology', () => {
|
|
1310
|
+
expect(unregisteredPeersFactory({ peers: [PEER], latestHandshakes: 'CURRENT=\t900', now: 1000 })).to.deep.equal(
|
|
1311
|
+
[],
|
|
1312
|
+
);
|
|
1313
|
+
expect(unregisteredPeersFactory({ peers: [PEER], latestHandshakes: '' })).to.deep.equal([]);
|
|
1314
|
+
});
|
|
1315
|
+
});
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
describe('node document resolution', () => {
|
|
1319
|
+
it('prefers the full hostname, then its short name', () => {
|
|
1320
|
+
expect(nodeNameCandidatesFactory('vultr.guest')).to.deep.equal(['vultr.guest', 'vultr']);
|
|
1321
|
+
expect(nodeNameCandidatesFactory('localhost.localdomain')).to.deep.equal(['localhost.localdomain', 'localhost']);
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it('offers one candidate when the hostname carries no domain', () => {
|
|
1325
|
+
expect(nodeNameCandidatesFactory('vultr')).to.deep.equal(['vultr']);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
it('rejects a hostname a node document could not be named after', () => {
|
|
1329
|
+
expect(() => nodeNameCandidatesFactory('bad/name')).to.throw('node names may contain only');
|
|
1330
|
+
});
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
describe('engine sync', () => {
|
|
1334
|
+
const account = process.env.GITHUB_USERNAME;
|
|
1335
|
+
const token = process.env.GITHUB_TOKEN;
|
|
1336
|
+
let getDefaultBranch;
|
|
1337
|
+
|
|
1338
|
+
beforeEach(() => {
|
|
1339
|
+
getDefaultBranch = Underpost.repo.getDefaultBranch;
|
|
1340
|
+
Underpost.repo.getDefaultBranch = (repo) => (repo.endsWith('/engine') ? 'master' : 'main');
|
|
1341
|
+
});
|
|
1342
|
+
afterEach(() => {
|
|
1343
|
+
Underpost.repo.getDefaultBranch = getDefaultBranch;
|
|
1344
|
+
if (account === undefined) delete process.env.GITHUB_USERNAME;
|
|
1345
|
+
else process.env.GITHUB_USERNAME = account;
|
|
1346
|
+
if (token === undefined) delete process.env.GITHUB_TOKEN;
|
|
1347
|
+
else process.env.GITHUB_TOKEN = token;
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
it('accepts a slug or a clone URL for the same repository', () => {
|
|
1351
|
+
for (const reference of [
|
|
1352
|
+
'underpostnet/engine',
|
|
1353
|
+
'https://github.com/underpostnet/engine',
|
|
1354
|
+
'https://github.com/underpostnet/engine.git',
|
|
1355
|
+
'git@github.com:underpostnet/engine.git',
|
|
1356
|
+
])
|
|
1357
|
+
expect(Underpost.repo.repoSlugFactory(reference), reference).to.equal('underpostnet/engine');
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
it('keeps GitHub credentials out of Git URLs and command arguments', () => {
|
|
1361
|
+
process.env.GITHUB_TOKEN = 'github-token-fixture';
|
|
1362
|
+
const auth = Underpost.repo.gitAuthFactory('https://x-access-token:stale-token@github.com/underpostnet/engine.git');
|
|
1363
|
+
|
|
1364
|
+
expect(auth.url).to.equal('https://github.com/underpostnet/engine.git');
|
|
1365
|
+
expect(auth.url).to.not.match(/token|@github/);
|
|
1366
|
+
expect(auth.env.GIT_TERMINAL_PROMPT).to.equal('0');
|
|
1367
|
+
expect(auth.env.GIT_CONFIG_KEY_1).to.equal('http.https://github.com/.extraheader');
|
|
1368
|
+
expect(Buffer.from(auth.env.GIT_CONFIG_VALUE_1.split(' ').pop(), 'base64').toString()).to.equal(
|
|
1369
|
+
'x-access-token:github-token-fixture',
|
|
1370
|
+
);
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
it('refuses a reference that names no owner', () => {
|
|
1374
|
+
expect(() => Underpost.repo.repoSlugFactory('engine')).to.throw('is not an owner/repo reference');
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
it('pairs an engine source with the conf repository of the id they share', () => {
|
|
1378
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1379
|
+
expect(Underpost.repo.enginePairFactory()).to.deep.equal({
|
|
1380
|
+
engine: 'someone/engine',
|
|
1381
|
+
enginePrivate: 'someone/engine-private',
|
|
1382
|
+
});
|
|
1383
|
+
for (const engine of ['underpostnet/engine-test-lampp', 'underpostnet/engine-lampp'])
|
|
1384
|
+
expect(Underpost.repo.enginePairFactory({ engine }), engine).to.deep.equal({
|
|
1385
|
+
engine,
|
|
1386
|
+
enginePrivate: 'underpostnet/engine-lampp-private',
|
|
1387
|
+
});
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
it('takes the conf repository owner from the engine, not from the account', () => {
|
|
1391
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1392
|
+
expect(Underpost.repo.enginePairFactory({ engine: 'https://github.com/underpostnet/engine.git' })).to.deep.equal({
|
|
1393
|
+
engine: 'underpostnet/engine',
|
|
1394
|
+
enginePrivate: 'underpostnet/engine-private',
|
|
1395
|
+
});
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
it('lets an explicit conf repository override the pairing', () => {
|
|
1399
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1400
|
+
expect(
|
|
1401
|
+
Underpost.repo.enginePairFactory({
|
|
1402
|
+
engine: 'underpostnet/engine-test-lampp',
|
|
1403
|
+
enginePrivate: 'https://github.com/underpostnet/engine-core-private.git',
|
|
1404
|
+
}),
|
|
1405
|
+
).to.deep.equal({
|
|
1406
|
+
engine: 'underpostnet/engine-test-lampp',
|
|
1407
|
+
enginePrivate: 'underpostnet/engine-core-private',
|
|
1408
|
+
});
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
it('binds every repository placeholder to the configured account', () => {
|
|
1412
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1413
|
+
const commands = UnderpostWireguard.API.syncCommands().map((step) => step.command);
|
|
1414
|
+
expect(commands.join('\n')).to.not.match(/<[a-z-]+>/);
|
|
1415
|
+
expect(commands.some((command) => command.includes('package.sh'))).to.equal(false);
|
|
1416
|
+
expect(commands).to.include('node bin cmt --switch-repo someone/engine --target-branch master');
|
|
1417
|
+
expect(commands).to.include(
|
|
1418
|
+
'node bin cmt ./engine-private --switch-repo someone/engine-private --target-branch main',
|
|
1419
|
+
);
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
it('pulls the named engine while the private repository follows the account', () => {
|
|
1423
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1424
|
+
const commands = UnderpostWireguard.API.syncCommands({
|
|
1425
|
+
repoEngine: 'https://github.com/underpostnet/engine.git',
|
|
1426
|
+
}).map((step) => step.command);
|
|
1427
|
+
expect(commands).to.include('node bin cmt --switch-repo underpostnet/engine --target-branch master');
|
|
1428
|
+
expect(commands).to.include(
|
|
1429
|
+
'node bin cmt ./engine-private --switch-repo underpostnet/engine-private --target-branch main',
|
|
1430
|
+
);
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it('overrides the private repository with --repo-engine-private', () => {
|
|
1434
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1435
|
+
const commands = UnderpostWireguard.API.syncCommands({
|
|
1436
|
+
repoEngine: 'https://github.com/underpostnet/engine.git',
|
|
1437
|
+
repoEnginePrivate: 'https://github.com/underposnet/engine-core-private.git',
|
|
1438
|
+
}).map((step) => step.command);
|
|
1439
|
+
expect(commands).to.include('node bin cmt --switch-repo underpostnet/engine --target-branch master');
|
|
1440
|
+
expect(commands).to.include(
|
|
1441
|
+
'node bin cmt ./engine-private --switch-repo underposnet/engine-core-private --target-branch main',
|
|
1442
|
+
);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
it('runs only --cmd in place of the sync sequence, no placeholders unresolved', () => {
|
|
1446
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1447
|
+
const commands = UnderpostWireguard.API.syncCommands({ cmd: 'uptime, hostname -I' }).map((step) => step.command);
|
|
1448
|
+
expect(commands).to.deep.equal(['uptime', 'hostname -I']);
|
|
1449
|
+
expect(commands.join('\n')).to.not.match(/<[a-z-]+>/);
|
|
1450
|
+
expect(commands).to.not.include('node bin cmt --switch-repo');
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
it('labels a --cmd fleet run as a command, not a sync', () => {
|
|
1454
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1455
|
+
const script = UnderpostWireguard.API.syncScript({ cmd: 'uptime, hostname' });
|
|
1456
|
+
expect(script).to.include("echo '[cmd] uptime'");
|
|
1457
|
+
expect(script).to.include("echo '[cmd] hostname'");
|
|
1458
|
+
expect(script).to.not.include('[sync]');
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
it('names the branch the node must fetch instead of letting it guess', () => {
|
|
1462
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1463
|
+
Underpost.repo.getDefaultBranch = () => 'trunk';
|
|
1464
|
+
expect(UnderpostWireguard.API.syncCommands().map((step) => step.command)).to.include(
|
|
1465
|
+
'node bin cmt --switch-repo someone/engine --target-branch trunk',
|
|
1466
|
+
);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
it('reaches a node once, chaining every declared step in order', () => {
|
|
1470
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1471
|
+
const script = UnderpostWireguard.API.syncScript();
|
|
1472
|
+
const commands = UnderpostWireguard.API.syncCommands().map((step) => step.command);
|
|
1473
|
+
expect(script.match(/echo '\[sync\] /g) || []).to.have.lengthOf(commands.length);
|
|
1474
|
+
expect(script).to.not.include(';\n');
|
|
1475
|
+
let cursor = -1;
|
|
1476
|
+
for (const command of commands) {
|
|
1477
|
+
const at = script.indexOf(`echo '[sync] ${redactSensitiveText(command)}'`);
|
|
1478
|
+
expect(at, command).to.be.greaterThan(cursor);
|
|
1479
|
+
cursor = at;
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
it('redacts a credential from the remote progress line without changing the executed step', () => {
|
|
1484
|
+
process.env.GITHUB_TOKEN = 'github-token-fixture';
|
|
1485
|
+
const script = UnderpostWireguard.API.syncScript();
|
|
1486
|
+
|
|
1487
|
+
expect(script).to.include("echo '[sync] node bin host set GITHUB_TOKEN [REDACTED]'");
|
|
1488
|
+
expect(script).to.include('node bin host set GITHUB_TOKEN github-token-fixture');
|
|
1489
|
+
expect(script.match(/github-token-fixture/g)).to.have.lengthOf(1);
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
it('reconciles the dispatcher for the role of the node it reached', () => {
|
|
1493
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1494
|
+
const dispatcher = (nodeRole) =>
|
|
1495
|
+
UnderpostWireguard.API.syncCommands({ nodeRole }).find((step) => step.command.includes('underpost-event'));
|
|
1496
|
+
|
|
1497
|
+
// Only a control node is in a position to answer Alertmanager and repair the tunnel, and
|
|
1498
|
+
// `event --service` refuses to install the dispatcher anywhere else. Regression: `systemctl
|
|
1499
|
+
// restart` reran a generated unit whose ExecStart named a Node binary systemd could not
|
|
1500
|
+
// execute, until it rate-limited; only the generator re-probes that path and rewrites it.
|
|
1501
|
+
expect(dispatcher('control').command).to.include('node bin event --service');
|
|
1502
|
+
expect(dispatcher('control').command).to.not.include('systemctl');
|
|
1503
|
+
for (const nodeRole of ['worker', 'hub'])
|
|
1504
|
+
expect(dispatcher(nodeRole).command, nodeRole).to.include('node bin event --service-stop');
|
|
1505
|
+
expect(dispatcher('')).to.equal(undefined);
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
it('reconciles the hub forward proxy, which runs the checkout the switch replaced', () => {
|
|
1509
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1510
|
+
const proxy = (nodeRole) =>
|
|
1511
|
+
UnderpostWireguard.API.syncCommands({ nodeRole }).find((step) => step.command.includes('forward-proxy'));
|
|
1512
|
+
|
|
1513
|
+
expect(proxy('hub').command).to.include('node bin wireguard --forward-proxy-server');
|
|
1514
|
+
expect(proxy('hub').command).to.include('test -f /etc/systemd/system/underpost-forward-proxy.service');
|
|
1515
|
+
expect(proxy('hub').halt).to.equal(false);
|
|
1516
|
+
// The proxy binds the hub's own tunnel address, and `--forward-proxy-server` refuses to
|
|
1517
|
+
// configure itself anywhere else.
|
|
1518
|
+
for (const nodeRole of ['control', 'worker', '']) expect(proxy(nodeRole), nodeRole).to.equal(undefined);
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
it('acts on the unit file rather than on the unit being active', () => {
|
|
1522
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1523
|
+
// Regression: guarding on `is-active` skipped exactly the dispatcher that needs the restart,
|
|
1524
|
+
// the one the previous package scope left dead. The unit file is what says a node runs one.
|
|
1525
|
+
const step = ENGINE_SYNC_STEPS.find((entry) => entry.command.includes('<event-service-command>'));
|
|
1526
|
+
expect(step.command).to.include('test -f /etc/systemd/system/underpost-event.service');
|
|
1527
|
+
expect(step.command).to.not.include('is-active');
|
|
1528
|
+
// A node that does not run the dispatcher must not fail its sync over it.
|
|
1529
|
+
expect(step.halt).to.equal(false);
|
|
1530
|
+
const reconcile = UnderpostWireguard.API.syncCommands({ nodeRole: 'control' }).at(-1);
|
|
1531
|
+
expect(UnderpostWireguard.API.syncScript({ nodeRole: 'control' })).to.include(`{ ${reconcile.command} || true; }`);
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
it('records the pair it switched the node onto, after the switch rather than before it', () => {
|
|
1535
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1536
|
+
// deploy/lib/host.sh reads these back out of the host configuration store to decide which
|
|
1537
|
+
// source a later `prepare_host` pulls from; without them the node's own deploy scripts
|
|
1538
|
+
// prepare it from their built-in default and undo the switch.
|
|
1539
|
+
const commands = UnderpostWireguard.API.syncCommands({
|
|
1540
|
+
repoEngine: 'underpostnet/engine-test-cyberia',
|
|
1541
|
+
}).map((step) => step.command);
|
|
1542
|
+
const at = (command) => commands.indexOf(command);
|
|
1543
|
+
|
|
1544
|
+
expect(at('node bin host set ENGINE_SRC_REPO underpostnet/engine-test-cyberia')).to.be.greaterThan(
|
|
1545
|
+
at('node bin cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
|
|
1546
|
+
);
|
|
1547
|
+
expect(at('node bin host set ENGINE_SRC_PRIVATE_REPO underpostnet/engine-cyberia-private')).to.be.greaterThan(
|
|
1548
|
+
at('node bin cmt --switch-repo underpostnet/engine-test-cyberia --target-branch master'),
|
|
1549
|
+
);
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
it('republishes the cron manifests from the control node alone', () => {
|
|
1553
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1554
|
+
const cron = (nodeRole) =>
|
|
1555
|
+
UnderpostWireguard.API.syncCommands({ nodeRole }).find((step) => step.command.includes('bin cron'));
|
|
1556
|
+
|
|
1557
|
+
// The pod bodies run the checkout the switch replaced, so the manifests are republished — but
|
|
1558
|
+
// only where they can be. Regression: a worker carrying kubectl without cluster access
|
|
1559
|
+
// rewrote the cron deploy's manifests and relabeled host directories before failing.
|
|
1560
|
+
expect(cron('control').command).to.equal('node bin cron --setup-start --git --apply');
|
|
1561
|
+
expect(cron('control').halt).to.equal(false);
|
|
1562
|
+
for (const nodeRole of ['worker', 'hub', '']) expect(cron(nodeRole), nodeRole).to.equal(undefined);
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
it.skipIf(!shipsCyberiaPackageScript)(
|
|
1566
|
+
'runs a deploy package script when the deploy ships one, and installs the checkout when it does not',
|
|
1567
|
+
() => {
|
|
1568
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1569
|
+
const scripts = (repoEngine) =>
|
|
1570
|
+
UnderpostWireguard.API.syncCommands({ repoEngine })
|
|
1571
|
+
.filter((step) => step.command.startsWith('bash '))
|
|
1572
|
+
.map((step) => step.command);
|
|
1573
|
+
|
|
1574
|
+
expect(scripts('underpostnet/engine-test-cyberia')).to.deep.equal(['bash ./deploy/dd-cyberia/package.sh']);
|
|
1575
|
+
expect(scripts('underpostnet/engine-test-absent')).to.deep.equal([]);
|
|
1576
|
+
expect(scripts('underpostnet/engine')).to.deep.equal([]);
|
|
1577
|
+
|
|
1578
|
+
// A sync that belongs to no deploy still lands new source, so the install is never dropped
|
|
1579
|
+
// — it falls back to the checkout's own manifest rather than leaving none.
|
|
1580
|
+
const commands = (repoEngine) => UnderpostWireguard.API.syncCommands({ repoEngine }).map((step) => step.command);
|
|
1581
|
+
expect(commands('underpostnet/engine')).to.include('npm install');
|
|
1582
|
+
expect(commands('underpostnet/engine-test-cyberia')).to.not.include('npm install');
|
|
1583
|
+
},
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
it('installs after the source switch with no CLI step in between', () => {
|
|
1587
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1588
|
+
// Regression: the conf switch sat between the source switch and the install, so it ran the
|
|
1589
|
+
// freshly landed source against the previous `node_modules` and died importing a dependency
|
|
1590
|
+
// that switch had just added. Every step here is the node's own CLI, so none of them may
|
|
1591
|
+
// observe a checkout whose packages predate it.
|
|
1592
|
+
for (const repoEngine of ['underpostnet/engine', 'underpostnet/engine-test-cyberia']) {
|
|
1593
|
+
const commands = UnderpostWireguard.API.syncCommands({ repoEngine, nodeRole: 'control' }).map(
|
|
1594
|
+
(step) => step.command,
|
|
1595
|
+
);
|
|
1596
|
+
const switchAt = commands.findIndex(
|
|
1597
|
+
(command) => command.startsWith('node bin cmt --switch-repo') && !command.includes('./engine-private'),
|
|
1598
|
+
);
|
|
1599
|
+
const installAt = commands.findIndex(
|
|
1600
|
+
(command, index) => index > switchAt && (command.startsWith('bash ') || command === 'npm install'),
|
|
1601
|
+
);
|
|
1602
|
+
|
|
1603
|
+
expect(switchAt, repoEngine).to.be.greaterThan(-1);
|
|
1604
|
+
expect(installAt, repoEngine).to.equal(switchAt + 1);
|
|
1605
|
+
// The conf switch has to precede it, since the install reads the manifest that switch lands.
|
|
1606
|
+
expect(
|
|
1607
|
+
commands.findIndex((command) => command.includes('./engine-private --switch-repo')),
|
|
1608
|
+
repoEngine,
|
|
1609
|
+
).to.be.lessThan(switchAt);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
it('restores the checkout manifest before the bootstrap install', () => {
|
|
1614
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1615
|
+
// A previous sync left the deploy manifest copied over ./package.json, so installing without
|
|
1616
|
+
// restoring it reinstalls the previous scope and leaves the CLI unable to load new source.
|
|
1617
|
+
const commands = UnderpostWireguard.API.syncCommands().map((step) => step.command);
|
|
1618
|
+
expect(commands[0]).to.equal('git checkout -- package.json; npm install');
|
|
1619
|
+
// Sequenced, not chained: a tree that is not a repository still has to reach the install.
|
|
1620
|
+
expect(commands[0]).to.not.include('&&');
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
it('neutralizes advisory steps in place and chains the rest', () => {
|
|
1624
|
+
process.env.GITHUB_USERNAME = 'someone';
|
|
1625
|
+
const script = UnderpostWireguard.API.syncScript();
|
|
1626
|
+
for (const step of UnderpostWireguard.API.syncCommands())
|
|
1627
|
+
expect(script, step.command).to.include(step.halt ? `' && ${step.command}` : `' && { ${step.command} || true; }`);
|
|
1628
|
+
});
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
describe('peer locality', () => {
|
|
1632
|
+
const addresses = new Set(['192.168.1.85', '10.0.0.2']);
|
|
1633
|
+
|
|
1634
|
+
it('is this machine only when the peer management address is one of its own', () => {
|
|
1635
|
+
expect(localPeerFactory('192.168.1.85', addresses)).to.equal(true);
|
|
1636
|
+
expect(localPeerFactory(' 192.168.1.85 ', addresses)).to.equal(true);
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
it('is remote for a peer that lives on another host sharing this hostname', () => {
|
|
1640
|
+
expect(localPeerFactory('192.168.1.191', addresses)).to.equal(false);
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
it('fails towards SSH when the peer has no verifiable management address', () => {
|
|
1644
|
+
expect(localPeerFactory('', addresses)).to.equal(false);
|
|
1645
|
+
expect(localPeerFactory('control-plane.lan', addresses)).to.equal(false);
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
describe('command credential redaction', () => {
|
|
1650
|
+
it('masks a token embedded in a clone URL', () => {
|
|
1651
|
+
expect(redactSensitiveText('git fetch --force "https://x-access-token:ghp_secret@github.com/o/r" main')).to.equal(
|
|
1652
|
+
'git fetch --force "https://***@github.com/o/r" main',
|
|
1653
|
+
);
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
it('leaves a URL that carries no credential alone', () => {
|
|
1657
|
+
expect(redactSensitiveText('git remote set-url origin "https://github.com/o/r"')).to.equal(
|
|
1658
|
+
'git remote set-url origin "https://github.com/o/r"',
|
|
1659
|
+
);
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
it('masks token environment assignments in failed command wrappers', () => {
|
|
1663
|
+
expect(redactSensitiveText('GITHUB_TOKEN=ghp_secret GITHUB_USERNAME=operator node bin db')).to.equal(
|
|
1664
|
+
'GITHUB_TOKEN=[REDACTED] GITHUB_USERNAME=operator node bin db',
|
|
1665
|
+
);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
it('keeps the secret out of the error a failed command throws', () => {
|
|
1669
|
+
const error = new ShellExecError('git push https://ghp_secret@github.com/o/r', 128, '', '');
|
|
1670
|
+
expect(error.message).to.not.include('ghp_secret');
|
|
1671
|
+
expect(error.cmd).to.not.include('ghp_secret');
|
|
1672
|
+
});
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
describe('shell argument quoting', () => {
|
|
1676
|
+
it('carries a comma-separated command list through as one argument', () => {
|
|
1677
|
+
expect(shellArgumentFactory('cd /home/dd, npm install, npm link')).to.equal("'cd /home/dd, npm install, npm link'");
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
it('closes, escapes and reopens an embedded single quote', () => {
|
|
1681
|
+
expect(shellArgumentFactory(`echo 'hi'`)).to.equal(`'echo '\\''hi'\\'''`);
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
it('leaves double quotes and expansions literal', () => {
|
|
1685
|
+
expect(shellArgumentFactory('echo "$HOME"')).to.equal('\'echo "$HOME"\'');
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
it('quotes an empty value rather than dropping the argument', () => {
|
|
1689
|
+
expect(shellArgumentFactory('')).to.equal("''");
|
|
1690
|
+
expect(shellArgumentFactory(undefined)).to.equal("''");
|
|
1691
|
+
});
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
describe('tunnel firewall zone', () => {
|
|
1695
|
+
const zoneRule = (role, remove = false) =>
|
|
1696
|
+
firewallCommandsFactory({ role, remove }).find((command) => command.includes('-interface=wg0'));
|
|
1697
|
+
|
|
1698
|
+
it('zones the tunnel interface for every role, the hub included', () => {
|
|
1699
|
+
for (const role of ['hub', 'control', 'worker'])
|
|
1700
|
+
expect(zoneRule(role), role).to.include('--zone=trusted --add-interface=wg0');
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
it('withdraws the same interface it added', () => {
|
|
1704
|
+
expect(zoneRule('hub', true)).to.include('--zone=trusted --remove-interface=wg0');
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
it('keeps the hub public ports without losing the zone', () => {
|
|
1708
|
+
const rules = firewallCommandsFactory({ role: 'hub' }).join('\n');
|
|
1709
|
+
expect(rules).to.include('--add-port=51820/udp');
|
|
1710
|
+
expect(rules).to.include('--add-masquerade');
|
|
1711
|
+
expect(rules).to.include('--add-interface=wg0');
|
|
1712
|
+
});
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
describe('probe scheduling', () => {
|
|
1716
|
+
it('gives each interval its own scrape job so cadences do not merge', () => {
|
|
1717
|
+
const groups = probeGroupsFactory([
|
|
1718
|
+
{ eventId: 'a', module: 'icmp', interval: '30s', targets: ['10.0.0.1'], labels: {} },
|
|
1719
|
+
{ eventId: 'b', module: 'icmp', interval: '30s', targets: ['10.0.0.3'], labels: {} },
|
|
1720
|
+
{ eventId: 'c', module: 'http_2xx', interval: '10m', targets: ['https://x/'], labels: {} },
|
|
1721
|
+
]);
|
|
1722
|
+
expect(groups.map((group) => `${group.module}@${group.interval}`)).to.deep.equal(['icmp@30s', 'http_2xx@10m']);
|
|
1723
|
+
expect(groups[0].groups).to.have.lengthOf(2);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
it('renders the declared interval as the job scrape period', () => {
|
|
1727
|
+
const conf = prometheusConfFactory({
|
|
1728
|
+
probes: [{ eventId: 'c', module: 'http_2xx', interval: '10m', targets: ['https://x/'], labels: {} }],
|
|
1175
1729
|
});
|
|
1730
|
+
expect(conf).to.include("job_name: 'blackbox-http_2xx'");
|
|
1731
|
+
expect(conf).to.include('scrape_interval: 10m');
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
it('omits the period when an event declares none, leaving the global default', () => {
|
|
1735
|
+
const conf = prometheusConfFactory({
|
|
1736
|
+
probes: [{ eventId: 'c', module: 'icmp', interval: '', targets: ['10.0.0.1'], labels: {} }],
|
|
1737
|
+
});
|
|
1738
|
+
expect(conf).to.not.include('scrape_interval: \n');
|
|
1739
|
+
});
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
describe('port-scoped ingress blocking', () => {
|
|
1743
|
+
it('keeps only well-formed TCP ports', () => {
|
|
1744
|
+
expect(Dns.portListFactory('80,443')).to.deep.equal([80, 443]);
|
|
1745
|
+
expect(Dns.portListFactory(' 80 , bad , 443 , 99999 , 0 ')).to.deep.equal([80, 443]);
|
|
1746
|
+
expect(Dns.portListFactory('')).to.deep.equal([]);
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
it('exposes the block and withdraw entry points the scenario calls', () => {
|
|
1750
|
+
for (const method of ['blockIngressPort', 'unblockIngressPort', 'portListFactory'])
|
|
1751
|
+
expect(Dns[method], method).to.be.a('function');
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
it('creates the chain its rules live in before touching it', () => {
|
|
1755
|
+
// `inet filter` exists on a firewalld host with no `input` chain, so every
|
|
1756
|
+
// ingress command fails on a table that looks present.
|
|
1757
|
+
const source = fs.readFileSync(new URL('../../../../src/server/network/dns.js', import.meta.url), 'utf8');
|
|
1758
|
+
for (const method of ['blockAllIngress', 'unblockAllIngress', 'blockIngressPort', 'unblockIngressPort']) {
|
|
1759
|
+
const body = source.slice(source.indexOf(`static ${method}(`));
|
|
1760
|
+
expect(body.slice(0, body.indexOf('\n }')), method).to.include('Dns.ensureIngressChain()');
|
|
1761
|
+
}
|
|
1762
|
+
expect(Dns.ensureIngressChain).to.be.a('function');
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
it('calls the helper on the class that declares it', () => {
|
|
1766
|
+
// The class is `Dns`; `UnderpostDns` is the CLI wrapper and carries no such
|
|
1767
|
+
// static, so naming it here fails only once the command runs on a host.
|
|
1768
|
+
const source = fs.readFileSync(new URL('../../../../src/server/network/dns.js', import.meta.url), 'utf8');
|
|
1769
|
+
expect(source).to.not.include('UnderpostDns.portListFactory');
|
|
1176
1770
|
});
|
|
1177
1771
|
});
|