cyberia 3.2.22 → 3.2.70
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/.env.example +127 -68
- 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 +49 -0
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
- package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
- package/.github/workflows/engine-cyberia.cd.yml +33 -24
- package/.github/workflows/engine-cyberia.ci.yml +13 -3
- package/.github/workflows/ghpkg.ci.yml +88 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/hardhat.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +10 -7
- package/.github/workflows/publish.ci.yml +2 -2
- package/.github/workflows/publish.cyberia.ci.yml +5 -16
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/CHANGELOG.md +301 -1
- package/CLI-HELP.md +71 -6
- package/Dockerfile +141 -43
- package/Dockerfile.dev +143 -0
- package/Dockerfile.test +165 -0
- package/README.md +1 -1
- package/baremetal/commission-workflows.json +1 -0
- package/bin/build.js +31 -0
- package/bin/cyberia.js +1080 -184
- package/bin/deploy.js +2 -2
- package/bin/index.js +1080 -184
- package/bump.config.js +1 -0
- package/compose.env +131 -0
- package/conf.js +18 -1
- package/deployment.yaml +2 -2
- package/docker-compose.yml +316 -0
- package/hardhat/hardhat.config.js +2 -2
- package/hardhat/package-lock.json +620 -2041
- package/hardhat/package.json +7 -5
- package/hardhat/scripts/deployObjectLayerToken.js +18 -18
- package/hardhat/test/ObjectLayerToken.js +378 -274
- package/ipfs/configure-ipfs.sh +13 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/mongodb/entrypoint.sh +76 -0
- package/nginx.conf +87 -0
- package/package.json +31 -19
- package/pv-pvc.yaml +32 -0
- package/scripts/disk-clean.sh +85 -60
- package/scripts/kubeadm-node-setup.sh +317 -0
- package/scripts/rocky-kickstart.sh +877 -185
- package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
- package/scripts/test-monitor.sh +3 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
- package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
- package/src/api/cyberia-action/cyberia-action.model.js +21 -29
- package/src/api/cyberia-action/cyberia-action.router.js +42 -7
- package/src/api/cyberia-action/cyberia-action.service.js +20 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
- package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -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 +63 -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 +62 -10
- package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
- package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
- package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
- package/src/api/cyberia-map/cyberia-map.router.js +21 -6
- package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
- package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
- package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
- package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
- package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
- package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -129
- package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
- package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
- package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
- package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
- package/src/api/ipfs/ipfs.service.js +28 -15
- package/src/api/object-layer/object-layer.router.js +25 -15
- package/src/api/object-layer/object-layer.service.js +15 -20
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
- package/src/cli/baremetal.js +717 -16
- package/src/cli/cluster.js +80 -5
- package/src/cli/deploy.js +127 -11
- package/src/cli/docker-compose.js +648 -0
- package/src/cli/env.js +11 -2
- package/src/cli/fs.js +75 -30
- package/src/cli/image.js +129 -51
- package/src/cli/index.js +110 -6
- package/src/cli/kickstart.js +142 -20
- package/src/cli/release.js +46 -4
- package/src/cli/repository.js +238 -33
- package/src/cli/run.js +520 -114
- package/src/cli/secrets.js +82 -48
- package/src/cli/ssh.js +234 -0
- package/src/cli/static.js +2 -2
- 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 +219 -13
- package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +45 -27
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
- package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +8 -1
- package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +5 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- 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 +2 -2
- package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
- 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/object-layer/object-layer.management.js +6 -6
- package/src/{server → client-builder}/client-build-docs.js +15 -5
- package/src/{server → client-builder}/client-build-live.js +3 -3
- package/src/{server → client-builder}/client-build.js +25 -22
- package/src/{server → client-builder}/client-dev-server.js +3 -3
- package/src/{server → client-builder}/client-icons.js +2 -2
- package/src/{server → client-builder}/ssr.js +5 -5
- package/src/client.build.js +1 -3
- package/src/client.dev.js +1 -1
- package/src/db/mongo/MongoBootstrap.js +12 -12
- package/src/grpc/cyberia/grpc-server.js +255 -70
- package/src/index.js +12 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
- package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +81 -0
- package/src/projects/cyberia/gemini-client.js +175 -0
- package/src/projects/cyberia/generate-saga.js +2107 -0
- package/src/{server → projects/cyberia}/ipfs-client.js +2 -2
- package/src/{server → projects/cyberia}/object-layer.js +12 -108
- package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
- package/src/{server → projects/cyberia}/shape-generator.js +2 -2
- package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
- package/src/runtime/cyberia-client/Dockerfile +27 -61
- package/src/runtime/cyberia-client/Dockerfile.dev +20 -12
- package/src/runtime/cyberia-server/Dockerfile +21 -20
- package/src/runtime/cyberia-server/Dockerfile.dev +17 -8
- package/src/runtime/engine-cyberia/Dockerfile +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
- package/src/runtime/engine-cyberia/compose.env +131 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +316 -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 +87 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/server/catalog.js +9 -14
- package/src/server/conf.js +3 -6
- package/src/server/runtime-status.js +18 -1
- package/src/server/start.js +12 -2
- package/src/server.js +6 -2
- package/test/cyberia-instance-conf-defaults.test.js +140 -0
- package/test/deploy-monitor.test.js +26 -10
- package/test/shape-generator.test.js +7 -1
- package/typedoc.dd-cyberia.json +3 -1
- package/typedoc.json +3 -1
- /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
- /package/src/{server → client-builder}/client-formatted.js +0 -0
package/src/cli/cluster.js
CHANGED
|
@@ -691,7 +691,9 @@ EOF
|
|
|
691
691
|
// shellExec(
|
|
692
692
|
// `sudo sed -i '/SystemdCgroup = true/a selinux_disabled = true' /etc/containerd/config.toml`,
|
|
693
693
|
// );
|
|
694
|
-
|
|
694
|
+
// Restart docker after containerd config changes. Rocky 9 uses systemctl,
|
|
695
|
+
// not the legacy service command.
|
|
696
|
+
shellExec(`sudo systemctl restart docker || sudo service docker restart || true`);
|
|
695
697
|
shellExec(`sudo systemctl enable --now containerd.service`);
|
|
696
698
|
shellExec(`sudo systemctl restart containerd`); // Restart containerd to apply changes
|
|
697
699
|
|
|
@@ -874,6 +876,74 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
|
|
|
874
876
|
}
|
|
875
877
|
},
|
|
876
878
|
|
|
879
|
+
/**
|
|
880
|
+
* @method _pruneContainerCaches
|
|
881
|
+
* @description Reclaims container-runtime disk left behind after a cluster
|
|
882
|
+
* teardown: stopped containers, unused images, build cache and anonymous
|
|
883
|
+
* volumes. Best-effort across every runtime present on the host (docker,
|
|
884
|
+
* podman, and optionally the CRI runtime via crictl). Each block is guarded
|
|
885
|
+
* by a command-existence check, so it is safe to call when a runtime is
|
|
886
|
+
* absent. This is what turns a "cluster deleted" into "disk actually freed":
|
|
887
|
+
* `kind delete` / `kubeadm reset` remove the cluster but leave gigabytes of
|
|
888
|
+
* images and overlay layers under /var/lib/{docker,containers}.
|
|
889
|
+
* @param {object} [options]
|
|
890
|
+
* @param {boolean} [options.all=true] - Remove all unused images, not just dangling ones.
|
|
891
|
+
* @param {boolean} [options.crictl=false] - Also prune the CRI runtime via crictl.
|
|
892
|
+
* @param {string} [options.criSocket] - Optional crictl --runtime-endpoint override.
|
|
893
|
+
* @private
|
|
894
|
+
*/
|
|
895
|
+
_pruneContainerCaches(options = {}) {
|
|
896
|
+
const all = options.all !== false;
|
|
897
|
+
const a = all ? '-a ' : '';
|
|
898
|
+
logger.info(` -> Pruning container-runtime caches (all=${all})...`);
|
|
899
|
+
// Docker (also matches the podman-docker shim when docker is symlinked to podman).
|
|
900
|
+
shellExec(
|
|
901
|
+
`if command -v docker >/dev/null 2>&1; then sudo docker system prune ${a}--volumes -f; sudo docker builder prune ${a}-f; fi`,
|
|
902
|
+
{ silentOnError: true },
|
|
903
|
+
);
|
|
904
|
+
// Podman native — on this host images/overlays live under /var/lib/containers/storage.
|
|
905
|
+
shellExec(
|
|
906
|
+
`if command -v podman >/dev/null 2>&1; then sudo podman system prune ${a}--volumes -f; fi`,
|
|
907
|
+
{ silentOnError: true },
|
|
908
|
+
);
|
|
909
|
+
if (options.crictl) {
|
|
910
|
+
const ep = options.criSocket ? `--runtime-endpoint ${options.criSocket} ` : '';
|
|
911
|
+
shellExec(
|
|
912
|
+
`if command -v crictl >/dev/null 2>&1; then sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl ${ep}rmi --prune; fi`,
|
|
913
|
+
{ silentOnError: true },
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
Underpost.cluster._unmountOrphanContainerOverlays();
|
|
917
|
+
},
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* @method _unmountOrphanContainerOverlays
|
|
921
|
+
* @description Unmounts leaked container overlay 'merged' mounts under
|
|
922
|
+
* /var/lib/{containers/storage,docker}/overlay. Pruning images/containers
|
|
923
|
+
* frees the backing data but leaves these mountpoints attached, so they
|
|
924
|
+
* keep showing up as identical `overlay ... /merged` rows in `df -h` long
|
|
925
|
+
* after the containers are gone. Only overlays NOT backing a still-existing
|
|
926
|
+
* container are unmounted, so running workloads are never disturbed.
|
|
927
|
+
* Best-effort; safe to call when no runtime is present.
|
|
928
|
+
* @private
|
|
929
|
+
*/
|
|
930
|
+
_unmountOrphanContainerOverlays() {
|
|
931
|
+
logger.info(' -> Unmounting orphaned container overlay mounts...');
|
|
932
|
+
shellExec(`if command -v podman >/dev/null 2>&1; then sudo podman umount --all >/dev/null 2>&1 || true; fi`, {
|
|
933
|
+
silentOnError: true,
|
|
934
|
+
});
|
|
935
|
+
shellExec(
|
|
936
|
+
`active="$(sudo podman ps -aq 2>/dev/null | xargs -r -I{} sudo podman inspect --format '{{.GraphDriver.Data.MergedDir}}' {} 2>/dev/null || true)"
|
|
937
|
+
findmnt -rn -o TARGET 2>/dev/null | grep -E '/var/lib/(containers/storage|docker)/overlay.*/merged' | sort -r | while IFS= read -r m; do
|
|
938
|
+
if ! printf '%s\\n' "$active" | grep -qxF "$m"; then
|
|
939
|
+
echo "Unmounting orphaned overlay: $m"
|
|
940
|
+
sudo umount -l "$m" 2>/dev/null || true
|
|
941
|
+
fi
|
|
942
|
+
done`,
|
|
943
|
+
{ silentOnError: true },
|
|
944
|
+
);
|
|
945
|
+
},
|
|
946
|
+
|
|
877
947
|
/**
|
|
878
948
|
* @method _lazyUmountKubeletMounts
|
|
879
949
|
* @description Lazy-unmounts every mount under /var/lib/kubelet so a
|
|
@@ -908,7 +978,7 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
|
|
|
908
978
|
if (options.removeVolumeHostPaths) Underpost.cluster._cleanHostPathPvs();
|
|
909
979
|
else logger.info(' -> Skipping (pass --remove-volume-host-paths to enable).');
|
|
910
980
|
|
|
911
|
-
logger.info('Phase 3/
|
|
981
|
+
logger.info('Phase 3/6: Deleting all Kind clusters...');
|
|
912
982
|
shellExec(`clusters=$(kind get clusters)
|
|
913
983
|
if [ -n "$clusters" ]; then
|
|
914
984
|
for c in $clusters; do
|
|
@@ -917,11 +987,14 @@ if [ -n "$clusters" ]; then
|
|
|
917
987
|
done
|
|
918
988
|
fi`);
|
|
919
989
|
|
|
920
|
-
logger.info('Phase 4/
|
|
990
|
+
logger.info('Phase 4/6: Cleaning kubeconfig and Kind Docker networks...');
|
|
921
991
|
shellExec(`rm -rf "$HOME/.kube"`);
|
|
922
992
|
Underpost.cluster.recoverKindDockerNetworks();
|
|
923
993
|
|
|
924
|
-
logger.info('Phase 5/
|
|
994
|
+
logger.info('Phase 5/6: Pruning container-runtime caches (kindest/node images, build cache, volumes)...');
|
|
995
|
+
Underpost.cluster._pruneContainerCaches({ all: true });
|
|
996
|
+
|
|
997
|
+
logger.info('Phase 6/6: Re-applying host configuration (Docker, containerd, sysctl).');
|
|
925
998
|
Underpost.cluster.config();
|
|
926
999
|
|
|
927
1000
|
logger.info('=== KIND SAFE RESET COMPLETE ===');
|
|
@@ -993,7 +1066,7 @@ fi`);
|
|
|
993
1066
|
shellExec(`if ip link show tunl0 >/dev/null 2>&1; then sudo ip link del tunl0; fi`);
|
|
994
1067
|
shellExec(`sudo iptables -F`);
|
|
995
1068
|
shellExec(`sudo iptables -t nat -F`);
|
|
996
|
-
|
|
1069
|
+
Underpost.cluster._pruneContainerCaches({ all: true, crictl: true });
|
|
997
1070
|
|
|
998
1071
|
logger.info('Phase 7/7: Re-applying host configuration (Docker, containerd, sysctl).');
|
|
999
1072
|
Underpost.cluster.config();
|
|
@@ -1040,6 +1113,8 @@ fi`);
|
|
|
1040
1113
|
shellExec(`if [ -d /etc/rancher/k3s ]; then sudo rm -rf /etc/rancher/k3s; fi`);
|
|
1041
1114
|
shellExec(`if ip link show flannel.1 >/dev/null 2>&1; then sudo ip link del flannel.1; fi`);
|
|
1042
1115
|
shellExec(`if ip link show cni0 >/dev/null 2>&1; then sudo ip link del cni0; fi`);
|
|
1116
|
+
// k3s-uninstall.sh removes /var/lib/rancher/k3s; still prune any host docker/podman leftovers.
|
|
1117
|
+
Underpost.cluster._pruneContainerCaches({ all: true });
|
|
1043
1118
|
|
|
1044
1119
|
logger.info('Phase 5/5: Re-applying minimal K3s host config.');
|
|
1045
1120
|
Underpost.cluster.configMinimalK3s();
|
package/src/cli/deploy.js
CHANGED
|
@@ -383,6 +383,10 @@ spec:
|
|
|
383
383
|
* @param {string} [options.imagePullPolicy] - Container imagePullPolicy override (`Always`, `IfNotPresent`, `Never`); forwarded to deploymentYamlPartsFactory. Defaults to `Never` for `localhost/` images and `IfNotPresent` otherwise.
|
|
384
384
|
* @param {boolean} [options.disableRuntimeProbes] - Omit internal-status HTTP probes from generated manifests. When true no readiness/liveness/startup probes are emitted.
|
|
385
385
|
* @param {boolean} [options.tcpProbes] - Emit legacy TCP socket probes instead of HTTP internal-status probes (migration path).
|
|
386
|
+
* @param {string} [options.node] - Explicit target node for hostPath PV nodeAffinity pinning; resolved through {@link UnderpostDeploy.resolveDeployNode} together with the cluster flags.
|
|
387
|
+
* @param {boolean} [options.kind] - Kind cluster context; affects the cluster-type node default when no explicit node is set.
|
|
388
|
+
* @param {boolean} [options.kubeadm] - Kubeadm cluster context; affects the cluster-type node default when no explicit node is set.
|
|
389
|
+
* @param {boolean} [options.k3s] - K3s cluster context; affects the cluster-type node default when no explicit node is set.
|
|
386
390
|
* @returns {Promise<void>} - Promise that resolves when the manifest is built.
|
|
387
391
|
* @memberof UnderpostDeploy
|
|
388
392
|
*/
|
|
@@ -455,6 +459,15 @@ ${Underpost.deploy
|
|
|
455
459
|
? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
|
|
456
460
|
: [];
|
|
457
461
|
if (confVolume.length > 0) {
|
|
462
|
+
// Mirror deployVolume's data-node resolution so the generated manifest
|
|
463
|
+
// pins the PV to the same node that physically receives the volume data.
|
|
464
|
+
const pvDataNode = Underpost.deploy.resolveDeployNode({
|
|
465
|
+
node: options.node,
|
|
466
|
+
kind: options.kind,
|
|
467
|
+
kubeadm: options.kubeadm,
|
|
468
|
+
k3s: options.k3s,
|
|
469
|
+
env,
|
|
470
|
+
});
|
|
458
471
|
let volumeYaml = '';
|
|
459
472
|
for (const deploymentVersion of deploymentVersions) {
|
|
460
473
|
for (const volume of confVolume) {
|
|
@@ -466,6 +479,7 @@ ${Underpost.deploy
|
|
|
466
479
|
pvcId,
|
|
467
480
|
namespace: options.namespace,
|
|
468
481
|
hostPath,
|
|
482
|
+
nodeName: pvDataNode,
|
|
469
483
|
})}\n`;
|
|
470
484
|
}
|
|
471
485
|
}
|
|
@@ -548,7 +562,7 @@ ${Underpost.deploy
|
|
|
548
562
|
const yamlPath = `./engine-private/conf/${deployId}/build/${env}/secret.yaml`;
|
|
549
563
|
fs.writeFileSync(yamlPath, secretYaml, 'utf8');
|
|
550
564
|
} else {
|
|
551
|
-
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml', 'pv-pvc.yaml'];
|
|
565
|
+
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml', 'pv-pvc.yaml', 'grpc-service.yaml'];
|
|
552
566
|
for (const file of deploymentsFiles) {
|
|
553
567
|
if (fs.existsSync(`./engine-private/conf/${deployId}/build/${env}/${file}`)) {
|
|
554
568
|
fs.copyFileSync(
|
|
@@ -720,7 +734,8 @@ spec:
|
|
|
720
734
|
* @param {string} options.image - Docker image for the deployment.
|
|
721
735
|
* @param {string} options.traffic - Traffic status for the deployment.
|
|
722
736
|
* @param {string} options.replicas - Number of replicas for the deployment.
|
|
723
|
-
* @param {string} options.node -
|
|
737
|
+
* @param {string} options.node - Explicit target node (highest precedence in the node chain). When empty, {@link UnderpostDeploy.resolveDeployNode} falls back to the cluster-type default (`kind-worker` for kind, host for kubeadm/k3s). Used for both volume placement and hostPath PV nodeAffinity.
|
|
738
|
+
* @param {string} [options.sshKeyPath] - Private key path for node SSH operations, forwarded to deployVolume when shipping a hostPath volume to a remote target node over SSH. Defaults to engine-private/deploy/id_rsa.
|
|
724
739
|
* @param {boolean} options.disableUpdateDeployment - Whether to disable deployment updates.
|
|
725
740
|
* @param {boolean} options.disableUpdateProxy - Whether to disable proxy updates.
|
|
726
741
|
* @param {boolean} options.disableDeploymentProxy - Whether to disable deployment proxy.
|
|
@@ -938,9 +953,16 @@ EOF`);
|
|
|
938
953
|
env,
|
|
939
954
|
version,
|
|
940
955
|
namespace,
|
|
941
|
-
nodeName:
|
|
956
|
+
nodeName: Underpost.deploy.resolveDeployNode({
|
|
957
|
+
node: options.node,
|
|
958
|
+
kind: options.kind,
|
|
959
|
+
kubeadm: options.kubeadm,
|
|
960
|
+
k3s: options.k3s,
|
|
961
|
+
env,
|
|
962
|
+
}),
|
|
942
963
|
clusterContext: options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind',
|
|
943
964
|
gitClean: options.gitClean || false,
|
|
965
|
+
sshKeyPath: options.sshKeyPath || '',
|
|
944
966
|
});
|
|
945
967
|
}
|
|
946
968
|
|
|
@@ -969,8 +991,12 @@ EOF`);
|
|
|
969
991
|
if (
|
|
970
992
|
Underpost.deploy.isValidTLSContext({ host: Object.keys(confServer)[0], env, options }) &&
|
|
971
993
|
!options.selfSigned
|
|
972
|
-
)
|
|
973
|
-
|
|
994
|
+
) {
|
|
995
|
+
const secretPath = `./${manifestsPath}/secret.yaml`;
|
|
996
|
+
if (fs.existsSync(secretPath) && fs.readFileSync(secretPath, 'utf8').trim()) {
|
|
997
|
+
shellExec(`sudo kubectl apply -f ${secretPath} -n ${namespace}`);
|
|
998
|
+
} else logger.info('Skipping secret.yaml apply (no objects yet; applied by the --cert step)');
|
|
999
|
+
}
|
|
974
1000
|
}
|
|
975
1001
|
}
|
|
976
1002
|
},
|
|
@@ -983,10 +1009,17 @@ EOF`);
|
|
|
983
1009
|
*/
|
|
984
1010
|
configMap(env, namespace = 'default') {
|
|
985
1011
|
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
1012
|
+
const envFilePath = `/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${env}`;
|
|
1013
|
+
// `--from-env-file` turns every KEY=VALUE into a secret key that the Deployment injects via
|
|
1014
|
+
// `envFrom`. Strip shell/runtime-critical keys (notably PATH) first — an injected PATH
|
|
1015
|
+
// overrides the image's own and breaks coreutils/sudo resolution inside the pod.
|
|
1016
|
+
const sanitizedEnvPath = `${envFilePath}.secret`;
|
|
1017
|
+
fs.writeFileSync(sanitizedEnvPath, Underpost.secret.sanitizeSecretEnvFile(fs.readFileSync(envFilePath, 'utf8')));
|
|
986
1018
|
shellExec(`kubectl delete secret underpost-config -n ${namespace} --ignore-not-found`);
|
|
987
1019
|
shellExec(
|
|
988
|
-
`kubectl create secret generic underpost-config --from-env-file
|
|
1020
|
+
`kubectl create secret generic underpost-config --from-env-file=${sanitizedEnvPath} --dry-run=client -o yaml | kubectl apply -f - -n ${namespace}`,
|
|
989
1021
|
);
|
|
1022
|
+
fs.removeSync(sanitizedEnvPath);
|
|
990
1023
|
},
|
|
991
1024
|
/**
|
|
992
1025
|
* Switches the traffic for a deployment.
|
|
@@ -1032,6 +1065,37 @@ EOF`);
|
|
|
1032
1065
|
Underpost.env.set(`${deployId}-${env}-traffic`, targetTraffic);
|
|
1033
1066
|
},
|
|
1034
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* Resolves the effective target node for a deployment, applying a single
|
|
1070
|
+
* precedence chain shared by every deploy workflow — the default `deploy`
|
|
1071
|
+
* callback, `run sync`, and custom `run instance` — so node customization
|
|
1072
|
+
* behaves identically everywhere:
|
|
1073
|
+
*
|
|
1074
|
+
* 1. **Explicit node** — `node` (the resolved `--node` value). Upstream
|
|
1075
|
+
* runners derive it from the comma-path field or `--node-name`
|
|
1076
|
+
* (`run sync`: `path.split(',')[4]` > `--node-name` > default) and from
|
|
1077
|
+
* `--node-name` directly (`run instance`).
|
|
1078
|
+
* 2. **Cluster-type default** — when no explicit node is given: `kind-worker`
|
|
1079
|
+
* for a kind cluster (the node that hosts kind hostPath volumes),
|
|
1080
|
+
* otherwise the control-plane / current host (`os.hostname()`) for
|
|
1081
|
+
* kubeadm / k3s. With no explicit cluster flag, `development` is treated
|
|
1082
|
+
* as kind and `production` as the host, preserving legacy behaviour.
|
|
1083
|
+
*
|
|
1084
|
+
* @param {object} params
|
|
1085
|
+
* @param {string} [params.node=''] - Explicit node (`--node`); highest precedence.
|
|
1086
|
+
* @param {boolean} [params.kind=false] - Kind cluster context.
|
|
1087
|
+
* @param {boolean} [params.kubeadm=false] - Kubeadm cluster context.
|
|
1088
|
+
* @param {boolean} [params.k3s=false] - K3s cluster context.
|
|
1089
|
+
* @param {string} [params.env=''] - Deployment environment; tie-breaker when no cluster flag is set.
|
|
1090
|
+
* @returns {string} The effective node name.
|
|
1091
|
+
* @memberof UnderpostDeploy
|
|
1092
|
+
*/
|
|
1093
|
+
resolveDeployNode({ node = '', kind = false, kubeadm = false, k3s = false, env = '' } = {}) {
|
|
1094
|
+
if (node) return node;
|
|
1095
|
+
const isKind = kind || (!kubeadm && !k3s && env !== 'production');
|
|
1096
|
+
return isKind ? 'kind-worker' : os.hostname();
|
|
1097
|
+
},
|
|
1098
|
+
|
|
1035
1099
|
/**
|
|
1036
1100
|
* Deploys a volume for a deployment.
|
|
1037
1101
|
* @param {object} volume - Volume configuration.
|
|
@@ -1043,9 +1107,10 @@ EOF`);
|
|
|
1043
1107
|
* @param {string} options.env - Environment for the deployment.
|
|
1044
1108
|
* @param {string} options.version - Version of the deployment.
|
|
1045
1109
|
* @param {string} options.namespace - Kubernetes namespace for the deployment.
|
|
1046
|
-
* @param {string} options.nodeName -
|
|
1110
|
+
* @param {string} options.nodeName - Effective target node (already resolved via {@link UnderpostDeploy.resolveDeployNode}). The volume data is written/shipped here and the PV is pinned to it; an empty value falls back to the cluster-type default inside this method.
|
|
1047
1111
|
* @param {string} [options.clusterContext='kind'] - Cluster context type ('kind', 'kubeadm', or 'k3s').
|
|
1048
1112
|
* @param {boolean} [options.gitClean=false] - Whether to run git clean on volumeMountPath before copying.
|
|
1113
|
+
* @param {string} [options.sshKeyPath=''] - Private key path used when the target node is remote and the volume is shipped over SSH. Empty falls back to copyDirToNode's default (engine-private/deploy/id_rsa).
|
|
1049
1114
|
* @memberof UnderpostDeploy
|
|
1050
1115
|
*/
|
|
1051
1116
|
deployVolume(
|
|
@@ -1058,6 +1123,7 @@ EOF`);
|
|
|
1058
1123
|
nodeName: '',
|
|
1059
1124
|
clusterContext: 'kind',
|
|
1060
1125
|
gitClean: false,
|
|
1126
|
+
sshKeyPath: '',
|
|
1061
1127
|
},
|
|
1062
1128
|
) {
|
|
1063
1129
|
if (!volume.claimName) {
|
|
@@ -1072,16 +1138,48 @@ EOF`);
|
|
|
1072
1138
|
if (options.gitClean && volume.volumeMountPath) {
|
|
1073
1139
|
Underpost.repo.clean({ paths: [volume.volumeMountPath] });
|
|
1074
1140
|
}
|
|
1141
|
+
// The node that physically receives the volume data. hostPath volumes are
|
|
1142
|
+
// node-local, so the data must land on the node where the pod will run, and
|
|
1143
|
+
// the PV is pinned there (nodeAffinity) so the scheduler co-locates the pod
|
|
1144
|
+
// with its volume — never mounting an empty DirectoryOrCreate on another node.
|
|
1145
|
+
let dataNode;
|
|
1075
1146
|
if (clusterContext === 'kind') {
|
|
1076
1147
|
const kindNode = options.nodeName || 'kind-worker';
|
|
1148
|
+
dataNode = kindNode;
|
|
1077
1149
|
shellExec(`docker exec -i ${kindNode} bash -c "mkdir -p ${rootVolumeHostPath}"`);
|
|
1078
1150
|
shellExec(`tar -C ${volume.volumeMountPath} -c . | docker cp - ${kindNode}:${rootVolumeHostPath}`);
|
|
1079
1151
|
shellExec(
|
|
1080
1152
|
`docker exec -i ${kindNode} bash -c "chown -R 1000:1000 ${rootVolumeHostPath}; chmod -R 755 ${rootVolumeHostPath}"`,
|
|
1081
1153
|
);
|
|
1082
1154
|
} else {
|
|
1083
|
-
|
|
1084
|
-
|
|
1155
|
+
const localHost = os.hostname();
|
|
1156
|
+
dataNode = options.nodeName || localHost;
|
|
1157
|
+
if (dataNode === localHost) {
|
|
1158
|
+
// Target node is the control plane / current host: write directly.
|
|
1159
|
+
if (!fs.existsSync(rootVolumeHostPath)) fs.mkdirSync(rootVolumeHostPath, { recursive: true });
|
|
1160
|
+
fs.copySync(volume.volumeMountPath, rootVolumeHostPath);
|
|
1161
|
+
} else {
|
|
1162
|
+
// Target node is remote: fs.copySync would only write the control-plane
|
|
1163
|
+
// filesystem, leaving the real node's hostPath empty. Ship the folder to
|
|
1164
|
+
// the node over SSH so the data exists where the pod is pinned.
|
|
1165
|
+
const nodeHost =
|
|
1166
|
+
shellExec(
|
|
1167
|
+
`kubectl get node ${dataNode} -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}'`,
|
|
1168
|
+
{ stdout: true, silent: true, silentOnError: true },
|
|
1169
|
+
).trim() || dataNode;
|
|
1170
|
+
logger.info('Shipping volume to remote node over SSH', {
|
|
1171
|
+
node: dataNode,
|
|
1172
|
+
host: nodeHost,
|
|
1173
|
+
src: volume.volumeMountPath,
|
|
1174
|
+
dest: rootVolumeHostPath,
|
|
1175
|
+
});
|
|
1176
|
+
Underpost.ssh.copyDirToNode({
|
|
1177
|
+
host: nodeHost,
|
|
1178
|
+
localDir: volume.volumeMountPath,
|
|
1179
|
+
remoteDir: rootVolumeHostPath,
|
|
1180
|
+
...(options.sshKeyPath ? { keyPath: options.sshKeyPath } : {}),
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1085
1183
|
}
|
|
1086
1184
|
shellExec(`kubectl delete pvc ${pvcId} -n ${namespace} --ignore-not-found`);
|
|
1087
1185
|
shellExec(`kubectl delete pv ${pvId} --ignore-not-found`);
|
|
@@ -1090,6 +1188,7 @@ ${Underpost.deploy.persistentVolumeFactory({
|
|
|
1090
1188
|
hostPath: rootVolumeHostPath,
|
|
1091
1189
|
pvcId,
|
|
1092
1190
|
namespace,
|
|
1191
|
+
nodeName: dataNode,
|
|
1093
1192
|
})}
|
|
1094
1193
|
EOF
|
|
1095
1194
|
`);
|
|
@@ -1178,11 +1277,28 @@ ${secret ? ` readOnly: true\n` : ''}`;
|
|
|
1178
1277
|
* @param {string} options.hostPath - Host path for the persistent volume.
|
|
1179
1278
|
* @param {string} options.pvcId - Persistent volume claim ID.
|
|
1180
1279
|
* @param {string} [options.namespace='default'] - Kubernetes namespace for the PVC claimRef.
|
|
1280
|
+
* @param {string} [options.nodeName=''] - Node name to which the persistent volume is pinned (optional).
|
|
1181
1281
|
* @returns {string} - YAML configuration for the persistent volume and claim.
|
|
1182
1282
|
* @memberof UnderpostDeploy
|
|
1183
1283
|
*/
|
|
1184
|
-
persistentVolumeFactory({ hostPath, pvcId, namespace = 'default' }) {
|
|
1284
|
+
persistentVolumeFactory({ hostPath, pvcId, namespace = 'default', nodeName = '' }) {
|
|
1185
1285
|
const pvId = pvcId.replace(/^pvc-/, 'pv-');
|
|
1286
|
+
// hostPath volumes are node-local: deployVolume writes the content to the
|
|
1287
|
+
// filesystem of a single node. Without nodeAffinity the scheduler can place
|
|
1288
|
+
// the pod on a different node and mount an empty DirectoryOrCreate hostPath
|
|
1289
|
+
// (missing the materialized assets). Pin the PV to the node that holds the
|
|
1290
|
+
// data so the pod is always co-located with its volume.
|
|
1291
|
+
const nodeAffinity = nodeName
|
|
1292
|
+
? `
|
|
1293
|
+
nodeAffinity:
|
|
1294
|
+
required:
|
|
1295
|
+
nodeSelectorTerms:
|
|
1296
|
+
- matchExpressions:
|
|
1297
|
+
- key: kubernetes.io/hostname
|
|
1298
|
+
operator: In
|
|
1299
|
+
values:
|
|
1300
|
+
- ${nodeName}`
|
|
1301
|
+
: '';
|
|
1186
1302
|
return `apiVersion: v1
|
|
1187
1303
|
kind: PersistentVolume
|
|
1188
1304
|
metadata:
|
|
@@ -1193,7 +1309,7 @@ spec:
|
|
|
1193
1309
|
accessModes:
|
|
1194
1310
|
- ReadWriteOnce
|
|
1195
1311
|
persistentVolumeReclaimPolicy: Retain
|
|
1196
|
-
storageClassName: manual
|
|
1312
|
+
storageClassName: manual${nodeAffinity}
|
|
1197
1313
|
claimRef:
|
|
1198
1314
|
apiVersion: v1
|
|
1199
1315
|
kind: PersistentVolumeClaim
|