@underpostnet/cyberia 3.3.0 → 3.3.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +8 -4
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +3 -19
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/downloader.js +0 -73
- package/src/server/environment.js +0 -98
package/src/cli/ssh.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { generateRandomPasswordSelection } from '../client/components/core/CommonJs.js';
|
|
8
|
-
import {
|
|
9
|
-
import { loggerFactory } from '../server/logger.js';
|
|
10
|
-
import { waitForPort } from '../server/conf.js';
|
|
8
|
+
import { shellExec } from '../server/runtime/process.js';
|
|
9
|
+
import { loggerFactory, redactSensitiveText } from '../server/ops/logger.js';
|
|
10
|
+
import { waitForPort } from '../server/runtime/conf.js';
|
|
11
11
|
import {
|
|
12
12
|
runSELinuxCommands,
|
|
13
13
|
selinuxPackagesCommandFactory,
|
|
@@ -15,12 +15,136 @@ import {
|
|
|
15
15
|
selinuxSshContextCommandsFactory,
|
|
16
16
|
selinuxSshPortCommandsFactory,
|
|
17
17
|
shellArgumentFactory,
|
|
18
|
-
} from '../server/selinux.js';
|
|
18
|
+
} from '../server/security/selinux.js';
|
|
19
|
+
import ContainerStorageService from '../server/security/container-storage.js';
|
|
19
20
|
import fs from 'fs-extra';
|
|
20
21
|
import Underpost from '../index.js';
|
|
21
22
|
|
|
22
23
|
const logger = loggerFactory(import.meta);
|
|
23
24
|
|
|
25
|
+
const shellExecRedacted = (command, options = {}) => {
|
|
26
|
+
const silent = options.silent === true;
|
|
27
|
+
try {
|
|
28
|
+
const result = shellExec(command, { ...options, silent: true });
|
|
29
|
+
if (!silent) {
|
|
30
|
+
if (result.stdout) process.stdout.write(redactSensitiveText(result.stdout));
|
|
31
|
+
if (result.stderr) process.stderr.write(redactSensitiveText(result.stderr));
|
|
32
|
+
}
|
|
33
|
+
return result.stdout;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
error.stdout = redactSensitiveText(error.stdout || '');
|
|
36
|
+
error.stderr = redactSensitiveText(error.stderr || '');
|
|
37
|
+
error.message = redactSensitiveText(error.message || error);
|
|
38
|
+
if (!silent) {
|
|
39
|
+
if (error.stdout) process.stdout.write(error.stdout);
|
|
40
|
+
if (error.stderr) process.stderr.write(error.stderr);
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* SSH users are cluster scoped: one registry and one key store serve every
|
|
48
|
+
* deploy id running on the cluster, so an account is provisioned once instead
|
|
49
|
+
* of once per app.
|
|
50
|
+
*
|
|
51
|
+
* A record is `{ user, password, groups, keyPath, pubKeyPath, hosts }`, where an
|
|
52
|
+
* empty password means key-only and `hosts` carries one connection per host:
|
|
53
|
+
*
|
|
54
|
+
* hosts: [{ host: '10.0.0.2', port: 22 }, { host: '10.0.0.3', port: 22 }]
|
|
55
|
+
*
|
|
56
|
+
* One account reaches many hosts — the same operator account on every WireGuard
|
|
57
|
+
* spoke is the normal case — so the host cannot be a field of the record. It was,
|
|
58
|
+
* and registering that account for a second host silently replaced the first,
|
|
59
|
+
* leaving remediation unable to resolve a spoke it had just been given.
|
|
60
|
+
*
|
|
61
|
+
* The port belongs to the connection for the same reason: two hosts of one
|
|
62
|
+
* account can listen on different ports, and a single record-level port could
|
|
63
|
+
* only describe one of them.
|
|
64
|
+
*/
|
|
65
|
+
const USERS_CONF_PATH = './engine-private/deploy/conf.users.json';
|
|
66
|
+
const USERS_KEYS_PATH = './engine-private/deploy/users';
|
|
67
|
+
const DEFAULT_SSH_PORT = 22;
|
|
68
|
+
/** The checkout's own key, and where a workload receives it instead: a projected Secret volume. */
|
|
69
|
+
const HOST_KEY_PATH = './engine-private/deploy/id_rsa';
|
|
70
|
+
const SECRET_KEY_PATH = '/etc/underpost/secrets/ssh/id_rsa';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The private key this process authenticates with.
|
|
74
|
+
*
|
|
75
|
+
* One resolver for every SSH caller, because the answer differs by where the process runs and
|
|
76
|
+
* nothing else should have to know that. A pod has the key as a projected Secret and no checkout
|
|
77
|
+
* to read it from; the host CLI has the checkout and no mount. The mount is preferred over the
|
|
78
|
+
* checkout default but never over a caller's own path, because on a host the key is per target —
|
|
79
|
+
* the hub answers to a different one than the LAN nodes — and only the caller knows which.
|
|
80
|
+
* @param {string} [keyPath] - Explicit path, which always wins.
|
|
81
|
+
* @returns {string} Path to the private key.
|
|
82
|
+
* @memberof UnderpostSSH
|
|
83
|
+
*/
|
|
84
|
+
const sshKeyPathFactory = (keyPath = '') =>
|
|
85
|
+
`${keyPath || ''}`.trim() || (fs.existsSync(SECRET_KEY_PATH) ? SECRET_KEY_PATH : '') || HOST_KEY_PATH;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @method hostNameFactory
|
|
89
|
+
* @description A host as the registry compares it: the bare address, so a value
|
|
90
|
+
* written with a prefix (`10.0.0.2/32`) matches one without.
|
|
91
|
+
* @param {string} host - Host or IP, with or without a CIDR suffix.
|
|
92
|
+
* @returns {string} Normalized host.
|
|
93
|
+
* @memberof UnderpostSSH
|
|
94
|
+
*/
|
|
95
|
+
const hostNameFactory = (host) => `${host || ''}`.trim().split('/')[0];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @method connectionFactory
|
|
99
|
+
* @description Normalizes one connection entry.
|
|
100
|
+
* @param {object} [connection] - Raw connection entry.
|
|
101
|
+
* @returns {?{host: string, port: number}} Normalized connection, or null without a host.
|
|
102
|
+
* @memberof UnderpostSSH
|
|
103
|
+
*/
|
|
104
|
+
const connectionFactory = (connection = {}) => {
|
|
105
|
+
const host = hostNameFactory(connection.host);
|
|
106
|
+
return host ? { host, port: Number(connection.port) || DEFAULT_SSH_PORT } : null;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @method upsertConnection
|
|
111
|
+
* @description Adds a connection to a host list, replacing the entry for that
|
|
112
|
+
* host when one is already present.
|
|
113
|
+
*
|
|
114
|
+
* The one write path for `hosts`, so re-registering an account for a host it
|
|
115
|
+
* already reaches updates that connection instead of appending a duplicate the
|
|
116
|
+
* reverse lookup would then have to choose between.
|
|
117
|
+
* @param {Array<{host: string, port: number}>} [hosts] - Existing connections.
|
|
118
|
+
* @param {object} connection - Connection to add.
|
|
119
|
+
* @returns {Array<{host: string, port: number}>} Updated connections.
|
|
120
|
+
* @memberof UnderpostSSH
|
|
121
|
+
*/
|
|
122
|
+
const upsertConnection = (hosts = [], connection) => {
|
|
123
|
+
const entry = connectionFactory(connection);
|
|
124
|
+
if (!entry) return hosts;
|
|
125
|
+
return [...hosts.filter((existing) => existing.host !== entry.host), entry];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @method userRecordFactory
|
|
130
|
+
* @description Normalizes one registry entry, so every consumer reads one shape
|
|
131
|
+
* whether the file was written by hand, partially, or not at all.
|
|
132
|
+
* @param {object} [entry] - Raw registry entry.
|
|
133
|
+
* @returns {object} Normalized record.
|
|
134
|
+
* @memberof UnderpostSSH
|
|
135
|
+
*/
|
|
136
|
+
const userRecordFactory = (entry = {}) => ({
|
|
137
|
+
user: `${entry.user || ''}`.trim(),
|
|
138
|
+
password: `${entry.password ?? ''}`,
|
|
139
|
+
groups: `${entry.groups || ''}`,
|
|
140
|
+
keyPath: `${entry.keyPath || ''}`,
|
|
141
|
+
pubKeyPath: `${entry.pubKeyPath || ''}`,
|
|
142
|
+
hosts: (Array.isArray(entry.hosts) ? entry.hosts : []).reduce(
|
|
143
|
+
(hosts, connection) => upsertConnection(hosts, connection),
|
|
144
|
+
[],
|
|
145
|
+
),
|
|
146
|
+
});
|
|
147
|
+
|
|
24
148
|
/**
|
|
25
149
|
* @class UnderpostSSH
|
|
26
150
|
* @description Manages SSH key generation and connection setup.
|
|
@@ -29,33 +153,103 @@ const logger = loggerFactory(import.meta);
|
|
|
29
153
|
class UnderpostSSH {
|
|
30
154
|
static API = {
|
|
31
155
|
/**
|
|
32
|
-
*
|
|
156
|
+
* Path of the cluster users registry.
|
|
33
157
|
* @method
|
|
34
|
-
* @function
|
|
158
|
+
* @function usersConfPath
|
|
35
159
|
* @memberof UnderpostSSH
|
|
36
|
-
* @
|
|
37
|
-
* @returns {{confNode: Object, confNodePath: string}} Configuration node and its file path
|
|
38
|
-
* @description Loads or creates a config node with users object structure
|
|
160
|
+
* @returns {string} Path to the users configuration file
|
|
39
161
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
162
|
+
usersConfPath: () => USERS_CONF_PATH,
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Key store directory for a user.
|
|
166
|
+
* @method
|
|
167
|
+
* @function userKeyDir
|
|
168
|
+
* @memberof UnderpostSSH
|
|
169
|
+
* @param {string} user - SSH user name
|
|
170
|
+
* @returns {string} Directory holding `<dir>/id_rsa` and `<dir>/id_rsa.pub`
|
|
171
|
+
*/
|
|
172
|
+
userKeyDir: (user) => `${USERS_KEYS_PATH}/${user}`,
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Loads the cluster users registry from disk.
|
|
176
|
+
* @method
|
|
177
|
+
* @function loadUsers
|
|
178
|
+
* @memberof UnderpostSSH
|
|
179
|
+
* @returns {Array<Object>} Registered user records, empty when unregistered
|
|
180
|
+
*/
|
|
181
|
+
loadUsers: () =>
|
|
182
|
+
(fs.existsSync(USERS_CONF_PATH) ? JSON.parse(fs.readFileSync(USERS_CONF_PATH, 'utf8')) : [])
|
|
183
|
+
.map(userRecordFactory)
|
|
184
|
+
.filter((entry) => entry.user),
|
|
45
185
|
|
|
46
186
|
/**
|
|
47
|
-
* Saves
|
|
187
|
+
* Saves the cluster users registry to disk.
|
|
48
188
|
* @method
|
|
49
|
-
* @function
|
|
189
|
+
* @function saveUsers
|
|
50
190
|
* @memberof UnderpostSSH
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {Object} confNode - Configuration object to save
|
|
191
|
+
* @param {Array<Object>} users - User records to persist
|
|
53
192
|
* @returns {void}
|
|
54
193
|
*/
|
|
55
|
-
|
|
56
|
-
fs.outputFileSync(
|
|
194
|
+
saveUsers: (users) => {
|
|
195
|
+
fs.outputFileSync(USERS_CONF_PATH, JSON.stringify(users.map(userRecordFactory), null, 2), 'utf8');
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Looks up a registered user record.
|
|
200
|
+
* @method
|
|
201
|
+
* @function findUser
|
|
202
|
+
* @memberof UnderpostSSH
|
|
203
|
+
* @param {string} user - SSH user name
|
|
204
|
+
* @returns {Object|undefined} The record, or undefined when unregistered
|
|
205
|
+
*/
|
|
206
|
+
findUser: (user) => Underpost.ssh.loadUsers().find((entry) => entry.user === user),
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolves the connection an account uses to reach one host.
|
|
210
|
+
* @method
|
|
211
|
+
* @function resolveConnection
|
|
212
|
+
* @memberof UnderpostSSH
|
|
213
|
+
* @param {object} params
|
|
214
|
+
* @param {string} params.host - Host or IP the account was registered against.
|
|
215
|
+
* @param {string} [params.user] - Restrict the lookup to this account.
|
|
216
|
+
* @returns {Object|undefined} Flat connection `{ user, host, port, password, keyPath, pubKeyPath }`, or undefined
|
|
217
|
+
* @description
|
|
218
|
+
* The reverse of {@link UnderpostSSH.findUser}, for callers that know where
|
|
219
|
+
* they must act but not who acts there — remediation resolving a WireGuard
|
|
220
|
+
* spoke's tunnel address to the account that can repair it. Returns the flat
|
|
221
|
+
* view every SSH caller needs, so no consumer has to know the registry is
|
|
222
|
+
* keyed by host.
|
|
223
|
+
*/
|
|
224
|
+
resolveConnection: ({ host, user = '' } = {}) => {
|
|
225
|
+
const target = hostNameFactory(host);
|
|
226
|
+
if (!target) return undefined;
|
|
227
|
+
for (const record of Underpost.ssh.loadUsers()) {
|
|
228
|
+
if (user && record.user !== user) continue;
|
|
229
|
+
const connection = record.hosts.find((entry) => entry.host === target);
|
|
230
|
+
if (!connection) continue;
|
|
231
|
+
return {
|
|
232
|
+
user: record.user,
|
|
233
|
+
host: connection.host,
|
|
234
|
+
port: connection.port,
|
|
235
|
+
password: record.password,
|
|
236
|
+
keyPath: record.keyPath,
|
|
237
|
+
pubKeyPath: record.pubKeyPath,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
57
241
|
},
|
|
58
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Lists every host an account is registered for.
|
|
245
|
+
* @method
|
|
246
|
+
* @function userHosts
|
|
247
|
+
* @memberof UnderpostSSH
|
|
248
|
+
* @param {string} user - SSH user name
|
|
249
|
+
* @returns {Array<{host: string, port: number}>} Connection metadata per host
|
|
250
|
+
*/
|
|
251
|
+
userHosts: (user) => Underpost.ssh.findUser(user)?.hosts || [],
|
|
252
|
+
|
|
59
253
|
/**
|
|
60
254
|
* Checks if a system user exists.
|
|
61
255
|
* @method
|
|
@@ -151,12 +345,12 @@ class UnderpostSSH {
|
|
|
151
345
|
* @memberof UnderpostSSH
|
|
152
346
|
* @param {string} sshDir - SSH directory path
|
|
153
347
|
* @param {string} pubKeyPath - Public key file path
|
|
154
|
-
* @param {boolean}
|
|
348
|
+
* @param {boolean} restrictForwarding - Whether to add no-forwarding restrictions
|
|
155
349
|
* @returns {void}
|
|
156
350
|
*/
|
|
157
|
-
configureAuthorizedKeys: (sshDir, pubKeyPath,
|
|
351
|
+
configureAuthorizedKeys: (sshDir, pubKeyPath, restrictForwarding) => {
|
|
158
352
|
const key = fs.readFileSync(pubKeyPath, 'utf8').trim();
|
|
159
|
-
const entry =
|
|
353
|
+
const entry = restrictForwarding ? `no-port-forwarding,no-X11-forwarding,no-agent-forwarding ${key}` : key;
|
|
160
354
|
const authorizedKeysPath = `${sshDir}/authorized_keys`;
|
|
161
355
|
shellExec(
|
|
162
356
|
`grep -qxF ${shellArgumentFactory(entry)} ${shellArgumentFactory(authorizedKeysPath)} || printf '%s\n' ${shellArgumentFactory(entry)} >> ${shellArgumentFactory(authorizedKeysPath)}`,
|
|
@@ -184,17 +378,16 @@ class UnderpostSSH {
|
|
|
184
378
|
},
|
|
185
379
|
|
|
186
380
|
/**
|
|
187
|
-
*
|
|
381
|
+
* Grants passwordless sudo to a key-only account, or sets the user password.
|
|
188
382
|
* @method
|
|
189
383
|
* @function configureSudoAccess
|
|
190
384
|
* @memberof UnderpostSSH
|
|
191
385
|
* @param {string} username - Username to configure
|
|
192
|
-
* @param {string} password - User password
|
|
193
|
-
* @param {boolean} disablePassword - Whether to enable passwordless sudo
|
|
386
|
+
* @param {string} password - User password; empty means key-only
|
|
194
387
|
* @returns {void}
|
|
195
388
|
*/
|
|
196
|
-
configureSudoAccess: (username, password
|
|
197
|
-
if (
|
|
389
|
+
configureSudoAccess: (username, password) => {
|
|
390
|
+
if (!password) {
|
|
198
391
|
if (!/^[a-z_][a-z0-9_-]*[$]?$/i.test(username)) throw new TypeError('Invalid sudo username');
|
|
199
392
|
const sudoersPath = `/etc/sudoers.d/90_${username}`;
|
|
200
393
|
const temporaryPath = `/tmp/underpost-sudoers-${process.pid}-${username}`;
|
|
@@ -224,25 +417,21 @@ class UnderpostSSH {
|
|
|
224
417
|
* @function initializeDefaultSshConfig
|
|
225
418
|
* @memberof UnderpostSSH
|
|
226
419
|
* @param {object} params - SSH configuration values.
|
|
227
|
-
* @param {string} params.userHome - Home directory of the SSH user.
|
|
228
420
|
* @param {string} params.user - SSH user name.
|
|
229
|
-
* @param {string} params.password - SSH key passphrase and user password.
|
|
421
|
+
* @param {string} params.password - SSH key passphrase and user password; empty means key-only.
|
|
230
422
|
* @param {string} params.host - Host used in the SSH key comment.
|
|
231
423
|
* @param {number} params.port - SSH port.
|
|
232
|
-
* @param {boolean} params.disablePassword - Whether to configure passwordless sudo.
|
|
233
424
|
* @param {string} [params.controllerPubKeyPath='./engine-private/deploy/id_rsa.pub'] - Preferred controller public key to authorize.
|
|
234
425
|
* @returns {void}
|
|
235
426
|
*/
|
|
236
427
|
initializeDefaultSshConfig: ({
|
|
237
|
-
userHome,
|
|
238
428
|
user,
|
|
239
429
|
password,
|
|
240
430
|
host,
|
|
241
431
|
port,
|
|
242
|
-
disablePassword,
|
|
243
432
|
controllerPubKeyPath = './engine-private/deploy/id_rsa.pub',
|
|
244
433
|
}) => {
|
|
245
|
-
const sshDir = `${
|
|
434
|
+
const sshDir = `${Underpost.ssh.getUserHome(user)}/.ssh`;
|
|
246
435
|
const keyPath = `${sshDir}/id_rsa`;
|
|
247
436
|
const pubKeyPath = `${sshDir}/id_rsa.pub`;
|
|
248
437
|
|
|
@@ -263,9 +452,9 @@ class UnderpostSSH {
|
|
|
263
452
|
);
|
|
264
453
|
|
|
265
454
|
const authorizedKeyPath = fs.existsSync(controllerPubKeyPath) ? controllerPubKeyPath : pubKeyPath;
|
|
266
|
-
Underpost.ssh.configureAuthorizedKeys(sshDir, authorizedKeyPath,
|
|
455
|
+
Underpost.ssh.configureAuthorizedKeys(sshDir, authorizedKeyPath, !password);
|
|
267
456
|
Underpost.ssh.configureKnownHosts(sshDir, port, host);
|
|
268
|
-
Underpost.ssh.configureSudoAccess(user, password
|
|
457
|
+
Underpost.ssh.configureSudoAccess(user, password);
|
|
269
458
|
Underpost.ssh.setSSHFilePermissions(sshDir, user, keyPath, pubKeyPath);
|
|
270
459
|
},
|
|
271
460
|
|
|
@@ -275,11 +464,10 @@ class UnderpostSSH {
|
|
|
275
464
|
* @function callback
|
|
276
465
|
* @memberof UnderpostSSH
|
|
277
466
|
* @param {Object} options - Configuration options for SSH operations
|
|
278
|
-
* @param {string} [options.deployId=''] - Deployment ID context for SSH operations
|
|
279
467
|
* @param {boolean} [options.generate=false] - Generate new SSH credentials
|
|
280
468
|
* @param {string} [options.user=''] - SSH user name (defaults to 'root')
|
|
281
|
-
* @param {string} [options.password=''] - SSH user password (
|
|
282
|
-
* @param {string} [options.host=''] - SSH host address (
|
|
469
|
+
* @param {string} [options.password=''] - SSH user password (taken from the registry, else generated unless --disable-password)
|
|
470
|
+
* @param {string} [options.host=''] - SSH host address (taken from the registry, else the public IP)
|
|
283
471
|
* @param {string} [options.filter=''] - Filter for user/group listings
|
|
284
472
|
* @param {string} [options.groups=''] - Comma-separated list of groups for the user (defaults to 'wheel')
|
|
285
473
|
* @param {number} [options.port=22] - SSH port number
|
|
@@ -290,24 +478,23 @@ class UnderpostSSH {
|
|
|
290
478
|
* @param {boolean} [options.reset=false] - Reset SSH configuration (clear authorized_keys and known_hosts)
|
|
291
479
|
* @param {boolean} [options.keysList=false] - List authorized SSH keys
|
|
292
480
|
* @param {boolean} [options.hostsList=false] - List known SSH hosts
|
|
293
|
-
* @param {boolean} [options.disablePassword=false] -
|
|
481
|
+
* @param {boolean} [options.disablePassword=false] - Create the account key-only instead of generating a password
|
|
294
482
|
* @param {boolean} [options.keyTest=false] - Test SSH key generation
|
|
295
483
|
* @param {boolean} [options.stop=false] - Stop SSH service
|
|
296
484
|
* @param {boolean} [options.status=false] - Check SSH service status
|
|
297
|
-
* @param {boolean} [options.connectUri=false] - Output SSH connection URI
|
|
298
|
-
* @param {boolean} [options.copy=false] - Copy SSH connection URI to clipboard
|
|
299
485
|
* @returns {Promise<void>}
|
|
300
486
|
* @description
|
|
301
|
-
* Handles
|
|
487
|
+
* Handles SSH operations against the cluster users registry
|
|
488
|
+
* (`engine-private/deploy/conf.users.json`) and key store
|
|
489
|
+
* (`engine-private/deploy/users/<user>`):
|
|
302
490
|
* - User creation with automatic key generation and backup
|
|
303
491
|
* - User removal with key cleanup
|
|
304
|
-
* - Key import/export between SSH directory and
|
|
492
|
+
* - Key import/export between SSH directory and the key store
|
|
305
493
|
* - SSH service initialization and hardening
|
|
306
494
|
* - User and group listing with optional filtering
|
|
307
495
|
*/
|
|
308
496
|
callback: async (
|
|
309
497
|
options = {
|
|
310
|
-
deployId: '',
|
|
311
498
|
generate: false,
|
|
312
499
|
user: '',
|
|
313
500
|
password: '',
|
|
@@ -326,62 +513,49 @@ class UnderpostSSH {
|
|
|
326
513
|
keyTest: false,
|
|
327
514
|
stop: false,
|
|
328
515
|
status: false,
|
|
329
|
-
connectUri: false,
|
|
330
|
-
copy: false,
|
|
331
516
|
},
|
|
332
517
|
) => {
|
|
333
|
-
let confNode, confNodePath;
|
|
334
|
-
|
|
335
|
-
// Set defaults
|
|
336
518
|
if (!options.user) options.user = 'root';
|
|
337
|
-
if (!options.host) options.host = await Underpost.dns.getPublicIp();
|
|
338
|
-
if (!options.password) options.password = options.disablePassword ? '' : generateRandomPasswordSelection(16);
|
|
339
|
-
if (!options.groups) options.groups = 'wheel';
|
|
340
|
-
if (!options.port) options.port = 22; // Handle connect uri
|
|
341
|
-
|
|
342
|
-
const userHome = Underpost.ssh.getUserHome(options.user);
|
|
343
|
-
options.userHome = userHome;
|
|
344
519
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
520
|
+
const users = Underpost.ssh.loadUsers();
|
|
521
|
+
const confUserIndex = users.findIndex((entry) => entry.user === options.user);
|
|
522
|
+
const confUser = users[confUserIndex];
|
|
523
|
+
const registeredHosts = confUser?.hosts || [];
|
|
524
|
+
|
|
525
|
+
// Explicit flags win; the registry fills in what was omitted, but only for
|
|
526
|
+
// the operations that act on one host. A single registered host is
|
|
527
|
+
// unambiguous and several are not — an account on many spokes must be told
|
|
528
|
+
// which one is meant rather than acting on whichever the registry lists
|
|
529
|
+
// first. Listing and removal are account-scoped and need no host at all.
|
|
530
|
+
const hostScoped = options.userAdd || options.start || options.keyTest || options.hostsList;
|
|
531
|
+
if (!options.host && hostScoped) {
|
|
532
|
+
if (registeredHosts.length > 1)
|
|
533
|
+
throw new Error(
|
|
534
|
+
`[ssh] user '${options.user}' is registered for several hosts ` +
|
|
535
|
+
`(${registeredHosts.map((entry) => entry.host).join(', ')}); pass --host to select one`,
|
|
536
|
+
);
|
|
537
|
+
options.host = registeredHosts[0]?.host || (await Underpost.dns.getPublicIp());
|
|
538
|
+
}
|
|
539
|
+
if (!options.port)
|
|
540
|
+
options.port =
|
|
541
|
+
registeredHosts.find((entry) => entry.host === hostNameFactory(options.host))?.port || DEFAULT_SSH_PORT;
|
|
542
|
+
if (!options.groups) options.groups = 'wheel';
|
|
543
|
+
if (!options.password) {
|
|
544
|
+
const generatePassword = !confUser && !options.disablePassword;
|
|
545
|
+
options.password = generatePassword ? generateRandomPasswordSelection(16) : confUser?.password || '';
|
|
367
546
|
}
|
|
368
547
|
|
|
369
|
-
|
|
548
|
+
const keyDir = Underpost.ssh.userKeyDir(options.user);
|
|
549
|
+
const keyPath = confUser?.keyPath || `${keyDir}/id_rsa`;
|
|
550
|
+
const pubKeyPath = confUser?.pubKeyPath || `${keyDir}/id_rsa.pub`;
|
|
551
|
+
const sshDirFactory = () => `${Underpost.ssh.getUserHome(options.user)}/.ssh`;
|
|
370
552
|
|
|
371
|
-
|
|
372
|
-
if (options.connectUri) {
|
|
373
|
-
const keyPath = `${userHome}/.ssh/id_rsa`;
|
|
374
|
-
const uri = `ssh ${options.user}@${options.host} -i ${keyPath} -p ${options.port}`;
|
|
375
|
-
if (options.copy) {
|
|
376
|
-
pbcopy(uri);
|
|
377
|
-
} else console.log(uri);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
553
|
+
logger.info('options', options);
|
|
380
554
|
|
|
381
|
-
// Handle reset operation
|
|
382
555
|
if (options.reset) {
|
|
383
|
-
|
|
384
|
-
shellExec(`> ${
|
|
556
|
+
const sshDir = sshDirFactory();
|
|
557
|
+
shellExec(`> ${sshDir}/authorized_keys`);
|
|
558
|
+
shellExec(`> ${sshDir}/known_hosts`);
|
|
385
559
|
}
|
|
386
560
|
|
|
387
561
|
if (options.userLs) {
|
|
@@ -400,36 +574,30 @@ class UnderpostSSH {
|
|
|
400
574
|
console.log('Users'.bold.blue);
|
|
401
575
|
console.log(`user : x : UID : GID : GECOS : home_dir : shell`.blue);
|
|
402
576
|
console.log(filter ? usersOut.replaceAll(filter, filter.red) : usersOut);
|
|
577
|
+
console.log('Registered cluster users'.bold.blue);
|
|
578
|
+
users.forEach((entry) =>
|
|
579
|
+
entry.hosts.forEach((connection) => console.log(`${entry.user}@${connection.host}:${connection.port}`)),
|
|
580
|
+
);
|
|
403
581
|
}
|
|
404
582
|
|
|
405
|
-
// Handle user removal (works with or without deployId)
|
|
406
583
|
if (options.userRemove) {
|
|
407
584
|
const groups = shellExec(`id -Gn ${options.user}`, { silent: true, stdout: true }).trim().replace(/ /g, ', ');
|
|
408
585
|
shellExec(`userdel -r ${options.user}`);
|
|
409
586
|
|
|
410
|
-
// Remove sudoers file if it exists
|
|
411
587
|
const sudoersFile = `/etc/sudoers.d/90_${options.user}`;
|
|
412
588
|
if (fs.existsSync(sudoersFile)) {
|
|
413
589
|
shellExec(`sudo rm -f ${sudoersFile}`);
|
|
414
590
|
logger.info(`Sudoers file removed: ${sudoersFile}`);
|
|
415
591
|
}
|
|
416
592
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
confNode = config.confNode;
|
|
422
|
-
confNodePath = config.confNodePath;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const privateCopyDir = `./engine-private/conf/${options.deployId}/users/${options.user}`;
|
|
426
|
-
if (fs.existsSync(privateCopyDir)) {
|
|
427
|
-
fs.removeSync(privateCopyDir);
|
|
428
|
-
logger.info(`Private key copy removed from ${privateCopyDir}`);
|
|
429
|
-
}
|
|
593
|
+
if (fs.existsSync(keyDir)) {
|
|
594
|
+
fs.removeSync(keyDir);
|
|
595
|
+
logger.info(`Key store removed from ${keyDir}`);
|
|
596
|
+
}
|
|
430
597
|
|
|
431
|
-
|
|
432
|
-
|
|
598
|
+
if (confUser) {
|
|
599
|
+
users.splice(confUserIndex, 1);
|
|
600
|
+
Underpost.ssh.saveUsers(users);
|
|
433
601
|
}
|
|
434
602
|
|
|
435
603
|
logger.info(`User removed`);
|
|
@@ -437,148 +605,83 @@ class UnderpostSSH {
|
|
|
437
605
|
return;
|
|
438
606
|
}
|
|
439
607
|
|
|
440
|
-
// Handle user addition (works with or without deployId)
|
|
441
608
|
if (options.userAdd) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// If deployId is provided, check for existing config and backup keys
|
|
445
|
-
if (options.deployId) {
|
|
446
|
-
if (!confNode) {
|
|
447
|
-
const config = Underpost.ssh.loadConfigNode(options.deployId);
|
|
448
|
-
confNode = config.confNode;
|
|
449
|
-
confNodePath = config.confNodePath;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
userExistsInConfig = confNode.users && confNode.users[options.user];
|
|
453
|
-
privateCopyDir = `./engine-private/conf/${options.deployId}/users/${options.user}`;
|
|
454
|
-
privateKeyPath = `${privateCopyDir}/id_rsa`;
|
|
455
|
-
publicKeyPath = `${privateCopyDir}/id_rsa.pub`;
|
|
456
|
-
keysExistInBackup = fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath);
|
|
457
|
-
|
|
458
|
-
// If user exists in config AND keys exist in backup, import those keys
|
|
459
|
-
if (userExistsInConfig && keysExistInBackup) {
|
|
460
|
-
logger.info(`User ${options.user} already exists in config. Importing existing keys...`);
|
|
461
|
-
|
|
462
|
-
// Create system user if it doesn't exist
|
|
463
|
-
const userExists = Underpost.ssh.checkUserExists(options.user);
|
|
464
|
-
if (!userExists) {
|
|
465
|
-
Underpost.ssh.createSystemUser(options.user, options.password, options.groups);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const userHome = Underpost.ssh.getUserHome(options.user);
|
|
469
|
-
const sshDir = `${userHome}/.ssh`;
|
|
470
|
-
Underpost.ssh.ensureSSHDirectory(sshDir);
|
|
471
|
-
|
|
472
|
-
const userKeyPath = `${sshDir}/id_rsa`;
|
|
473
|
-
const userPubKeyPath = `${sshDir}/id_rsa.pub`;
|
|
474
|
-
|
|
475
|
-
// Import keys from backup
|
|
476
|
-
fs.copyFileSync(privateKeyPath, userKeyPath);
|
|
477
|
-
fs.copyFileSync(publicKeyPath, userPubKeyPath);
|
|
478
|
-
|
|
479
|
-
Underpost.ssh.configureAuthorizedKeys(sshDir, userPubKeyPath, options.disablePassword);
|
|
480
|
-
Underpost.ssh.configureSudoAccess(options.user, options.password, options.disablePassword);
|
|
481
|
-
Underpost.ssh.configureKnownHosts(sshDir, options.port, options.host);
|
|
482
|
-
Underpost.ssh.setSSHFilePermissions(sshDir, options.user, userKeyPath, userPubKeyPath);
|
|
483
|
-
|
|
484
|
-
logger.info(`Keys imported from ${privateCopyDir} to ${sshDir}`);
|
|
485
|
-
logger.info(`User added with existing keys`);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// New user or no existing keys - create new user and generate keys
|
|
491
|
-
Underpost.ssh.createSystemUser(options.user, options.password, options.groups);
|
|
609
|
+
if (!Underpost.ssh.checkUserExists(options.user))
|
|
610
|
+
Underpost.ssh.createSystemUser(options.user, options.password, options.groups);
|
|
492
611
|
|
|
493
|
-
const
|
|
494
|
-
const sshDir = `${userHome}/.ssh`;
|
|
612
|
+
const sshDir = sshDirFactory();
|
|
495
613
|
Underpost.ssh.ensureSSHDirectory(sshDir);
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
614
|
+
const hostKeyPath = `${sshDir}/id_rsa`;
|
|
615
|
+
const hostPubKeyPath = `${sshDir}/id_rsa.pub`;
|
|
616
|
+
|
|
617
|
+
// A key already trusted cluster wide is reinstalled, never re-issued:
|
|
618
|
+
// regenerating it would invalidate every host that already holds it.
|
|
619
|
+
const storedKeys = fs.existsSync(keyPath) && fs.existsSync(pubKeyPath);
|
|
620
|
+
if (storedKeys) {
|
|
621
|
+
fs.copyFileSync(keyPath, hostKeyPath);
|
|
622
|
+
fs.copyFileSync(pubKeyPath, hostPubKeyPath);
|
|
623
|
+
logger.info(`Keys imported from ${keyDir} to ${sshDir}`);
|
|
624
|
+
} else if (!fs.existsSync(hostKeyPath)) {
|
|
501
625
|
shellExec(
|
|
502
|
-
`ssh-keygen -t ed25519 -f ${
|
|
626
|
+
`ssh-keygen -t ed25519 -f ${hostKeyPath} -N "${options.password}" -q -C "${options.user}@${options.host}"`,
|
|
627
|
+
{ disableLog: true },
|
|
503
628
|
);
|
|
629
|
+
} else if (!fs.existsSync(hostPubKeyPath)) {
|
|
630
|
+
shellExec(`ssh-keygen -y -f ${hostKeyPath} -P "${options.password}" > ${hostPubKeyPath}`, {
|
|
631
|
+
disableLog: true,
|
|
632
|
+
});
|
|
504
633
|
}
|
|
505
634
|
|
|
506
|
-
Underpost.ssh.configureAuthorizedKeys(sshDir,
|
|
507
|
-
Underpost.ssh.configureSudoAccess(options.user, options.password
|
|
635
|
+
Underpost.ssh.configureAuthorizedKeys(sshDir, hostPubKeyPath, !options.password);
|
|
636
|
+
Underpost.ssh.configureSudoAccess(options.user, options.password);
|
|
508
637
|
Underpost.ssh.configureKnownHosts(sshDir, options.port, options.host);
|
|
509
|
-
Underpost.ssh.setSSHFilePermissions(sshDir, options.user,
|
|
510
|
-
|
|
511
|
-
// Save a copy of the keys to the private folder only if deployId is provided
|
|
512
|
-
if (options.deployId) {
|
|
513
|
-
if (!privateCopyDir) privateCopyDir = `./engine-private/conf/${options.deployId}/users/${options.user}`;
|
|
514
|
-
fs.ensureDirSync(privateCopyDir);
|
|
515
|
-
|
|
516
|
-
const privateKeyCopyPath = `${privateCopyDir}/id_rsa`;
|
|
517
|
-
const publicKeyCopyPath = `${privateCopyDir}/id_rsa.pub`;
|
|
518
|
-
|
|
519
|
-
fs.copyFileSync(keyPath, privateKeyCopyPath);
|
|
520
|
-
fs.copyFileSync(pubKeyPath, publicKeyCopyPath);
|
|
521
|
-
|
|
522
|
-
logger.info(`Keys backed up to ${privateCopyDir}`);
|
|
523
|
-
|
|
524
|
-
confNode.users[options.user] = {
|
|
525
|
-
...confNode.users[options.user],
|
|
526
|
-
...options,
|
|
527
|
-
keyPath,
|
|
528
|
-
pubKeyPath,
|
|
529
|
-
privateKeyCopyPath,
|
|
530
|
-
publicKeyCopyPath,
|
|
531
|
-
};
|
|
532
|
-
Underpost.ssh.saveConfigNode(confNodePath, confNode);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
logger.info(`User added`);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
638
|
+
Underpost.ssh.setSSHFilePermissions(sshDir, options.user, hostKeyPath, hostPubKeyPath);
|
|
538
639
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
confNodePath = config.confNodePath;
|
|
640
|
+
if (!storedKeys) {
|
|
641
|
+
fs.ensureDirSync(keyDir);
|
|
642
|
+
fs.copyFileSync(hostKeyPath, keyPath);
|
|
643
|
+
fs.copyFileSync(hostPubKeyPath, pubKeyPath);
|
|
644
|
+
logger.info(`Keys backed up to ${keyDir}`);
|
|
545
645
|
}
|
|
546
646
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
647
|
+
// Additive: registering the account for another host adds that host,
|
|
648
|
+
// it does not replace the record. One operator account across several
|
|
649
|
+
// spokes is the normal case, and replacing here is what previously left
|
|
650
|
+
// remediation unable to resolve a spoke that had just been registered.
|
|
651
|
+
const record = {
|
|
652
|
+
...(confUser || {}),
|
|
653
|
+
user: options.user,
|
|
654
|
+
password: options.password,
|
|
655
|
+
groups: options.groups,
|
|
656
|
+
keyPath,
|
|
657
|
+
pubKeyPath,
|
|
658
|
+
hosts: upsertConnection(registeredHosts, { host: options.host, port: options.port }),
|
|
659
|
+
};
|
|
660
|
+
if (confUser) users[confUserIndex] = record;
|
|
661
|
+
else users.push(record);
|
|
662
|
+
Underpost.ssh.saveUsers(users);
|
|
663
|
+
|
|
664
|
+
logger.info(`User added`, { user: options.user, hosts: record.hosts });
|
|
665
|
+
return;
|
|
553
666
|
}
|
|
554
667
|
|
|
555
|
-
// Handle generate root keys
|
|
556
668
|
if (options.generate)
|
|
557
669
|
Underpost.ssh.generateKeys({ user: options.user, password: options.password, host: options.host });
|
|
558
670
|
|
|
559
|
-
|
|
560
|
-
if (options.
|
|
561
|
-
if (options.hostsList) shellExec(`cat ${userHome}/.ssh/known_hosts`);
|
|
671
|
+
if (options.keysList) shellExec(`cat ${sshDirFactory()}/authorized_keys`);
|
|
672
|
+
if (options.hostsList) shellExec(`cat ${sshDirFactory()}/known_hosts`);
|
|
562
673
|
|
|
563
|
-
|
|
564
|
-
if (options.keyTest) {
|
|
565
|
-
const keyPath = `${userHome}/.ssh/id_rsa`;
|
|
566
|
-
shellExec(`ssh-keygen -y -f ${keyPath} -P "${options.password}"`);
|
|
567
|
-
}
|
|
674
|
+
if (options.keyTest) shellExec(`ssh-keygen -y -f ${keyPath} -P "${options.password}"`);
|
|
568
675
|
|
|
569
|
-
// Handle stop server
|
|
570
676
|
if (options.stop) shellExec('service sshd stop');
|
|
571
677
|
|
|
572
|
-
// Handle start server
|
|
573
678
|
if (options.start) {
|
|
574
|
-
if (!
|
|
679
|
+
if (!confUser) {
|
|
575
680
|
Underpost.ssh.initializeDefaultSshConfig({
|
|
576
|
-
userHome,
|
|
577
681
|
user: options.user,
|
|
578
682
|
password: options.password,
|
|
579
683
|
host: options.host,
|
|
580
684
|
port: options.port,
|
|
581
|
-
disablePassword: options.disablePassword,
|
|
582
685
|
});
|
|
583
686
|
} else {
|
|
584
687
|
// Hardening sshd on a host where the account does not exist produces a
|
|
@@ -587,32 +690,29 @@ class UnderpostSSH {
|
|
|
587
690
|
if (!Underpost.ssh.checkUserExists(options.user))
|
|
588
691
|
throw new Error(
|
|
589
692
|
`[ssh] user '${options.user}' does not exist on this host; ` +
|
|
590
|
-
`run: node bin ssh --
|
|
693
|
+
`run: node bin ssh --user ${options.user} --user-add`,
|
|
591
694
|
);
|
|
592
|
-
|
|
593
|
-
// The registry records the key pair this deploy authenticates with, so
|
|
695
|
+
// The registry records the key pair this cluster authenticates with, so
|
|
594
696
|
// a box whose authorized_keys was lost converges here rather than
|
|
595
697
|
// needing the key re-issued — which would invalidate every other host
|
|
596
698
|
// holding it.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const sshDir = `${userHome}/.ssh`;
|
|
699
|
+
if (fs.existsSync(pubKeyPath)) {
|
|
700
|
+
const sshDir = sshDirFactory();
|
|
600
701
|
Underpost.ssh.ensureSSHDirectory(sshDir);
|
|
601
702
|
fs.ensureFileSync(`${sshDir}/authorized_keys`);
|
|
602
|
-
Underpost.ssh.configureAuthorizedKeys(sshDir,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
logger.warn('Recorded public key is missing; authorized_keys left untouched', {
|
|
703
|
+
Underpost.ssh.configureAuthorizedKeys(sshDir, pubKeyPath, !options.password);
|
|
704
|
+
logger.info(`Authorized key present for ${options.user}`, { source: pubKeyPath });
|
|
705
|
+
} else {
|
|
706
|
+
logger.warn('Registered public key is missing; authorized_keys left untouched', {
|
|
607
707
|
user: options.user,
|
|
608
|
-
expected:
|
|
708
|
+
expected: pubKeyPath,
|
|
609
709
|
});
|
|
610
710
|
}
|
|
711
|
+
Underpost.ssh.chmod({ user: options.user });
|
|
611
712
|
}
|
|
612
713
|
Underpost.ssh.initService({ port: options.port });
|
|
613
714
|
}
|
|
614
715
|
|
|
615
|
-
// Handle status server
|
|
616
716
|
if (options.status) shellExec('service sshd status');
|
|
617
717
|
},
|
|
618
718
|
|
|
@@ -634,9 +734,13 @@ class UnderpostSSH {
|
|
|
634
734
|
* @param {string} params.remoteDir - Destination directory on the node.
|
|
635
735
|
* @param {number} [params.port=22] - SSH port.
|
|
636
736
|
* @param {string} [params.user='root'] - SSH user (key-only).
|
|
637
|
-
* @param {string} [params.keyPath
|
|
737
|
+
* @param {string} [params.keyPath] - Private key path; resolved by {@link sshKeyPathFactory} when empty.
|
|
638
738
|
* @param {string} [params.owner='1000:1000'] - chown target on the node (empty to skip).
|
|
639
739
|
* @param {string} [params.mode='755'] - chmod mode on the node (empty to skip).
|
|
740
|
+
* @param {boolean} [params.containerStorage=false] - Give the destination tree the persistent
|
|
741
|
+
* `container_file_t` mapping on the node. Required for a hostPath volume: the label has to
|
|
742
|
+
* exist on the node that actually mounts it, and a directory created here by `mkdir -p`
|
|
743
|
+
* otherwise inherits whatever the parent carries — which no unprivileged pod can read.
|
|
640
744
|
* @returns {void}
|
|
641
745
|
*/
|
|
642
746
|
copyDirToNode: ({
|
|
@@ -645,16 +749,18 @@ class UnderpostSSH {
|
|
|
645
749
|
remoteDir,
|
|
646
750
|
port = 22,
|
|
647
751
|
user = 'root',
|
|
648
|
-
keyPath = '
|
|
752
|
+
keyPath = '',
|
|
649
753
|
owner = '1000:1000',
|
|
650
754
|
mode = '755',
|
|
755
|
+
containerStorage = false,
|
|
651
756
|
}) => {
|
|
652
757
|
if (!host) throw new Error('copyDirToNode requires a host');
|
|
653
758
|
if (!localDir || !fs.existsSync(localDir)) throw new Error(`copyDirToNode: local dir not found: ${localDir}`);
|
|
654
759
|
if (!remoteDir) throw new Error('copyDirToNode requires a remoteDir');
|
|
760
|
+
const key = sshKeyPathFactory(keyPath);
|
|
655
761
|
try {
|
|
656
|
-
shellExec(`chmod 600 ${
|
|
657
|
-
const sshOpts = `-i ${
|
|
762
|
+
shellExec(`chmod 600 ${key}`, { silent: true, silentOnError: true, disableLog: true });
|
|
763
|
+
const sshOpts = `-i ${key} -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p ${port}`;
|
|
658
764
|
shellExec(`ssh ${sshOpts} ${user}@${host} 'mkdir -p ${remoteDir}'`, {
|
|
659
765
|
silent: true,
|
|
660
766
|
disableLog: true,
|
|
@@ -670,12 +776,32 @@ class UnderpostSSH {
|
|
|
670
776
|
silent: true,
|
|
671
777
|
disableLog: true,
|
|
672
778
|
});
|
|
779
|
+
// After the fixups, so the restorecon pass covers the files tar just extracted. A no-op
|
|
780
|
+
// on a node without SELinux, and it never runs unless the caller asked for it.
|
|
781
|
+
if (containerStorage)
|
|
782
|
+
shellExec(
|
|
783
|
+
`ssh ${sshOpts} ${user}@${host} ${shellArgumentFactory(
|
|
784
|
+
ContainerStorageService.containerStorageSubtreeCommandsFactory(remoteDir, {
|
|
785
|
+
sudo: user !== 'root',
|
|
786
|
+
}).join('; '),
|
|
787
|
+
)}`,
|
|
788
|
+
{ silent: true, disableLog: true },
|
|
789
|
+
);
|
|
673
790
|
} catch (err) {
|
|
674
791
|
logger.error(`copyDirToNode failed`);
|
|
675
792
|
process.exit(1);
|
|
676
793
|
}
|
|
677
794
|
},
|
|
678
795
|
|
|
796
|
+
/**
|
|
797
|
+
* The private key this process authenticates with; see {@link sshKeyPathFactory}.
|
|
798
|
+
* @function keyPathFactory
|
|
799
|
+
* @param {string} [keyPath] - Explicit path, which always wins.
|
|
800
|
+
* @returns {string} Path to the private key.
|
|
801
|
+
* @memberof UnderpostSSH
|
|
802
|
+
*/
|
|
803
|
+
keyPathFactory: sshKeyPathFactory,
|
|
804
|
+
|
|
679
805
|
/**
|
|
680
806
|
* Generic SSH remote command runner that SSH execution logic.
|
|
681
807
|
* Executes arbitrary shell commands on a remote server via SSH with proper credential handling.
|
|
@@ -683,46 +809,68 @@ class UnderpostSSH {
|
|
|
683
809
|
* @function sshRemoteRunner
|
|
684
810
|
* @param {string} remoteCommand - The command to execute on the remote server
|
|
685
811
|
* @param {Object} options - Configuration options for SSH execution
|
|
686
|
-
* @param {string} [options.
|
|
687
|
-
* @param {string} [options.
|
|
812
|
+
* @param {string} [options.user] - SSH user for cluster credential lookup
|
|
813
|
+
* @param {string} [options.host] - Host to reach; selects the connection when the user has several
|
|
688
814
|
* @param {boolean} [options.dev=false] - Development mode flag
|
|
689
815
|
* @param {string} [options.cd='/home/dd/engine'] - Working directory on remote server
|
|
690
816
|
* @param {boolean} [options.useSudo=true] - Whether to use sudo for command execution
|
|
691
|
-
* @param {boolean} [options.remote=true] - Whether to execute as remote command (if false, runs locally)
|
|
817
|
+
* @param {boolean} [options.remote=true] - Whether to execute as remote command (if false, runs locally in `cd`)
|
|
818
|
+
* @param {boolean} [options.silent=false] - Suppress the command's output; it is still returned to the caller
|
|
692
819
|
* @returns {Promise<string>} Output from the shell execution
|
|
693
820
|
* @memberof UnderpostSSH
|
|
694
821
|
*/
|
|
695
822
|
sshRemoteRunner: async (remoteCommand, options = {}) => {
|
|
696
|
-
const {
|
|
697
|
-
|
|
698
|
-
|
|
823
|
+
const {
|
|
824
|
+
user = '',
|
|
825
|
+
host = '',
|
|
826
|
+
dev = false,
|
|
827
|
+
cd = '/home/dd/engine',
|
|
828
|
+
useSudo = true,
|
|
829
|
+
remote = true,
|
|
830
|
+
silent = false,
|
|
831
|
+
} = options;
|
|
832
|
+
|
|
833
|
+
// Local execution still honours `cd`: the underpost CLI resolves its deploy
|
|
834
|
+
// configuration relative to the working directory, so running it from
|
|
835
|
+
// wherever the caller happened to start would act on a different cluster's
|
|
836
|
+
// config, or none. It honours it only when that directory exists — a checkout
|
|
837
|
+
// that lives elsewhere (a CI container, a developer tree) has no deploy path to
|
|
838
|
+
// enter, and chdir failing there turns a runnable local command into an error
|
|
839
|
+
// reported as a failed remediation.
|
|
840
|
+
// The generated wrapper is transport, not information: echoing it buries
|
|
841
|
+
// whatever the command actually said under a page of boilerplate.
|
|
699
842
|
if (!remote) {
|
|
700
|
-
|
|
843
|
+
const cwd = cd && fs.existsSync(cd) ? cd : '';
|
|
844
|
+
return shellExecRedacted(remoteCommand, { ...(cwd ? { cwd } : {}), silent, disableLog: true });
|
|
701
845
|
}
|
|
702
846
|
|
|
703
|
-
// Set up SSH credentials from config
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
847
|
+
// Set up SSH credentials from the cluster config
|
|
848
|
+
if (user) await Underpost.ssh.setDefautlSshCredentials({ user, host });
|
|
849
|
+
|
|
850
|
+
// The store holds the key `setDefautlSshCredentials` just resolved for this target, which is
|
|
851
|
+
// the only per-target answer; a pod has no such key on disk and takes its mount instead.
|
|
852
|
+
const sshKey = fs.existsSync(SECRET_KEY_PATH)
|
|
853
|
+
? shellArgumentFactory(SECRET_KEY_PATH)
|
|
854
|
+
: '$(node bin host get --plain DEFAULT_SSH_KEY_PATH)';
|
|
707
855
|
|
|
708
|
-
// Build the complete SSH command
|
|
709
856
|
const sshScript = `#!/usr/bin/env bash
|
|
710
857
|
set -euo pipefail
|
|
711
858
|
|
|
712
|
-
REMOTE_USER=$(node bin
|
|
713
|
-
REMOTE_HOST=$(node bin
|
|
714
|
-
REMOTE_PORT=$(node bin
|
|
715
|
-
SSH_KEY=$
|
|
859
|
+
REMOTE_USER=$(node bin host get --plain DEFAULT_SSH_USER)
|
|
860
|
+
REMOTE_HOST=$(node bin host get --plain DEFAULT_SSH_HOST)
|
|
861
|
+
REMOTE_PORT=$(node bin host get --plain DEFAULT_SSH_PORT)
|
|
862
|
+
SSH_KEY=${sshKey}
|
|
716
863
|
|
|
717
|
-
|
|
864
|
+
# A projected Secret is read-only, and its mode is already restrictive enough for ssh.
|
|
865
|
+
chmod 600 "$SSH_KEY" 2>/dev/null || true
|
|
718
866
|
|
|
719
|
-
ssh -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$REMOTE_USER@$REMOTE_HOST" -p $REMOTE_PORT sh <<EOF
|
|
867
|
+
ssh -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR "$REMOTE_USER@$REMOTE_HOST" -p $REMOTE_PORT sh <<EOF
|
|
720
868
|
${cd ? `cd ${cd}` : ''}
|
|
721
869
|
${useSudo ? `sudo -n -- /bin/bash -lc "${remoteCommand}"` : remoteCommand}
|
|
722
870
|
EOF
|
|
723
871
|
`;
|
|
724
872
|
|
|
725
|
-
return
|
|
873
|
+
return shellExecRedacted(sshScript, { silent, disableLog: true });
|
|
726
874
|
},
|
|
727
875
|
|
|
728
876
|
/**
|
|
@@ -770,7 +918,7 @@ EOF
|
|
|
770
918
|
* @param {string} params.command - Remote command batch to execute.
|
|
771
919
|
* @param {number} [params.port=22] - SSH port.
|
|
772
920
|
* @param {string} [params.user='root'] - SSH user (key-only).
|
|
773
|
-
* @param {string} [params.keyPath] - Private key path
|
|
921
|
+
* @param {string} [params.keyPath] - Private key path; resolved by {@link sshKeyPathFactory} when empty.
|
|
774
922
|
* @param {number} [params.connectTimeoutSec=15] - Per-attempt SSH connect timeout.
|
|
775
923
|
* @param {number} [params.retries=3] - Auth/exec retry attempts.
|
|
776
924
|
* @param {number} [params.retryDelayMs=5000] - Base backoff between retries.
|
|
@@ -782,7 +930,7 @@ EOF
|
|
|
782
930
|
command,
|
|
783
931
|
port = 22,
|
|
784
932
|
user = 'root',
|
|
785
|
-
keyPath = '
|
|
933
|
+
keyPath = '',
|
|
786
934
|
connectTimeoutSec = 15,
|
|
787
935
|
retries = 3,
|
|
788
936
|
retryDelayMs = 5000,
|
|
@@ -796,10 +944,11 @@ EOF
|
|
|
796
944
|
if (!reachable) return { ok: false, code: 255, stdout: '', stderr: 'ssh port unreachable', attempts: 0 };
|
|
797
945
|
}
|
|
798
946
|
|
|
799
|
-
|
|
947
|
+
const key = sshKeyPathFactory(keyPath);
|
|
948
|
+
shellExec(`chmod 600 ${key}`, { silent: true, silentOnError: true, disableLog: true });
|
|
800
949
|
|
|
801
950
|
const sshOpts = [
|
|
802
|
-
`-i ${
|
|
951
|
+
`-i ${key}`,
|
|
803
952
|
`-o BatchMode=yes`,
|
|
804
953
|
`-o PreferredAuthentications=publickey`,
|
|
805
954
|
`-o PubkeyAuthentication=yes`,
|
|
@@ -875,7 +1024,7 @@ EOF
|
|
|
875
1024
|
remotePath = '/tmp/underpost-remote-script.sh',
|
|
876
1025
|
port = 22,
|
|
877
1026
|
user = 'root',
|
|
878
|
-
keyPath = '
|
|
1027
|
+
keyPath = '',
|
|
879
1028
|
retries = 3,
|
|
880
1029
|
waitForPortMs = 0,
|
|
881
1030
|
}) => {
|
|
@@ -898,25 +1047,73 @@ EOF
|
|
|
898
1047
|
},
|
|
899
1048
|
|
|
900
1049
|
/**
|
|
901
|
-
* Loads
|
|
1050
|
+
* Loads a user's SSH credentials for one host and sets them in the host
|
|
1051
|
+
* configuration store.
|
|
1052
|
+
*
|
|
1053
|
+
* `host` is what selects the connection: an account registered for several
|
|
1054
|
+
* hosts has several, and picking one without being told would send a repair
|
|
1055
|
+
* to the wrong machine. Omitting it is only unambiguous for an account with
|
|
1056
|
+
* exactly one registered host.
|
|
1057
|
+
*
|
|
1058
|
+
* A named host is resolved against the registry and nowhere else. The root
|
|
1059
|
+
* env store is ambient — whatever the previous command left there — so
|
|
1060
|
+
* substituting it for a host the registry does not know would silently point
|
|
1061
|
+
* an operation at a different machine, which for remediation means repairing
|
|
1062
|
+
* one host because another was unreachable. Its `DEFAULT_SSH_*` values are
|
|
1063
|
+
* promoted only when no host was named, for a controller that dispatches
|
|
1064
|
+
* work but was never itself provisioned by `--user-add`.
|
|
902
1065
|
* @async
|
|
903
1066
|
* @function setDefautlSshCredentials
|
|
904
1067
|
* @memberof UnderpostSSH
|
|
905
1068
|
* @param {Object} options - Options for setting default SSH credentials
|
|
906
|
-
* @param {string} options.
|
|
907
|
-
* @param {string} options.
|
|
1069
|
+
* @param {string} options.user - SSH user name registered at cluster scope
|
|
1070
|
+
* @param {string} [options.host] - Host to connect to; required when the account has several
|
|
908
1071
|
* @returns {Promise<void>}
|
|
1072
|
+
* @throws {Error} When a named host has no connection registered for that account.
|
|
909
1073
|
*/
|
|
910
|
-
setDefautlSshCredentials: async (options = {
|
|
911
|
-
const
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1074
|
+
setDefautlSshCredentials: async (options = { user: '', host: '' }) => {
|
|
1075
|
+
const hosts = Underpost.ssh.userHosts(options.user);
|
|
1076
|
+
if (!options.host && hosts.length > 1)
|
|
1077
|
+
throw new Error(
|
|
1078
|
+
`[ssh] user '${options.user}' is registered for several hosts ` +
|
|
1079
|
+
`(${hosts.map((entry) => entry.host).join(', ')}); the caller must name one`,
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
const registered = Underpost.ssh.resolveConnection({
|
|
1083
|
+
user: options.user,
|
|
1084
|
+
host: options.host || hosts[0]?.host,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
if (!registered && options.host)
|
|
1088
|
+
throw new Error(
|
|
1089
|
+
`[ssh] no connection is registered for '${options.user}@${options.host}' in ${USERS_CONF_PATH}; ` +
|
|
1090
|
+
`run: node bin ssh --user ${options.user} --host ${options.host} --user-add`,
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
const connection =
|
|
1094
|
+
registered ||
|
|
1095
|
+
(process.env.DEFAULT_SSH_USER && process.env.DEFAULT_SSH_HOST && process.env.DEFAULT_SSH_KEY_PATH
|
|
1096
|
+
? {
|
|
1097
|
+
user: process.env.DEFAULT_SSH_USER,
|
|
1098
|
+
host: process.env.DEFAULT_SSH_HOST,
|
|
1099
|
+
keyPath: process.env.DEFAULT_SSH_KEY_PATH,
|
|
1100
|
+
port: process.env.DEFAULT_SSH_PORT || DEFAULT_SSH_PORT,
|
|
1101
|
+
}
|
|
1102
|
+
: undefined);
|
|
1103
|
+
|
|
1104
|
+
if (!connection) {
|
|
1105
|
+
logger.warn(`No SSH credentials for '${options.user}'`, {
|
|
1106
|
+
host: options.host || '(unspecified)',
|
|
1107
|
+
registry: USERS_CONF_PATH,
|
|
1108
|
+
});
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (!registered) logger.warn(`Using the deploy environment DEFAULT_SSH_* values`, { requested: options.user });
|
|
1112
|
+
|
|
1113
|
+
Underpost.host.store.set('DEFAULT_SSH_USER', connection.user);
|
|
1114
|
+
Underpost.host.store.set('DEFAULT_SSH_HOST', connection.host);
|
|
1115
|
+
Underpost.host.store.set('DEFAULT_SSH_KEY_PATH', connection.keyPath);
|
|
1116
|
+
Underpost.host.store.set('DEFAULT_SSH_PORT', connection.port);
|
|
920
1117
|
},
|
|
921
1118
|
|
|
922
1119
|
/**
|
|
@@ -1073,4 +1270,6 @@ Subsystem sftp /usr/libexec/openssh/sftp-server
|
|
|
1073
1270
|
};
|
|
1074
1271
|
}
|
|
1075
1272
|
|
|
1273
|
+
export { connectionFactory, hostNameFactory, upsertConnection, userRecordFactory };
|
|
1274
|
+
|
|
1076
1275
|
export default UnderpostSSH;
|