@underpostnet/cyberia 3.2.80
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 +14 -0
- package/.env.example +144 -0
- package/.github/workflows/cyberia-client.cd.yml +40 -0
- package/.github/workflows/cyberia-server.cd.yml +40 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +71 -0
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +70 -0
- package/.github/workflows/docker-image.cyberia-server.ci.yml +71 -0
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +71 -0
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +53 -0
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +53 -0
- package/.github/workflows/engine-cyberia.cd.yml +157 -0
- package/.github/workflows/engine-cyberia.ci.yml +125 -0
- package/.github/workflows/ghpkg.ci.yml +160 -0
- package/.github/workflows/gitlab.ci.yml +20 -0
- package/.github/workflows/hardhat.ci.yml +82 -0
- package/.github/workflows/npmpkg.ci.yml +126 -0
- package/.github/workflows/publish.ci.yml +87 -0
- package/.github/workflows/publish.cyberia.ci.yml +79 -0
- package/.github/workflows/pwa-microservices-template-page.cd.yml +65 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +33 -0
- package/.github/workflows/release.cd.yml +46 -0
- package/.nycrc +9 -0
- package/.prettierignore +12 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +26 -0
- package/CHANGELOG.md +2516 -0
- package/CLI-HELP.md +1099 -0
- package/Dockerfile +161 -0
- package/Dockerfile.dev +161 -0
- package/Dockerfile.test +165 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/baremetal/commission-workflows.json +211 -0
- package/baremetal/packer-workflows.json +24 -0
- package/bin/build.js +261 -0
- package/bin/build.template.js +33 -0
- package/bin/cyberia.js +5627 -0
- package/bin/deploy.js +1547 -0
- package/bin/index.js +5627 -0
- package/bump.config.js +27 -0
- package/compose.env +144 -0
- package/conf.js +952 -0
- package/deployment.yaml +418 -0
- package/docker-compose.yml +434 -0
- package/examples/static-page/README.md +150 -0
- package/examples/static-page/ssr-components/CustomPage.js +554 -0
- package/examples/static-page/static-config-example.json +57 -0
- package/hardhat/.env.example +31 -0
- package/hardhat/contracts/ObjectLayerToken.sol +391 -0
- package/hardhat/deployments/.gitkeep +0 -0
- package/hardhat/deployments/hardhat-ObjectLayerToken.json +11 -0
- package/hardhat/hardhat.config.js +136 -0
- package/hardhat/ignition/modules/.gitkeep +0 -0
- package/hardhat/ignition/modules/ObjectLayerToken.js +21 -0
- package/hardhat/networks/besu-object-layer.network.json +138 -0
- package/hardhat/package-lock.json +2871 -0
- package/hardhat/package.json +43 -0
- package/hardhat/scripts/deployObjectLayerToken.js +98 -0
- package/hardhat/test/ObjectLayerToken.js +696 -0
- package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
- package/hardhat/types/ethers-contracts/common.ts +92 -0
- package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
- package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
- package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
- package/hardhat/types/ethers-contracts/index.ts +6 -0
- package/ipfs/configure-ipfs.sh +13 -0
- package/jsconfig.json +9 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +48 -0
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +48 -0
- package/manifests/deployment/adminer/deployment.yaml +32 -0
- package/manifests/deployment/adminer/kustomization.yaml +7 -0
- package/manifests/deployment/adminer/service.yaml +13 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +418 -0
- package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +189 -0
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +164 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +163 -0
- package/manifests/deployment/dd-default-development/proxy.yaml +46 -0
- package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
- package/manifests/deployment/fastapi/backend-service.yml +19 -0
- package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
- package/manifests/deployment/fastapi/frontend-service.yml +15 -0
- package/manifests/deployment/fastapi/initial_data.sh +8 -0
- package/manifests/deployment/kafka/deployment.yaml +67 -0
- package/manifests/deployment/mongo-express/deployment.yaml +62 -0
- package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
- package/manifests/deployment/playwright/deployment.yaml +52 -0
- package/manifests/deployment/spark/spark-pi-py.yaml +20 -0
- package/manifests/deployment/tensorflow/tf-gpu-test.yaml +63 -0
- package/manifests/envoy-service-nodeport.yaml +22 -0
- package/manifests/grafana/deployment.yaml +64 -0
- package/manifests/grafana/kustomization.yaml +7 -0
- package/manifests/grafana/pvc.yaml +12 -0
- package/manifests/grafana/service.yaml +14 -0
- package/manifests/grafana/storage-class.yaml +9 -0
- package/manifests/ipfs/configmap.yaml +64 -0
- package/manifests/ipfs/headless-service.yaml +35 -0
- package/manifests/ipfs/kustomization.yaml +8 -0
- package/manifests/ipfs/statefulset.yaml +149 -0
- package/manifests/ipfs/storage-class.yaml +9 -0
- package/manifests/kind-config-dev.yaml +20 -0
- package/manifests/kind-config.yaml +12 -0
- package/manifests/kubeadm-calico-config.yaml +14 -0
- package/manifests/letsencrypt-prod.yaml +14 -0
- package/manifests/lxd/lxd-admin-profile.yaml +26 -0
- package/manifests/lxd/lxd-preseed.yaml +30 -0
- package/manifests/mariadb/kustomization.yaml +9 -0
- package/manifests/mariadb/pv.yaml +12 -0
- package/manifests/mariadb/pvc.yaml +10 -0
- package/manifests/mariadb/service.yaml +10 -0
- package/manifests/mariadb/statefulset.yaml +56 -0
- package/manifests/mariadb/storage-class.yaml +10 -0
- package/manifests/mongodb/headless-service.yaml +10 -0
- package/manifests/mongodb/kustomization.yaml +11 -0
- package/manifests/mongodb/pv-pvc.yaml +59 -0
- package/manifests/mongodb/statefulset.yaml +113 -0
- package/manifests/mongodb/storage-class.yaml +9 -0
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +9 -0
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +19 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +61 -0
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/mysql/kustomization.yaml +7 -0
- package/manifests/mysql/pv-pvc.yaml +27 -0
- package/manifests/mysql/statefulset.yaml +55 -0
- package/manifests/postgresql/configmap.yaml +9 -0
- package/manifests/postgresql/kustomization.yaml +10 -0
- package/manifests/postgresql/pv.yaml +15 -0
- package/manifests/postgresql/pvc.yaml +13 -0
- package/manifests/postgresql/service.yaml +10 -0
- package/manifests/postgresql/statefulset.yaml +37 -0
- package/manifests/prometheus/deployment.yaml +82 -0
- package/manifests/pv-pvc-dd.yaml +34 -0
- package/manifests/valkey/kustomization.yaml +7 -0
- package/manifests/valkey/service.yaml +10 -0
- package/manifests/valkey/statefulset.yaml +37 -0
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/mongodb/entrypoint.sh +76 -0
- package/nginx.conf +128 -0
- package/nodemon.json +6 -0
- package/package.json +101 -0
- package/packer/images/Rocky9Amd64/Makefile +62 -0
- package/packer/images/Rocky9Amd64/QUICKSTART.md +113 -0
- package/packer/images/Rocky9Amd64/README.md +122 -0
- package/packer/images/Rocky9Amd64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +164 -0
- package/packer/images/Rocky9Arm64/Makefile +69 -0
- package/packer/images/Rocky9Arm64/README.md +122 -0
- package/packer/images/Rocky9Arm64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Arm64/rocky9.pkr.hcl +171 -0
- package/packer/scripts/fuse-nbd +64 -0
- package/packer/scripts/fuse-tar-root +63 -0
- package/proxy.yaml +189 -0
- package/pv-pvc.yaml +164 -0
- package/scripts/device-scan.sh +65 -0
- package/scripts/disk-clean.sh +182 -0
- package/scripts/disk-devices.sh +13 -0
- package/scripts/gen-fqdns.sh +14 -0
- package/scripts/gpu-diag.sh +19 -0
- package/scripts/ip-info.sh +118 -0
- package/scripts/ipxe-setup.sh +200 -0
- package/scripts/k3s-node-setup.sh +124 -0
- package/scripts/kubeadm-node-setup.sh +317 -0
- package/scripts/link-local-underpost-cli.sh +6 -0
- package/scripts/lxd-vm-setup.sh +208 -0
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/scripts/maas-setup.sh +135 -0
- package/scripts/maas-upload-boot-resource.sh +183 -0
- package/scripts/nat-iptables.sh +111 -0
- package/scripts/nvim.sh +91 -0
- package/scripts/packer-init-vars-file.sh +40 -0
- package/scripts/packer-setup.sh +289 -0
- package/scripts/ports-ls.sh +33 -0
- package/scripts/quick-tftp.sh +19 -0
- package/scripts/rhel-grpc-setup.sh +56 -0
- package/scripts/rocky-kickstart.sh +986 -0
- package/scripts/rocky-pwa.sh +200 -0
- package/scripts/rocky-setup.sh +106 -0
- package/scripts/rpmfusion-ffmpeg-setup.sh +32 -0
- package/scripts/ssl.sh +170 -0
- package/scripts/test-monitor.sh +248 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +30 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +155 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +39 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +382 -0
- package/src/api/core/core.controller.js +8 -0
- package/src/api/core/core.model.js +11 -0
- package/src/api/core/core.router.js +23 -0
- package/src/api/core/core.service.js +30 -0
- package/src/api/crypto/crypto.controller.js +10 -0
- package/src/api/crypto/crypto.model.js +23 -0
- package/src/api/crypto/crypto.router.js +23 -0
- package/src/api/crypto/crypto.service.js +64 -0
- package/src/api/cyberia-action/cyberia-action.controller.js +8 -0
- package/src/api/cyberia-action/cyberia-action.model.js +79 -0
- package/src/api/cyberia-action/cyberia-action.router.js +21 -0
- package/src/api/cyberia-action/cyberia-action.service.js +58 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +27 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +100 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +70 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +8 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +20 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +57 -0
- package/src/api/cyberia-entity/cyberia-entity.controller.js +6 -0
- package/src/api/cyberia-entity/cyberia-entity.model.js +25 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +17 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +6 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +337 -0
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +57 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +583 -0
- package/src/api/cyberia-instance/cyberia-instance.controller.js +42 -0
- package/src/api/cyberia-instance/cyberia-instance.model.js +117 -0
- package/src/api/cyberia-instance/cyberia-instance.router.js +75 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +190 -0
- package/src/api/cyberia-instance/cyberia-portal-connector.js +260 -0
- package/src/api/cyberia-instance/cyberia-world-generator.js +618 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +6 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +200 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +17 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +46 -0
- package/src/api/cyberia-map/cyberia-map.controller.js +6 -0
- package/src/api/cyberia-map/cyberia-map.model.js +33 -0
- package/src/api/cyberia-map/cyberia-map.router.js +19 -0
- package/src/api/cyberia-map/cyberia-map.service.js +76 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +9 -0
- package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +26 -0
- package/src/api/cyberia-quest/cyberia-quest.service.js +97 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +6 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +21 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
- package/src/api/cyberia-saga/cyberia-saga.controller.js +6 -0
- package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
- package/src/api/cyberia-saga/cyberia-saga.router.js +17 -0
- package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +1076 -0
- package/src/api/cyberia-skill/cyberia-skill.controller.js +6 -0
- package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
- package/src/api/cyberia-skill/cyberia-skill.router.js +17 -0
- package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
- package/src/api/default/default.controller.js +6 -0
- package/src/api/default/default.model.js +20 -0
- package/src/api/default/default.router.js +21 -0
- package/src/api/default/default.service.js +42 -0
- package/src/api/document/document.controller.js +9 -0
- package/src/api/document/document.model.js +95 -0
- package/src/api/document/document.router.js +32 -0
- package/src/api/document/document.service.js +633 -0
- package/src/api/file/file.controller.js +15 -0
- package/src/api/file/file.model.js +127 -0
- package/src/api/file/file.ref.json +39 -0
- package/src/api/file/file.router.js +36 -0
- package/src/api/file/file.service.js +499 -0
- package/src/api/instance/instance.controller.js +8 -0
- package/src/api/instance/instance.model.js +34 -0
- package/src/api/instance/instance.router.js +27 -0
- package/src/api/instance/instance.service.js +75 -0
- package/src/api/ipfs/ipfs.controller.js +8 -0
- package/src/api/ipfs/ipfs.model.js +77 -0
- package/src/api/ipfs/ipfs.router.js +26 -0
- package/src/api/ipfs/ipfs.service.js +145 -0
- package/src/api/object-layer/object-layer.controller.js +18 -0
- package/src/api/object-layer/object-layer.model.js +200 -0
- package/src/api/object-layer/object-layer.router.js +729 -0
- package/src/api/object-layer/object-layer.service.js +894 -0
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +6 -0
- package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +93 -0
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +17 -0
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +46 -0
- package/src/api/test/test.controller.js +14 -0
- package/src/api/test/test.model.js +14 -0
- package/src/api/test/test.router.js +23 -0
- package/src/api/test/test.service.js +35 -0
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +100 -0
- package/src/api/user/postman_collection.json +216 -0
- package/src/api/user/user.build.js +16 -0
- package/src/api/user/user.controller.js +35 -0
- package/src/api/user/user.model.js +142 -0
- package/src/api/user/user.router.js +367 -0
- package/src/api/user/user.service.js +582 -0
- package/src/api.js +23 -0
- package/src/cli/baremetal.js +3947 -0
- package/src/cli/cloud-init.js +673 -0
- package/src/cli/cluster.js +1395 -0
- package/src/cli/db.js +1533 -0
- package/src/cli/deploy.js +1542 -0
- package/src/cli/docker-compose.js +802 -0
- package/src/cli/env.js +177 -0
- package/src/cli/fs.js +380 -0
- package/src/cli/image.js +389 -0
- package/src/cli/index.js +1059 -0
- package/src/cli/ipfs.js +182 -0
- package/src/cli/kickstart.js +271 -0
- package/src/cli/kubectl.js +214 -0
- package/src/cli/lxd.js +1232 -0
- package/src/cli/monitor.js +659 -0
- package/src/cli/release.js +595 -0
- package/src/cli/repository.js +1969 -0
- package/src/cli/run.js +3326 -0
- package/src/cli/secrets.js +151 -0
- package/src/cli/ssh.js +975 -0
- package/src/cli/static.js +653 -0
- package/src/cli/system.js +332 -0
- package/src/cli/test.js +176 -0
- package/src/client/Cryptokoyn.index.js +30 -0
- package/src/client/CyberiaPortal.index.js +31 -0
- package/src/client/Default.index.js +77 -0
- package/src/client/Itemledger.index.js +35 -0
- package/src/client/Underpost.index.js +31 -0
- package/src/client/components/core/404.js +20 -0
- package/src/client/components/core/500.js +20 -0
- package/src/client/components/core/Account.js +630 -0
- package/src/client/components/core/AgGrid.js +238 -0
- package/src/client/components/core/Alert.js +76 -0
- package/src/client/components/core/AppStore.js +69 -0
- package/src/client/components/core/Auth.js +353 -0
- package/src/client/components/core/Badge.js +26 -0
- package/src/client/components/core/Blockchain.js +41 -0
- package/src/client/components/core/Blog.js +9 -0
- package/src/client/components/core/BtnIcon.js +109 -0
- package/src/client/components/core/CalendarCore.js +437 -0
- package/src/client/components/core/Chat.js +62 -0
- package/src/client/components/core/ClientEvents.js +163 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/CommonJs.js +1014 -0
- package/src/client/components/core/Content.js +349 -0
- package/src/client/components/core/Css.js +1134 -0
- package/src/client/components/core/CssCore.js +881 -0
- package/src/client/components/core/D3Chart.js +44 -0
- package/src/client/components/core/Docs.js +478 -0
- package/src/client/components/core/DropDown.js +321 -0
- package/src/client/components/core/EventBus.js +96 -0
- package/src/client/components/core/EventsUI.js +130 -0
- package/src/client/components/core/FileExplorer.js +1406 -0
- package/src/client/components/core/FullScreen.js +201 -0
- package/src/client/components/core/Input.js +537 -0
- package/src/client/components/core/Keyboard.js +80 -0
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +152 -0
- package/src/client/components/core/LogIn.js +212 -0
- package/src/client/components/core/LogOut.js +72 -0
- package/src/client/components/core/Logger.js +22 -0
- package/src/client/components/core/Modal.js +2956 -0
- package/src/client/components/core/NotificationManager.js +80 -0
- package/src/client/components/core/Pagination.js +252 -0
- package/src/client/components/core/Panel.js +1308 -0
- package/src/client/components/core/PanelForm.js +915 -0
- package/src/client/components/core/Polyhedron.js +947 -0
- package/src/client/components/core/PublicProfile.js +895 -0
- package/src/client/components/core/Recover.js +211 -0
- package/src/client/components/core/Responsive.js +146 -0
- package/src/client/components/core/RichText.js +33 -0
- package/src/client/components/core/Router.js +523 -0
- package/src/client/components/core/Scroll.js +76 -0
- package/src/client/components/core/SearchBox.js +1007 -0
- package/src/client/components/core/SignUp.js +153 -0
- package/src/client/components/core/SocketIo.js +163 -0
- package/src/client/components/core/SocketIoHandler.js +75 -0
- package/src/client/components/core/Stream.js +161 -0
- package/src/client/components/core/ToggleSwitch.js +89 -0
- package/src/client/components/core/ToolTip.js +78 -0
- package/src/client/components/core/Translate.js +694 -0
- package/src/client/components/core/Validator.js +140 -0
- package/src/client/components/core/VanillaJs.js +485 -0
- package/src/client/components/core/Wallet.js +95 -0
- package/src/client/components/core/WebComponent.js +44 -0
- package/src/client/components/core/Webhook.js +58 -0
- package/src/client/components/core/Worker.js +313 -0
- package/src/client/components/core/windowGetDimensions.js +269 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +352 -0
- package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
- package/src/client/components/cryptokoyn/CssCryptokoyn.js +212 -0
- package/src/client/components/cryptokoyn/LogInCryptokoyn.js +16 -0
- package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +12 -0
- package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +16 -0
- package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +11 -0
- package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +6 -0
- package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
- package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
- package/src/client/components/cyberia/InstanceEngineCyberia.js +1088 -0
- package/src/client/components/cyberia/InstanceSelectionView.js +900 -0
- package/src/client/components/cyberia/MapEngineCyberia.js +1976 -0
- package/src/client/components/cyberia/ObjectLayerEngine.js +1540 -0
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +2420 -0
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +1522 -0
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +528 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +772 -0
- package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +254 -0
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +21 -0
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +16 -0
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +518 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +72 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +16 -0
- package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +13 -0
- package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +6 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +48 -0
- package/src/client/components/default/AppShellDefault.js +765 -0
- package/src/client/components/default/AppStoreDefault.js +5 -0
- package/src/client/components/default/CommonDefault.js +29 -0
- package/src/client/components/default/CssDefault.js +27 -0
- package/src/client/components/default/LogInDefault.js +16 -0
- package/src/client/components/default/LogOutDefault.js +12 -0
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +16 -0
- package/src/client/components/default/SignUpDefault.js +11 -0
- package/src/client/components/default/SocketIoDefault.js +6 -0
- package/src/client/components/default/TranslateDefault.js +7 -0
- package/src/client/components/itemledger/AppShellItemledger.js +353 -0
- package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
- package/src/client/components/itemledger/CssItemledger.js +212 -0
- package/src/client/components/itemledger/LogInItemledger.js +17 -0
- package/src/client/components/itemledger/LogOutItemledger.js +12 -0
- package/src/client/components/itemledger/RouterItemledger.js +38 -0
- package/src/client/components/itemledger/SettingsItemledger.js +16 -0
- package/src/client/components/itemledger/SignUpItemledger.js +11 -0
- package/src/client/components/itemledger/SocketIoItemledger.js +6 -0
- package/src/client/components/itemledger/TranslateItemledger.js +7 -0
- package/src/client/components/underpost/AppShellUnderpost.js +691 -0
- package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
- package/src/client/components/underpost/CssUnderpost.js +281 -0
- package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +879 -0
- package/src/client/components/underpost/DocumentSearchProvider.js +448 -0
- package/src/client/components/underpost/LabGalleryUnderpost.js +79 -0
- package/src/client/components/underpost/LogInUnderpost.js +25 -0
- package/src/client/components/underpost/LogOutUnderpost.js +17 -0
- package/src/client/components/underpost/RouterUnderpost.js +45 -0
- package/src/client/components/underpost/SettingsUnderpost.js +16 -0
- package/src/client/components/underpost/SignUpUnderpost.js +11 -0
- package/src/client/components/underpost/SocketIoUnderpost.js +6 -0
- package/src/client/components/underpost/TranslateUnderpost.js +10 -0
- package/src/client/public/cryptokoyn/assets/banner/koyn-social.png +0 -0
- package/src/client/public/cryptokoyn/assets/logo/base-icon.png +0 -0
- package/src/client/public/cryptokoyn/assets/logo/cryptokoyn.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-144x144.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-192x192.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-256x256.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-36x36.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-48x48.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-72x72.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/android-chrome-96x96.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-114x114-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-120x120-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-144x144-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-152x152-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-180x180-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-57x57-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-60x60-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-72x72-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-76x76-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/apple-touch-icon.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/favicon-16x16.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/favicon-32x32.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/mstile-144x144.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/mstile-150x150.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/mstile-310x150.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/mstile-310x310.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/mstile-70x70.png +0 -0
- package/src/client/public/cryptokoyn/assets/splash/safari-pinned-tab.svg +42 -0
- package/src/client/public/cryptokoyn/browserconfig.xml +12 -0
- package/src/client/public/cryptokoyn/favicon.ico +0 -0
- package/src/client/public/cryptokoyn/microdata.json +85 -0
- package/src/client/public/cryptokoyn/site.webmanifest +57 -0
- package/src/client/public/cryptokoyn/sitemap +148 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +307 -0
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +309 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +204 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +341 -0
- package/src/client/public/cyberia-docs/CYBERIA-LORE.md +84 -0
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +294 -0
- package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +242 -0
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +309 -0
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +228 -0
- package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +740 -0
- 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-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152-precomposed.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-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76-precomposed.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/dark.jpg +0 -0
- package/src/client/public/default/assets/background/dark.svg +557 -0
- package/src/client/public/default/assets/background/white.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/default/assets/logo/base-icon.png +0 -0
- package/src/client/public/default/assets/logo/underpost.gif +0 -0
- package/src/client/public/default/assets/mailer/api-user-check.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-invalid-token.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-recover.png +0 -0
- package/src/client/public/default/browserconfig.xml +12 -0
- 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 +69 -0
- 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/safari-pinned-tab.svg +24 -0
- package/src/client/public/default/site.webmanifest +69 -0
- package/src/client/public/default/sitemap +148 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +9 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +148 -0
- package/src/client/public/itemledger/android-chrome-144x144.png +0 -0
- package/src/client/public/itemledger/android-chrome-192x192.png +0 -0
- package/src/client/public/itemledger/android-chrome-256x256.png +0 -0
- package/src/client/public/itemledger/android-chrome-36x36.png +0 -0
- package/src/client/public/itemledger/android-chrome-384x384.png +0 -0
- package/src/client/public/itemledger/android-chrome-48x48.png +0 -0
- package/src/client/public/itemledger/android-chrome-512x512.png +0 -0
- package/src/client/public/itemledger/android-chrome-72x72.png +0 -0
- package/src/client/public/itemledger/android-chrome-96x96.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/itemledger/apple-touch-icon.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/itemledger/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/itemledger/assets/logo/base-icon.png +0 -0
- package/src/client/public/itemledger/browserconfig.xml +12 -0
- package/src/client/public/itemledger/favicon-16x16.png +0 -0
- package/src/client/public/itemledger/favicon-32x32.png +0 -0
- package/src/client/public/itemledger/favicon-48x48.png +0 -0
- package/src/client/public/itemledger/favicon.ico +0 -0
- package/src/client/public/itemledger/manifest.webmanifest +69 -0
- package/src/client/public/itemledger/microdata.json +71 -0
- package/src/client/public/itemledger/mstile-144x144.png +0 -0
- package/src/client/public/itemledger/mstile-150x150.png +0 -0
- package/src/client/public/itemledger/mstile-310x150.png +0 -0
- package/src/client/public/itemledger/mstile-310x310.png +0 -0
- package/src/client/public/itemledger/mstile-70x70.png +0 -0
- package/src/client/public/itemledger/sitemap +148 -0
- package/src/client/public/itemledger/yandex-browser-50x50.png +0 -0
- package/src/client/public/itemledger/yandex-browser-manifest.json +9 -0
- package/src/client/public/test/favicon.ico +0 -0
- package/src/client/public/test/sitemap +148 -0
- package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +137 -0
- package/src/client/services/core/core.service.js +309 -0
- package/src/client/services/crypto/crypto.service.js +68 -0
- package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
- package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
- package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +99 -0
- package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
- package/src/client/services/cyberia-entity/cyberia-entity.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +137 -0
- package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +99 -0
- package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
- package/src/client/services/cyberia-map/cyberia-map.service.js +120 -0
- package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
- package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
- package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
- package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
- package/src/client/services/default/default.management.js +1015 -0
- package/src/client/services/default/default.service.js +99 -0
- package/src/client/services/document/document.service.js +134 -0
- package/src/client/services/file/file.service.js +94 -0
- package/src/client/services/instance/instance.management.js +78 -0
- package/src/client/services/instance/instance.service.js +107 -0
- package/src/client/services/ipfs/ipfs.service.js +118 -0
- package/src/client/services/object-layer/object-layer.management.js +414 -0
- package/src/client/services/object-layer/object-layer.service.js +208 -0
- package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +99 -0
- package/src/client/services/test/test.service.js +68 -0
- package/src/client/services/user/guest.service.js +86 -0
- package/src/client/services/user/user.management.js +51 -0
- package/src/client/services/user/user.service.js +106 -0
- package/src/client/ssr/RootDocument.js +237 -0
- package/src/client/ssr/body/404.js +77 -0
- package/src/client/ssr/body/500.js +76 -0
- package/src/client/ssr/body/CacheControl.js +117 -0
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +97 -0
- package/src/client/ssr/body/DefaultSplashScreen.js +97 -0
- package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +90 -0
- package/src/client/ssr/head/CryptokoynScripts.js +4 -0
- package/src/client/ssr/head/Css.js +241 -0
- package/src/client/ssr/head/CyberiaPortalScripts.js +6 -0
- package/src/client/ssr/head/DefaultScripts.js +6 -0
- package/src/client/ssr/head/ItemledgerScripts.js +4 -0
- package/src/client/ssr/head/Microdata.js +11 -0
- package/src/client/ssr/head/Production.js +1 -0
- package/src/client/ssr/head/Pwa.js +146 -0
- package/src/client/ssr/head/PwaItemledger.js +197 -0
- package/src/client/ssr/head/Seo.js +15 -0
- package/src/client/ssr/head/UnderpostScripts.js +6 -0
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +20 -0
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +16 -0
- package/src/client/ssr/views/Cyberia404.js +328 -0
- package/src/client/ssr/views/CyberiaServerMetrics.js +1070 -0
- package/src/client/ssr/views/Maintenance.js +64 -0
- package/src/client/ssr/views/NoNetworkConnection.js +68 -0
- package/src/client/ssr/views/Test.js +199 -0
- package/src/client/sw/core.sw.js +274 -0
- package/src/client-builder/client-build-docs.js +493 -0
- package/src/client-builder/client-build-live.js +100 -0
- package/src/client-builder/client-build.js +1092 -0
- package/src/client-builder/client-dev-server.js +88 -0
- package/src/client-builder/client-formatted.js +170 -0
- package/src/client-builder/client-icons.js +108 -0
- package/src/client-builder/ssr.js +124 -0
- package/src/client.build.js +17 -0
- package/src/client.dev.js +21 -0
- package/src/db/DataBaseProvider.js +213 -0
- package/src/db/mariadb/MariaDB.js +72 -0
- package/src/db/mongo/MongoBootstrap.js +657 -0
- package/src/db/mongo/MongooseDB.js +199 -0
- package/src/grpc/cyberia/grpc-server.js +162 -0
- package/src/index.js +357 -0
- package/src/mailer/EmailRender.js +116 -0
- package/src/mailer/MailerProvider.js +213 -0
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +413 -0
- package/src/projects/cyberia/besu-genesis-generator.js +1630 -0
- package/src/projects/cyberia/catalog-cyberia.js +90 -0
- package/src/projects/cyberia/gemini-client.js +175 -0
- package/src/projects/cyberia/generate-saga.js +2137 -0
- package/src/projects/cyberia/hot-reload-trigger.js +163 -0
- package/src/projects/cyberia/instance-data.js +719 -0
- package/src/projects/cyberia/ipfs-client.js +599 -0
- package/src/projects/cyberia/map-preview-generator.js +304 -0
- package/src/projects/cyberia/object-layer.js +978 -0
- package/src/projects/cyberia/semantic-layer-generator-floor.js +319 -0
- package/src/projects/cyberia/semantic-layer-generator-resource.js +259 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +1164 -0
- package/src/projects/cyberia/semantic-layer-generator.js +753 -0
- package/src/projects/cyberia/shape-generator.js +1060 -0
- package/src/projects/underpost/catalog-underpost.js +60 -0
- package/src/proxy.js +19 -0
- package/src/runtime/cyberia-client/Dockerfile +49 -0
- package/src/runtime/cyberia-client/Dockerfile.dev +75 -0
- package/src/runtime/cyberia-server/Dockerfile +43 -0
- package/src/runtime/cyberia-server/Dockerfile.dev +56 -0
- package/src/runtime/engine-cyberia/Dockerfile +161 -0
- package/src/runtime/engine-cyberia/Dockerfile.dev +161 -0
- package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
- package/src/runtime/engine-cyberia/compose.env +144 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +434 -0
- package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
- package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
- package/src/runtime/engine-cyberia/nginx.conf +128 -0
- package/src/runtime/express/Dockerfile +45 -0
- package/src/runtime/express/Express.js +285 -0
- package/src/runtime/lampp/Dockerfile +62 -0
- package/src/runtime/lampp/Lampp.js +359 -0
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/runtime/wp/Dockerfile +62 -0
- package/src/runtime/wp/Wp.js +642 -0
- package/src/server/auth.js +713 -0
- package/src/server/backup.js +102 -0
- package/src/server/catalog.js +72 -0
- package/src/server/conf.js +2316 -0
- package/src/server/cron.js +476 -0
- package/src/server/crypto.js +210 -0
- package/src/server/data-query.js +461 -0
- package/src/server/dns.js +553 -0
- package/src/server/downloader.js +73 -0
- package/src/server/logger.js +229 -0
- package/src/server/middlewares.js +152 -0
- package/src/server/peer.js +75 -0
- package/src/server/process.js +299 -0
- package/src/server/proxy.js +139 -0
- package/src/server/runtime-status.js +252 -0
- package/src/server/runtime.js +183 -0
- package/src/server/start.js +272 -0
- package/src/server/tls.js +254 -0
- package/src/server/valkey.js +201 -0
- package/src/server.js +25 -0
- package/src/ws/IoInterface.js +129 -0
- package/src/ws/IoServer.js +68 -0
- package/src/ws/core/channels/core.ws.chat.js +68 -0
- package/src/ws/core/channels/core.ws.mailer.js +116 -0
- package/src/ws/core/channels/core.ws.stream.js +90 -0
- package/src/ws/core/core.ws.connection.js +41 -0
- package/src/ws/core/core.ws.emit.js +37 -0
- package/src/ws/core/core.ws.server.js +44 -0
- package/src/ws/default/channels/default.ws.main.js +57 -0
- package/src/ws/default/default.ws.connection.js +35 -0
- package/src/ws/default/default.ws.emit.js +14 -0
- package/src/ws/default/default.ws.server.js +39 -0
- package/test/api.test.js +53 -0
- package/test/crypto.test.js +117 -0
- package/test/cyberia-instance-conf-defaults.test.js +140 -0
- package/test/deploy-monitor.test.js +267 -0
- package/test/shape-generator.test.js +463 -0
- package/tsconfig.docs.json +15 -0
- package/typedoc.dd-cyberia.json +31 -0
- package/typedoc.json +31 -0
package/src/cli/run.js
ADDED
|
@@ -0,0 +1,3326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description The main entry point for the Underpost CLI applications.
|
|
3
|
+
* @module src/cli/run.js
|
|
4
|
+
* @namespace UnderpostRun
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { daemonProcess, getTerminalPid, pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
import {
|
|
10
|
+
awaitDeployMonitor,
|
|
11
|
+
buildKindPorts,
|
|
12
|
+
Config,
|
|
13
|
+
cronDeployIdResolve,
|
|
14
|
+
etcHostFactory,
|
|
15
|
+
getNpmRootPath,
|
|
16
|
+
isDeployRunnerContext,
|
|
17
|
+
loadConfInstances,
|
|
18
|
+
loadConfServerJson,
|
|
19
|
+
selectConfInstances,
|
|
20
|
+
loadReplicas,
|
|
21
|
+
writeEnv,
|
|
22
|
+
} from '../server/conf.js';
|
|
23
|
+
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
24
|
+
|
|
25
|
+
import fs from 'fs-extra';
|
|
26
|
+
import net from 'net';
|
|
27
|
+
import { range, s4, setPad, timer } from '../client/components/core/CommonJs.js';
|
|
28
|
+
|
|
29
|
+
import os from 'os';
|
|
30
|
+
import Underpost from '../index.js';
|
|
31
|
+
import dotenv from 'dotenv';
|
|
32
|
+
import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
|
|
33
|
+
|
|
34
|
+
const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
|
|
35
|
+
new Promise((resolve, reject) => {
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
const tryConnect = () => {
|
|
38
|
+
attempts++;
|
|
39
|
+
const socket = net.createConnection({ port, host }, () => {
|
|
40
|
+
socket.destroy();
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
socket.on('error', () => {
|
|
44
|
+
socket.destroy();
|
|
45
|
+
if (attempts >= maxAttempts) return reject(new Error(`Port ${port} not ready after ${maxAttempts} attempts`));
|
|
46
|
+
setTimeout(tryConnect, interval);
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
tryConnect();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const logger = loggerFactory(import.meta);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @method instanceProxyRoutesFactory
|
|
56
|
+
* @description Renders the HTTPProxy route block for every instance sharing a host.
|
|
57
|
+
* Routes are emitted longest-prefix first so a specific instance path (`/FOREST`)
|
|
58
|
+
* is never shadowed by the default instance's catch-all (`/`).
|
|
59
|
+
* @param {string} deployId - Parent deployment identifier.
|
|
60
|
+
* @param {Array<object>} instances - Expanded instance entries bound to one host.
|
|
61
|
+
* @param {string} env - `development` | `production`.
|
|
62
|
+
* @param {Object<string,string>} trafficById - Instance id → traffic colour.
|
|
63
|
+
* @returns {string} Concatenated route YAML.
|
|
64
|
+
*/
|
|
65
|
+
const instanceProxyRoutesFactory = ({ deployId, instances, env, trafficById }) =>
|
|
66
|
+
[...instances]
|
|
67
|
+
.sort((a, b) => (b.path || '/').length - (a.path || '/').length)
|
|
68
|
+
.map((instance) =>
|
|
69
|
+
Underpost.deploy.deploymentYamlServiceFactory({
|
|
70
|
+
path: instance.path,
|
|
71
|
+
port: env === 'development' && instance.fromDebugPort ? instance.fromDebugPort : instance.fromPort,
|
|
72
|
+
deployId: `${deployId}-${instance.id}`,
|
|
73
|
+
env,
|
|
74
|
+
deploymentVersions: [trafficById[instance.id] || 'blue'],
|
|
75
|
+
pathRewritePolicy: instance.pathRewritePolicy,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
.join('');
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @constant DEFAULT_OPTION
|
|
82
|
+
* @description Default options for the UnderpostRun class.
|
|
83
|
+
* @typedef {Object} UnderpostRunDefaultOptions
|
|
84
|
+
* @type {Object}
|
|
85
|
+
* @property {boolean} dev - Whether to run in development mode.
|
|
86
|
+
* @property {string} podName - The name of the pod to run.
|
|
87
|
+
* @property {string} nodeName - The name of the node to run.
|
|
88
|
+
* @property {string} sshKeyPath - Private key path for node SSH operations, forwarded to volume shipping over SSH.
|
|
89
|
+
* @property {number} port - Custom port to use.
|
|
90
|
+
* @property {string} volumeHostPath - The host path for the volume.
|
|
91
|
+
* @property {string} volumeMountPath - The mount path for the volume.
|
|
92
|
+
* @property {string} imageName - The name of the image to run.
|
|
93
|
+
* @property {string} containerName - The name of the container to run.
|
|
94
|
+
* @property {string} namespace - The namespace to run in.
|
|
95
|
+
* @property {string} timeoutResponse - The response timeout duration.
|
|
96
|
+
* @property {string} timeoutIdle - The idle timeout duration.
|
|
97
|
+
* @property {string} retryCount - The number of retries.
|
|
98
|
+
* @property {string} retryPerTryTimeout - The timeout duration per retry.
|
|
99
|
+
* @property {boolean} build - Whether to build the image.
|
|
100
|
+
* @property {number} replicas - The number of replicas to run.
|
|
101
|
+
* @property {boolean} force - Whether to force the operation.
|
|
102
|
+
* @property {boolean} reset - Whether to reset the operation.
|
|
103
|
+
* @property {boolean} tls - Whether to use TLS.
|
|
104
|
+
* @property {string} cmd - The command to run in the container.
|
|
105
|
+
* @property {string} tty - The TTY option for the container.
|
|
106
|
+
* @property {string} stdin - The stdin option for the container.
|
|
107
|
+
* @property {string} restartPolicy - The restart policy for the container.
|
|
108
|
+
* @property {string} runtimeClassName - The runtime class name for the container.
|
|
109
|
+
* @property {string} imagePullPolicy - The image pull policy for the container.
|
|
110
|
+
* @property {string} apiVersion - The API version for the container.
|
|
111
|
+
* @property {string} claimName - The claim name for the volume.
|
|
112
|
+
* @property {string} kindType - The kind of resource to create.
|
|
113
|
+
* @property {number} devProxyPortOffset - The port offset for the development proxy.
|
|
114
|
+
* @property {boolean} hostNetwork - Whether to use host networking.
|
|
115
|
+
* @property {string} requestsMemory - The memory request for the container.
|
|
116
|
+
* @property {string} requestsCpu - The CPU request for the container.
|
|
117
|
+
* @property {string} limitsMemory - The memory limit for the container.
|
|
118
|
+
* @property {string} limitsCpu - The CPU limit for the container.
|
|
119
|
+
* @property {string} resourceTemplateId - The resource template ID.
|
|
120
|
+
* @property {boolean} expose - Whether to expose the service.
|
|
121
|
+
* @property {boolean} etcHosts - Whether to modify /etc/hosts.
|
|
122
|
+
* @property {string} confServerPath - The configuration server path.
|
|
123
|
+
* @property {string} underpostRoot - The root path of the Underpost installation.
|
|
124
|
+
* @property {string} cmdCronJobs - Pre-script commands to run before cron job execution.
|
|
125
|
+
* @property {string} deployIdCronJobs - The deployment ID for cron jobs.
|
|
126
|
+
* @property {string} timezone - The timezone to set.
|
|
127
|
+
* @property {boolean} kubeadm - Whether to run in kubeadm mode.
|
|
128
|
+
* @property {boolean} kind - Whether to run in kind mode.
|
|
129
|
+
* @property {boolean} k3s - Whether to run in k3s mode.
|
|
130
|
+
* @property {string} hosts - The hosts to use.
|
|
131
|
+
* @property {string} deployId - The deployment ID.
|
|
132
|
+
* @property {string} instanceId - The instance ID.
|
|
133
|
+
* @property {string} user - The user to run as.
|
|
134
|
+
* @property {string} group - The group to use.
|
|
135
|
+
* @property {string} pid - The process ID.
|
|
136
|
+
* @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
|
|
137
|
+
* @property {string} monitorStatus - The monitor status option.
|
|
138
|
+
* @property {string} monitorStatusKindType - The monitor status kind type option.
|
|
139
|
+
* @property {string} monitorStatusDeltaMs - The monitor status delta in milliseconds.
|
|
140
|
+
* @property {string} monitorStatusMaxAttempts - The maximum number of attempts for monitor status.
|
|
141
|
+
* @property {boolean} logs - Whether to enable logs.
|
|
142
|
+
* @property {boolean} dryRun - Whether to perform a dry run.
|
|
143
|
+
* @property {boolean} createJobNow - Whether to create the job immediately.
|
|
144
|
+
* @property {number} fromNCommit - Number of commits back to use for message propagation (default: 1, last commit only).
|
|
145
|
+
* @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
|
|
146
|
+
* As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
|
|
147
|
+
* As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
|
|
148
|
+
* @property {boolean} gitClean - Whether to perform a `git clean` before running.
|
|
149
|
+
* @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
|
|
150
|
+
* @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
|
|
151
|
+
* @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
|
|
152
|
+
* @property {boolean} remove - Whether to remove/teardown resources instead of creating them (e.g. delete-expose for k3s proxy devices in dev-cluster).
|
|
153
|
+
* @property {boolean} test - Whether to enable test/generic-purpose mode (e.g. use self-signed TLS instead of cert-manager).
|
|
154
|
+
* @property {string} branch - The Git branch to use for operations (e.g., for template-deploy, ssh-deploy).
|
|
155
|
+
* @memberof UnderpostRun
|
|
156
|
+
*/
|
|
157
|
+
const DEFAULT_OPTION = {
|
|
158
|
+
dev: false,
|
|
159
|
+
podName: '',
|
|
160
|
+
nodeName: '',
|
|
161
|
+
sshKeyPath: '',
|
|
162
|
+
port: 0,
|
|
163
|
+
volumeHostPath: '',
|
|
164
|
+
volumeMountPath: '',
|
|
165
|
+
imageName: '',
|
|
166
|
+
containerName: '',
|
|
167
|
+
namespace: 'default',
|
|
168
|
+
timeoutResponse: '',
|
|
169
|
+
timeoutIdle: '',
|
|
170
|
+
retryCount: '',
|
|
171
|
+
retryPerTryTimeout: '',
|
|
172
|
+
build: false,
|
|
173
|
+
replicas: 1,
|
|
174
|
+
force: false,
|
|
175
|
+
reset: false,
|
|
176
|
+
tls: false,
|
|
177
|
+
cmd: '',
|
|
178
|
+
tty: '',
|
|
179
|
+
stdin: '',
|
|
180
|
+
restartPolicy: '',
|
|
181
|
+
runtimeClassName: '',
|
|
182
|
+
imagePullPolicy: '',
|
|
183
|
+
apiVersion: '',
|
|
184
|
+
claimName: '',
|
|
185
|
+
kindType: '',
|
|
186
|
+
devProxyPortOffset: 0,
|
|
187
|
+
hostNetwork: false,
|
|
188
|
+
requestsMemory: '',
|
|
189
|
+
requestsCpu: '',
|
|
190
|
+
limitsMemory: '',
|
|
191
|
+
limitsCpu: '',
|
|
192
|
+
resourceTemplateId: '',
|
|
193
|
+
expose: false,
|
|
194
|
+
etcHosts: false,
|
|
195
|
+
confServerPath: '',
|
|
196
|
+
underpostRoot: '',
|
|
197
|
+
cmdCronJobs: '',
|
|
198
|
+
deployIdCronJobs: '',
|
|
199
|
+
timezone: '',
|
|
200
|
+
kubeadm: false,
|
|
201
|
+
kind: false,
|
|
202
|
+
k3s: false,
|
|
203
|
+
hosts: '',
|
|
204
|
+
deployId: '',
|
|
205
|
+
instanceId: '',
|
|
206
|
+
user: '',
|
|
207
|
+
group: '',
|
|
208
|
+
pid: '',
|
|
209
|
+
disablePrivateConfUpdate: false,
|
|
210
|
+
monitorStatus: '',
|
|
211
|
+
monitorStatusKindType: '',
|
|
212
|
+
monitorStatusDeltaMs: '',
|
|
213
|
+
monitorStatusMaxAttempts: '',
|
|
214
|
+
logs: false,
|
|
215
|
+
dryRun: false,
|
|
216
|
+
createJobNow: false,
|
|
217
|
+
fromNCommit: 0,
|
|
218
|
+
hostAliases: '',
|
|
219
|
+
gitClean: false,
|
|
220
|
+
copy: false,
|
|
221
|
+
skipFullBuild: false,
|
|
222
|
+
pullBundle: false,
|
|
223
|
+
remove: false,
|
|
224
|
+
test: false,
|
|
225
|
+
branch: '',
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @class UnderpostRun
|
|
230
|
+
* @description Manages the execution of various CLI commands and operations.
|
|
231
|
+
* This class provides a set of static methods to perform different tasks
|
|
232
|
+
* such as running tests, deploying applications, managing environment variables,
|
|
233
|
+
* and more. It also includes a default option configuration and a collection of
|
|
234
|
+
* runners for executing specific commands.
|
|
235
|
+
* @memberof UnderpostRun
|
|
236
|
+
*/
|
|
237
|
+
class UnderpostRun {
|
|
238
|
+
/**
|
|
239
|
+
* @static
|
|
240
|
+
* @description Collection of runners for executing specific commands.
|
|
241
|
+
* @type {Object}
|
|
242
|
+
* @memberof UnderpostRun
|
|
243
|
+
*/
|
|
244
|
+
static RUNNERS = {
|
|
245
|
+
/**
|
|
246
|
+
* @method dev-cluster
|
|
247
|
+
* @description Resets and deploys a full development cluster including MongoDB, Valkey, exposes services, and updates `/etc/hosts` for local access.
|
|
248
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
249
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
250
|
+
* @memberof UnderpostRun
|
|
251
|
+
*/
|
|
252
|
+
'dev-cluster': (path, options = DEFAULT_OPTION) => {
|
|
253
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
254
|
+
const mongoHosts = ['mongodb-0.mongodb-service'];
|
|
255
|
+
let primaryMongoHost = 'mongodb-0.mongodb-service';
|
|
256
|
+
if (!options.expose) {
|
|
257
|
+
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --reset`);
|
|
258
|
+
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''}`);
|
|
259
|
+
|
|
260
|
+
shellExec(
|
|
261
|
+
`${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb --service-host ${mongoHosts.join(
|
|
262
|
+
',',
|
|
263
|
+
)} --pull-image`,
|
|
264
|
+
);
|
|
265
|
+
shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --valkey --pull-image`);
|
|
266
|
+
}
|
|
267
|
+
if (options.k3s) {
|
|
268
|
+
if (options.remove) {
|
|
269
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:27017`);
|
|
270
|
+
shellExec(`${baseCommand} lxd --delete-expose k3s-control:6379`);
|
|
271
|
+
} else {
|
|
272
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:27017 --node-port 32017`);
|
|
273
|
+
shellExec(`${baseCommand} lxd --expose k3s-control:6379 --node-port 32079`);
|
|
274
|
+
}
|
|
275
|
+
shellExec(`lxc config device show k3s-control`);
|
|
276
|
+
} else {
|
|
277
|
+
try {
|
|
278
|
+
const primaryPodName =
|
|
279
|
+
MongoBootstrap.getPrimaryPodName({
|
|
280
|
+
namespace: options.namespace,
|
|
281
|
+
podName: 'mongodb-0',
|
|
282
|
+
disableAuth: options.dev,
|
|
283
|
+
}) || 'mongodb-0';
|
|
284
|
+
shellExec(
|
|
285
|
+
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config mongo`,
|
|
286
|
+
{ async: true },
|
|
287
|
+
);
|
|
288
|
+
shellExec(
|
|
289
|
+
`${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config valkey`,
|
|
290
|
+
{ async: true },
|
|
291
|
+
);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger.warn('Failed to detect MongoDB primary pod, using default', {
|
|
294
|
+
error: error.message,
|
|
295
|
+
default: primaryMongoHost,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const hostListenResult = etcHostFactory([primaryMongoHost]);
|
|
300
|
+
logger.info(hostListenResult.renderHosts);
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @method etc-hosts
|
|
305
|
+
* @description Modifies the `/etc/hosts` file to add entries for local access to services,
|
|
306
|
+
* based on the provided path input.
|
|
307
|
+
* @param {string} path - The input value, identifier, or path for the operation (used to specify the entries to add to /etc/hosts).
|
|
308
|
+
*/
|
|
309
|
+
'etc-hosts': (path = '', options = DEFAULT_OPTION) => {
|
|
310
|
+
etcHostFactory(path.split(','));
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @method ipfs-expose
|
|
315
|
+
* @description Exposes IPFS Cluster services on specified ports for local access.
|
|
316
|
+
* @type {Function}
|
|
317
|
+
* @memberof UnderpostRun
|
|
318
|
+
*/
|
|
319
|
+
'ipfs-expose': (path, options = DEFAULT_OPTION) => {
|
|
320
|
+
const ports = [5001, 9094, 8080];
|
|
321
|
+
for (const port of ports)
|
|
322
|
+
shellExec(`node bin deploy --expose ipfs-cluster --expose-port ${port} --disable-update-underpost-config`, {
|
|
323
|
+
async: true,
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @method metadata
|
|
329
|
+
* @description Generates metadata for the specified path after exposing the development cluster.
|
|
330
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
331
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
332
|
+
* @memberof UnderpostRun
|
|
333
|
+
*/
|
|
334
|
+
metadata: async (path, options = DEFAULT_OPTION) => {
|
|
335
|
+
const ports = '6379,27017';
|
|
336
|
+
shellExec(`node bin run kill '${ports}'`);
|
|
337
|
+
shellExec(`node bin run dev-cluster --dev --expose --namespace ${options.namespace}`, { async: true });
|
|
338
|
+
logger.info('Waiting for port-forward services to be ready...');
|
|
339
|
+
try {
|
|
340
|
+
await Promise.all([waitForPort(27017), waitForPort(6379)]);
|
|
341
|
+
logger.info('Port-forward services are ready');
|
|
342
|
+
} catch (err) {
|
|
343
|
+
logger.error('Port-forward services failed to become ready', { error: err.message });
|
|
344
|
+
shellExec(`node bin run kill '${ports}'`);
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
shellExec(`node bin metadata --generate ${path}`);
|
|
348
|
+
shellExec(`node bin db --dev --clean-fs-collection dd`);
|
|
349
|
+
shellExec(`node bin run kill '${ports}'`);
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @method svc-ls
|
|
354
|
+
* @description Lists systemd services and installed packages, optionally filtering by the provided path.
|
|
355
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional filter for services and packages).
|
|
356
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
357
|
+
* @memberof UnderpostRun
|
|
358
|
+
*/
|
|
359
|
+
'svc-ls': (path, options = DEFAULT_OPTION) => {
|
|
360
|
+
const log = shellExec(`systemctl list-units --type=service${path ? ` | grep ${path}` : ''}`, {
|
|
361
|
+
silent: true,
|
|
362
|
+
stdout: true,
|
|
363
|
+
});
|
|
364
|
+
console.log(path ? log.replaceAll(path, path.red) : log);
|
|
365
|
+
const log0 = shellExec(`sudo dnf list installed${path ? ` | grep ${path}` : ''}`, {
|
|
366
|
+
silent: true,
|
|
367
|
+
stdout: true,
|
|
368
|
+
});
|
|
369
|
+
console.log(path ? log0.replaceAll(path, path.red) : log0);
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @method svc-rm
|
|
374
|
+
* @description Removes a systemd service by stopping it, disabling it, uninstalling the package, and deleting related files.
|
|
375
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the service name).
|
|
376
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
377
|
+
* @memberof UnderpostRun
|
|
378
|
+
*/
|
|
379
|
+
'svc-rm': (path, options = DEFAULT_OPTION) => {
|
|
380
|
+
shellExec(`sudo systemctl stop ${path}`);
|
|
381
|
+
shellExec(`sudo systemctl disable --now ${path}`);
|
|
382
|
+
shellExec(`sudo dnf remove -y ${path}*`);
|
|
383
|
+
shellExec(`sudo rm -f /usr/lib/systemd/system/${path}.service`);
|
|
384
|
+
shellExec(`sudo rm -f /etc/yum.repos.d/${path}*.repo`);
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @method ssh-deploy-info
|
|
389
|
+
* @description Retrieves deployment status and pod information from a remote server via SSH.
|
|
390
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
391
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
392
|
+
* @memberof UnderpostRun
|
|
393
|
+
*/
|
|
394
|
+
'ssh-deploy-info': async (path = '', options = DEFAULT_OPTION) => {
|
|
395
|
+
const env = options.dev ? 'development' : 'production';
|
|
396
|
+
await Underpost.ssh.sshRemoteRunner(
|
|
397
|
+
`node bin deploy ${path ? path : 'dd'} ${env} --status && kubectl get pods -A`,
|
|
398
|
+
{
|
|
399
|
+
deployId: options.deployId,
|
|
400
|
+
user: options.user,
|
|
401
|
+
dev: options.dev,
|
|
402
|
+
remote: true,
|
|
403
|
+
useSudo: true,
|
|
404
|
+
cd: '/home/dd/engine',
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* @method node-move
|
|
411
|
+
* @description Abstract runner that relocates any schedulable Kubernetes workload
|
|
412
|
+
* (Deployment, StatefulSet, DaemonSet, ReplicaSet, Job, CronJob, ReplicationController)
|
|
413
|
+
* onto a target node by patching its pod-template `nodeSelector` and rolling it out.
|
|
414
|
+
* Resource-kind agnostic: it resolves the kind dynamically and applies the right
|
|
415
|
+
* patch path, so it works for `sts`, `deployment`, etc. without bespoke logic.
|
|
416
|
+
*
|
|
417
|
+
* Selection grammar via `path`:
|
|
418
|
+
* - `<kind>/<name>` -> a single resource (e.g. `deployment/dd-core-production-blue`)
|
|
419
|
+
* - `<kind>` -> every resource of that kind in the namespace (e.g. `statefulset`)
|
|
420
|
+
* - `` -> all movable workloads (deployment, statefulset, daemonset) in the namespace
|
|
421
|
+
*
|
|
422
|
+
* Placement:
|
|
423
|
+
* - default: built-in `kubernetes.io/hostname=<node>` (no node mutation required)
|
|
424
|
+
* - `--labels k=v,...`: label the target node with those pairs and use them as the
|
|
425
|
+
* nodeSelector (matches the "label node + nodeSelector" pattern), enabling reusable
|
|
426
|
+
* workload pools instead of pinning to a single hostname.
|
|
427
|
+
*
|
|
428
|
+
* Flags: `--node-name <node>` (target), `--namespace <ns>`, `--dry-run` (preview only),
|
|
429
|
+
* `--remove` (clear the nodeSelector / unpin placement).
|
|
430
|
+
*
|
|
431
|
+
* Caveats: Services/ConfigMaps and bare Pods are not schedulable controllers and are
|
|
432
|
+
* skipped (move the owning controller). StatefulSets bound to node-local PVs may stay
|
|
433
|
+
* Pending after a move until their volume is available on the target node.
|
|
434
|
+
* @param {string} path - Resource selector (`kind/name`, `kind`, or empty).
|
|
435
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
436
|
+
* @memberof UnderpostRun
|
|
437
|
+
* @returns {Array<{ref:string,kind:string,status:string,node?:string}>} Per-resource outcome.
|
|
438
|
+
*/
|
|
439
|
+
'node-move': (path = '', options = DEFAULT_OPTION) => {
|
|
440
|
+
const node = options.nodeName;
|
|
441
|
+
const ns = options.namespace || 'default';
|
|
442
|
+
const dryRun = options.dryRun === true;
|
|
443
|
+
const remove = options.remove === true;
|
|
444
|
+
|
|
445
|
+
if (!remove && !node) {
|
|
446
|
+
throw new Error('node-move requires --node-name <target-node> (or --remove to clear placement)');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const normalizeKind = (k) =>
|
|
450
|
+
({
|
|
451
|
+
deploy: 'deployment',
|
|
452
|
+
deployments: 'deployment',
|
|
453
|
+
deployment: 'deployment',
|
|
454
|
+
sts: 'statefulset',
|
|
455
|
+
statefulsets: 'statefulset',
|
|
456
|
+
statefulset: 'statefulset',
|
|
457
|
+
ds: 'daemonset',
|
|
458
|
+
daemonsets: 'daemonset',
|
|
459
|
+
daemonset: 'daemonset',
|
|
460
|
+
rs: 'replicaset',
|
|
461
|
+
replicasets: 'replicaset',
|
|
462
|
+
replicaset: 'replicaset',
|
|
463
|
+
rc: 'replicationcontroller',
|
|
464
|
+
replicationcontroller: 'replicationcontroller',
|
|
465
|
+
job: 'job',
|
|
466
|
+
jobs: 'job',
|
|
467
|
+
cj: 'cronjob',
|
|
468
|
+
cronjob: 'cronjob',
|
|
469
|
+
cronjobs: 'cronjob',
|
|
470
|
+
po: 'pod',
|
|
471
|
+
pod: 'pod',
|
|
472
|
+
pods: 'pod',
|
|
473
|
+
svc: 'service',
|
|
474
|
+
service: 'service',
|
|
475
|
+
services: 'service',
|
|
476
|
+
})[k] || k;
|
|
477
|
+
|
|
478
|
+
// Kinds that own a pod template we can patch; rolloutKinds additionally
|
|
479
|
+
// support `kubectl rollout restart` to reschedule existing pods now.
|
|
480
|
+
const templated = [
|
|
481
|
+
'deployment',
|
|
482
|
+
'statefulset',
|
|
483
|
+
'daemonset',
|
|
484
|
+
'replicaset',
|
|
485
|
+
'job',
|
|
486
|
+
'cronjob',
|
|
487
|
+
'replicationcontroller',
|
|
488
|
+
];
|
|
489
|
+
const rolloutKinds = ['deployment', 'statefulset', 'daemonset'];
|
|
490
|
+
const templateSelectorPath = (kind) =>
|
|
491
|
+
kind === 'cronjob'
|
|
492
|
+
? ['spec', 'jobTemplate', 'spec', 'template', 'spec', 'nodeSelector']
|
|
493
|
+
: ['spec', 'template', 'spec', 'nodeSelector'];
|
|
494
|
+
|
|
495
|
+
// Resolve the desired nodeSelector. Custom --labels enables reusable pools;
|
|
496
|
+
// otherwise pin by the always-present hostname label.
|
|
497
|
+
let selector = { 'kubernetes.io/hostname': node };
|
|
498
|
+
if (!remove && options.labels) {
|
|
499
|
+
selector = {};
|
|
500
|
+
for (const pair of `${options.labels}`
|
|
501
|
+
.split(',')
|
|
502
|
+
.map((s) => s.trim())
|
|
503
|
+
.filter(Boolean)) {
|
|
504
|
+
const eq = pair.indexOf('=');
|
|
505
|
+
if (eq < 0) continue;
|
|
506
|
+
selector[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Verify the target node exists, and apply custom labels to it if provided.
|
|
511
|
+
if (!remove) {
|
|
512
|
+
const found = shellExec(`kubectl get node ${node} -o name`, {
|
|
513
|
+
silent: true,
|
|
514
|
+
stdout: true,
|
|
515
|
+
silentOnError: true,
|
|
516
|
+
}).trim();
|
|
517
|
+
if (!found) throw new Error(`Target node not found: ${node}`);
|
|
518
|
+
if (options.labels) {
|
|
519
|
+
const labelArgs = Object.entries(selector)
|
|
520
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
521
|
+
.join(' ');
|
|
522
|
+
const labelCmd = `kubectl label node ${node} ${labelArgs} --overwrite`;
|
|
523
|
+
if (dryRun) logger.info(`[dry-run] ${labelCmd}`);
|
|
524
|
+
else shellExec(labelCmd);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const kubectlNames = (kind) =>
|
|
529
|
+
(
|
|
530
|
+
shellExec(`kubectl get ${kind} -n ${ns} -o name`, {
|
|
531
|
+
silent: true,
|
|
532
|
+
stdout: true,
|
|
533
|
+
silentOnError: true,
|
|
534
|
+
}).trim() || ''
|
|
535
|
+
)
|
|
536
|
+
.split('\n')
|
|
537
|
+
.map((s) => s.trim())
|
|
538
|
+
.filter(Boolean);
|
|
539
|
+
|
|
540
|
+
// Build the list of "kind/name" targets from the selection grammar.
|
|
541
|
+
let targets = [];
|
|
542
|
+
if (!path) {
|
|
543
|
+
for (const kind of ['deployment', 'statefulset', 'daemonset']) targets.push(...kubectlNames(kind));
|
|
544
|
+
} else if (path.includes('/')) {
|
|
545
|
+
targets = [path];
|
|
546
|
+
} else {
|
|
547
|
+
targets = kubectlNames(path);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (targets.length === 0) {
|
|
551
|
+
logger.warn('node-move: no matching resources found', { path, namespace: ns });
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Merge-patch body that sets (or clears, when --remove) the nodeSelector.
|
|
556
|
+
const buildPatch = (kind) => {
|
|
557
|
+
const keys = templateSelectorPath(kind);
|
|
558
|
+
let obj = remove ? null : selector;
|
|
559
|
+
for (let i = keys.length - 1; i >= 0; i--) obj = { [keys[i]]: obj };
|
|
560
|
+
return JSON.stringify(obj);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const results = [];
|
|
564
|
+
for (const ref of targets) {
|
|
565
|
+
const slash = ref.indexOf('/');
|
|
566
|
+
const rawKind = slash >= 0 ? ref.slice(0, slash) : path && !path.includes('/') ? path : '';
|
|
567
|
+
const name = slash >= 0 ? ref.slice(slash + 1) : ref;
|
|
568
|
+
const kind = normalizeKind(`${rawKind}`.split('.')[0].toLowerCase());
|
|
569
|
+
|
|
570
|
+
if (!templated.includes(kind)) {
|
|
571
|
+
logger.warn(`node-move: ${kind}/${name} is not a schedulable controller; skipping`, {
|
|
572
|
+
hint: 'move its owning controller (deployment/statefulset/daemonset) instead',
|
|
573
|
+
});
|
|
574
|
+
results.push({ ref, kind, status: 'skipped' });
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Idempotency: skip the patch + rollout if the resource is already where
|
|
579
|
+
// we want it. Compares the live pod-template nodeSelector against the
|
|
580
|
+
// desired placement so a repeated run does not trigger an unnecessary
|
|
581
|
+
// rollout restart.
|
|
582
|
+
const basePath = kind === 'cronjob' ? 'spec.jobTemplate.spec.template.spec' : 'spec.template.spec';
|
|
583
|
+
const jsonpath = (expr) =>
|
|
584
|
+
shellExec(`kubectl get ${kind} ${name} -n ${ns} -o jsonpath='${expr}'`, {
|
|
585
|
+
silent: true,
|
|
586
|
+
stdout: true,
|
|
587
|
+
silentOnError: true,
|
|
588
|
+
disableLog: true,
|
|
589
|
+
}).trim();
|
|
590
|
+
|
|
591
|
+
if (remove) {
|
|
592
|
+
const current = jsonpath(`{.${basePath}.nodeSelector}`);
|
|
593
|
+
if (!current || current === 'map[]') {
|
|
594
|
+
logger.info(`node-move: ${kind}/${name} already has no nodeSelector; nothing to clear`);
|
|
595
|
+
results.push({ ref, kind, status: 'already-cleared' });
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
const alreadyOnNode = Object.entries(selector).every(([k, v]) => {
|
|
600
|
+
const esc = k.replace(/\./g, '\\.');
|
|
601
|
+
return jsonpath(`{.${basePath}.nodeSelector.${esc}}`) === v;
|
|
602
|
+
});
|
|
603
|
+
if (alreadyOnNode) {
|
|
604
|
+
logger.info(`node-move: ${kind}/${name} already pinned to ${node}; skipping`, { namespace: ns });
|
|
605
|
+
results.push({ ref, kind, status: 'already-on-node', node });
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const patchCmd = `kubectl patch ${kind} ${name} -n ${ns} --type=merge -p '${buildPatch(kind)}'`;
|
|
611
|
+
const restartCmd = `kubectl rollout restart ${kind} ${name} -n ${ns}`;
|
|
612
|
+
if (dryRun) {
|
|
613
|
+
logger.info(`[dry-run] ${patchCmd}`);
|
|
614
|
+
if (rolloutKinds.includes(kind)) logger.info(`[dry-run] ${restartCmd}`);
|
|
615
|
+
results.push({ ref, kind, status: 'dry-run', node: remove ? undefined : node });
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
shellExec(patchCmd);
|
|
620
|
+
if (rolloutKinds.includes(kind)) shellExec(restartCmd);
|
|
621
|
+
logger.info(remove ? `Cleared node placement: ${kind}/${name}` : `Moved ${kind}/${name} -> ${node}`, {
|
|
622
|
+
namespace: ns,
|
|
623
|
+
});
|
|
624
|
+
results.push({ ref, kind, status: remove ? 'cleared' : 'moved', node: remove ? undefined : node });
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
logger.info('node-move complete', { namespace: ns, node: remove ? null : node, count: results.length });
|
|
628
|
+
return results;
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @method dev-hosts-expose
|
|
633
|
+
* @description Deploys a specified service in development mode with `/etc/hosts` modification for local access.
|
|
634
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID to deploy).
|
|
635
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
636
|
+
* @memberof UnderpostRun
|
|
637
|
+
*/
|
|
638
|
+
'dev-hosts-expose': (path, options = DEFAULT_OPTION) => {
|
|
639
|
+
shellExec(
|
|
640
|
+
`node bin deploy ${path} development --disable-update-deployment --disable-update-proxy --kubeadm --etc-hosts`,
|
|
641
|
+
);
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* @method dev-hosts-restore
|
|
646
|
+
* @description Restores the `/etc/hosts` file to its original state after modifications made during development deployments.
|
|
647
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
648
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
649
|
+
* @memberof UnderpostRun
|
|
650
|
+
*/
|
|
651
|
+
'dev-hosts-restore': (path, options = DEFAULT_OPTION) => {
|
|
652
|
+
shellExec(`node bin deploy --restore-hosts`);
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* @method cluster-build
|
|
657
|
+
* @description Build configuration for cluster deployment.
|
|
658
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
659
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
660
|
+
* @memberof UnderpostRun
|
|
661
|
+
*/
|
|
662
|
+
'cluster-build': (path, options = DEFAULT_OPTION) => {
|
|
663
|
+
const nodeOptions =
|
|
664
|
+
(options.nodeName ? ` --node-name ${options.nodeName}` : '') +
|
|
665
|
+
(options.sshKeyPath ? ` --ssh-key-path ${options.sshKeyPath}` : '');
|
|
666
|
+
shellExec(`node bin run clean`);
|
|
667
|
+
shellExec(`node bin run --dev sync-replica template-deploy${nodeOptions}`);
|
|
668
|
+
shellExec(`node bin run sync-replica template-deploy${nodeOptions}`);
|
|
669
|
+
shellExec(`node bin env clean`);
|
|
670
|
+
for (const deployId of fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').split(','))
|
|
671
|
+
shellExec(`node bin new --default-conf --deploy-id ${deployId.trim()}`);
|
|
672
|
+
if (path === 'cmt') {
|
|
673
|
+
shellExec(`git add . && underpost cmt . build cluster-build`);
|
|
674
|
+
shellExec(`cd engine-private && git add . && underpost cmt . build cluster-build`);
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
/**
|
|
678
|
+
* @method template-deploy
|
|
679
|
+
* @description Pushes `engine-private`, dispatches CI workflow to build `pwa-microservices-template`,
|
|
680
|
+
* and optionally triggers engine-<conf-id> CI with sync/init which in turn dispatches the CD workflow
|
|
681
|
+
* after the build chain completes (template → ghpkg → engine-<conf-id> → CD).
|
|
682
|
+
* @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
|
|
683
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
684
|
+
* @memberof UnderpostRun
|
|
685
|
+
*/
|
|
686
|
+
'template-deploy': (path = '', options = DEFAULT_OPTION) => {
|
|
687
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
688
|
+
shellExec(`npm run security:secrets`);
|
|
689
|
+
const reportPath = './gitleaks-report.json';
|
|
690
|
+
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
691
|
+
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
shellExec(`${baseCommand} run pull`);
|
|
695
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
696
|
+
|
|
697
|
+
// Capture the sanitized message from the last N commits (--from-n-commit, default 1) for
|
|
698
|
+
// propagation to pwa-microservices-template and every engine-* repo.
|
|
699
|
+
const fromN = parseInt(options.fromNCommit) > 0 ? parseInt(options.fromNCommit) : 1;
|
|
700
|
+
const sanitizedMessage = shellExec(`node bin cmt --changelog-msg --from-n-commit ${fromN} --changelog-no-hash`, {
|
|
701
|
+
silent: true,
|
|
702
|
+
stdout: true,
|
|
703
|
+
}).trim();
|
|
704
|
+
|
|
705
|
+
shellExec(
|
|
706
|
+
`${baseCommand} push ./engine-private ${options.force ? '-f ' : ''}${
|
|
707
|
+
process.env.GITHUB_USERNAME
|
|
708
|
+
}/engine-private`,
|
|
709
|
+
);
|
|
710
|
+
shellCd('/home/dd/engine');
|
|
711
|
+
|
|
712
|
+
// Push engine repo so workflow YAML changes reach GitHub
|
|
713
|
+
shellExec(`git reset`);
|
|
714
|
+
shellExec(`${baseCommand} push . ${options.force ? '-f ' : ''}${process.env.GITHUB_USERNAME}/engine`);
|
|
715
|
+
|
|
716
|
+
// Determine deploy conf and type from path (sync-engine-core, init-engine-core, etc.)
|
|
717
|
+
let deployConfId = '';
|
|
718
|
+
let deployType = '';
|
|
719
|
+
if (path.startsWith('sync-')) {
|
|
720
|
+
deployConfId = path.replace(/^sync-/, '');
|
|
721
|
+
deployType = 'sync-and-deploy';
|
|
722
|
+
} else if (path.startsWith('init-')) {
|
|
723
|
+
deployConfId = path.replace(/^init-/, '');
|
|
724
|
+
deployType = 'init';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// If --build is set and path is a sync-engine-* target, push the pre-built client bundle
|
|
728
|
+
// to Cloudinary so the remote container can pull it instead of rebuilding from source.
|
|
729
|
+
if (options.build && deployConfId && deployConfId.startsWith('engine-')) {
|
|
730
|
+
const confName = deployConfId.replace(/^engine-/, '');
|
|
731
|
+
const pushDeployId = options.deployId || `dd-${confName}`;
|
|
732
|
+
logger.info(`[template-deploy] Running push-bundle for deployId: ${pushDeployId}`);
|
|
733
|
+
shellExec(`${baseCommand} run push-bundle --deploy-id ${pushDeployId}`);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
|
|
737
|
+
// If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
|
|
738
|
+
// with sync=true after template build completes. The engine CI then dispatches
|
|
739
|
+
// the CD workflow after the engine repo build finishes — ensuring correct sequence:
|
|
740
|
+
// npmpkg.ci → engine-<id>.ci → engine-<id>.cd
|
|
741
|
+
const repo = `${process.env.GITHUB_USERNAME}/engine`;
|
|
742
|
+
const inputs = {};
|
|
743
|
+
if (sanitizedMessage) inputs.message = sanitizedMessage;
|
|
744
|
+
if (deployConfId) inputs.deploy_conf_id = deployConfId;
|
|
745
|
+
if (deployType) inputs.deploy_type = deployType;
|
|
746
|
+
|
|
747
|
+
// Omit `ref` so dispatchWorkflow auto-detects the repo's default branch
|
|
748
|
+
// (a fork may default to `main` rather than the monorepo's `master`).
|
|
749
|
+
Underpost.repo.dispatchWorkflow({
|
|
750
|
+
repo,
|
|
751
|
+
workflowFile: 'npmpkg.ci.yml',
|
|
752
|
+
inputs,
|
|
753
|
+
});
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* @method template-deploy-local
|
|
758
|
+
* @description Similar to `template-deploy` but runs the workflow locally without dispatching GitHub Actions. It pulls the latest changes, pushes to GitHub, builds the template, and optionally triggers a local release with CI push.
|
|
759
|
+
* @param {string} path - The deployment path identifier (e.g., 'sync-engine-core', 'init-engine-core', or empty for build-only).
|
|
760
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
761
|
+
* @memberof UnderpostRun
|
|
762
|
+
*/
|
|
763
|
+
'template-deploy-local': async (path, options = DEFAULT_OPTION) => {
|
|
764
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
765
|
+
shellExec(`npm run security:secrets`);
|
|
766
|
+
const reportPath = './gitleaks-report.json';
|
|
767
|
+
if (fs.existsSync(reportPath) && JSON.parse(fs.readFileSync(reportPath, 'utf8')).length > 0) {
|
|
768
|
+
logger.error('Secrets detected in gitleaks-report.json, aborting template-deploy');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
shellExec(`${baseCommand} run pull`);
|
|
772
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
773
|
+
|
|
774
|
+
// Capture the sanitized message from the last N commits (--from-n-commit, default 1).
|
|
775
|
+
const fromN = parseInt(options.fromNCommit) > 0 ? parseInt(options.fromNCommit) : 1;
|
|
776
|
+
const sanitizedMessage = shellExec(`node bin cmt --changelog-msg --from-n-commit ${fromN} --changelog-no-hash`, {
|
|
777
|
+
silent: true,
|
|
778
|
+
stdout: true,
|
|
779
|
+
}).trim();
|
|
780
|
+
|
|
781
|
+
const { triggerCmd } = path
|
|
782
|
+
? await Underpost.release.ci(path, sanitizedMessage, options)
|
|
783
|
+
: await Underpost.release.pwa(sanitizedMessage, options);
|
|
784
|
+
pbcopy(triggerCmd + ' && cd /home/dd/engine');
|
|
785
|
+
},
|
|
786
|
+
/**
|
|
787
|
+
* @method docker-image
|
|
788
|
+
* @description Dispatches the Docker image CI workflow (`docker-image[.<runtime>].ci.yml`) via `workflow_dispatch`.
|
|
789
|
+
* Repository resolution is delegated to `Underpost.repo.resolveInstanceRepo(path)`.
|
|
790
|
+
* @param {string} path - Optional runtime / workflow suffix (e.g. `cyberia-server`, `cyberia-client`).
|
|
791
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
792
|
+
* @memberof UnderpostRun
|
|
793
|
+
*/
|
|
794
|
+
'docker-image': (path, options = DEFAULT_OPTION) => {
|
|
795
|
+
const repo = Underpost.repo.resolveInstanceRepo(path, !options.test);
|
|
796
|
+
Underpost.repo.dispatchWorkflow({
|
|
797
|
+
repo,
|
|
798
|
+
workflowFile: `docker-image${path ? `.${path}` : ''}${options.dev ? '.dev' : ''}.ci.yml`,
|
|
799
|
+
inputs: {},
|
|
800
|
+
});
|
|
801
|
+
},
|
|
802
|
+
/**
|
|
803
|
+
* @method clean
|
|
804
|
+
* @description Changes directory to the provided path (defaulting to `/home/dd/engine`) and runs `node bin/deploy clean-core-repo`.
|
|
805
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional directory path).
|
|
806
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
807
|
+
* @memberof UnderpostRun
|
|
808
|
+
*/
|
|
809
|
+
clean: (path = '', options = DEFAULT_OPTION) => {
|
|
810
|
+
Underpost.repo.clean({ paths: path ? path.split(',') : ['/home/dd/engine', '/home/dd/engine/engine-private'] });
|
|
811
|
+
if (options.dev) shellExec(`node bin run shared-dir ${path ? path : '/home/dd/engine'}`);
|
|
812
|
+
},
|
|
813
|
+
/**
|
|
814
|
+
* @method pull
|
|
815
|
+
* @description Clones or pulls updates for the `engine` and `engine-private` repositories into `/home/dd/engine` and `/home/dd/engine/engine-private`.
|
|
816
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
817
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
818
|
+
* @memberof UnderpostRun
|
|
819
|
+
*/
|
|
820
|
+
pull: (path, options = DEFAULT_OPTION) => {
|
|
821
|
+
// shellExec is fail-fast by default — any non-zero exit throws and
|
|
822
|
+
// propagates up to the workflow step. No per-call flag required.
|
|
823
|
+
if (!fs.existsSync(`/home/dd`) || !fs.existsSync(`/home/dd/engine`)) {
|
|
824
|
+
fs.mkdirSync(`/home/dd`, { recursive: true });
|
|
825
|
+
shellExec(`cd /home/dd && underpost clone ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
|
|
826
|
+
} else {
|
|
827
|
+
shellExec(`underpost run clean`);
|
|
828
|
+
shellExec(`cd /home/dd/engine && underpost pull . ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
|
|
829
|
+
}
|
|
830
|
+
if (!fs.existsSync(`/home/dd/engine/engine-private`))
|
|
831
|
+
shellExec(`cd /home/dd/engine && underpost clone ${process.env.GITHUB_USERNAME}/engine-private`, {
|
|
832
|
+
silent: true,
|
|
833
|
+
});
|
|
834
|
+
else
|
|
835
|
+
shellExec(
|
|
836
|
+
`cd /home/dd/engine/engine-private && underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`,
|
|
837
|
+
{ silent: true },
|
|
838
|
+
);
|
|
839
|
+
},
|
|
840
|
+
/**
|
|
841
|
+
* @method release-deploy
|
|
842
|
+
* @description Executes deployment (`underpost run deploy`) for all deployment IDs listed in `./engine-private/deploy/dd.router`.
|
|
843
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
844
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
845
|
+
* @memberof UnderpostRun
|
|
846
|
+
*/
|
|
847
|
+
'release-deploy': (path, options = DEFAULT_OPTION) => {
|
|
848
|
+
actionInitLog();
|
|
849
|
+
shellExec(`underpost --version`);
|
|
850
|
+
shellCd(`/home/dd/engine`);
|
|
851
|
+
for (const _deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
|
|
852
|
+
const deployId = _deployId.trim();
|
|
853
|
+
shellExec(`underpost run deploy ${deployId}`, { async: true });
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
/**
|
|
857
|
+
* @method ssh-deploy
|
|
858
|
+
* @description Dispatches the corresponding CD workflow for SSH-based deployment, replacing empty commits with workflow_dispatch.
|
|
859
|
+
* @param {string} path - The deployment identifier (e.g., 'engine-core', 'sync-engine-core', 'init-engine-core').
|
|
860
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
861
|
+
* @memberof UnderpostRun
|
|
862
|
+
*/
|
|
863
|
+
'ssh-deploy': (path, options = DEFAULT_OPTION) => {
|
|
864
|
+
actionInitLog();
|
|
865
|
+
|
|
866
|
+
let job = 'deploy';
|
|
867
|
+
let confId = path;
|
|
868
|
+
if (path.startsWith('sync-')) {
|
|
869
|
+
job = 'sync-and-deploy';
|
|
870
|
+
confId = path.replace(/^sync-/, '');
|
|
871
|
+
} else if (path.startsWith('init-')) {
|
|
872
|
+
job = 'init';
|
|
873
|
+
confId = path.replace(/^init-/, '');
|
|
874
|
+
}
|
|
875
|
+
const repo = Underpost.repo.resolveInstanceRepo(confId, !options.test);
|
|
876
|
+
// Omit `ref` so dispatchWorkflow auto-detects the target repo's default
|
|
877
|
+
// branch (getDefaultBranch): the monorepo is `master` but instance repos
|
|
878
|
+
// like engine-cyberia default to `main` — hardcoding either 422s.
|
|
879
|
+
Underpost.repo.dispatchWorkflow({
|
|
880
|
+
repo,
|
|
881
|
+
workflowFile: `${confId}.cd.yml`,
|
|
882
|
+
inputs: { job },
|
|
883
|
+
});
|
|
884
|
+
},
|
|
885
|
+
/**
|
|
886
|
+
* @method ide
|
|
887
|
+
* @description Opens a Visual Studio Code (VS Code) session for the specified path using `node ${underpostRoot}/bin/zed ${path}`,
|
|
888
|
+
* or installs Zed and sublime-text IDE if `path` is 'install'.
|
|
889
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the path to the directory to open in the IDE).
|
|
890
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
891
|
+
* @memberof UnderpostRun
|
|
892
|
+
*/
|
|
893
|
+
ide: (path = '', options = DEFAULT_OPTION) => {
|
|
894
|
+
const underpostRoot = options.dev ? '.' : options.underpostRoot;
|
|
895
|
+
const [projectPath, customIde] = path.split(',');
|
|
896
|
+
if (projectPath === 'install') {
|
|
897
|
+
if (customIde === 'zed') shellExec(`sudo curl -f https://zed.dev/install.sh | sh`);
|
|
898
|
+
else if (customIde === 'subl') {
|
|
899
|
+
shellExec(
|
|
900
|
+
`sudo dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo`,
|
|
901
|
+
);
|
|
902
|
+
shellExec(`sudo dnf install -y sublime-text`);
|
|
903
|
+
} else {
|
|
904
|
+
shellExec(`sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc &&
|
|
905
|
+
echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\nautorefresh=1\ntype=rpm-md\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" | sudo tee /etc/yum.repos.d/vscode.repo > /dev/null`);
|
|
906
|
+
shellExec(`sudo dnf install -y code`);
|
|
907
|
+
}
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (customIde === 'zed') shellExec(`node ${underpostRoot}/bin/zed ${projectPath}`);
|
|
911
|
+
else shellExec(`node ${underpostRoot}/bin/vs ${projectPath}`);
|
|
912
|
+
},
|
|
913
|
+
/**
|
|
914
|
+
* @method crypto-policy
|
|
915
|
+
* @description Sets the system's crypto policies to `DEFAULT:SHA1` using `update-crypto-policies` command.
|
|
916
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
917
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
918
|
+
* @memberof UnderpostRun
|
|
919
|
+
*/
|
|
920
|
+
'crypto-policy': (path, options = DEFAULT_OPTION) => {
|
|
921
|
+
shellExec(`sudo update-crypto-policies --set DEFAULT:SHA1`);
|
|
922
|
+
},
|
|
923
|
+
/**
|
|
924
|
+
* @method sync
|
|
925
|
+
* @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`) using parameters parsed from `path` (deployId, replicas, versions, image, node).
|
|
926
|
+
*
|
|
927
|
+
* Forwards `--image-pull-policy <policy>` to the underlying `deploy --build-manifest` invocation when `options.imagePullPolicy` is set,
|
|
928
|
+
* which then plumbs through `buildManifest` and `deploymentYamlPartsFactory` to override the container `imagePullPolicy` in the generated
|
|
929
|
+
* `deployment.yaml`. Useful when you want to force `Always` so the kubelet re-pulls a mutable tag on every rollout. Example:
|
|
930
|
+
* `node bin run sync dd-core --kubeadm --image-pull-policy Always`
|
|
931
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string containing deploy parameters).
|
|
932
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
933
|
+
* @memberof UnderpostRun
|
|
934
|
+
*/
|
|
935
|
+
sync: async (path, options = DEFAULT_OPTION) => {
|
|
936
|
+
// Dev usage: node bin run --dev --build sync dd-default
|
|
937
|
+
const env = options.dev ? 'development' : 'production';
|
|
938
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
939
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
940
|
+
const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
|
|
941
|
+
const defaultPath = [
|
|
942
|
+
'dd-default',
|
|
943
|
+
options.replicas,
|
|
944
|
+
``,
|
|
945
|
+
``,
|
|
946
|
+
!options.kubeadm && !options.k3s ? 'kind-control-plane' : os.hostname(),
|
|
947
|
+
];
|
|
948
|
+
let [deployId, replicas, versions, image, node] = path ? path.split(',') : defaultPath;
|
|
949
|
+
deployId = deployId ? deployId : defaultPath[0];
|
|
950
|
+
replicas = replicas ? replicas : defaultPath[1];
|
|
951
|
+
versions = versions ? versions.replaceAll('+', ',') : defaultPath[2];
|
|
952
|
+
image = image ? image : defaultPath[3];
|
|
953
|
+
node = node ? node : options.nodeName ? options.nodeName : defaultPath[4];
|
|
954
|
+
shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
|
|
955
|
+
|
|
956
|
+
if (image && !image.startsWith('localhost'))
|
|
957
|
+
Underpost.image.pullDockerHubImage({
|
|
958
|
+
dockerhubImage: image,
|
|
959
|
+
kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
|
|
960
|
+
kubeadm: options.nodeName || options.kubeadm,
|
|
961
|
+
k3s: options.k3s,
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
if (isDeployRunnerContext(path, options)) {
|
|
965
|
+
if (!options.disablePrivateConfUpdate) {
|
|
966
|
+
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
967
|
+
if (!validVersion) throw new Error('Version mismatch');
|
|
968
|
+
}
|
|
969
|
+
if (options.timezone !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} tz`);
|
|
970
|
+
if (options.deployIdCronJobs !== 'none')
|
|
971
|
+
shellExec(`node bin cron${baseClusterCommand}${clusterFlag} --setup-start --git --apply`);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const currentTraffic = isDeployRunnerContext(path, options)
|
|
975
|
+
? Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace })
|
|
976
|
+
: '';
|
|
977
|
+
let targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'green';
|
|
978
|
+
if (targetTraffic) versions = versions ? versions : targetTraffic;
|
|
979
|
+
|
|
980
|
+
const ignorePods =
|
|
981
|
+
isDeployRunnerContext(path, options) && targetTraffic
|
|
982
|
+
? Underpost.kubectl.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace).map((p) => p.NAME)
|
|
983
|
+
: [];
|
|
984
|
+
|
|
985
|
+
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
986
|
+
const cmdString = options.cmd
|
|
987
|
+
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
988
|
+
: '';
|
|
989
|
+
const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
|
|
990
|
+
|
|
991
|
+
const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
|
|
992
|
+
const pullBundleFlag = options.pullBundle ? ' --pull-bundle' : '';
|
|
993
|
+
const imagePullPolicyFlag = options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : '';
|
|
994
|
+
const sshKeyPathFlag = options.sshKeyPath ? ` --ssh-key-path ${options.sshKeyPath}` : '';
|
|
995
|
+
|
|
996
|
+
shellExec(
|
|
997
|
+
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
998
|
+
image ? ` --image ${image}` : ''
|
|
999
|
+
}${versions ? ` --versions ${versions}` : ''}${
|
|
1000
|
+
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
1001
|
+
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag}${imagePullPolicyFlag}${sshKeyPathFlag} ${deployId} ${env}`,
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
if (isDeployRunnerContext(path, options)) {
|
|
1005
|
+
// Backup app/services repositories with repo-backup configured
|
|
1006
|
+
shellExec(
|
|
1007
|
+
`${baseCommand} db ${deployId} ${clusterFlag}${baseClusterCommand} --repo-backup --primary-pod --git --force-clone --preserveUUID ${options.namespace ? ` --ns ${options.namespace}` : ''}`,
|
|
1008
|
+
);
|
|
1009
|
+
shellExec(
|
|
1010
|
+
`${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --node ${node} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
|
|
1011
|
+
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
1012
|
+
}${timeoutFlags}${gitCleanFlag}${imagePullPolicyFlag}${sshKeyPathFlag}`,
|
|
1013
|
+
);
|
|
1014
|
+
if (!targetTraffic)
|
|
1015
|
+
targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
1016
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
1017
|
+
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, replicas, options.namespace, options);
|
|
1018
|
+
} else
|
|
1019
|
+
logger.info('current traffic', Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }));
|
|
1020
|
+
},
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* @method stop
|
|
1024
|
+
* @description Stops a deployment by deleting the corresponding Kubernetes deployment and service resources.
|
|
1025
|
+
* @param {string} path - The input value, identifier, or path for the operation (used to determine which traffic to stop).
|
|
1026
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1027
|
+
* @memberof UnderpostRun
|
|
1028
|
+
*/
|
|
1029
|
+
stop: async (path = '', options = DEFAULT_OPTION) => {
|
|
1030
|
+
let currentTraffic = Underpost.deploy.getCurrentTraffic(options.deployId, {
|
|
1031
|
+
namespace: options.namespace,
|
|
1032
|
+
hostTest: options.hosts,
|
|
1033
|
+
});
|
|
1034
|
+
const env = options.dev ? 'development' : 'production';
|
|
1035
|
+
|
|
1036
|
+
if (!path.match('current')) currentTraffic === 'blue' ? (currentTraffic = 'green') : (currentTraffic = 'blue');
|
|
1037
|
+
const [_deployId] = path.split(',');
|
|
1038
|
+
const deploymentId = `${_deployId ? _deployId : options.deployId}${
|
|
1039
|
+
options.instanceId ? `-${options.instanceId}` : ''
|
|
1040
|
+
}-${env}-${currentTraffic}`;
|
|
1041
|
+
|
|
1042
|
+
shellExec(`kubectl delete deployment ${deploymentId} -n ${options.namespace}`);
|
|
1043
|
+
shellExec(`kubectl delete svc ${deploymentId}-service -n ${options.namespace}`);
|
|
1044
|
+
},
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* @method ssh-deploy-stop
|
|
1048
|
+
* @description Stops a remote deployment via SSH by executing the appropriate Underpost command on the remote server.
|
|
1049
|
+
* @param {string} path - The input value, identifier, or path for the operation (used to determine which traffic to stop).
|
|
1050
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1051
|
+
* @memberof UnderpostRun
|
|
1052
|
+
*/
|
|
1053
|
+
'ssh-deploy-stop': async (path, options = DEFAULT_OPTION) => {
|
|
1054
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1055
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1056
|
+
|
|
1057
|
+
const remoteCommand = [
|
|
1058
|
+
`${baseCommand} run${baseClusterCommand} stop${path ? ` ${path}` : ''}`,
|
|
1059
|
+
` --deploy-id ${options.deployId}${options.instanceId ? ` --instance-id ${options.instanceId}` : ''}`,
|
|
1060
|
+
` --namespace ${options.namespace}${options.hosts ? ` --hosts ${options.hosts}` : ''}`,
|
|
1061
|
+
].join('');
|
|
1062
|
+
|
|
1063
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
1064
|
+
deployId: options.deployId,
|
|
1065
|
+
user: options.user,
|
|
1066
|
+
dev: options.dev,
|
|
1067
|
+
remote: true,
|
|
1068
|
+
useSudo: true,
|
|
1069
|
+
cd: '/home/dd/engine',
|
|
1070
|
+
});
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* @method ssh-deploy-db-rollback
|
|
1075
|
+
* @description Performs a database rollback on remote deployment via SSH.
|
|
1076
|
+
* @param {string} path - Comma-separated deployId and optional number of commits to reset (format: "deployId,nCommits")
|
|
1077
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1078
|
+
* @param {string} options.deployId - The deployment identifier
|
|
1079
|
+
* @param {string} options.user - The SSH user for credential lookup
|
|
1080
|
+
* @param {boolean} options.dev - Development mode flag
|
|
1081
|
+
* @memberof UnderpostRun
|
|
1082
|
+
*/
|
|
1083
|
+
'ssh-deploy-db-rollback': async (path = '', options = DEFAULT_OPTION) => {
|
|
1084
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1085
|
+
let [deployId, nCommitsReset] = path.split(',');
|
|
1086
|
+
if (!nCommitsReset) nCommitsReset = 1;
|
|
1087
|
+
|
|
1088
|
+
const remoteCommand = `${baseCommand} db ${deployId} --git --kubeadm --primary-pod --force-clone --macro-rollback-export ${nCommitsReset}${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
1089
|
+
|
|
1090
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
1091
|
+
deployId: options.deployId,
|
|
1092
|
+
user: options.user,
|
|
1093
|
+
dev: options.dev,
|
|
1094
|
+
remote: true,
|
|
1095
|
+
useSudo: true,
|
|
1096
|
+
cd: '/home/dd/engine',
|
|
1097
|
+
});
|
|
1098
|
+
},
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* @method ssh-deploy-db
|
|
1102
|
+
* @description Imports/restores a database on remote deployment via SSH.
|
|
1103
|
+
* @param {string} path - The deployment ID for database import
|
|
1104
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1105
|
+
* @param {string} options.deployId - The deployment identifier
|
|
1106
|
+
* @param {string} options.user - The SSH user for credential lookup
|
|
1107
|
+
* @param {boolean} options.dev - Development mode flag
|
|
1108
|
+
* @memberof UnderpostRun
|
|
1109
|
+
*/
|
|
1110
|
+
'ssh-deploy-db': async (path, options = DEFAULT_OPTION) => {
|
|
1111
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1112
|
+
|
|
1113
|
+
const remoteCommand = `${baseCommand} db ${path} --import --drop --preserveUUID --git --kubeadm --primary-pod --force-clone${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
1114
|
+
|
|
1115
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
1116
|
+
deployId: options.deployId,
|
|
1117
|
+
user: options.user,
|
|
1118
|
+
dev: options.dev,
|
|
1119
|
+
remote: true,
|
|
1120
|
+
useSudo: true,
|
|
1121
|
+
cd: '/home/dd/engine',
|
|
1122
|
+
});
|
|
1123
|
+
},
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* @method ssh-deploy-db-status
|
|
1127
|
+
* @description Retrieves database status/stats for a deployment (or all deployments from dd.router) via SSH.
|
|
1128
|
+
* @param {string} path - Comma-separated deployId(s) or 'dd' to use the dd.router list.
|
|
1129
|
+
* @param {UnderpostRunDefaultOptions} options - Runner options (uses options.deployId for SSH host lookup).
|
|
1130
|
+
* @param {string} options.deployId - Deployment identifier used for SSH config lookup.
|
|
1131
|
+
* @param {string} options.user - SSH user for credential lookup.
|
|
1132
|
+
* @param {boolean} options.dev - Development mode flag.
|
|
1133
|
+
* @param {string} [options.namespace] - Kubernetes namespace to pass to the db check.
|
|
1134
|
+
* @memberof UnderpostRun
|
|
1135
|
+
*/
|
|
1136
|
+
'ssh-deploy-db-status': async (path = '', options = DEFAULT_OPTION) => {
|
|
1137
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1138
|
+
|
|
1139
|
+
let deployList = [];
|
|
1140
|
+
if (!path || path === 'dd') {
|
|
1141
|
+
if (!fs.existsSync('./engine-private/deploy/dd.router')) {
|
|
1142
|
+
logger.warn('dd.router not found; nothing to run');
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
deployList = fs
|
|
1146
|
+
.readFileSync('./engine-private/deploy/dd.router', 'utf8')
|
|
1147
|
+
.split(',')
|
|
1148
|
+
.map((d) => d.trim())
|
|
1149
|
+
.filter(Boolean);
|
|
1150
|
+
} else {
|
|
1151
|
+
deployList = path
|
|
1152
|
+
.split(',')
|
|
1153
|
+
.map((d) => d.trim())
|
|
1154
|
+
.filter(Boolean);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
for (const deployId of deployList) {
|
|
1158
|
+
const remoteCommand = `${baseCommand} db ${deployId} --stats --kubeadm --primary-pod${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
1159
|
+
|
|
1160
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
1161
|
+
deployId: options.deployId,
|
|
1162
|
+
user: options.user,
|
|
1163
|
+
dev: options.dev,
|
|
1164
|
+
remote: true,
|
|
1165
|
+
useSudo: true,
|
|
1166
|
+
cd: '/home/dd/engine',
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* @method tz
|
|
1173
|
+
* @description Sets the system timezone using `timedatectl set-timezone` command.
|
|
1174
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the timezone string).
|
|
1175
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1176
|
+
* @memberof UnderpostRun
|
|
1177
|
+
*/
|
|
1178
|
+
tz: (path, options = DEFAULT_OPTION) => {
|
|
1179
|
+
const tz =
|
|
1180
|
+
options.timezone && options.timezone !== 'none'
|
|
1181
|
+
? options.timezone
|
|
1182
|
+
: path
|
|
1183
|
+
? path
|
|
1184
|
+
: Underpost.env.get('TIME_ZONE', undefined, { disableLog: true })
|
|
1185
|
+
? Underpost.env.get('TIME_ZONE')
|
|
1186
|
+
: process.env.TIME_ZONE
|
|
1187
|
+
? process.env.TIME_ZONE
|
|
1188
|
+
: 'America/New_York';
|
|
1189
|
+
shellExec(`sudo timedatectl set-timezone ${tz}`);
|
|
1190
|
+
},
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* @method get-proxy
|
|
1194
|
+
* @description Retrieves and logs the HTTPProxy resources in the specified namespace using `kubectl get HTTPProxy`.
|
|
1195
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as an optional filter for the HTTPProxy resources).
|
|
1196
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1197
|
+
* @memberof UnderpostRun
|
|
1198
|
+
*/
|
|
1199
|
+
'get-proxy': async (path = '', options = DEFAULT_OPTION) => {
|
|
1200
|
+
console.log(
|
|
1201
|
+
shellExec(`kubectl get HTTPProxy -n ${options.namespace} ${path} -o yaml`, {
|
|
1202
|
+
silent: true,
|
|
1203
|
+
stdout: true,
|
|
1204
|
+
})
|
|
1205
|
+
.replaceAll(`blue`, `blue`.bgBlue.bold.black)
|
|
1206
|
+
.replaceAll('green', 'green'.bgGreen.bold.black)
|
|
1207
|
+
.replaceAll('Error', 'Error'.bold.red)
|
|
1208
|
+
.replaceAll('error', 'error'.bold.red)
|
|
1209
|
+
.replaceAll('ERROR', 'ERROR'.bold.red)
|
|
1210
|
+
.replaceAll('Invalid', 'Invalid'.bold.red)
|
|
1211
|
+
.replaceAll('invalid', 'invalid'.bold.red)
|
|
1212
|
+
.replaceAll('INVALID', 'INVALID'.bold.red),
|
|
1213
|
+
);
|
|
1214
|
+
},
|
|
1215
|
+
|
|
1216
|
+
'instance-promote': async (path, options = DEFAULT_OPTION) => {
|
|
1217
|
+
const env = options.dev ? 'development' : 'production';
|
|
1218
|
+
let [deployId, id] = path.split(',');
|
|
1219
|
+
const confInstances = loadConfInstances(deployId);
|
|
1220
|
+
const promoted = selectConfInstances(confInstances, id);
|
|
1221
|
+
let promotedTraffic = '';
|
|
1222
|
+
|
|
1223
|
+
// A Contour HTTPProxy is named after its host, so every instance sharing a
|
|
1224
|
+
// host shares one object. Rebuilding it from the promoted instance alone
|
|
1225
|
+
// would drop its siblings' routes, so each host is rendered from the full
|
|
1226
|
+
// set: promoted instances flip colour, the rest keep their live colour.
|
|
1227
|
+
const promotedIds = new Set(promoted.map((instance) => instance.id));
|
|
1228
|
+
const hosts = [...new Set(promoted.map((instance) => instance.host))];
|
|
1229
|
+
const affected = confInstances.filter((instance) => hosts.includes(instance.host));
|
|
1230
|
+
const trafficById = {};
|
|
1231
|
+
for (const instance of affected) {
|
|
1232
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(`${deployId}-${instance.id}`, {
|
|
1233
|
+
hostTest: instance.host,
|
|
1234
|
+
namespace: options.namespace,
|
|
1235
|
+
env,
|
|
1236
|
+
});
|
|
1237
|
+
if (!promotedIds.has(instance.id)) {
|
|
1238
|
+
trafficById[instance.id] = currentTraffic || 'blue';
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
trafficById[instance.id] = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
|
|
1242
|
+
promotedTraffic = trafficById[instance.id];
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
for (const host of hosts) {
|
|
1246
|
+
const hostInstances = affected.filter((instance) => instance.host === host);
|
|
1247
|
+
let proxyYaml =
|
|
1248
|
+
Underpost.deploy.baseProxyYamlFactory({ host, env: options.tls ? 'production' : env, options }) +
|
|
1249
|
+
instanceProxyRoutesFactory({ deployId, instances: hostInstances, env, trafficById });
|
|
1250
|
+
if (options.tls) {
|
|
1251
|
+
if (options.test) {
|
|
1252
|
+
const sslDir = `./engine-private/ssl/${host}`;
|
|
1253
|
+
const nameSafe = host.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
1254
|
+
fs.mkdirpSync(sslDir);
|
|
1255
|
+
shellExec(`bash ./scripts/ssl.sh "${sslDir}" "${host}"`);
|
|
1256
|
+
shellExec(`kubectl delete secret ${host} -n ${options.namespace} --ignore-not-found`);
|
|
1257
|
+
shellExec(
|
|
1258
|
+
`kubectl create secret tls ${host} --cert="${sslDir}/${nameSafe}.pem" --key="${sslDir}/${nameSafe}-key.pem" -n ${options.namespace}`,
|
|
1259
|
+
);
|
|
1260
|
+
} else {
|
|
1261
|
+
shellExec(`sudo kubectl delete Certificate ${host} -n ${options.namespace} --ignore-not-found`);
|
|
1262
|
+
proxyYaml += Underpost.deploy.buildCertManagerCertificate({ ...options, host });
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
shellExec(`kubectl delete HTTPProxy ${host} --namespace ${options.namespace} --ignore-not-found`);
|
|
1266
|
+
shellExec(
|
|
1267
|
+
`kubectl apply -f - -n ${options.namespace} <<EOF
|
|
1268
|
+
${proxyYaml}
|
|
1269
|
+
EOF
|
|
1270
|
+
`,
|
|
1271
|
+
{ disableLog: true },
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
// Refresh the gRPC service to ensure it points to the parent deploy's current traffic.
|
|
1275
|
+
if (promotedTraffic) {
|
|
1276
|
+
const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
1277
|
+
const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
|
|
1278
|
+
deployId,
|
|
1279
|
+
env,
|
|
1280
|
+
confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
1281
|
+
namespace: options.namespace,
|
|
1282
|
+
traffic: [parentTraffic],
|
|
1283
|
+
});
|
|
1284
|
+
if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* @method instance
|
|
1290
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string containing workflow parameters).
|
|
1291
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1292
|
+
* @memberof UnderpostRun
|
|
1293
|
+
*/
|
|
1294
|
+
instance: async (path = '', options = DEFAULT_OPTION) => {
|
|
1295
|
+
const env = options.dev ? 'development' : 'production';
|
|
1296
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1297
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1298
|
+
let [deployId, id, replicas] = path.split(',');
|
|
1299
|
+
if (!replicas) replicas = options.replicas;
|
|
1300
|
+
const confInstances = selectConfInstances(loadConfInstances(deployId), id);
|
|
1301
|
+
const etcHosts = [];
|
|
1302
|
+
for (const instance of confInstances) {
|
|
1303
|
+
let {
|
|
1304
|
+
id: _id,
|
|
1305
|
+
host: _host,
|
|
1306
|
+
path: _path,
|
|
1307
|
+
image: _image,
|
|
1308
|
+
fromPort: _fromPort,
|
|
1309
|
+
toPort: _toPort,
|
|
1310
|
+
fromDebugPort: _fromDebugPort,
|
|
1311
|
+
toDebugPort: _toDebugPort,
|
|
1312
|
+
cmd: _cmd,
|
|
1313
|
+
volumes: _volumes,
|
|
1314
|
+
metadata: _metadata,
|
|
1315
|
+
lifecycle: _lifecycle,
|
|
1316
|
+
readinessProbe: _readinessProbe,
|
|
1317
|
+
livenessProbe: _livenessProbe,
|
|
1318
|
+
} = instance;
|
|
1319
|
+
const _deployId = `${deployId}-${_id}`;
|
|
1320
|
+
// Use debug ports in development when defined, fall back to production ports.
|
|
1321
|
+
if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
|
|
1322
|
+
if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
|
|
1323
|
+
etcHosts.push(_host);
|
|
1324
|
+
if (options.expose) continue;
|
|
1325
|
+
// Examples images:
|
|
1326
|
+
// `underpost/underpost-engine:${Underpost.version}`
|
|
1327
|
+
// `localhost/rockylinux9-underpost:${Underpost.version}`
|
|
1328
|
+
if (options.imageName) _image = options.imageName;
|
|
1329
|
+
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
1330
|
+
|
|
1331
|
+
if (_image && !_image.startsWith('localhost'))
|
|
1332
|
+
Underpost.image.pullDockerHubImage({
|
|
1333
|
+
dockerhubImage: _image,
|
|
1334
|
+
kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
|
|
1335
|
+
kubeadm: options.nodeName || options.kubeadm,
|
|
1336
|
+
k3s: options.k3s,
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
|
|
1340
|
+
hostTest: _host,
|
|
1341
|
+
namespace: options.namespace,
|
|
1342
|
+
env,
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
|
|
1346
|
+
const podId = `${_deployId}-${env}-${targetTraffic}`;
|
|
1347
|
+
const ignorePods = Underpost.kubectl.get(podId, 'pods', options.namespace).map((p) => p.NAME);
|
|
1348
|
+
Underpost.deploy.configMap(env, options.namespace);
|
|
1349
|
+
shellExec(`kubectl delete service ${podId}-service --namespace ${options.namespace} --ignore-not-found`);
|
|
1350
|
+
shellExec(`kubectl delete deployment ${podId} --namespace ${options.namespace} --ignore-not-found`);
|
|
1351
|
+
for (const _volume of _volumes)
|
|
1352
|
+
if (_volume.claimName)
|
|
1353
|
+
Underpost.deploy.deployVolume(_volume, {
|
|
1354
|
+
namespace: options.namespace,
|
|
1355
|
+
deployId: _deployId,
|
|
1356
|
+
env,
|
|
1357
|
+
version: targetTraffic,
|
|
1358
|
+
nodeName: Underpost.deploy.resolveDeployNode({
|
|
1359
|
+
node: options.nodeName,
|
|
1360
|
+
kind: options.kind,
|
|
1361
|
+
kubeadm: options.kubeadm,
|
|
1362
|
+
k3s: options.k3s,
|
|
1363
|
+
env,
|
|
1364
|
+
}),
|
|
1365
|
+
clusterContext: options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind',
|
|
1366
|
+
gitClean: options.gitClean || false,
|
|
1367
|
+
sshKeyPath: options.sshKeyPath || '',
|
|
1368
|
+
});
|
|
1369
|
+
// Regenerate the parent deploy's gRPC ClusterIP service pointing to the
|
|
1370
|
+
// parent's current traffic colour and apply it before the instance pod starts so
|
|
1371
|
+
// DNS is resolvable the moment the pod boots.
|
|
1372
|
+
const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
1373
|
+
const grpcServicePath = Underpost.deploy.buildGrpcServiceManifest({
|
|
1374
|
+
deployId,
|
|
1375
|
+
env,
|
|
1376
|
+
confServer: loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`),
|
|
1377
|
+
namespace: options.namespace,
|
|
1378
|
+
traffic: [targetTraffic],
|
|
1379
|
+
host: _host,
|
|
1380
|
+
});
|
|
1381
|
+
if (grpcServicePath) shellExec(`kubectl apply -f ${grpcServicePath} -n ${options.namespace}`);
|
|
1382
|
+
|
|
1383
|
+
const resolvedCmd = _cmd[env].map((c) =>
|
|
1384
|
+
c.replaceAll(
|
|
1385
|
+
'{{grpc-service-dns}}',
|
|
1386
|
+
`${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
|
|
1387
|
+
),
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
// Resolve env-scoped lifecycle/probe blocks: each can be either
|
|
1391
|
+
// { ...envObj } // shared shape
|
|
1392
|
+
// { development: {...}, production: {...} } // env-specific
|
|
1393
|
+
const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
|
|
1394
|
+
|
|
1395
|
+
// Convention: an instance config may place `imagePullPolicy` inside
|
|
1396
|
+
// the env-scoped lifecycle block (alongside postStart/preStop).
|
|
1397
|
+
// Extract it onto the container spec (where K8S expects it) and
|
|
1398
|
+
// strip it from the lifecycle hash so the rendered YAML stays valid.
|
|
1399
|
+
// CLI override (`--image-pull-policy`) wins over the conf value.
|
|
1400
|
+
const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
|
|
1401
|
+
Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
|
|
1402
|
+
const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
|
|
1403
|
+
|
|
1404
|
+
let deploymentYaml = `---
|
|
1405
|
+
${Underpost.deploy
|
|
1406
|
+
.deploymentYamlPartsFactory({
|
|
1407
|
+
deployId: _deployId,
|
|
1408
|
+
env,
|
|
1409
|
+
suffix: targetTraffic,
|
|
1410
|
+
resources: Underpost.deploy.resourcesFactory(options),
|
|
1411
|
+
replicas,
|
|
1412
|
+
image: _image,
|
|
1413
|
+
namespace: options.namespace,
|
|
1414
|
+
volumes: _volumes,
|
|
1415
|
+
cmd: resolvedCmd,
|
|
1416
|
+
lifecycle: lifecycleForManifest,
|
|
1417
|
+
readinessProbe: pickEnv(_readinessProbe),
|
|
1418
|
+
livenessProbe: pickEnv(_livenessProbe),
|
|
1419
|
+
containerPort: _toPort,
|
|
1420
|
+
imagePullPolicy: instanceImagePullPolicy,
|
|
1421
|
+
})
|
|
1422
|
+
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
|
|
1423
|
+
`;
|
|
1424
|
+
// console.log(deploymentYaml);
|
|
1425
|
+
shellExec(
|
|
1426
|
+
`kubectl apply -f - -n ${options.namespace} <<EOF
|
|
1427
|
+
${deploymentYaml}
|
|
1428
|
+
EOF
|
|
1429
|
+
`,
|
|
1430
|
+
{ disableLog: true },
|
|
1431
|
+
);
|
|
1432
|
+
// Custom instances run a bare binary (no `underpost start` / internal
|
|
1433
|
+
// HTTP endpoint): Kubernetes readiness is the running signal and
|
|
1434
|
+
// container-status is read via exec. See `Deploy custom instance to K8S.md`.
|
|
1435
|
+
const { ready, readyPods } = await Underpost.monitor.monitorReadyRunner(
|
|
1436
|
+
_deployId,
|
|
1437
|
+
env,
|
|
1438
|
+
targetTraffic,
|
|
1439
|
+
ignorePods,
|
|
1440
|
+
options.namespace,
|
|
1441
|
+
{ readyGate: 'kubernetes', statusTransport: 'exec' },
|
|
1442
|
+
);
|
|
1443
|
+
|
|
1444
|
+
if (!ready) {
|
|
1445
|
+
logger.error(`Deployment ${deployId} did not become ready in time.`);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
// Promote (switch the HTTPProxy to the new colour) ONLY after EVERY
|
|
1450
|
+
// instance in the family is deployed and ready — never per-instance inside
|
|
1451
|
+
// the loop. A per-variant switch would flip server.cyberiaonline.com to the
|
|
1452
|
+
// default's new deployment while -forest/-test are still being created,
|
|
1453
|
+
// pointing routes at services that don't exist yet. instance-promote
|
|
1454
|
+
// rebuilds the whole host proxy (all variant routes) in one shot, so a
|
|
1455
|
+
// single call with the family id promotes the family atomically.
|
|
1456
|
+
if (!options.expose) await UnderpostRun.RUNNERS['instance-promote'](`${deployId},${id}`, options);
|
|
1457
|
+
if (options.etcHosts) {
|
|
1458
|
+
const hostListenResult = etcHostFactory(etcHosts);
|
|
1459
|
+
logger.info(hostListenResult.renderHosts);
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* @method deploy-key
|
|
1465
|
+
* @description Copies the deploy key for a specific user and deployId to a temporary location on the local machine.
|
|
1466
|
+
* @param {string} path - The input value, identifier, or path for the operation (not used in this method).
|
|
1467
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1468
|
+
* @param {string} options.user - The user for which to copy the deploy key.
|
|
1469
|
+
* @param {string} options.deployId - The deployment identifier associated with the deploy key.
|
|
1470
|
+
* @memberof UnderpostRun
|
|
1471
|
+
*/
|
|
1472
|
+
'deploy-key': (path, options = DEFAULT_OPTION) => {
|
|
1473
|
+
const prefix = 'dd-key';
|
|
1474
|
+
if (options.reset) {
|
|
1475
|
+
shellExec(`rm -rf /home/dd/tmp/${prefix}_*`);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (!options.user || !options.deployId) {
|
|
1479
|
+
logger.error('Both --user and --deploy-id options are required to copy the deploy key.');
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const targetPath = `/home/dd/tmp/${prefix}_${s4()}${s4()}`;
|
|
1483
|
+
fs.mkdirSync('/home/dd/tmp', { recursive: true });
|
|
1484
|
+
fs.copyFileSync(`./engine-private/conf/${options.deployId}/users/${options.user}/id_rsa`, targetPath);
|
|
1485
|
+
logger.info(`Copied deploy key to ${targetPath}`);
|
|
1486
|
+
if (options.copy) pbcopy(targetPath);
|
|
1487
|
+
},
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* @method instance-build-manifest
|
|
1491
|
+
* @description Builds a Kubernetes Deployment + Service manifest for a specific instance entry
|
|
1492
|
+
* from `conf.instances.json` and writes it to a file. This is a purely local
|
|
1493
|
+
* artifact generator: it never probes a live cluster. Traffic colour defaults
|
|
1494
|
+
* to the canonical initial `blue` and can be overridden with `--traffic`; the
|
|
1495
|
+
* real blue/green swap is resolved at deploy time (`deploy --sync`).
|
|
1496
|
+
*
|
|
1497
|
+
* If `--build` is supplied the image is built from the project Dockerfile and loaded into the
|
|
1498
|
+
* cluster before the manifest is written (kind by default; `--kubeadm` / `--k3s` override).
|
|
1499
|
+
*
|
|
1500
|
+
* @param {string} path - Comma-separated: `deployId,instanceId[,projectPath]`.
|
|
1501
|
+
* `projectPath` is the root directory that contains the `Dockerfile` (e.g. `./cyberia-client`).
|
|
1502
|
+
* Artifacts are written to `<projectPath>/manifests/<env>/Dockerfile` and
|
|
1503
|
+
* `<projectPath>/manifests/<env>/deployment.yaml`.
|
|
1504
|
+
* In production, files are also copied to `<projectPath>/Dockerfile` and
|
|
1505
|
+
* `<projectPath>/deployment.yaml`.
|
|
1506
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1507
|
+
* @memberof UnderpostRun
|
|
1508
|
+
*/
|
|
1509
|
+
'instance-build-manifest': (path, options = DEFAULT_OPTION) => {
|
|
1510
|
+
const env = options.dev ? 'development' : 'production';
|
|
1511
|
+
let [deployId, id, projectPath] = path.split(',');
|
|
1512
|
+
const rootPath = projectPath ? projectPath : '.';
|
|
1513
|
+
|
|
1514
|
+
const confInstances = loadConfInstances(deployId);
|
|
1515
|
+
// Targeting a template id builds every world in the family. The fan-out
|
|
1516
|
+
// re-enters this runner with `instanceOnly` because the default world
|
|
1517
|
+
// keeps the template id verbatim — without it, selecting `mmo-server`
|
|
1518
|
+
// would match the family again and recurse forever. Only that default
|
|
1519
|
+
// world publishes to the project root, so the repo keeps exactly one
|
|
1520
|
+
// canonical Dockerfile/deployment.yaml pair.
|
|
1521
|
+
const selected = options.instanceOnly
|
|
1522
|
+
? confInstances.filter((instance) => instance.id === id)
|
|
1523
|
+
: selectConfInstances(confInstances, id);
|
|
1524
|
+
if (selected.length === 0) {
|
|
1525
|
+
logger.error(`Instance with id '${id}' not found in conf.instances.json for deployId '${deployId}'`);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (!options.instanceOnly && (selected.length > 1 || selected[0].id !== id)) {
|
|
1529
|
+
for (const instance of selected)
|
|
1530
|
+
UnderpostRun.RUNNERS['instance-build-manifest'](
|
|
1531
|
+
[deployId, instance.id, projectPath].filter((v) => v !== undefined).join(','),
|
|
1532
|
+
{ ...options, instanceOnly: true },
|
|
1533
|
+
);
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const envManifestPath = `${rootPath}/manifests/deployments/${id}-${env}`;
|
|
1538
|
+
const outputPath = `${envManifestPath}/deployment.yaml`;
|
|
1539
|
+
const dockerfileManifestPath = `${envManifestPath}/Dockerfile`;
|
|
1540
|
+
|
|
1541
|
+
fs.mkdirpSync(envManifestPath);
|
|
1542
|
+
|
|
1543
|
+
const instance = selected[0];
|
|
1544
|
+
const isDefaultInstance = instance.id === instance.templateId || !instance.templateId;
|
|
1545
|
+
|
|
1546
|
+
let {
|
|
1547
|
+
id: _id,
|
|
1548
|
+
host: _host,
|
|
1549
|
+
image: _image,
|
|
1550
|
+
fromPort: _fromPort,
|
|
1551
|
+
toPort: _toPort,
|
|
1552
|
+
fromDebugPort: _fromDebugPort,
|
|
1553
|
+
toDebugPort: _toDebugPort,
|
|
1554
|
+
cmd: _cmd,
|
|
1555
|
+
volumes: _volumes,
|
|
1556
|
+
metadata: _metadata,
|
|
1557
|
+
runtime: _runtime,
|
|
1558
|
+
lifecycle: _lifecycle,
|
|
1559
|
+
readinessProbe: _readinessProbe,
|
|
1560
|
+
livenessProbe: _livenessProbe,
|
|
1561
|
+
} = instance;
|
|
1562
|
+
|
|
1563
|
+
// Resolve Dockerfile source. Dev/prod variant rules:
|
|
1564
|
+
// - When the instance defines a `runtime`, look under
|
|
1565
|
+
// `src/runtime/<runtime>/`. In `--dev` mode prefer `Dockerfile.dev`
|
|
1566
|
+
// when it exists, falling back to `Dockerfile`.
|
|
1567
|
+
// - When `runtime` is not set, look in the project root with the
|
|
1568
|
+
// same `.dev` → no-suffix precedence.
|
|
1569
|
+
// Dockerfile.dev is a full Dockerfile (not an overlay) — each runtime
|
|
1570
|
+
// owns the contract between its dev image and its prod image (debug
|
|
1571
|
+
// build flags, extra tooling, default ports, etc.).
|
|
1572
|
+
const dockerfileBase = _runtime ? `src/runtime/${_runtime}` : rootPath;
|
|
1573
|
+
const dockerfileCandidates = options.dev
|
|
1574
|
+
? [`${dockerfileBase}/Dockerfile.dev`, `${dockerfileBase}/Dockerfile`]
|
|
1575
|
+
: [`${dockerfileBase}/Dockerfile`];
|
|
1576
|
+
const dockerfileSourcePath = dockerfileCandidates.find((p) => fs.existsSync(p));
|
|
1577
|
+
if (dockerfileSourcePath) {
|
|
1578
|
+
if (options.dev && !dockerfileSourcePath.endsWith('.dev')) {
|
|
1579
|
+
logger.warn(
|
|
1580
|
+
`[instance-build-manifest] --dev requested but no Dockerfile.dev present; falling back to ${dockerfileSourcePath}`,
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
fs.copyFileSync(dockerfileSourcePath, dockerfileManifestPath);
|
|
1584
|
+
} else {
|
|
1585
|
+
logger.warn(`[instance-build-manifest] Dockerfile not found; tried: ${dockerfileCandidates.join(', ')}`);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const _deployId = `${deployId}-${_id}`;
|
|
1589
|
+
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
1590
|
+
// Use debug ports in development when defined, fall back to production ports.
|
|
1591
|
+
if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
|
|
1592
|
+
if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
|
|
1593
|
+
|
|
1594
|
+
// Build image from projectPath Dockerfile and load into cluster when --build is set.
|
|
1595
|
+
if (options.build && projectPath) {
|
|
1596
|
+
const isKind = !options.kubeadm && !options.k3s;
|
|
1597
|
+
Underpost.image.build({
|
|
1598
|
+
path: projectPath,
|
|
1599
|
+
imageName: _image,
|
|
1600
|
+
podmanSave: true,
|
|
1601
|
+
imageOutPath: projectPath,
|
|
1602
|
+
kind: isKind,
|
|
1603
|
+
kubeadm: !!options.kubeadm,
|
|
1604
|
+
k3s: !!options.k3s,
|
|
1605
|
+
reset: !!options.reset,
|
|
1606
|
+
dev: options.dev,
|
|
1607
|
+
});
|
|
1608
|
+
logger.info(`[instance-build-manifest] Image built and loaded`, {
|
|
1609
|
+
image: _image,
|
|
1610
|
+
cluster: isKind ? 'kind' : options.kubeadm ? 'kubeadm' : 'k3s',
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const targetTraffic = options.traffic || 'blue';
|
|
1615
|
+
const parentTraffic = targetTraffic;
|
|
1616
|
+
const resolvedCmd = _cmd[env].map((c) =>
|
|
1617
|
+
c.replaceAll(
|
|
1618
|
+
'{{grpc-service-dns}}',
|
|
1619
|
+
`${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
|
|
1620
|
+
),
|
|
1621
|
+
);
|
|
1622
|
+
|
|
1623
|
+
// Env-aware lifecycle / probe selection. Each block may either be
|
|
1624
|
+
// a single object (shared across envs) or `{ development, production }`.
|
|
1625
|
+
const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
|
|
1626
|
+
|
|
1627
|
+
// Convention: an instance config may place `imagePullPolicy` inside
|
|
1628
|
+
// the env-scoped lifecycle block (alongside postStart/preStop).
|
|
1629
|
+
// Extract it onto the container spec and strip it from the lifecycle hash.
|
|
1630
|
+
const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
|
|
1631
|
+
Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
|
|
1632
|
+
const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
|
|
1633
|
+
|
|
1634
|
+
const deploymentYaml =
|
|
1635
|
+
`---\n` +
|
|
1636
|
+
Underpost.deploy
|
|
1637
|
+
.deploymentYamlPartsFactory({
|
|
1638
|
+
deployId: _deployId,
|
|
1639
|
+
env,
|
|
1640
|
+
suffix: targetTraffic,
|
|
1641
|
+
resources: Underpost.deploy.resourcesFactory(options),
|
|
1642
|
+
replicas: options.replicas,
|
|
1643
|
+
image: _image,
|
|
1644
|
+
namespace: options.namespace,
|
|
1645
|
+
volumes: _volumes,
|
|
1646
|
+
cmd: resolvedCmd,
|
|
1647
|
+
lifecycle: lifecycleForManifest,
|
|
1648
|
+
readinessProbe: pickEnv(_readinessProbe),
|
|
1649
|
+
livenessProbe: pickEnv(_livenessProbe),
|
|
1650
|
+
containerPort: _toPort,
|
|
1651
|
+
imagePullPolicy: instanceImagePullPolicy,
|
|
1652
|
+
})
|
|
1653
|
+
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort));
|
|
1654
|
+
|
|
1655
|
+
fs.writeFileSync(outputPath, deploymentYaml, 'utf8');
|
|
1656
|
+
logger.info(`[instance-build-manifest] Manifest written to ${outputPath}`, {
|
|
1657
|
+
deployId: _deployId,
|
|
1658
|
+
env,
|
|
1659
|
+
traffic: targetTraffic,
|
|
1660
|
+
image: _image,
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
// --- Sibling manifests (pv-pvc, proxy, grpc-service) ------------------
|
|
1664
|
+
// Emit the same apply-able set the parent deploy writes to build/<env>,
|
|
1665
|
+
// scoped to this instance so the project repo and engine-private ship
|
|
1666
|
+
// more than just deployment.yaml. Content matches what `run instance`
|
|
1667
|
+
// creates dynamically at deploy time (deployVolume / instance-promote /
|
|
1668
|
+
// the parent gRPC ClusterIP), so a static `kubectl apply` is equivalent.
|
|
1669
|
+
const pvDataNode = Underpost.deploy.resolveDeployNode({
|
|
1670
|
+
node: options.nodeName,
|
|
1671
|
+
kind: options.kind,
|
|
1672
|
+
kubeadm: options.kubeadm,
|
|
1673
|
+
k3s: options.k3s,
|
|
1674
|
+
env,
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
// pv-pvc.yaml — one PV+PVC per instance volume; names mirror deployVolume.
|
|
1678
|
+
let pvPvcYaml = '';
|
|
1679
|
+
for (const volume of _volumes || []) {
|
|
1680
|
+
if (!volume.claimName) continue;
|
|
1681
|
+
const pvcId = `${volume.claimName}-${_deployId}-${env}-${targetTraffic}`;
|
|
1682
|
+
const pvId = `${volume.claimName.replace('pvc-', 'pv-')}-${_deployId}-${env}-${targetTraffic}`;
|
|
1683
|
+
pvPvcYaml += `---\n${Underpost.deploy.persistentVolumeFactory({
|
|
1684
|
+
pvcId,
|
|
1685
|
+
namespace: options.namespace,
|
|
1686
|
+
hostPath: `/home/dd/engine/volume/${pvId}`,
|
|
1687
|
+
nodeName: pvDataNode,
|
|
1688
|
+
})}\n`;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// proxy.yaml — this instance's OWN route only (its sub-path → its own
|
|
1692
|
+
// service), so each variant's build dir carries a distinct, instance-scoped
|
|
1693
|
+
// fragment rather than an identical copy of the whole host proxy. The
|
|
1694
|
+
// complete host HTTPProxy — every variant's route aggregated onto the one
|
|
1695
|
+
// fqdn, each pointing at its live colour — is assembled and applied by
|
|
1696
|
+
// `instance-promote` at deploy time, and only once EVERY variant is ready.
|
|
1697
|
+
const proxyYaml =
|
|
1698
|
+
Underpost.deploy.baseProxyYamlFactory({ host: _host, env, options }) +
|
|
1699
|
+
instanceProxyRoutesFactory({
|
|
1700
|
+
deployId,
|
|
1701
|
+
instances: [instance],
|
|
1702
|
+
env,
|
|
1703
|
+
trafficById: { [instance.id]: targetTraffic },
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// grpc-service.yaml — the parent deploy's gRPC ClusterIP (shared; the
|
|
1707
|
+
// instance cmd resolves {{grpc-service-dns}} to it). Reuse the parent's
|
|
1708
|
+
// generated manifest when present rather than regenerating it here.
|
|
1709
|
+
const parentGrpcServicePath = `./engine-private/conf/${deployId}/build/${env}/grpc-service.yaml`;
|
|
1710
|
+
const grpcServiceYaml = fs.existsSync(parentGrpcServicePath)
|
|
1711
|
+
? fs.readFileSync(parentGrpcServicePath, 'utf8')
|
|
1712
|
+
: '';
|
|
1713
|
+
|
|
1714
|
+
// Write the sibling set next to deployment.yaml (project) and into the
|
|
1715
|
+
// engine-private per-instance build dir (mirrors instances/<id>/ layout).
|
|
1716
|
+
const instanceBuildDir = `./engine-private/conf/${deployId}/instances/${_id}/build/${env}`;
|
|
1717
|
+
fs.mkdirpSync(instanceBuildDir);
|
|
1718
|
+
fs.writeFileSync(`${instanceBuildDir}/deployment.yaml`, deploymentYaml, 'utf8');
|
|
1719
|
+
const siblingManifests = {
|
|
1720
|
+
'pv-pvc.yaml': pvPvcYaml,
|
|
1721
|
+
'proxy.yaml': proxyYaml,
|
|
1722
|
+
'grpc-service.yaml': grpcServiceYaml,
|
|
1723
|
+
};
|
|
1724
|
+
for (const [name, content] of Object.entries(siblingManifests)) {
|
|
1725
|
+
if (!content) continue;
|
|
1726
|
+
fs.writeFileSync(`${envManifestPath}/${name}`, content, 'utf8');
|
|
1727
|
+
fs.writeFileSync(`${instanceBuildDir}/${name}`, content, 'utf8');
|
|
1728
|
+
}
|
|
1729
|
+
logger.info('[instance-build-manifest] Sibling manifests written', {
|
|
1730
|
+
project: envManifestPath,
|
|
1731
|
+
enginePrivate: instanceBuildDir,
|
|
1732
|
+
pvPvc: !!pvPvcYaml,
|
|
1733
|
+
proxy: !!proxyYaml,
|
|
1734
|
+
grpcService: !!grpcServiceYaml,
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
// --- Per-instance env files -----------------------------------------
|
|
1738
|
+
// Each env file is seeded from the template instance's env file for the
|
|
1739
|
+
// same mode so operator-owned keys (API keys, memory limits, service URLs)
|
|
1740
|
+
// are inherited verbatim; only the keys the instance declares under
|
|
1741
|
+
// `multiInstance.env` — plus the derived container id — are (re)written.
|
|
1742
|
+
//
|
|
1743
|
+
// A derived instance's env dir is generated in full: both development.env
|
|
1744
|
+
// and production.env are written on every build, so a deploy in either
|
|
1745
|
+
// environment always finds the env file its `cmd` sources, no matter which
|
|
1746
|
+
// mode this build ran. The default/template instance owns the committed
|
|
1747
|
+
// source files, so only its current-mode file is idempotently refreshed.
|
|
1748
|
+
if (instance.templateId) {
|
|
1749
|
+
const instanceEnvDir = `./engine-private/conf/${deployId}/instances/${_id}/env`;
|
|
1750
|
+
fs.mkdirpSync(instanceEnvDir);
|
|
1751
|
+
const envsToWrite = isDefaultInstance ? [env] : ['development', 'production'];
|
|
1752
|
+
for (const targetEnv of envsToWrite) {
|
|
1753
|
+
const templateEnvPath = `./engine-private/conf/${deployId}/instances/${instance.templateId}/env/${targetEnv}.env`;
|
|
1754
|
+
const baseEnv = fs.existsSync(templateEnvPath) ? dotenv.parse(fs.readFileSync(templateEnvPath, 'utf8')) : {};
|
|
1755
|
+
writeEnv(`${instanceEnvDir}/${targetEnv}.env`, {
|
|
1756
|
+
...baseEnv,
|
|
1757
|
+
...(instance.instanceEnv?.[targetEnv] || {}),
|
|
1758
|
+
CONTAINER_DEPLOY_ID: `${_deployId}-${targetEnv}`,
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
logger.info('[instance-build-manifest] Instance env written', {
|
|
1762
|
+
dir: instanceEnvDir,
|
|
1763
|
+
instanceCode: instance.instanceCode,
|
|
1764
|
+
envs: envsToWrite,
|
|
1765
|
+
keys: Object.keys(instance.instanceEnv?.[env] || {}),
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
if (env === 'production' && isDefaultInstance) {
|
|
1770
|
+
if (fs.existsSync(dockerfileManifestPath)) {
|
|
1771
|
+
fs.copyFileSync(dockerfileManifestPath, `${rootPath}/Dockerfile`);
|
|
1772
|
+
}
|
|
1773
|
+
fs.copyFileSync(outputPath, `${rootPath}/deployment.yaml`);
|
|
1774
|
+
// Sibling manifests alongside deployment.yaml at the project root.
|
|
1775
|
+
for (const name of ['pv-pvc.yaml', 'proxy.yaml', 'grpc-service.yaml']) {
|
|
1776
|
+
const src = `${envManifestPath}/${name}`;
|
|
1777
|
+
if (fs.existsSync(src)) fs.copyFileSync(src, `${rootPath}/${name}`);
|
|
1778
|
+
}
|
|
1779
|
+
logger.info('[instance-build-manifest] Production artifacts copied to project root', {
|
|
1780
|
+
rootPath,
|
|
1781
|
+
dockerfile: `${rootPath}/Dockerfile`,
|
|
1782
|
+
deployment: `${rootPath}/deployment.yaml`,
|
|
1783
|
+
});
|
|
1784
|
+
const ciSrc = `./.github/workflows/docker-image.${_runtime}.ci.yml`;
|
|
1785
|
+
if (fs.existsSync(ciSrc)) {
|
|
1786
|
+
if (!fs.existsSync(`${rootPath}/.github/workflows`)) fs.mkdirpSync(`${rootPath}/.github/workflows`);
|
|
1787
|
+
fs.copyFileSync(ciSrc, `${rootPath}/.github/workflows/docker-image.${_runtime}.ci.yml`);
|
|
1788
|
+
logger.info(`[instance-build-manifest] CI workflow copied`, { src: ciSrc });
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Ship the development variant alongside production so the instance repo
|
|
1792
|
+
// is self-contained: the dev Dockerfile (built by the -dev CI workflow
|
|
1793
|
+
// into underpost/<runtime>-dev, consumed by the development compose
|
|
1794
|
+
// stack) and its dispatchable workflow. Both are optional — synced only
|
|
1795
|
+
// when the source-of-truth files exist in the engine repo.
|
|
1796
|
+
if (_runtime) {
|
|
1797
|
+
const devDockerfileSrc = `src/runtime/${_runtime}/Dockerfile.dev`;
|
|
1798
|
+
if (fs.existsSync(devDockerfileSrc)) {
|
|
1799
|
+
fs.copyFileSync(devDockerfileSrc, `${rootPath}/Dockerfile.dev`);
|
|
1800
|
+
logger.info('[instance-build-manifest] Dev Dockerfile copied', { src: devDockerfileSrc });
|
|
1801
|
+
}
|
|
1802
|
+
const devCiSrc = `./.github/workflows/docker-image.${_runtime}.dev.ci.yml`;
|
|
1803
|
+
if (fs.existsSync(devCiSrc)) {
|
|
1804
|
+
if (!fs.existsSync(`${rootPath}/.github/workflows`)) fs.mkdirpSync(`${rootPath}/.github/workflows`);
|
|
1805
|
+
fs.copyFileSync(devCiSrc, `${rootPath}/.github/workflows/docker-image.${_runtime}.dev.ci.yml`);
|
|
1806
|
+
logger.info(`[instance-build-manifest] Dev CI workflow copied`, { src: devCiSrc });
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
},
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* @method ls-deployments
|
|
1814
|
+
* @description Retrieves and logs a table of Kubernetes deployments using `Underpost.deploy.get`.
|
|
1815
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as an optional deployment name filter).
|
|
1816
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1817
|
+
* @memberof UnderpostRun
|
|
1818
|
+
*/
|
|
1819
|
+
'ls-deployments': async (path, options = DEFAULT_OPTION) => {
|
|
1820
|
+
console.table(await Underpost.kubectl.get(path, 'deployments', options.namespace));
|
|
1821
|
+
},
|
|
1822
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* @method host-update
|
|
1825
|
+
* @description Executes the `rocky-setup.sh` script to update the host system configuration.
|
|
1826
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1827
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1828
|
+
* @memberof UnderpostRun
|
|
1829
|
+
*/
|
|
1830
|
+
'host-update': async (path, options = DEFAULT_OPTION) => {
|
|
1831
|
+
// const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1832
|
+
shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
|
|
1833
|
+
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
1834
|
+
},
|
|
1835
|
+
|
|
1836
|
+
/**
|
|
1837
|
+
* @method install-crio
|
|
1838
|
+
* @description Installs and configures CRI-O as the container runtime for kubeadm clusters.
|
|
1839
|
+
* Adds the stable v1.33 CRI-O yum repository, installs the `cri-o` package, configures
|
|
1840
|
+
* the systemd cgroup driver, enables the `crio` service, and writes `/etc/crictl.yaml`
|
|
1841
|
+
* so that `crictl` targets the CRI-O socket by default.
|
|
1842
|
+
* @param {string} path - Unused.
|
|
1843
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
1844
|
+
* @memberof UnderpostRun
|
|
1845
|
+
*/
|
|
1846
|
+
'install-crio': (path, options = DEFAULT_OPTION) => {
|
|
1847
|
+
logger.info('Installing CRI-O...');
|
|
1848
|
+
shellExec(`cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
|
|
1849
|
+
[cri-o]
|
|
1850
|
+
name=CRI-O
|
|
1851
|
+
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/
|
|
1852
|
+
enabled=1
|
|
1853
|
+
gpgcheck=1
|
|
1854
|
+
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/repodata/repomd.xml.key
|
|
1855
|
+
EOF`);
|
|
1856
|
+
shellExec(`sudo dnf -y install cri-o`);
|
|
1857
|
+
// Add the Kubernetes repo so cri-tools (crictl CLI) is available.
|
|
1858
|
+
// The repo has exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni, so we
|
|
1859
|
+
// use --disableexcludes=kubernetes to override and install cri-tools.
|
|
1860
|
+
shellExec(`cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
|
|
1861
|
+
[kubernetes]
|
|
1862
|
+
name=Kubernetes
|
|
1863
|
+
baseurl=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/
|
|
1864
|
+
enabled=1
|
|
1865
|
+
gpgcheck=1
|
|
1866
|
+
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/repodata/repomd.xml.key
|
|
1867
|
+
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
|
|
1868
|
+
EOF`);
|
|
1869
|
+
shellExec(`sudo yum install -y cri-tools --disableexcludes=kubernetes 2>/dev/null || {
|
|
1870
|
+
CRICTL_VERSION="v1.31.0"
|
|
1871
|
+
ARCH="amd64"
|
|
1872
|
+
log "Kubernetes repo not available, downloading crictl binary directly..."
|
|
1873
|
+
curl -sL "https://github.com/kubernetes-sigs/cri-tools/releases/download/\${CRICTL_VERSION}/crictl-\${CRICTL_VERSION}-\${ARCH}.tar.gz" | sudo tar -C /usr/local/bin -xz
|
|
1874
|
+
}`);
|
|
1875
|
+
// Ensure CRI-O uses systemd cgroup driver (matches kubelet default)
|
|
1876
|
+
shellExec(`sudo sed -i 's/^#\?cgroup_manager =.*/cgroup_manager = "systemd"/' /etc/crio/crio.conf`, {
|
|
1877
|
+
silentOnError: true,
|
|
1878
|
+
});
|
|
1879
|
+
shellExec(`sudo systemctl enable --now crio`);
|
|
1880
|
+
logger.info('CRI-O installed and started.');
|
|
1881
|
+
// Write crictl config so all crictl calls default to the CRI-O socket
|
|
1882
|
+
shellExec(`cat <<EOF | sudo tee /etc/crictl.yaml
|
|
1883
|
+
runtime-endpoint: unix:///var/run/crio/crio.sock
|
|
1884
|
+
image-endpoint: unix:///var/run/crio/crio.sock
|
|
1885
|
+
timeout: 10
|
|
1886
|
+
debug: false
|
|
1887
|
+
EOF`);
|
|
1888
|
+
},
|
|
1889
|
+
|
|
1890
|
+
/**
|
|
1891
|
+
* @method dd-container
|
|
1892
|
+
* @description Deploys a development or debug container tasks jobs, setting up necessary volumes and images, and running specified commands within the container.
|
|
1893
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the command to run inside the container).
|
|
1894
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1895
|
+
* @memberof UnderpostRun
|
|
1896
|
+
*/
|
|
1897
|
+
'dd-container': async (path = '', options = DEFAULT_OPTION) => {
|
|
1898
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
1899
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1900
|
+
const currentImage = options.imageName
|
|
1901
|
+
? options.imageName
|
|
1902
|
+
: Underpost.image
|
|
1903
|
+
.getCurrentLoaded(options.nodeName ? options.nodeName : 'kind-worker', false)
|
|
1904
|
+
.find((o) => o.IMAGE.match('underpost'));
|
|
1905
|
+
const podName = options.podName || `underpost-dev-container`;
|
|
1906
|
+
const volumeHostPath = options.claimName || '/home/dd';
|
|
1907
|
+
const claimName = options.claimName || `pvc-dd`;
|
|
1908
|
+
|
|
1909
|
+
if (!options.nodeName) {
|
|
1910
|
+
shellExec(`docker exec -i kind-worker bash -c "rm -rf ${volumeHostPath}"`);
|
|
1911
|
+
shellExec(`docker exec -i kind-worker bash -c "mkdir -p ${volumeHostPath}"`);
|
|
1912
|
+
shellExec(`docker cp ${volumeHostPath}/engine kind-worker:${volumeHostPath}/engine`);
|
|
1913
|
+
shellExec(
|
|
1914
|
+
`docker exec -i kind-worker bash -c "chown -R 1000:1000 ${volumeHostPath}; chmod -R 755 ${volumeHostPath}"`,
|
|
1915
|
+
);
|
|
1916
|
+
} else {
|
|
1917
|
+
shellExec(`kubectl apply -f ${options.underpostRoot}/manifests/pv-pvc-dd.yaml -n ${options.namespace}`);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
if (!currentImage)
|
|
1921
|
+
shellExec(
|
|
1922
|
+
`${baseCommand} image${baseClusterCommand} --pull-base --build --path ${
|
|
1923
|
+
options.dev ? '.' : options.underpostRoot
|
|
1924
|
+
} ${options.dev ? '--kind' : '--kubeadm'}`,
|
|
1925
|
+
);
|
|
1926
|
+
// shellExec(`kubectl delete pod ${podName} --ignore-not-found`);
|
|
1927
|
+
|
|
1928
|
+
const payload = {
|
|
1929
|
+
...options,
|
|
1930
|
+
podName,
|
|
1931
|
+
imageName: currentImage
|
|
1932
|
+
? currentImage.image
|
|
1933
|
+
? currentImage.image
|
|
1934
|
+
: currentImage.IMAGE
|
|
1935
|
+
? `${currentImage.IMAGE}:${currentImage.TAG}`
|
|
1936
|
+
: `localhost/rockylinux9-underpost:${Underpost.version}`
|
|
1937
|
+
: `localhost/rockylinux9-underpost:${Underpost.version}`,
|
|
1938
|
+
volumeMountPath: volumeHostPath,
|
|
1939
|
+
...(options.dev ? { volumeHostPath } : { claimName }),
|
|
1940
|
+
on: {
|
|
1941
|
+
init: async () => {},
|
|
1942
|
+
},
|
|
1943
|
+
args: [daemonProcess(path ? path : `cd /home/dd/engine && npm install && npm run test`)],
|
|
1944
|
+
};
|
|
1945
|
+
|
|
1946
|
+
await Underpost.run.CALL('deploy-job', path, payload);
|
|
1947
|
+
},
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* @method ip-info
|
|
1951
|
+
* @description Executes the `ip-info.sh` script to display IP-related information for the specified path.
|
|
1952
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as an argument to the script).
|
|
1953
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1954
|
+
* @memberof UnderpostRun
|
|
1955
|
+
*/
|
|
1956
|
+
'ip-info': (path, options = DEFAULT_OPTION) => {
|
|
1957
|
+
const { underpostRoot } = options;
|
|
1958
|
+
shellExec(`chmod +x ${underpostRoot}/scripts/ip-info.sh`);
|
|
1959
|
+
shellExec(`${underpostRoot}/scripts/ip-info.sh ${path}`);
|
|
1960
|
+
},
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* @method db-client
|
|
1964
|
+
* @description Deploys and exposes the Adminer database client application (using `adminer:4.7.6-standalone` image) on the cluster.
|
|
1965
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1966
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1967
|
+
* @memberof UnderpostRun
|
|
1968
|
+
*/
|
|
1969
|
+
'db-client': async (path, options = DEFAULT_OPTION) => {
|
|
1970
|
+
const { underpostRoot } = options;
|
|
1971
|
+
|
|
1972
|
+
Underpost.image.pullDockerHubImage({
|
|
1973
|
+
dockerhubImage: 'adminer',
|
|
1974
|
+
version: '4.7.6-standalone',
|
|
1975
|
+
kind: options.kind,
|
|
1976
|
+
kubeadm: options.kubeadm,
|
|
1977
|
+
k3s: options.k3s,
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
shellExec(`kubectl delete deployment adminer -n ${options.namespace} --ignore-not-found`);
|
|
1981
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/deployment/adminer/. -n ${options.namespace}`);
|
|
1982
|
+
const successInstance = await Underpost.test.statusMonitor('adminer', 'Running', 'pods', 1000, 60 * 10);
|
|
1983
|
+
|
|
1984
|
+
if (successInstance) {
|
|
1985
|
+
shellExec(`underpost deploy --expose adminer --namespace ${options.namespace}`);
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* @method git-conf
|
|
1991
|
+
* @description Configures Git global and local user name and email settings based on the provided `path` (formatted as `username,email`), or defaults to environment variables.
|
|
1992
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string: `username,email`).
|
|
1993
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
1994
|
+
* @memberof UnderpostRun
|
|
1995
|
+
*/
|
|
1996
|
+
'git-conf': (path = '', options = DEFAULT_OPTION) => {
|
|
1997
|
+
const defaultUsername = Underpost.env.get('GITHUB_USERNAME');
|
|
1998
|
+
const defaultEmail = Underpost.env.get('GITHUB_EMAIL');
|
|
1999
|
+
const validPath = path && path.split(',').length;
|
|
2000
|
+
const [username, email] = validPath ? path.split(',') : [defaultUsername, defaultEmail];
|
|
2001
|
+
if (validPath) {
|
|
2002
|
+
Underpost.env.set('GITHUB_USERNAME', username);
|
|
2003
|
+
Underpost.env.set('GITHUB_EMAIL', email);
|
|
2004
|
+
Underpost.env.get('GITHUB_USERNAME');
|
|
2005
|
+
Underpost.env.get('GITHUB_EMAIL');
|
|
2006
|
+
}
|
|
2007
|
+
shellExec(
|
|
2008
|
+
`git config --global credential.helper "" && ` +
|
|
2009
|
+
`git config credential.helper "" && ` +
|
|
2010
|
+
`git config --global user.name '${username}' && ` +
|
|
2011
|
+
`git config --global user.email '${email}' && ` +
|
|
2012
|
+
`git config --global credential.interactive always && ` +
|
|
2013
|
+
`git config user.name '${username}' && ` +
|
|
2014
|
+
`git config user.email '${email}' && ` +
|
|
2015
|
+
`git config credential.interactive always &&` +
|
|
2016
|
+
`git config pull.rebase false && ` +
|
|
2017
|
+
`git config core.filemode false`,
|
|
2018
|
+
{
|
|
2019
|
+
disableLog: true,
|
|
2020
|
+
silent: true,
|
|
2021
|
+
},
|
|
2022
|
+
);
|
|
2023
|
+
|
|
2024
|
+
if (options.logs)
|
|
2025
|
+
console.log(
|
|
2026
|
+
shellExec(`git config list`, { silent: true, stdout: true })
|
|
2027
|
+
.replaceAll('user.email', 'user.email'.yellow)
|
|
2028
|
+
.replaceAll(username, username.green)
|
|
2029
|
+
.replaceAll('user.name', 'user.name'.yellow)
|
|
2030
|
+
.replaceAll(email, email.green),
|
|
2031
|
+
);
|
|
2032
|
+
},
|
|
2033
|
+
|
|
2034
|
+
/**
|
|
2035
|
+
* @method promote
|
|
2036
|
+
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.router` for 'dd', or a specific ID).
|
|
2037
|
+
* When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
|
|
2038
|
+
* TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
|
|
2039
|
+
* (cert-manager Certificate resources) for each affected deployment.
|
|
2040
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string: `deployId,env,replicas`).
|
|
2041
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2042
|
+
* @memberof UnderpostRun
|
|
2043
|
+
*/
|
|
2044
|
+
promote: async (path, options = DEFAULT_OPTION) => {
|
|
2045
|
+
let [inputDeployId, inputEnv, inputReplicas] = path.split(',');
|
|
2046
|
+
if (!inputEnv) inputEnv = 'production';
|
|
2047
|
+
if (!inputReplicas) inputReplicas = 1;
|
|
2048
|
+
// TODO: normalize: --tls maps to --cert for deploy.js isValidTLSContext compatibility
|
|
2049
|
+
if (options.tls) options.cert = true;
|
|
2050
|
+
|
|
2051
|
+
const applyCerts = (deployId, targetTraffic) => {
|
|
2052
|
+
if (!options.tls) return;
|
|
2053
|
+
// Rebuild proxy.yaml with --cert so the HTTPProxy includes TLS virtualhost config
|
|
2054
|
+
shellExec(
|
|
2055
|
+
`node bin deploy --build-manifest --cert --traffic ${targetTraffic} --replicas ${inputReplicas} --namespace ${options.namespace} ${deployId} ${inputEnv}`,
|
|
2056
|
+
);
|
|
2057
|
+
// Delete stale Certificate resources before reapplying
|
|
2058
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2059
|
+
if (fs.existsSync(confServerPath)) {
|
|
2060
|
+
for (const host of Object.keys(JSON.parse(fs.readFileSync(confServerPath, 'utf8'))))
|
|
2061
|
+
shellExec(`sudo kubectl delete Certificate ${host} -n ${options.namespace} --ignore-not-found`);
|
|
2062
|
+
}
|
|
2063
|
+
shellExec(
|
|
2064
|
+
`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${inputEnv}/proxy.yaml -n ${options.namespace}`,
|
|
2065
|
+
);
|
|
2066
|
+
const secretPath = `./engine-private/conf/${deployId}/build/${inputEnv}/secret.yaml`;
|
|
2067
|
+
if (fs.existsSync(secretPath)) shellExec(`kubectl apply -f ${secretPath} -n ${options.namespace}`);
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
if (inputDeployId === 'dd') {
|
|
2071
|
+
for (const deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
|
|
2072
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
2073
|
+
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
2074
|
+
Underpost.deploy.switchTraffic(deployId, inputEnv, targetTraffic, inputReplicas, options.namespace, options);
|
|
2075
|
+
applyCerts(deployId, targetTraffic);
|
|
2076
|
+
}
|
|
2077
|
+
} else {
|
|
2078
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(inputDeployId, { namespace: options.namespace });
|
|
2079
|
+
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
2080
|
+
Underpost.deploy.switchTraffic(
|
|
2081
|
+
inputDeployId,
|
|
2082
|
+
inputEnv,
|
|
2083
|
+
targetTraffic,
|
|
2084
|
+
inputReplicas,
|
|
2085
|
+
options.namespace,
|
|
2086
|
+
options,
|
|
2087
|
+
);
|
|
2088
|
+
applyCerts(inputDeployId, targetTraffic);
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
/**
|
|
2092
|
+
* @method metrics
|
|
2093
|
+
* @description Deploys Prometheus and Grafana for metrics monitoring, targeting the hosts defined in the deployment configuration files.
|
|
2094
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2095
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2096
|
+
* @memberof UnderpostRun
|
|
2097
|
+
*/
|
|
2098
|
+
metrics: async (path, options = DEFAULT_OPTION) => {
|
|
2099
|
+
if (path === 'server') {
|
|
2100
|
+
shellExec(
|
|
2101
|
+
`kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/high-availability-1.21+.yaml`,
|
|
2102
|
+
);
|
|
2103
|
+
await timer(2000);
|
|
2104
|
+
|
|
2105
|
+
shellExec(`kubectl patch deployment metrics-server -n kube-system \
|
|
2106
|
+
--type='json' \
|
|
2107
|
+
-p='[
|
|
2108
|
+
{
|
|
2109
|
+
"op":"add",
|
|
2110
|
+
"path":"/spec/template/spec/containers/0/args/-",
|
|
2111
|
+
"value":"--kubelet-insecure-tls"
|
|
2112
|
+
}
|
|
2113
|
+
]'`);
|
|
2114
|
+
shellExec(`kubectl scale deployment metrics-server \
|
|
2115
|
+
-n kube-system \
|
|
2116
|
+
--replicas=1`);
|
|
2117
|
+
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
const deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',');
|
|
2121
|
+
let hosts = [];
|
|
2122
|
+
for (const deployId of deployList) {
|
|
2123
|
+
const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
|
|
2124
|
+
hosts = hosts.concat(Object.keys(confServer));
|
|
2125
|
+
}
|
|
2126
|
+
shellExec(`node bin cluster --prom ${hosts.join(',')}`);
|
|
2127
|
+
shellExec(`node bin cluster --grafana`);
|
|
2128
|
+
},
|
|
2129
|
+
/**
|
|
2130
|
+
* @method cluster
|
|
2131
|
+
* @description Deploys a full production/development ready Kubernetes cluster environment including MongoDB, MariaDB, Valkey, Contour (Ingress), and Cert-Manager, and deploys all services.
|
|
2132
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2133
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2134
|
+
* @memberof UnderpostRun
|
|
2135
|
+
*/
|
|
2136
|
+
cluster: async (path = '', options = DEFAULT_OPTION) => {
|
|
2137
|
+
const { underpostRoot } = options;
|
|
2138
|
+
const env = options.dev ? 'development' : 'production';
|
|
2139
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2140
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
2141
|
+
const clusterType = options.k3s ? 'k3s' : 'kubeadm';
|
|
2142
|
+
shellCd(`/home/dd/engine`);
|
|
2143
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --reset --${clusterType}`);
|
|
2144
|
+
await timer(5000);
|
|
2145
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
2146
|
+
await timer(5000);
|
|
2147
|
+
let [runtimeImage, deployList] =
|
|
2148
|
+
path && path.trim() && path.split(',')
|
|
2149
|
+
? path.split(',')
|
|
2150
|
+
: [
|
|
2151
|
+
'express',
|
|
2152
|
+
fs.readFileSync(`${underpostRoot}/engine-private/deploy/dd.router`, 'utf8').replaceAll(',', '+'),
|
|
2153
|
+
];
|
|
2154
|
+
shellExec(
|
|
2155
|
+
`${baseCommand} image${baseClusterCommand} --build ${
|
|
2156
|
+
runtimeImage ? ` --pull-base --path ${underpostRoot}/src/runtime/${runtimeImage}` : ''
|
|
2157
|
+
} --${clusterType}`,
|
|
2158
|
+
);
|
|
2159
|
+
if (!deployList) {
|
|
2160
|
+
deployList = [];
|
|
2161
|
+
logger.warn('No deploy list provided');
|
|
2162
|
+
} else deployList = deployList.split('+');
|
|
2163
|
+
await timer(5000);
|
|
2164
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --pull-image --mongodb`);
|
|
2165
|
+
if (runtimeImage === 'lampp') {
|
|
2166
|
+
await timer(5000);
|
|
2167
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --pull-image --mariadb`);
|
|
2168
|
+
}
|
|
2169
|
+
await timer(5000);
|
|
2170
|
+
for (const deployId of deployList) {
|
|
2171
|
+
shellExec(
|
|
2172
|
+
`${baseCommand} db ${deployId} --import --git --drop --preserveUUID --primary-pod${options.namespace ? ` --ns ${options.namespace}` : ''}`,
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
await timer(5000);
|
|
2176
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --pull-image --valkey`);
|
|
2177
|
+
await timer(5000);
|
|
2178
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --contour`);
|
|
2179
|
+
if (env === 'production') {
|
|
2180
|
+
await timer(5000);
|
|
2181
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --cert-manager`);
|
|
2182
|
+
}
|
|
2183
|
+
for (const deployId of deployList) {
|
|
2184
|
+
shellExec(
|
|
2185
|
+
`${baseCommand} deploy ${deployId} ${env} --${clusterType}${env === 'production' ? ' --cert' : ''}${
|
|
2186
|
+
env === 'development' ? ' --etc-hosts' : ''
|
|
2187
|
+
}${options.namespace ? ` --namespace ${options.namespace}` : ''}`,
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
},
|
|
2191
|
+
/**
|
|
2192
|
+
* @method deploy
|
|
2193
|
+
* @description Deploys a specified service (identified by `path`) using blue/green strategy, monitors its status, and switches traffic upon readiness.
|
|
2194
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID to deploy).
|
|
2195
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2196
|
+
* @memberof UnderpostRun
|
|
2197
|
+
*/
|
|
2198
|
+
deploy: async (path, options = DEFAULT_OPTION) => {
|
|
2199
|
+
const deployId = path;
|
|
2200
|
+
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
2201
|
+
if (!validVersion) throw new Error('Version mismatch');
|
|
2202
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
2203
|
+
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
2204
|
+
const env = options.dev ? 'development' : 'production';
|
|
2205
|
+
const ignorePods = Underpost.kubectl
|
|
2206
|
+
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace)
|
|
2207
|
+
.map((p) => p.NAME);
|
|
2208
|
+
|
|
2209
|
+
shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic} -n ${options.namespace}`);
|
|
2210
|
+
|
|
2211
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
2212
|
+
|
|
2213
|
+
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas, options.namespace, options);
|
|
2214
|
+
},
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* @method disk-clean
|
|
2218
|
+
* @description Executes the `disk-clean-sh` script to perform disk cleanup operations.
|
|
2219
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2220
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2221
|
+
* @memberof UnderpostRun
|
|
2222
|
+
*/
|
|
2223
|
+
'disk-clean': async (path, options = DEFAULT_OPTION) => {
|
|
2224
|
+
const { underpostRoot } = options;
|
|
2225
|
+
shellExec(`chmod +x ${underpostRoot}/scripts/disk-clean.sh`);
|
|
2226
|
+
shellExec(`./scripts/disk-clean.sh`);
|
|
2227
|
+
},
|
|
2228
|
+
|
|
2229
|
+
/**
|
|
2230
|
+
* @method disk-devices
|
|
2231
|
+
* @description Executes the `disk-devices.sh` script to display information about disk devices.
|
|
2232
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2233
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2234
|
+
* @memberof UnderpostRun
|
|
2235
|
+
*/
|
|
2236
|
+
'disk-devices': async (path = '/', options = DEFAULT_OPTION) => {
|
|
2237
|
+
const { underpostRoot } = options;
|
|
2238
|
+
shellExec(`chmod +x ${underpostRoot}/scripts/disk-devices.sh`);
|
|
2239
|
+
shellExec(`${underpostRoot}/scripts/disk-devices.sh`);
|
|
2240
|
+
},
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* @method disk-usage
|
|
2244
|
+
* @description Displays disk usage statistics using the `du` command, sorted by size.
|
|
2245
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2246
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2247
|
+
* @memberof UnderpostRun
|
|
2248
|
+
*/
|
|
2249
|
+
'disk-usage': async (path = '/', options = DEFAULT_OPTION) => {
|
|
2250
|
+
if (!path) path = '/';
|
|
2251
|
+
logger.info('Mount filesystem');
|
|
2252
|
+
shellExec(`df -h${path === '/' ? '' : ` ${path}`}`);
|
|
2253
|
+
logger.info('Files disks usage');
|
|
2254
|
+
shellExec(`du -xh ${path} --max-depth=1 | sort -h`);
|
|
2255
|
+
},
|
|
2256
|
+
|
|
2257
|
+
/**
|
|
2258
|
+
* @method dev
|
|
2259
|
+
* @description Starts development servers for client, API, and proxy based on provided parameters (deployId, host, path, clientHostPort).
|
|
2260
|
+
* @param {string} path - The input value, identifier, or path for the operation (formatted as `deployId,subConf,host,path,clientHostPort`).
|
|
2261
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2262
|
+
* @memberof UnderpostRun
|
|
2263
|
+
*/
|
|
2264
|
+
dev: async (path = '', options = DEFAULT_OPTION) => {
|
|
2265
|
+
let [deployId, subConf, host, _path, clientHostPort] = path.split(',');
|
|
2266
|
+
if (options.confServerPath) {
|
|
2267
|
+
const confServer = JSON.parse(fs.readFileSync(options.confServerPath, 'utf8'));
|
|
2268
|
+
fs.writeFileSync(
|
|
2269
|
+
`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`,
|
|
2270
|
+
JSON.stringify(
|
|
2271
|
+
{
|
|
2272
|
+
[host]: {
|
|
2273
|
+
[_path]: confServer[host][_path],
|
|
2274
|
+
},
|
|
2275
|
+
},
|
|
2276
|
+
null,
|
|
2277
|
+
4,
|
|
2278
|
+
),
|
|
2279
|
+
'utf8',
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
if (!deployId) deployId = 'dd-default';
|
|
2283
|
+
if (!host) host = 'default.net';
|
|
2284
|
+
if (!_path) _path = '/';
|
|
2285
|
+
if (!clientHostPort) clientHostPort = 'localhost:4004';
|
|
2286
|
+
if (!subConf) subConf = 'local';
|
|
2287
|
+
if (options.reset && fs.existsSync(`./engine-private/conf/${deployId}`))
|
|
2288
|
+
fs.removeSync(`./engine-private/conf/${deployId}`);
|
|
2289
|
+
if (options.devProxyPortOffset) {
|
|
2290
|
+
const envPath = `./engine-private/conf/${deployId}/.env.development`;
|
|
2291
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
2292
|
+
envObj.DEV_PROXY_PORT_OFFSET = options.devProxyPortOffset;
|
|
2293
|
+
writeEnv(envPath, envObj);
|
|
2294
|
+
}
|
|
2295
|
+
dotenv.config({ path: `./engine-private/conf/${deployId}/.env.development`, override: true });
|
|
2296
|
+
shellExec(`node bin run dev-cluster --expose --namespace ${options.namespace}`, { async: true });
|
|
2297
|
+
{
|
|
2298
|
+
const cmd = `npm run dev:api ${deployId} ${subConf} ${host} ${_path} ${clientHostPort} proxy${
|
|
2299
|
+
options.tls ? ' tls' : ''
|
|
2300
|
+
}`;
|
|
2301
|
+
shellExec(cmd, { async: true });
|
|
2302
|
+
}
|
|
2303
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
2304
|
+
{
|
|
2305
|
+
const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
2306
|
+
|
|
2307
|
+
shellExec(cmd, {
|
|
2308
|
+
async: true,
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
2312
|
+
shellExec(
|
|
2313
|
+
`NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
|
|
2314
|
+
);
|
|
2315
|
+
},
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* @method service
|
|
2319
|
+
* @description Deploys and exposes specific services (like `mongo-express-service`) on the cluster, updating deployment configurations and monitoring status.
|
|
2320
|
+
* @param {string} path - The input value, identifier, or path for the operation (formatted as `deployId,serviceId,host,path,replicas,image,node`).
|
|
2321
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2322
|
+
* @memberof UnderpostRun
|
|
2323
|
+
*/
|
|
2324
|
+
service: async (path = '', options = DEFAULT_OPTION) => {
|
|
2325
|
+
const env = options.dev ? 'development' : 'production';
|
|
2326
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2327
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
2328
|
+
shellCd(`/home/dd/engine`);
|
|
2329
|
+
let [deployId, serviceId, host, _path, replicas, image, node] = path.split(',');
|
|
2330
|
+
if (!replicas) replicas = options.replicas;
|
|
2331
|
+
// const confClient = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.client.json`, 'utf8'));
|
|
2332
|
+
const confServer = loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`);
|
|
2333
|
+
// const confSSR = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.ssr.json`, 'utf8'));
|
|
2334
|
+
// const packageData = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/package.json`, 'utf8'));
|
|
2335
|
+
const services = fs.existsSync(`./engine-private/deploy/${deployId}/conf.services.json`)
|
|
2336
|
+
? JSON.parse(fs.readFileSync(`./engine-private/deploy/${deployId}/conf.services.json`, 'utf8'))
|
|
2337
|
+
: [];
|
|
2338
|
+
let serviceData = services.findIndex((s) => s.serviceId === serviceId);
|
|
2339
|
+
const payload = {
|
|
2340
|
+
serviceId,
|
|
2341
|
+
path: _path,
|
|
2342
|
+
port: options.port,
|
|
2343
|
+
host,
|
|
2344
|
+
};
|
|
2345
|
+
let podToMonitor;
|
|
2346
|
+
if (!payload.port)
|
|
2347
|
+
switch (serviceId) {
|
|
2348
|
+
case 'mongo-express-service': {
|
|
2349
|
+
payload.port = 8081;
|
|
2350
|
+
break;
|
|
2351
|
+
}
|
|
2352
|
+
case 'grafana': {
|
|
2353
|
+
payload.port = 3000;
|
|
2354
|
+
// payload.pathRewritePolicy = [
|
|
2355
|
+
// {
|
|
2356
|
+
// prefix: '/grafana',
|
|
2357
|
+
// replacement: '/',
|
|
2358
|
+
// },
|
|
2359
|
+
// ];
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
if (serviceData == -1) {
|
|
2364
|
+
services.push(payload);
|
|
2365
|
+
} else {
|
|
2366
|
+
services[serviceData] = payload;
|
|
2367
|
+
}
|
|
2368
|
+
fs.writeFileSync(
|
|
2369
|
+
`./engine-private/conf/${deployId}/conf.services.json`,
|
|
2370
|
+
JSON.stringify(services, null, 4),
|
|
2371
|
+
'utf8',
|
|
2372
|
+
);
|
|
2373
|
+
switch (serviceId) {
|
|
2374
|
+
case 'mongo-express-service': {
|
|
2375
|
+
shellExec(`kubectl delete svc mongo-express-service -n ${options.namespace} --ignore-not-found`);
|
|
2376
|
+
shellExec(`kubectl delete deployment mongo-express -n ${options.namespace} --ignore-not-found`);
|
|
2377
|
+
shellExec(`kubectl apply -f manifests/deployment/mongo-express/deployment.yaml -n ${options.namespace}`);
|
|
2378
|
+
podToMonitor = 'mongo-express';
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
case 'grafana': {
|
|
2382
|
+
shellExec(
|
|
2383
|
+
`node bin cluster${baseClusterCommand} --grafana --hosts '${host}' --prom '${Object.keys(confServer)}'`,
|
|
2384
|
+
);
|
|
2385
|
+
podToMonitor = 'grafana';
|
|
2386
|
+
break;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
const success = await Underpost.test.statusMonitor(podToMonitor);
|
|
2390
|
+
if (success) {
|
|
2391
|
+
const versions = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
2392
|
+
if (!node) node = os.hostname();
|
|
2393
|
+
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
2394
|
+
shellExec(
|
|
2395
|
+
`${baseCommand} deploy${options.dev ? '' : ' --kubeadm'}${
|
|
2396
|
+
options.devProxyPortOffset ? ' --disable-deployment-proxy' : ''
|
|
2397
|
+
} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
2398
|
+
image ? ` --image ${image}` : ''
|
|
2399
|
+
}${versions ? ` --versions ${versions}` : ''}${timeoutFlags} dd ${env}`,
|
|
2400
|
+
);
|
|
2401
|
+
shellExec(
|
|
2402
|
+
`${baseCommand} deploy${options.dev ? '' : ' --kubeadm'}${
|
|
2403
|
+
options.devProxyPortOffset ? ' --disable-deployment-proxy' : ''
|
|
2404
|
+
} --disable-update-deployment ${deployId} ${env} --versions ${versions}`,
|
|
2405
|
+
);
|
|
2406
|
+
} else logger.error(`Service pod ${podToMonitor} failed to start in time.`);
|
|
2407
|
+
if (options.etcHosts === true) {
|
|
2408
|
+
const hostListenResult = etcHostFactory([host]);
|
|
2409
|
+
logger.info(hostListenResult.renderHosts);
|
|
2410
|
+
}
|
|
2411
|
+
},
|
|
2412
|
+
|
|
2413
|
+
/**
|
|
2414
|
+
* @method etc-hosts
|
|
2415
|
+
* @description Generates and logs the contents for the `/etc/hosts` file based on provided hosts or deployment configurations.
|
|
2416
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated list of hosts).
|
|
2417
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2418
|
+
* @memberof UnderpostRun
|
|
2419
|
+
*/
|
|
2420
|
+
'etc-hosts': async (path = '', options = DEFAULT_OPTION) => {
|
|
2421
|
+
const hosts = path ? path.split(',') : [];
|
|
2422
|
+
if (options.deployId) {
|
|
2423
|
+
const confServer = loadConfServerJson(`./engine-private/conf/${options.deployId}/conf.server.json`);
|
|
2424
|
+
hosts.push(...Object.keys(confServer));
|
|
2425
|
+
}
|
|
2426
|
+
const hostListenResult = etcHostFactory(hosts);
|
|
2427
|
+
logger.info(hostListenResult.renderHosts);
|
|
2428
|
+
},
|
|
2429
|
+
|
|
2430
|
+
/**
|
|
2431
|
+
* @method sh
|
|
2432
|
+
* @description Enables remote control for the Kitty terminal emulator.
|
|
2433
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2434
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2435
|
+
* @memberof UnderpostRun
|
|
2436
|
+
*/
|
|
2437
|
+
sh: async (path = '', options = DEFAULT_OPTION) => {
|
|
2438
|
+
let [operator, arg0, arg1] = path.split(',');
|
|
2439
|
+
if (operator == 'copy') {
|
|
2440
|
+
shellExec(
|
|
2441
|
+
`kitty @ get-text ${arg0 === 'all' ? '--match all' : '--self'} --extent all${
|
|
2442
|
+
arg1 === 'ansi' ? ' --ansi yes' : ''
|
|
2443
|
+
} | kitty +kitten clipboard`,
|
|
2444
|
+
);
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
shellExec(`kitty -o allow_remote_control=yes`);
|
|
2448
|
+
},
|
|
2449
|
+
|
|
2450
|
+
/**
|
|
2451
|
+
* @method log
|
|
2452
|
+
* @description Searches and highlights keywords in a specified log file, optionally showing surrounding lines.
|
|
2453
|
+
* @param {string} path - The input value, identifier, or path for the operation (formatted as `filePath,keywords,lines`).
|
|
2454
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2455
|
+
* @memberof UnderpostRun
|
|
2456
|
+
*/
|
|
2457
|
+
log: async (path, options = DEFAULT_OPTION) => {
|
|
2458
|
+
const [filePath, keywords, lines] = path.split(',');
|
|
2459
|
+
let result = shellExec(`grep -i -E ${lines ? `-C ${lines} ` : ''}'${keywords}' ${filePath}`, {
|
|
2460
|
+
stdout: true,
|
|
2461
|
+
silent: true,
|
|
2462
|
+
}).replaceAll(`--`, `==============================`.green.bold);
|
|
2463
|
+
for (const keyword of keywords.split('|')) result = result.replaceAll(keyword, keyword.bgYellow.black.bold);
|
|
2464
|
+
console.log(result);
|
|
2465
|
+
},
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* @method ps
|
|
2469
|
+
* @description Displays running processes that match a specified path or keyword.
|
|
2470
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter processes).
|
|
2471
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2472
|
+
* @memberof UnderpostRun
|
|
2473
|
+
*/
|
|
2474
|
+
ps: async (path = '', options = DEFAULT_OPTION) => {
|
|
2475
|
+
const out = shellExec(
|
|
2476
|
+
path.startsWith('top-consumers')
|
|
2477
|
+
? `ps -eo pid,%cpu,%mem,rss,cmd --sort=-%cpu | head -n ${path.split(',')[1] || 15}`
|
|
2478
|
+
: path
|
|
2479
|
+
? `(ps -eo pid,%cpu,%mem,rss,cmd -ww | head -n1; ps -eo pid,%cpu,%mem,rss,cmd -ww | tail -n +2 | grep -F ${path})`
|
|
2480
|
+
: `ps -eo pid,%cpu,%mem,rss,cmd -ww`,
|
|
2481
|
+
{
|
|
2482
|
+
stdout: true,
|
|
2483
|
+
silent: true,
|
|
2484
|
+
},
|
|
2485
|
+
);
|
|
2486
|
+
|
|
2487
|
+
console.log(
|
|
2488
|
+
path ? out.replaceAll(path.split(',')[2] || path, (path.split(',')[2] || path).bgYellow.black.bold) : out,
|
|
2489
|
+
);
|
|
2490
|
+
},
|
|
2491
|
+
|
|
2492
|
+
/**
|
|
2493
|
+
* @method pid-info
|
|
2494
|
+
* @description Displays detailed information about a process by PID, including service details, command line, executable path, working directory, environment variables, and parent process tree.
|
|
2495
|
+
* @param {string} path - The PID of the process to inspect.
|
|
2496
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2497
|
+
* @memberof UnderpostRun
|
|
2498
|
+
*/
|
|
2499
|
+
'pid-info': (path, options = DEFAULT_OPTION) => {
|
|
2500
|
+
const pid = path;
|
|
2501
|
+
if (!pid) {
|
|
2502
|
+
logger.error('PID is required. Usage: underpost run pid-info <pid>');
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// Services
|
|
2507
|
+
logger.info('Process info');
|
|
2508
|
+
shellExec(`sudo ps -p ${pid} -o pid,ppid,user,stime,etime,cmd`);
|
|
2509
|
+
logger.info('Command line');
|
|
2510
|
+
shellExec(`sudo cat /proc/${pid}/cmdline | tr '\\0' ' ' ; echo`);
|
|
2511
|
+
logger.info('Executable path');
|
|
2512
|
+
shellExec(`sudo readlink -f /proc/${pid}/exe`);
|
|
2513
|
+
logger.info('Working directory');
|
|
2514
|
+
shellExec(`sudo readlink -f /proc/${pid}/cwd`);
|
|
2515
|
+
logger.info('Environment variables (first 200)');
|
|
2516
|
+
shellExec(`sudo tr '\\0' '\\n' </proc/${pid}/environ | head -200`);
|
|
2517
|
+
|
|
2518
|
+
// Parent
|
|
2519
|
+
logger.info('Parent process');
|
|
2520
|
+
const parentInfo = shellExec(`sudo ps -o pid,ppid,user,cmd -p ${pid}`, { stdout: true, silent: true });
|
|
2521
|
+
console.log(parentInfo);
|
|
2522
|
+
const ppidMatch = parentInfo.split('\n').find((l) => l.trim().startsWith(pid));
|
|
2523
|
+
if (ppidMatch) {
|
|
2524
|
+
const ppid = ppidMatch.trim().split(/\s+/)[1];
|
|
2525
|
+
logger.info(`Parent PID: ${ppid}`);
|
|
2526
|
+
shellExec(`ps -fp ${ppid}`);
|
|
2527
|
+
}
|
|
2528
|
+
logger.info('Process tree');
|
|
2529
|
+
shellExec(`pstree -s ${pid}`);
|
|
2530
|
+
},
|
|
2531
|
+
|
|
2532
|
+
/**
|
|
2533
|
+
* @method background
|
|
2534
|
+
* @description Runs a custom command in the background using nohup, logging output to `/var/log/<id>.log` and saving the PID to `/var/run/<id>.pid`.
|
|
2535
|
+
* @param {string} path - The command to run in the background (e.g. 'npm run prod:container dd-cyberia-r3').
|
|
2536
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2537
|
+
* @memberof UnderpostRun
|
|
2538
|
+
*/
|
|
2539
|
+
background: (path, options = DEFAULT_OPTION) => {
|
|
2540
|
+
if (!path) {
|
|
2541
|
+
logger.error('Command is required. Usage: underpost run background <command>');
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
const id = path.split(/\s+/).pop();
|
|
2545
|
+
const logFile = `/var/log/${id}.log`;
|
|
2546
|
+
const pidFile = `/var/run/${id}.pid`;
|
|
2547
|
+
logger.info(`Starting background process`, { id, logFile, pidFile });
|
|
2548
|
+
shellExec(`nohup ${path} > ${logFile} 2>&1 & pid=$!; echo $pid > ${pidFile}; disown`);
|
|
2549
|
+
logger.info(`Background process started for '${id}'`);
|
|
2550
|
+
},
|
|
2551
|
+
|
|
2552
|
+
/**
|
|
2553
|
+
* @method ports
|
|
2554
|
+
* @description Set on ~/.bashrc alias: ports <port> Command to list listening ports that match the given keyword.
|
|
2555
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as a keyword to filter listening ports).
|
|
2556
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2557
|
+
* @memberof UnderpostRun
|
|
2558
|
+
*/
|
|
2559
|
+
ports: async (path = '', options = DEFAULT_OPTION) => {
|
|
2560
|
+
shellExec(`chmod +x ${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
2561
|
+
shellExec(`${options.underpostRoot}/scripts/ports-ls.sh`);
|
|
2562
|
+
},
|
|
2563
|
+
|
|
2564
|
+
/**
|
|
2565
|
+
* @method deploy-test
|
|
2566
|
+
* @description Deploys a test deployment (`dd-test`) in either development or production mode, setting up necessary secrets and starting the deployment.
|
|
2567
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the deployment ID).
|
|
2568
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2569
|
+
* @memberof UnderpostRun
|
|
2570
|
+
*/
|
|
2571
|
+
'deploy-test': async (path, options = DEFAULT_OPTION) => {
|
|
2572
|
+
// Note: use recomendation empty deploy cluster: node bin --dev cluster
|
|
2573
|
+
const env = options.dev ? 'development' : 'production';
|
|
2574
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2575
|
+
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
2576
|
+
const inputs = path ? path.split(',') : [];
|
|
2577
|
+
const deployId = inputs[0] ? inputs[0] : 'dd-test';
|
|
2578
|
+
const cmd = options.cmd
|
|
2579
|
+
? options.cmd
|
|
2580
|
+
: [
|
|
2581
|
+
`npm install -g npm@11.2.0`,
|
|
2582
|
+
`npm install -g underpost`,
|
|
2583
|
+
`${baseCommand} secret underpost --create-from-env`,
|
|
2584
|
+
`${baseCommand} start --build --run ${deployId} ${env}`,
|
|
2585
|
+
];
|
|
2586
|
+
shellExec(`node bin run sync${baseClusterCommand} --deploy-id-cron-jobs none dd-test --cmd "${cmd}"`);
|
|
2587
|
+
},
|
|
2588
|
+
|
|
2589
|
+
/**
|
|
2590
|
+
* @method tf-vae-test
|
|
2591
|
+
* @description Creates and runs a job pod (`tf-vae-test`) that installs TensorFlow dependencies, clones the TensorFlow docs, and runs the CVAE tutorial script, with a terminal monitor attached.
|
|
2592
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2593
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2594
|
+
* @memberof UnderpostRun
|
|
2595
|
+
*/
|
|
2596
|
+
'tf-vae-test': async (path, options = DEFAULT_OPTION) => {
|
|
2597
|
+
const podName = 'tf-vae-test';
|
|
2598
|
+
await Underpost.run.CALL('deploy-job', '', {
|
|
2599
|
+
logs: options.logs,
|
|
2600
|
+
podName,
|
|
2601
|
+
// volumeMountPath: '/custom_images',
|
|
2602
|
+
// volumeHostPath: '/home/dd/engine/src/client/public/cyberia/assets/skin',
|
|
2603
|
+
on: {
|
|
2604
|
+
init: async () => {
|
|
2605
|
+
// const pid = getTerminalPid();
|
|
2606
|
+
// shellExec(`sudo kill -9 ${pid}`);
|
|
2607
|
+
(async () => {
|
|
2608
|
+
const nameSpace = options.namespace;
|
|
2609
|
+
const basePath = '/home/dd';
|
|
2610
|
+
const scriptPath = '/site/en/tutorials/generative/cvae.py';
|
|
2611
|
+
|
|
2612
|
+
const { close } = await (async () => {
|
|
2613
|
+
const checkAwaitPath = '/await';
|
|
2614
|
+
while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
|
|
2615
|
+
logger.info('monitor', checkAwaitPath);
|
|
2616
|
+
await timer(1000);
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
return {
|
|
2620
|
+
close: () => shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf ${checkAwaitPath}"`),
|
|
2621
|
+
};
|
|
2622
|
+
})();
|
|
2623
|
+
|
|
2624
|
+
const localScriptPath = `${basePath}/lab/src/${scriptPath.split('/').pop()}`;
|
|
2625
|
+
if (!fs.existsSync(localScriptPath)) {
|
|
2626
|
+
throw new Error(`Local override script not found: ${localScriptPath}`);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
shellExec(`sudo kubectl cp ${localScriptPath} ${nameSpace}/${podName}:${basePath}/docs${scriptPath}`);
|
|
2630
|
+
|
|
2631
|
+
close();
|
|
2632
|
+
|
|
2633
|
+
{
|
|
2634
|
+
const checkPath = `/latent_space_plot.png`;
|
|
2635
|
+
const outsPaths = [];
|
|
2636
|
+
const labDir = `${basePath}/lab`;
|
|
2637
|
+
|
|
2638
|
+
logger.info('monitor', checkPath);
|
|
2639
|
+
{
|
|
2640
|
+
const checkAwaitPath = `/home/dd/docs${checkPath}`;
|
|
2641
|
+
while (!Underpost.kubectl.existsFile({ podName, path: checkAwaitPath })) {
|
|
2642
|
+
logger.info('waiting for', checkAwaitPath);
|
|
2643
|
+
await timer(1000);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
{
|
|
2648
|
+
const toPath = `${labDir}${checkPath}`;
|
|
2649
|
+
outsPaths.push(toPath);
|
|
2650
|
+
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${checkPath} ${toPath}`);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
for (let i of range(1, 10)) {
|
|
2654
|
+
const fileName = `image_at_epoch_${setPad(i, '0', 4)}.png`;
|
|
2655
|
+
const fromPath = `/${fileName}`;
|
|
2656
|
+
const toPath = `${labDir}/${fileName}`;
|
|
2657
|
+
outsPaths.push(toPath);
|
|
2658
|
+
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${fromPath} ${toPath}`);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
shellExec(`firefox ${outsPaths.join(' ')}`);
|
|
2662
|
+
process.exit(0);
|
|
2663
|
+
}
|
|
2664
|
+
})();
|
|
2665
|
+
},
|
|
2666
|
+
},
|
|
2667
|
+
args: [
|
|
2668
|
+
`pip install --upgrade \
|
|
2669
|
+
nbconvert \
|
|
2670
|
+
tensorflow-probability==0.23.0 \
|
|
2671
|
+
imageio \
|
|
2672
|
+
git+https://github.com/tensorflow/docs \
|
|
2673
|
+
matplotlib \
|
|
2674
|
+
"numpy<1.25,>=1.21"`,
|
|
2675
|
+
'mkdir -p /home/dd',
|
|
2676
|
+
'cd /home/dd',
|
|
2677
|
+
'git clone https://github.com/tensorflow/docs.git',
|
|
2678
|
+
'cd docs',
|
|
2679
|
+
'jupyter nbconvert --to python site/en/tutorials/generative/cvae.ipynb',
|
|
2680
|
+
`echo '' > /await`,
|
|
2681
|
+
`echo '=== WAITING SCRIPT LAUNCH ==='`,
|
|
2682
|
+
`while [ -f /await ]; do sleep 1; done`,
|
|
2683
|
+
`echo '=== FINISHED ==='`,
|
|
2684
|
+
daemonProcess(`ipython site/en/tutorials/generative/cvae.py`),
|
|
2685
|
+
],
|
|
2686
|
+
});
|
|
2687
|
+
},
|
|
2688
|
+
|
|
2689
|
+
/**
|
|
2690
|
+
* @method spark-template
|
|
2691
|
+
* @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
|
|
2692
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2693
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2694
|
+
* @memberof UnderpostRun
|
|
2695
|
+
*/
|
|
2696
|
+
'spark-template': (path, options = DEFAULT_OPTION) => {
|
|
2697
|
+
const dir = '/home/dd/spark-template';
|
|
2698
|
+
shellExec(`sudo rm -rf ${dir}`);
|
|
2699
|
+
shellCd('/home/dd');
|
|
2700
|
+
|
|
2701
|
+
// pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
|
|
2702
|
+
// await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
2703
|
+
shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
|
|
2704
|
+
|
|
2705
|
+
shellCd(dir);
|
|
2706
|
+
|
|
2707
|
+
Underpost.repo.initLocalRepo({ path: dir });
|
|
2708
|
+
shellExec(`git add . && git commit -m "Base implementation"`);
|
|
2709
|
+
shellExec(`chmod +x ./replace_params.sh`);
|
|
2710
|
+
shellExec(`chmod +x ./build.sh`);
|
|
2711
|
+
|
|
2712
|
+
shellExec(`./replace_params.sh`);
|
|
2713
|
+
shellExec(`./build.sh`);
|
|
2714
|
+
|
|
2715
|
+
shellCd('/home/dd/engine');
|
|
2716
|
+
},
|
|
2717
|
+
/**
|
|
2718
|
+
* @method pull-rocky-image
|
|
2719
|
+
* @description Pulls the base `rockylinux:9` image from Docker Hub via Podman.
|
|
2720
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2721
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2722
|
+
* @memberof UnderpostRun
|
|
2723
|
+
*/
|
|
2724
|
+
'pull-rocky-image': (path, options = DEFAULT_OPTION) => {
|
|
2725
|
+
shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
|
|
2726
|
+
},
|
|
2727
|
+
/**
|
|
2728
|
+
* @method rmi
|
|
2729
|
+
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
2730
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2731
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2732
|
+
* @memberof UnderpostRun
|
|
2733
|
+
*/
|
|
2734
|
+
rmi: (path, options = DEFAULT_OPTION) => {
|
|
2735
|
+
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
2736
|
+
},
|
|
2737
|
+
/**
|
|
2738
|
+
* @method kill
|
|
2739
|
+
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
2740
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
2741
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2742
|
+
* @memberof UnderpostRun
|
|
2743
|
+
*/
|
|
2744
|
+
kill: (path = '', options = DEFAULT_OPTION) => {
|
|
2745
|
+
if (options.pid)
|
|
2746
|
+
return shellExec(`sudo kill -9 ${options.pid}`, {
|
|
2747
|
+
silentOnError: true,
|
|
2748
|
+
});
|
|
2749
|
+
for (const _path of path.split(',')) {
|
|
2750
|
+
if (_path.split('+')[1]) {
|
|
2751
|
+
let [port, sumPortOffSet] = _path.split('+');
|
|
2752
|
+
port = parseInt(port);
|
|
2753
|
+
sumPortOffSet = parseInt(sumPortOffSet);
|
|
2754
|
+
for (const sumPort of range(0, sumPortOffSet))
|
|
2755
|
+
shellExec(
|
|
2756
|
+
`PIDS=$(lsof -t -i:${parseInt(port) + parseInt(sumPort)}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`,
|
|
2757
|
+
{
|
|
2758
|
+
silentOnError: true,
|
|
2759
|
+
},
|
|
2760
|
+
);
|
|
2761
|
+
} else
|
|
2762
|
+
shellExec(`PIDS=$(lsof -t -i:${_path}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`, {
|
|
2763
|
+
silentOnError: true,
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
/**
|
|
2768
|
+
* @method generate-pass
|
|
2769
|
+
* @description Generates a cryptographically secure random password that satisfies all validatePassword
|
|
2770
|
+
* constraints (lowercase, uppercase, digit, special char, min 8 chars). Logs the plain password
|
|
2771
|
+
* to the console or, when `--copy` is set, copies it to the clipboard via pbcopy.
|
|
2772
|
+
* @param {string} path - Optional password length (default: 16).
|
|
2773
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
2774
|
+
* @param {boolean} options.copy - When true, copies to clipboard instead of logging.
|
|
2775
|
+
* @memberof UnderpostRun
|
|
2776
|
+
*/
|
|
2777
|
+
'generate-pass': (path, options = DEFAULT_OPTION) => {
|
|
2778
|
+
const length = path && parseInt(path) > 0 ? parseInt(path) : 16;
|
|
2779
|
+
const lower = 'abcdefghijklmnopqrstuvwxyz';
|
|
2780
|
+
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
2781
|
+
const digits = '0123456789';
|
|
2782
|
+
const special = '@#$%^&*()_+';
|
|
2783
|
+
const all = lower + upper + digits + special;
|
|
2784
|
+
const buf = crypto.randomBytes(length + 4);
|
|
2785
|
+
// Guarantee at least one character from each required class
|
|
2786
|
+
const chars = [
|
|
2787
|
+
lower[buf[0] % lower.length],
|
|
2788
|
+
upper[buf[1] % upper.length],
|
|
2789
|
+
digits[buf[2] % digits.length],
|
|
2790
|
+
special[buf[3] % special.length],
|
|
2791
|
+
];
|
|
2792
|
+
for (let i = 4; i < length; i++) chars.push(all[buf[i] % all.length]);
|
|
2793
|
+
// Fisher-Yates shuffle using an independent random buffer
|
|
2794
|
+
const shuf = crypto.randomBytes(length);
|
|
2795
|
+
for (let i = chars.length - 1; i > 0; i--) {
|
|
2796
|
+
const j = shuf[i % shuf.length] % (i + 1);
|
|
2797
|
+
[chars[i], chars[j]] = [chars[j], chars[i]];
|
|
2798
|
+
}
|
|
2799
|
+
const password = chars.join('');
|
|
2800
|
+
if (options.copy) pbcopy(password);
|
|
2801
|
+
else console.log(password);
|
|
2802
|
+
},
|
|
2803
|
+
/**
|
|
2804
|
+
* @method secret
|
|
2805
|
+
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
2806
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
2807
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2808
|
+
* @memberof UnderpostRun
|
|
2809
|
+
*/
|
|
2810
|
+
secret: (path, options = DEFAULT_OPTION) => {
|
|
2811
|
+
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
2812
|
+
Underpost.secret.underpost.createFromEnvFile(
|
|
2813
|
+
`/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${options.dev ? 'development' : 'production'}`,
|
|
2814
|
+
);
|
|
2815
|
+
},
|
|
2816
|
+
/**
|
|
2817
|
+
* @method underpost-config
|
|
2818
|
+
* @description Calls `Underpost.deploy.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
2819
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
2820
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2821
|
+
* @memberof UnderpostRun
|
|
2822
|
+
*/
|
|
2823
|
+
'underpost-config': (path = '', options = DEFAULT_OPTION) => {
|
|
2824
|
+
Underpost.deploy.configMap(path ? path : 'production', options.namespace);
|
|
2825
|
+
},
|
|
2826
|
+
/**
|
|
2827
|
+
* @method gpu-env
|
|
2828
|
+
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
2829
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2830
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2831
|
+
* @memberof UnderpostRun
|
|
2832
|
+
*/
|
|
2833
|
+
'gpu-env': (path, options = DEFAULT_OPTION) => {
|
|
2834
|
+
const clusterType = 'kubeadm';
|
|
2835
|
+
shellExec(
|
|
2836
|
+
`node bin cluster --dev --reset --${clusterType} && node bin cluster --dev --dedicated-gpu --${clusterType} && kubectl get pods --all-namespaces -o wide -w`,
|
|
2837
|
+
);
|
|
2838
|
+
},
|
|
2839
|
+
/**
|
|
2840
|
+
* @method tf-gpu-test
|
|
2841
|
+
* @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
|
|
2842
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2843
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2844
|
+
* @memberof UnderpostRun
|
|
2845
|
+
*/
|
|
2846
|
+
'tf-gpu-test': (path, options = DEFAULT_OPTION) => {
|
|
2847
|
+
const { underpostRoot, namespace } = options;
|
|
2848
|
+
shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
|
|
2849
|
+
shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
|
|
2850
|
+
shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
|
|
2851
|
+
},
|
|
2852
|
+
|
|
2853
|
+
/**
|
|
2854
|
+
* @method deploy-job
|
|
2855
|
+
* @description Creates and applies a custom Kubernetes Pod manifest (Job) for running arbitrary commands inside a container image (defaulting to a TensorFlow/NVIDIA image).
|
|
2856
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional script path or job argument).
|
|
2857
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
2858
|
+
* @memberof UnderpostRun
|
|
2859
|
+
*/
|
|
2860
|
+
'deploy-job': async (path, options = DEFAULT_OPTION) => {
|
|
2861
|
+
const podName = options.podName || 'deploy-job';
|
|
2862
|
+
const volumeName = `${podName}-volume`;
|
|
2863
|
+
if (typeof options.args === 'string') options.args = options.args.split(',');
|
|
2864
|
+
const args = (options.args ? options.args : path ? [path] : [`python ${path}`]).filter((c) => c.trim());
|
|
2865
|
+
const imageName = options.imageName || 'nvcr.io/nvidia/tensorflow:24.04-tf2-py3';
|
|
2866
|
+
const containerName = options.containerName || `${podName}-container`;
|
|
2867
|
+
const gpuEnable = imageName.match('nvidia');
|
|
2868
|
+
const runtimeClassName = options.runtimeClassName ? options.runtimeClassName : gpuEnable ? 'nvidia' : '';
|
|
2869
|
+
const namespace = options.namespace || 'default';
|
|
2870
|
+
const volumeMountPath = options.volumeMountPath || path;
|
|
2871
|
+
const volumeHostPath = options.volumeHostPath || path;
|
|
2872
|
+
const claimName = options.claimName || '';
|
|
2873
|
+
const enableVolumeMount = volumeMountPath && (volumeHostPath || claimName);
|
|
2874
|
+
const tty = options.tty ? 'true' : 'false';
|
|
2875
|
+
const stdin = options.stdin ? 'true' : 'false';
|
|
2876
|
+
const restartPolicy = options.restartPolicy || 'Never';
|
|
2877
|
+
const kindType = options.kindType || 'Pod';
|
|
2878
|
+
const imagePullPolicy = options.imagePullPolicy || 'IfNotPresent';
|
|
2879
|
+
const hostNetwork = options.hostNetwork ? options.hostNetwork : '';
|
|
2880
|
+
const apiVersion = options.apiVersion || 'v1';
|
|
2881
|
+
// Parse hostAliases option:
|
|
2882
|
+
// - string from CLI: "ip1=host1,host2;ip2=host3,host4"
|
|
2883
|
+
// - array from programmatic callers: [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]
|
|
2884
|
+
const hostAliases = options.hostAliases
|
|
2885
|
+
? Array.isArray(options.hostAliases)
|
|
2886
|
+
? options.hostAliases
|
|
2887
|
+
: options.hostAliases
|
|
2888
|
+
.split(';')
|
|
2889
|
+
.filter((entry) => entry.trim())
|
|
2890
|
+
.map((entry) => {
|
|
2891
|
+
const [ip, hostnamesStr] = entry.split('=');
|
|
2892
|
+
const hostnames = hostnamesStr ? hostnamesStr.split(',').map((h) => h.trim()) : [];
|
|
2893
|
+
return { ip: ip.trim(), hostnames };
|
|
2894
|
+
})
|
|
2895
|
+
: [];
|
|
2896
|
+
const hostAliasesYaml =
|
|
2897
|
+
hostAliases.length > 0
|
|
2898
|
+
? ` hostAliases:\n${hostAliases
|
|
2899
|
+
.map(
|
|
2900
|
+
(alias) =>
|
|
2901
|
+
` - ip: "${alias.ip}"\n hostnames:\n${alias.hostnames.map((h) => ` - "${h}"`).join('\n')}`,
|
|
2902
|
+
)
|
|
2903
|
+
.join('\n')}`
|
|
2904
|
+
: '';
|
|
2905
|
+
const labels = options.labels
|
|
2906
|
+
? options.labels
|
|
2907
|
+
.split(',')
|
|
2908
|
+
.map((keyValue) => {
|
|
2909
|
+
const [key, value] = keyValue.split('=');
|
|
2910
|
+
return ` ${key}: ${value}
|
|
2911
|
+
`;
|
|
2912
|
+
})
|
|
2913
|
+
.join('')
|
|
2914
|
+
: ` app: ${podName}`;
|
|
2915
|
+
if (options.volumeType === 'dev') options.volumeType = 'FileOrCreate';
|
|
2916
|
+
const volumeType =
|
|
2917
|
+
options.volumeType || (enableVolumeMount && volumeHostPath && fs.statSync(volumeHostPath).isDirectory())
|
|
2918
|
+
? 'Directory'
|
|
2919
|
+
: 'File';
|
|
2920
|
+
|
|
2921
|
+
const envs = Underpost.env.list();
|
|
2922
|
+
|
|
2923
|
+
const cmd = `kubectl apply -f - <<EOF
|
|
2924
|
+
apiVersion: ${apiVersion}
|
|
2925
|
+
kind: ${kindType}
|
|
2926
|
+
metadata:
|
|
2927
|
+
name: ${podName}
|
|
2928
|
+
namespace: ${namespace}
|
|
2929
|
+
labels:
|
|
2930
|
+
${labels}
|
|
2931
|
+
spec:
|
|
2932
|
+
restartPolicy: ${restartPolicy}
|
|
2933
|
+
${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
|
|
2934
|
+
${hostNetwork ? ` hostNetwork: ${hostNetwork}` : ''}
|
|
2935
|
+
${hostAliasesYaml}
|
|
2936
|
+
containers:
|
|
2937
|
+
- name: ${containerName}
|
|
2938
|
+
image: ${imageName}
|
|
2939
|
+
imagePullPolicy: ${imagePullPolicy}
|
|
2940
|
+
tty: ${tty}
|
|
2941
|
+
stdin: ${stdin}
|
|
2942
|
+
command: ${JSON.stringify(options.cmd ? options.cmd : ['/bin/bash', '-c'])}
|
|
2943
|
+
${
|
|
2944
|
+
args.length > 0
|
|
2945
|
+
? ` args:
|
|
2946
|
+
- |
|
|
2947
|
+
${args.map((arg) => ` ${arg}`).join('\n')}`
|
|
2948
|
+
: ''
|
|
2949
|
+
}
|
|
2950
|
+
${`${
|
|
2951
|
+
gpuEnable
|
|
2952
|
+
? ` resources:
|
|
2953
|
+
limits:
|
|
2954
|
+
nvidia.com/gpu: '1'
|
|
2955
|
+
`
|
|
2956
|
+
: ''
|
|
2957
|
+
} env:
|
|
2958
|
+
${Object.keys(envs)
|
|
2959
|
+
.map((key) => ({ key, value: typeof envs[key] === 'number' ? envs[key] : `"${envs[key]}"` }))
|
|
2960
|
+
.concat(gpuEnable ? [{ key: 'NVIDIA_VISIBLE_DEVICES', value: 'all' }] : [])
|
|
2961
|
+
.map((env) => ` - name: ${env.key}\n value: ${env.value}`)
|
|
2962
|
+
.join('\n')}`}
|
|
2963
|
+
${
|
|
2964
|
+
enableVolumeMount
|
|
2965
|
+
? Underpost.deploy.volumeFactory([{ volumeMountPath, volumeName, volumeHostPath, volumeType, claimName }]).render
|
|
2966
|
+
: ''
|
|
2967
|
+
}
|
|
2968
|
+
EOF`;
|
|
2969
|
+
shellExec(`kubectl delete pod ${podName} -n ${namespace} --ignore-not-found`);
|
|
2970
|
+
console.log(cmd);
|
|
2971
|
+
shellExec(cmd, { disableLog: true });
|
|
2972
|
+
const successInstance = await Underpost.test.statusMonitor(
|
|
2973
|
+
podName,
|
|
2974
|
+
options.monitorStatus || 'Running',
|
|
2975
|
+
options.monitorStatusKindType || 'pods',
|
|
2976
|
+
options.monitorStatusDeltaMs || 1000,
|
|
2977
|
+
options.monitorStatusMaxAttempts || 600,
|
|
2978
|
+
);
|
|
2979
|
+
if (successInstance) {
|
|
2980
|
+
options.on?.init ? await options.on.init() : null;
|
|
2981
|
+
if (options.logs) shellExec(`kubectl logs -f ${podName} -n ${namespace}`, { async: true });
|
|
2982
|
+
}
|
|
2983
|
+
},
|
|
2984
|
+
|
|
2985
|
+
/**
|
|
2986
|
+
* @method push-bundle
|
|
2987
|
+
* @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
|
|
2988
|
+
* Steps: set env, build+split zip, upload only the zip parts belonging to the deploy-id's hosts (from conf.server.json).
|
|
2989
|
+
* Only files matching `<host>-<route>.zip.part*` or `<host>-<route>.zip` for each non-skipped route are uploaded.
|
|
2990
|
+
* @param {string} path - Optional `fsPath.splitOption` string.
|
|
2991
|
+
* Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
|
|
2992
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
2993
|
+
* @param {string} [options.deployId] - Override deploy ID.
|
|
2994
|
+
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
2995
|
+
* @memberof UnderpostRun
|
|
2996
|
+
*/
|
|
2997
|
+
'push-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2998
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
2999
|
+
const env = options.dev ? 'development' : 'production';
|
|
3000
|
+
const deployId = options.deployId || 'dd-default';
|
|
3001
|
+
const pathParts = (path || '').split('.');
|
|
3002
|
+
const fsPath = (pathParts[0] || '').trim() || 'build';
|
|
3003
|
+
const splitOption = (pathParts[1] || '').trim();
|
|
3004
|
+
|
|
3005
|
+
let splitFlag = '--split 8';
|
|
3006
|
+
if (splitOption) {
|
|
3007
|
+
if (splitOption === 'none-split') {
|
|
3008
|
+
splitFlag = '';
|
|
3009
|
+
} else {
|
|
3010
|
+
const splitMb = Number(splitOption);
|
|
3011
|
+
if (Number.isFinite(splitMb) && splitMb > 0) {
|
|
3012
|
+
splitFlag = `--split ${splitMb}`;
|
|
3013
|
+
} else {
|
|
3014
|
+
logger.warn('push-bundle: invalid split option, using default split 8', {
|
|
3015
|
+
path,
|
|
3016
|
+
splitOption,
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
3023
|
+
const confServer = fs.existsSync(confServerPath)
|
|
3024
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
3025
|
+
: {};
|
|
3026
|
+
const storageFilePath = `engine-private/conf/${deployId}/storage.bundle.json`;
|
|
3027
|
+
|
|
3028
|
+
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
3029
|
+
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
3030
|
+
|
|
3031
|
+
const pushBundleFiles = (host, routePath) => {
|
|
3032
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
3033
|
+
const buildDir = `./${fsPath}`;
|
|
3034
|
+
if (!fs.existsSync(buildDir)) return;
|
|
3035
|
+
const partFiles = fs
|
|
3036
|
+
.readdirSync(buildDir)
|
|
3037
|
+
.filter(
|
|
3038
|
+
(name) =>
|
|
3039
|
+
name.startsWith(`${buildId}.zip.part`) ||
|
|
3040
|
+
name.startsWith(`${buildId}.zip-part`) ||
|
|
3041
|
+
name === `${buildId}.zip`,
|
|
3042
|
+
)
|
|
3043
|
+
.map((name) => `${fsPath}/${name}`);
|
|
3044
|
+
if (partFiles.length === 0) {
|
|
3045
|
+
logger.warn(`push-bundle: no bundle files found for '${host}${routePath}'`, { buildId });
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
for (const partFile of partFiles) {
|
|
3049
|
+
shellExec(
|
|
3050
|
+
`${baseCommand} fs ${partFile} --deploy-id ${deployId} --storage-file-path ${storageFilePath} --force`,
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
|
|
3055
|
+
for (const host of Object.keys(confServer)) {
|
|
3056
|
+
for (const routePath of Object.keys(confServer[host])) {
|
|
3057
|
+
const routeConf = confServer[host][routePath] || {};
|
|
3058
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
3059
|
+
if (routeConf.singleReplica) {
|
|
3060
|
+
if (routeConf.replicas) {
|
|
3061
|
+
for (const replica of routeConf.replicas) {
|
|
3062
|
+
pushBundleFiles(host, replica);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
continue;
|
|
3066
|
+
}
|
|
3067
|
+
pushBundleFiles(host, routePath);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
},
|
|
3071
|
+
|
|
3072
|
+
/**
|
|
3073
|
+
* @method pull-bundle
|
|
3074
|
+
* @description Downloads split zip parts from file storage, merges and extracts them, and moves the result into the public directory.
|
|
3075
|
+
* Steps: set env, download parts (omit-unzip), merge zip, unzip, remove zip + parts, move to public/<host>[/path].
|
|
3076
|
+
* Iterates over every non-singleReplica, non-redirect, non-disabledRebuild route in conf.server.json
|
|
3077
|
+
* so that multi-path deployments are handled correctly.
|
|
3078
|
+
* @param {string} path - Optional comma-separated host name(s) to restrict processing (e.g. 'underpost.net' or 'a.com,b.com').
|
|
3079
|
+
* If omitted, all hosts from `engine-private/conf/<deployId>/conf.server.json` are used.
|
|
3080
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
3081
|
+
* @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
|
|
3082
|
+
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
3083
|
+
* @memberof UnderpostRun
|
|
3084
|
+
*/
|
|
3085
|
+
'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
3086
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
3087
|
+
const env = options.dev ? 'development' : 'production';
|
|
3088
|
+
const deployId = options.deployId || 'dd-default';
|
|
3089
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
3090
|
+
const confServer = fs.existsSync(confServerPath)
|
|
3091
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
3092
|
+
: {};
|
|
3093
|
+
const hostsArg = path
|
|
3094
|
+
? path
|
|
3095
|
+
.split(',')
|
|
3096
|
+
.map((h) => h.trim())
|
|
3097
|
+
.filter(Boolean)
|
|
3098
|
+
: Object.keys(confServer);
|
|
3099
|
+
|
|
3100
|
+
if (hostsArg.length === 0) {
|
|
3101
|
+
logger.error('pull-bundle: no hosts resolved', { deployId, path, confServerPath });
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
3106
|
+
if (!fs.existsSync('./build')) fs.mkdirSync('./build', { recursive: true });
|
|
3107
|
+
shellExec(
|
|
3108
|
+
`${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
|
|
3109
|
+
);
|
|
3110
|
+
|
|
3111
|
+
const pullBundleRoute = (host, routePath) => {
|
|
3112
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
3113
|
+
const zipPath = `build/${buildId}.zip`;
|
|
3114
|
+
const buildDir = './build';
|
|
3115
|
+
const hasZip = fs.existsSync(zipPath);
|
|
3116
|
+
const hasParts =
|
|
3117
|
+
fs.existsSync(buildDir) &&
|
|
3118
|
+
fs
|
|
3119
|
+
.readdirSync(buildDir)
|
|
3120
|
+
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
3121
|
+
|
|
3122
|
+
if (!hasZip && !hasParts) {
|
|
3123
|
+
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
3128
|
+
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
3129
|
+
shellExec(`sudo rm -rf ${zipPath}`);
|
|
3130
|
+
|
|
3131
|
+
if (fs.existsSync(buildDir)) {
|
|
3132
|
+
fs.readdirSync(buildDir)
|
|
3133
|
+
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
3134
|
+
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
3138
|
+
if (!fs.existsSync(extractedDir)) {
|
|
3139
|
+
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
3144
|
+
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
3145
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
3146
|
+
fs.copySync(`${extractedDir}`, `${publicDestPath}`);
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
for (const host of hostsArg) {
|
|
3150
|
+
const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
|
|
3151
|
+
|
|
3152
|
+
for (const routePath of routePaths) {
|
|
3153
|
+
const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
|
|
3154
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
3155
|
+
if (routeConf.singleReplica) {
|
|
3156
|
+
if (routeConf.replicas) {
|
|
3157
|
+
for (const replica of routeConf.replicas) {
|
|
3158
|
+
pullBundleRoute(host, replica);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
continue;
|
|
3162
|
+
}
|
|
3163
|
+
pullBundleRoute(host, routePath);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
},
|
|
3167
|
+
|
|
3168
|
+
/**
|
|
3169
|
+
* @method build-cluster-deployment-manifests
|
|
3170
|
+
* @description Builds deployment manifests for both production and development environments using `node bin deploy --build-manifest`, syncing them, and setting replicas to 1 for the `dd` deployment.
|
|
3171
|
+
* @param {string} path - Unused.
|
|
3172
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
3173
|
+
* @memberof UnderpostRun
|
|
3174
|
+
*/
|
|
3175
|
+
'build-cluster-deployment-manifests': (path = '', options = DEFAULT_OPTION) => {
|
|
3176
|
+
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd development`);
|
|
3177
|
+
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd production --cert`);
|
|
3178
|
+
},
|
|
3179
|
+
|
|
3180
|
+
/**
|
|
3181
|
+
* @method monitor-ui
|
|
3182
|
+
* @description Installs and enables the Cockpit KVM Dashboard (cockpit, cockpit-machines, libvirt)
|
|
3183
|
+
* and opens the cockpit firewall service. With `--remove`, closes the firewall service instead.
|
|
3184
|
+
* @param {string} path - Unused.
|
|
3185
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
3186
|
+
* `options.remove` — when true, removes the cockpit firewall rule instead of adding it.
|
|
3187
|
+
* @memberof UnderpostRun
|
|
3188
|
+
*/
|
|
3189
|
+
'monitor-ui': (path, options = DEFAULT_OPTION) => {
|
|
3190
|
+
if (options.remove) {
|
|
3191
|
+
shellExec(`sudo firewall-cmd --zone=public --remove-service=cockpit --permanent`);
|
|
3192
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
shellExec(`sudo dnf install -y cockpit cockpit-machines libvirt`);
|
|
3196
|
+
shellExec(`sudo systemctl enable --now cockpit.socket libvirtd`);
|
|
3197
|
+
shellExec(`sudo firewall-cmd --permanent --add-service=cockpit`);
|
|
3198
|
+
shellExec(`sudo firewall-cmd --reload`);
|
|
3199
|
+
},
|
|
3200
|
+
|
|
3201
|
+
/**
|
|
3202
|
+
* @method shared-dir
|
|
3203
|
+
* @description Run once for initial shared-directory setup. Creates the group, adds the user,
|
|
3204
|
+
* creates the directory, sets ownership, applies the SGID bit, and configures default ACLs so
|
|
3205
|
+
* all future files inside the directory automatically inherit group write permissions.
|
|
3206
|
+
* Use `reload-shared-dir` for subsequent permission repairs without recreating the group.
|
|
3207
|
+
* @param {string} path - Target directory to set up (defaults to `/home/dd/engine`).
|
|
3208
|
+
* Customise via the `path` argument or leave empty to use the default.
|
|
3209
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow.
|
|
3210
|
+
* Key fields: `options.user` (default `'admin'`), `options.group` (default `'engine-dev'`).
|
|
3211
|
+
* @memberof UnderpostRun
|
|
3212
|
+
*/
|
|
3213
|
+
'shared-dir': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
|
|
3214
|
+
const dir = path || '/home/dd/engine';
|
|
3215
|
+
const user = options.user || 'admin';
|
|
3216
|
+
const group = options.group || 'engine-dev';
|
|
3217
|
+
|
|
3218
|
+
logger.info(`[setup-shared-dir] dir=${dir} user=${user} group=${group}`);
|
|
3219
|
+
|
|
3220
|
+
shellExec(`sudo groupadd ${group} 2>/dev/null || true`);
|
|
3221
|
+
shellExec(`sudo usermod -aG ${group} ${user}`);
|
|
3222
|
+
shellExec(`sudo mkdir -p ${dir}`);
|
|
3223
|
+
shellExec(`sudo chown -R ${user}:${group} ${dir}`);
|
|
3224
|
+
shellExec(`sudo chmod -R 2775 ${dir}`);
|
|
3225
|
+
shellExec(`sudo setfacl -d -m g:${group}:rwx ${dir}`);
|
|
3226
|
+
shellExec(`sudo setfacl -m g:${group}:rwx ${dir}`);
|
|
3227
|
+
|
|
3228
|
+
logger.info(`[setup-shared-dir] Shared directory setup complete: ${dir}`);
|
|
3229
|
+
},
|
|
3230
|
+
/**
|
|
3231
|
+
* @method shared-dir-add-user
|
|
3232
|
+
* @description Add a user to an existing shared directory without changing
|
|
3233
|
+
* file owners. Grants recursive group/ACL access so the user can read and
|
|
3234
|
+
* write throughout the shared workspace while preserving existing ownership.
|
|
3235
|
+
*
|
|
3236
|
+
* @param {string} path - Shared directory (defaults to `/home/dd/engine`).
|
|
3237
|
+
* @param {UnderpostRunDefaultOptions} options - Underpost runner options.
|
|
3238
|
+
* Key fields:
|
|
3239
|
+
* - options.user (default: 'admin')
|
|
3240
|
+
* - options.group (default: 'engine-dev')
|
|
3241
|
+
*
|
|
3242
|
+
* @memberof UnderpostRun
|
|
3243
|
+
*/ 'shared-dir-add-user': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
|
|
3244
|
+
const dir = path || '/home/dd/engine';
|
|
3245
|
+
const user = options.user || 'admin';
|
|
3246
|
+
|
|
3247
|
+
logger.info(`[shared-dir-add-user] dir=${dir} user=${user}`);
|
|
3248
|
+
|
|
3249
|
+
// Give the user direct access without changing owners or group ownership.
|
|
3250
|
+
shellExec(`sudo setfacl -R -m u:${user}:rwx ${dir}`);
|
|
3251
|
+
|
|
3252
|
+
// Make future files/directories inherit the same user ACL.
|
|
3253
|
+
shellExec(`sudo find ${dir} -type d -exec setfacl -d -m u:${user}:rwx {} \\;`);
|
|
3254
|
+
|
|
3255
|
+
logger.info(`[shared-dir-add-user] User '${user}' added to shared directory: ${dir}`);
|
|
3256
|
+
},
|
|
3257
|
+
};
|
|
3258
|
+
|
|
3259
|
+
static API = {
|
|
3260
|
+
/**
|
|
3261
|
+
* @method DEFAULT_OPTION
|
|
3262
|
+
* @description The default options for Underpost runners, including development mode, namespace, replicas, and underpost root path.
|
|
3263
|
+
* @memberof UnderpostRun
|
|
3264
|
+
* @static
|
|
3265
|
+
* @returns {Object} The default options object.
|
|
3266
|
+
*/
|
|
3267
|
+
get DEFAULT_OPTION() {
|
|
3268
|
+
return DEFAULT_OPTION;
|
|
3269
|
+
},
|
|
3270
|
+
/**
|
|
3271
|
+
* @method RUNNERS
|
|
3272
|
+
* @description Retrieves the list of available runner IDs from the UnderpostRun class.
|
|
3273
|
+
* @memberof UnderpostRun
|
|
3274
|
+
* @returns {string[]} An array of runner IDs.
|
|
3275
|
+
*/
|
|
3276
|
+
get RUNNERS() {
|
|
3277
|
+
return Object.keys(UnderpostRun.RUNNERS);
|
|
3278
|
+
},
|
|
3279
|
+
|
|
3280
|
+
/**
|
|
3281
|
+
* @method CALL
|
|
3282
|
+
* @description Executes a specified runner function from the UnderpostRun class with the provided path and options.
|
|
3283
|
+
* @param {string} runner - The name of the runner to execute.
|
|
3284
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
3285
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
3286
|
+
* @memberof UnderpostRun
|
|
3287
|
+
* @returns {Promise<any>} The result of the runner execution.
|
|
3288
|
+
*/
|
|
3289
|
+
async CALL(runner = '', path = '', options = DEFAULT_OPTION) {
|
|
3290
|
+
return await UnderpostRun.RUNNERS[runner](path, options);
|
|
3291
|
+
},
|
|
3292
|
+
|
|
3293
|
+
/**
|
|
3294
|
+
* @method callback
|
|
3295
|
+
* @description Initiates the execution of a specified CLI command (runner) with the given input value (`path`) and processed options.
|
|
3296
|
+
* @param {string} runner - The name of the runner to execute.
|
|
3297
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
3298
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options for customizing workflow
|
|
3299
|
+
* @memberof UnderpostRun
|
|
3300
|
+
* @returns {Promise<any>} The result of the callback execution.
|
|
3301
|
+
*/
|
|
3302
|
+
async callback(runner, path, options = DEFAULT_OPTION) {
|
|
3303
|
+
try {
|
|
3304
|
+
const npmRoot = getNpmRootPath();
|
|
3305
|
+
const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
|
|
3306
|
+
if (options.cmd) options.cmd = options.cmd.split(',');
|
|
3307
|
+
if (options.args) options.args = options.args.split(',');
|
|
3308
|
+
if (!options.underpostRoot) options.underpostRoot = underpostRoot;
|
|
3309
|
+
if (!options.namespace) options.namespace = 'default';
|
|
3310
|
+
if (options.replicas === '' || options.replicas === null || options.replicas === undefined)
|
|
3311
|
+
options.replicas = 1;
|
|
3312
|
+
options.npmRoot = npmRoot;
|
|
3313
|
+
logger.info(`Executing runner`, { runner, namespace: options.namespace });
|
|
3314
|
+
if (!Underpost.run.RUNNERS.includes(runner)) throw new Error(`Runner not found: ${runner}`);
|
|
3315
|
+
const result = await Underpost.run.CALL(runner, path, options);
|
|
3316
|
+
return result;
|
|
3317
|
+
} catch (error) {
|
|
3318
|
+
console.log(error);
|
|
3319
|
+
logger.error(error);
|
|
3320
|
+
process.exit(1);
|
|
3321
|
+
}
|
|
3322
|
+
},
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
export default UnderpostRun;
|