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.
Files changed (185) hide show
  1. package/.env.example +127 -68
  2. package/.github/workflows/cyberia-client.cd.yml +40 -0
  3. package/.github/workflows/cyberia-server.cd.yml +40 -0
  4. package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
  5. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
  6. package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
  7. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
  8. package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
  9. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
  10. package/.github/workflows/engine-cyberia.cd.yml +33 -24
  11. package/.github/workflows/engine-cyberia.ci.yml +13 -3
  12. package/.github/workflows/ghpkg.ci.yml +88 -1
  13. package/.github/workflows/gitlab.ci.yml +1 -1
  14. package/.github/workflows/hardhat.ci.yml +1 -1
  15. package/.github/workflows/npmpkg.ci.yml +10 -7
  16. package/.github/workflows/publish.ci.yml +2 -2
  17. package/.github/workflows/publish.cyberia.ci.yml +5 -16
  18. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
  19. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  20. package/CHANGELOG.md +301 -1
  21. package/CLI-HELP.md +71 -6
  22. package/Dockerfile +141 -43
  23. package/Dockerfile.dev +143 -0
  24. package/Dockerfile.test +165 -0
  25. package/README.md +1 -1
  26. package/baremetal/commission-workflows.json +1 -0
  27. package/bin/build.js +31 -0
  28. package/bin/cyberia.js +1080 -184
  29. package/bin/deploy.js +2 -2
  30. package/bin/index.js +1080 -184
  31. package/bump.config.js +1 -0
  32. package/compose.env +131 -0
  33. package/conf.js +18 -1
  34. package/deployment.yaml +2 -2
  35. package/docker-compose.yml +316 -0
  36. package/hardhat/hardhat.config.js +2 -2
  37. package/hardhat/package-lock.json +620 -2041
  38. package/hardhat/package.json +7 -5
  39. package/hardhat/scripts/deployObjectLayerToken.js +18 -18
  40. package/hardhat/test/ObjectLayerToken.js +378 -274
  41. package/ipfs/configure-ipfs.sh +13 -0
  42. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  43. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  44. package/manifests/deployment/dd-cyberia-development/deployment.yaml +2 -2
  45. package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
  46. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
  47. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  48. package/mongodb/entrypoint.sh +76 -0
  49. package/nginx.conf +87 -0
  50. package/package.json +31 -19
  51. package/pv-pvc.yaml +32 -0
  52. package/scripts/disk-clean.sh +85 -60
  53. package/scripts/kubeadm-node-setup.sh +317 -0
  54. package/scripts/rocky-kickstart.sh +877 -185
  55. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  56. package/scripts/test-monitor.sh +3 -5
  57. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
  58. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
  59. package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
  60. package/src/api/cyberia-action/cyberia-action.model.js +21 -29
  61. package/src/api/cyberia-action/cyberia-action.router.js +42 -7
  62. package/src/api/cyberia-action/cyberia-action.service.js +20 -4
  63. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
  64. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
  65. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
  66. package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
  67. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
  68. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
  69. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
  70. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
  71. package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
  72. package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
  73. package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
  74. package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
  75. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
  76. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
  77. package/src/api/cyberia-map/cyberia-map.router.js +21 -6
  78. package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
  79. package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
  80. package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
  81. package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
  82. package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
  83. package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
  84. package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
  85. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -129
  86. package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
  87. package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
  88. package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
  89. package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
  90. package/src/api/ipfs/ipfs.service.js +28 -15
  91. package/src/api/object-layer/object-layer.router.js +25 -15
  92. package/src/api/object-layer/object-layer.service.js +15 -20
  93. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
  94. package/src/cli/baremetal.js +717 -16
  95. package/src/cli/cluster.js +80 -5
  96. package/src/cli/deploy.js +127 -11
  97. package/src/cli/docker-compose.js +648 -0
  98. package/src/cli/env.js +11 -2
  99. package/src/cli/fs.js +75 -30
  100. package/src/cli/image.js +129 -51
  101. package/src/cli/index.js +110 -6
  102. package/src/cli/kickstart.js +142 -20
  103. package/src/cli/release.js +46 -4
  104. package/src/cli/repository.js +238 -33
  105. package/src/cli/run.js +520 -114
  106. package/src/cli/secrets.js +82 -48
  107. package/src/cli/ssh.js +234 -0
  108. package/src/cli/static.js +2 -2
  109. package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
  110. package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
  111. package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
  112. package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
  113. package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
  114. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
  115. package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
  116. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
  117. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
  118. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
  119. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +45 -27
  120. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
  121. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
  122. package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
  123. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
  124. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +8 -1
  125. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  126. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +5 -5
  127. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  128. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  129. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
  130. package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
  131. package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
  132. package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
  133. package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
  134. package/src/client/services/object-layer/object-layer.management.js +6 -6
  135. package/src/{server → client-builder}/client-build-docs.js +15 -5
  136. package/src/{server → client-builder}/client-build-live.js +3 -3
  137. package/src/{server → client-builder}/client-build.js +25 -22
  138. package/src/{server → client-builder}/client-dev-server.js +3 -3
  139. package/src/{server → client-builder}/client-icons.js +2 -2
  140. package/src/{server → client-builder}/ssr.js +5 -5
  141. package/src/client.build.js +1 -3
  142. package/src/client.dev.js +1 -1
  143. package/src/db/mongo/MongoBootstrap.js +12 -12
  144. package/src/grpc/cyberia/grpc-server.js +255 -70
  145. package/src/index.js +12 -1
  146. package/src/mailer/EmailRender.js +1 -1
  147. package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
  148. package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
  149. package/src/projects/cyberia/catalog-cyberia.js +81 -0
  150. package/src/projects/cyberia/gemini-client.js +175 -0
  151. package/src/projects/cyberia/generate-saga.js +2107 -0
  152. package/src/{server → projects/cyberia}/ipfs-client.js +2 -2
  153. package/src/{server → projects/cyberia}/object-layer.js +12 -108
  154. package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
  155. package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
  156. package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
  157. package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
  158. package/src/{server → projects/cyberia}/shape-generator.js +2 -2
  159. package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
  160. package/src/runtime/cyberia-client/Dockerfile +27 -61
  161. package/src/runtime/cyberia-client/Dockerfile.dev +20 -12
  162. package/src/runtime/cyberia-server/Dockerfile +21 -20
  163. package/src/runtime/cyberia-server/Dockerfile.dev +17 -8
  164. package/src/runtime/engine-cyberia/Dockerfile +143 -0
  165. package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
  166. package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
  167. package/src/runtime/engine-cyberia/compose.env +131 -0
  168. package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
  169. package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
  170. package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
  171. package/src/runtime/engine-cyberia/nginx.conf +87 -0
  172. package/src/runtime/express/Express.js +2 -2
  173. package/src/runtime/nginx/Nginx.js +250 -0
  174. package/src/server/catalog.js +9 -14
  175. package/src/server/conf.js +3 -6
  176. package/src/server/runtime-status.js +18 -1
  177. package/src/server/start.js +12 -2
  178. package/src/server.js +6 -2
  179. package/test/cyberia-instance-conf-defaults.test.js +140 -0
  180. package/test/deploy-monitor.test.js +26 -10
  181. package/test/shape-generator.test.js +7 -1
  182. package/typedoc.dd-cyberia.json +3 -1
  183. package/typedoc.json +3 -1
  184. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  185. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -7,20 +7,20 @@ set -euo pipefail
7
7
 
8
8
  VACUUM_SIZE="200M" # journalctl vacuum target
9
9
  LARGE_LOG_SIZE="+100M" # threshold for truncation (find syntax)
10
- TMP_AGE_DAYS=3 # remove files older than this from /tmp and /var/tmp
11
- CACHE_AGE_DAYS=30 # remove caches older than this from /var/cache
10
+ TMP_AGE_DAYS=1 # remove files older than this from /tmp and /var/tmp
11
+ CACHE_AGE_DAYS=5 # remove caches older than this from /var/cache
12
12
  KEEP_KERNELS=2 # keep this many latest kernels
13
13
  REMOVE_OPT=0 # ALWAYS allow removing /opt entries (user requested)
14
14
 
15
15
  timestamp() { date +"%F %T"; }
16
16
  log() {
17
- echo "$(timestamp) $*"
17
+ echo "$(timestamp) $*"
18
18
  }
19
19
 
20
20
  # must be root
21
21
  if [ "$EUID" -ne 0 ]; then
22
- echo "This script must be run as root. Exiting."
23
- exit 1
22
+ echo "This script must be run as root. Exiting."
23
+ exit 1
24
24
  fi
25
25
 
26
26
  log "=== Starting Rocky Linux 9 automated cleanup (batch mode) ==="
@@ -46,8 +46,8 @@ journalctl --vacuum-size="$VACUUM_SIZE"
46
46
  # 3) Truncate very large logs in /var/log (safer than delete)
47
47
  log "Truncating logs in /var/log larger than ${LARGE_LOG_SIZE} (keeps zero-sized file so services don't break)..."
48
48
  find /var/log -type f -size "$LARGE_LOG_SIZE" -print0 2>/dev/null | while IFS= read -r -d '' f; do
49
- echo "Truncating $f"
50
- : > "$f" || echo "Failed to truncate $f"
49
+ echo "Truncating $f"
50
+ : > "$f" || echo "Failed to truncate $f"
51
51
  done
52
52
 
53
53
  # 4) Clean /tmp and /var/tmp older than TMP_AGE_DAYS
@@ -62,87 +62,110 @@ find /var/cache -mindepth 1 -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xarg
62
62
  # 6) Clean user caches (~/.cache) and Downloads older than CACHE_AGE_DAYS
63
63
  log "Cleaning user caches and old downloads (home dirs)..."
64
64
  for homedir in /home/* /root; do
65
- [ -d "$homedir" ] || continue
66
- usercache="$homedir/.cache"
67
- userdownloads="$homedir/Downloads"
68
- usertrash="$homedir/.local/share/Trash"
69
-
70
- if [ -d "$usercache" ]; then
71
- log "Removing contents of $usercache"
72
- rm -rf "${usercache:?}/"* 2>/dev/null
73
- fi
74
- if [ -d "$userdownloads" ]; then
75
- log "Removing files older than ${CACHE_AGE_DAYS} days from $userdownloads"
76
- find "$userdownloads" -type f -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xargs -0r rm -f -- 2>/dev/null
77
- fi
78
- if [ -d "$usertrash" ]; then
79
- log "Emptying trash in $usertrash"
80
- rm -rf "${usertrash:?}/"* 2>/dev/null
81
- fi
65
+ [ -d "$homedir" ] || continue
66
+ usercache="$homedir/.cache"
67
+ userdownloads="$homedir/Downloads"
68
+ usertrash="$homedir/.local/share/Trash"
69
+
70
+ if [ -d "$usercache" ]; then
71
+ log "Removing contents of $usercache"
72
+ rm -rf "${usercache:?}/"* 2>/dev/null
73
+ fi
74
+ if [ -d "$userdownloads" ]; then
75
+ log "Removing files older than ${CACHE_AGE_DAYS} days from $userdownloads"
76
+ find "$userdownloads" -type f -mtime +"$CACHE_AGE_DAYS" -print0 2>/dev/null | xargs -0r rm -f -- 2>/dev/null
77
+ fi
78
+ if [ -d "$usertrash" ]; then
79
+ log "Emptying trash in $usertrash"
80
+ rm -rf "${usertrash:?}/"* 2>/dev/null
81
+ fi
82
82
  done
83
83
 
84
- # 7) Docker / Podman cleanup (if present)
84
+ # 7) Docker / Podman cleanup (if present) — this is what actually reclaims the
85
+ # overlay storage behind those duplicated `overlay` df lines.
85
86
  if command -v docker >/dev/null 2>&1; then
86
- log "Docker detected: pruning images/containers/volumes (aggressive)..."
87
- docker system df
88
- docker system prune -a --volumes -f
87
+ log "Docker detected: pruning images/containers/volumes/build cache (aggressive)..."
88
+ docker system df || true
89
+ docker system prune -a --volumes -f || true
90
+ docker builder prune -a -f || true
89
91
  fi
90
92
 
91
93
  if command -v podman >/dev/null 2>&1; then
92
- log "Podman detected: pruning images/containers (aggressive)..."
93
- podman system df
94
- podman system prune -a -f
94
+ log "Podman detected: pruning images/containers/volumes (aggressive)..."
95
+ podman system df || true
96
+ podman system prune -a --volumes -f || true
95
97
  fi
96
98
 
99
+ if command -v buildah >/dev/null 2>&1; then
100
+ log "Buildah detected: removing leftover working containers..."
101
+ buildah rm --all || true
102
+ fi
103
+
104
+ # 7b) Unmount leaked container overlay 'merged' mounts. Pruning frees the data
105
+ # but leaves these mountpoints attached, so they keep flooding `df -h` with
106
+ # identical `overlay ... /merged` rows. Only unmount overlays not backing a
107
+ # still-existing container, so running workloads are never disturbed.
108
+ log "Unmounting orphaned container overlay mounts..."
109
+ if command -v podman >/dev/null 2>&1; then
110
+ podman umount --all >/dev/null 2>&1 || true
111
+ fi
112
+ active_merged="$(podman ps -aq 2>/dev/null | xargs -r -I{} podman inspect --format '{{.GraphDriver.Data.MergedDir}}' {} 2>/dev/null || true)"
113
+ findmnt -rn -o TARGET 2>/dev/null | grep -E '/var/lib/(containers/storage|docker)/overlay.*/merged' | sort -r | while IFS= read -r m; do
114
+ if ! printf '%s\n' "$active_merged" | grep -qxF "$m"; then
115
+ echo "Unmounting orphaned overlay: $m"
116
+ umount -l "$m" 2>/dev/null || true
117
+ fi
118
+ done
119
+
97
120
  # 8) Flatpak unused runtimes/apps (if present)
98
121
  if command -v flatpak >/dev/null 2>&1; then
99
- log "Flatpak detected: trying to uninstall unused runtimes/apps..."
100
- flatpak uninstall --unused -y || flatpak uninstall --unused
101
- flatpak repair
122
+ log "Flatpak detected: trying to uninstall unused runtimes/apps..."
123
+ flatpak uninstall --unused -y || flatpak uninstall --unused
124
+ flatpak repair
102
125
  fi
103
126
 
104
127
  # 9) Python Pip cleanup
105
128
  if command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1; then
106
- log "Python Pip detected: cleaning cache..."
107
- if command -v pip3 >/dev/null 2>&1; then
108
- pip3 cache purge
109
- elif command -v pip >/dev/null 2>&1; then
110
- pip cache purge
111
- fi
129
+ log "Python Pip detected: cleaning cache..."
130
+ if command -v pip3 >/dev/null 2>&1; then
131
+ pip3 cache purge
132
+ elif command -v pip >/dev/null 2>&1; then
133
+ pip cache purge
134
+ fi
112
135
  fi
113
136
 
114
137
  # 10) Conda cleanup
115
138
  if command -v conda >/dev/null 2>&1; then
116
- log "Conda detected: cleaning all..."
117
- conda clean --all -y
139
+ log "Conda detected: cleaning all..."
140
+ conda clean --all -y
118
141
  fi
119
142
 
120
143
  # 11) Remove old kernels but keep the last KEEP_KERNELS (safe)
121
144
  log "Removing old kernels, keeping the last $KEEP_KERNELS kernels..."
122
145
  OLD_KERNELS=$(dnf repoquery --installonly --latest-limit=-$KEEP_KERNELS -q 2>/dev/null)
123
146
  if [ -n "$OLD_KERNELS" ]; then
124
- log "Old kernels to remove: $OLD_KERNELS"
125
- dnf -y remove $OLD_KERNELS
147
+ log "Old kernels to remove: $OLD_KERNELS"
148
+ dnf -y remove $OLD_KERNELS
126
149
  else
127
- log "No old kernels found by DNF repoquery. Only $KEEP_KERNELS or fewer are currently installed."
150
+ log "No old kernels found by DNF repoquery. Only $KEEP_KERNELS or fewer are currently installed."
128
151
  fi
129
152
 
130
153
  # 12) /opt review and removal (ON by default now)
131
154
  if [ -d /opt ]; then
132
- log "/opt usage (top entries):"
133
- du -sh /opt/* 2>/dev/null | sort -h | head -n 20
134
-
135
- if [ "$REMOVE_OPT" = "1" ]; then
136
- OPT_TARGETS=(/opt/local-path-provisioner)
137
- for t in "${OPT_TARGETS[@]}"; do
138
- if [ -d "$t" ]; then
139
- log "Removing $t as REMOVE_OPT=1"
140
- rm -rf "$t"
141
- fi
142
- done
143
- else
144
- log "Automatic /opt removals disabled (set REMOVE_OPT=1 to enable)."
145
- fi
155
+ log "/opt usage (top entries):"
156
+ du -sh /opt/* 2>/dev/null | sort -h | head -n 20
157
+
158
+ if [ "$REMOVE_OPT" = "1" ]; then
159
+ OPT_TARGETS=(/opt/local-path-provisioner)
160
+ for t in "${OPT_TARGETS[@]}"; do
161
+ if [ -d "$t" ]; then
162
+ log "Removing $t as REMOVE_OPT=1"
163
+ rm -rf "$t"
164
+ fi
165
+ done
166
+ else
167
+ log "Automatic /opt removals disabled (set REMOVE_OPT=1 to enable)."
168
+ fi
146
169
  fi
147
170
 
148
171
  # 13) Find large files > 100MB (report only)
@@ -150,8 +173,10 @@ log "Listing files larger than 100MB (report only):"
150
173
  find / -xdev -type f -size +100M -printf '%s\t%p\n' 2>/dev/null | sort -nr | head -n 50 | awk '{printf "%.1fMB\t%s\n",$1/1024/1024,$2}'
151
174
 
152
175
  # Final disk usage
153
- log "Disk usage after cleanup:"
176
+ log "Disk usage cleanup:"
154
177
  df -h
178
+ log "Top-level usage ( / ):"
179
+ du -xh --max-depth=1 / | sort -rh | head -n 20
155
180
 
156
181
  log "=== Cleanup finished ==="
157
182
  exit 0
@@ -0,0 +1,317 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # ---------------------------------------------------------------------------
5
+ # Underpost Kubeadm Node Setup (bare-metal physical node)
6
+ #
7
+ # Mirrors scripts/k3s-node-setup.sh but for kubeadm on real hardware: it runs on
8
+ # the freshly deployed OS (disk-installed Rocky) after first boot, installs NVM +
9
+ # Node.js, ensures the engine source is present, and drives the cluster bring-up
10
+ # through the local engine entrypoint `node bin cluster` (src/cli/cluster.js) —
11
+ # reusing its kubeadm + Contour + host-init logic instead of reimplementing it.
12
+ #
13
+ # Modes:
14
+ # --control Initialize a new kubeadm control-plane (default).
15
+ # --worker Join an existing cluster.
16
+ #
17
+ # Worker join (controller supplies this over SSH; no manual token paste):
18
+ # --join-command="kubeadm join <ip>:6443 --token <t> --discovery-token-ca-cert-hash <h>"
19
+ # or: --control-ip=<ip> --token=<t> --discovery-token-ca-cert-hash=<h>
20
+ #
21
+ # Engine source / options:
22
+ # --engine-root=<path> Engine source path (default /home/dd/engine).
23
+ # --engine-repo=<url> Git repo cloned if the engine source is missing.
24
+ # --engine-branch=<branch> Branch to clone (default: repo default).
25
+ # --no-contour Control mode: skip the Contour ingress install.
26
+ # ---------------------------------------------------------------------------
27
+
28
+ ROLE="control"
29
+ JOIN_COMMAND=""
30
+ CONTROL_IP=""
31
+ CONTROL_ENDPOINT_HOST=""
32
+ TOKEN=""
33
+ CA_CERT_HASH=""
34
+ JOIN_ONLY="0"
35
+ INSTALL_CONTOUR="1"
36
+ ENGINE_ROOT="/home/dd/engine"
37
+ ENGINE_REPO="https://github.com/underpostnet/engine.git"
38
+ ENGINE_BRANCH=""
39
+ # Private repo holding secrets (engine-private/conf/.../.env.production) cloned
40
+ # with a GitHub token. GITHUB_TOKEN/GITHUB_USERNAME come from the environment
41
+ # (passed over SSH by the controller) or the --github-* args.
42
+ ENGINE_PRIVATE_REPO="https://github.com/underpostnet/engine-private.git"
43
+ ENGINE_PRIVATE_BRANCH=""
44
+ GITHUB_TOKEN="${GITHUB_TOKEN:-}"
45
+ GITHUB_USERNAME="${GITHUB_USERNAME:-}"
46
+ CRI_SOCKET="unix:///var/run/crio/crio.sock"
47
+
48
+ for arg in "$@"; do
49
+ case $arg in
50
+ --control) ROLE="control" ;;
51
+ --worker) ROLE="worker" ;;
52
+ --join-command=*) JOIN_COMMAND="${arg#*=}" ;;
53
+ --control-ip=*) CONTROL_IP="${arg#*=}" ;;
54
+ --control-endpoint-host=*) CONTROL_ENDPOINT_HOST="${arg#*=}" ;;
55
+ --token=*) TOKEN="${arg#*=}" ;;
56
+ --discovery-token-ca-cert-hash=*) CA_CERT_HASH="${arg#*=}" ;;
57
+ --join-only) JOIN_ONLY="1" ;;
58
+ --engine-root=*) ENGINE_ROOT="${arg#*=}" ;;
59
+ --engine-repo=*) ENGINE_REPO="${arg#*=}" ;;
60
+ --engine-branch=*) ENGINE_BRANCH="${arg#*=}" ;;
61
+ --engine-private-repo=*) ENGINE_PRIVATE_REPO="${arg#*=}" ;;
62
+ --engine-private-branch=*) ENGINE_PRIVATE_BRANCH="${arg#*=}" ;;
63
+ --github-token=*) GITHUB_TOKEN="${arg#*=}" ;;
64
+ --github-username=*) GITHUB_USERNAME="${arg#*=}" ;;
65
+ --no-contour) INSTALL_CONTOUR="0" ;;
66
+ esac
67
+ done
68
+
69
+ log() { echo "$(date): [kubeadm-setup] $*"; }
70
+
71
+ # worker_join: lightweight, idempotent kubeadm join. Centralizes the join logic
72
+ # used by both the full worker flow and the --join-only short-circuit. Returns
73
+ # non-zero on failure so `set -e` aborts the script (no false-positive success).
74
+ worker_join() {
75
+ if [ -z "$JOIN_COMMAND" ]; then
76
+ if [ -n "$CONTROL_IP" ] && [ -n "$TOKEN" ] && [ -n "$CA_CERT_HASH" ]; then
77
+ JOIN_COMMAND="kubeadm join ${CONTROL_IP}:6443 --token ${TOKEN} --discovery-token-ca-cert-hash ${CA_CERT_HASH}"
78
+ else
79
+ echo "ERROR: worker mode needs --join-command=... OR --control-ip/--token/--discovery-token-ca-cert-hash" >&2
80
+ return 1
81
+ fi
82
+ fi
83
+
84
+ command -v kubeadm >/dev/null 2>&1 || {
85
+ echo "ERROR: kubeadm not found on node. Run a full (non --join-only) setup first." >&2
86
+ return 1
87
+ }
88
+
89
+ # Make the control-plane endpoint hostname resolve to the control IP. kubeadm
90
+ # reads the cluster-info / kubeadm-config ConfigMaps whose server URL is the
91
+ # control-plane endpoint (often 'localhost.localdomain:6443'); without this the
92
+ # worker dials [::1]:6443 and fails with "connection refused".
93
+ if [ -n "$CONTROL_ENDPOINT_HOST" ] && [ -n "$CONTROL_IP" ] && [ "$CONTROL_ENDPOINT_HOST" != "$CONTROL_IP" ]; then
94
+ log "Mapping control-plane endpoint '$CONTROL_ENDPOINT_HOST' -> $CONTROL_IP in /etc/hosts"
95
+ ESC_HOST="$(printf '%s' "$CONTROL_ENDPOINT_HOST" | sed 's/[.]/\\./g')"
96
+ # Strip the endpoint host from any existing (loopback) line so it no longer
97
+ # resolves to 127.0.0.1, then point it at the real control-plane IP.
98
+ sudo sed -i -E "s/[[:space:]]+${ESC_HOST}([[:space:]]|\$)/\1/g" /etc/hosts || true
99
+ if ! grep -qE "^${CONTROL_IP}[[:space:]].*${ESC_HOST}([[:space:]]|\$)" /etc/hosts; then
100
+ echo "${CONTROL_IP} ${CONTROL_ENDPOINT_HOST}" | sudo tee -a /etc/hosts >/dev/null
101
+ fi
102
+ fi
103
+
104
+ # Kernel/sysctl prereqs the kubeadm preflight needs (no engine/node required).
105
+ sudo swapoff -a || true
106
+ sudo sed -i '/swap/d' /etc/fstab || true
107
+ sudo modprobe br_netfilter || true
108
+ printf 'net.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1\n' \
109
+ | sudo tee /etc/sysctl.d/99-kubernetes.conf >/dev/null
110
+ sudo sysctl --system >/dev/null 2>&1 || true
111
+
112
+ # Replace --discovery-token-ca-cert-hash with --discovery-token-unsafe-skip-ca-verification
113
+ # so token discovery does not depend on the pinned CA hash.
114
+ UNSAFE_JOIN="$(echo "${JOIN_COMMAND}" | sed 's/--discovery-token-ca-cert-hash [^ ]*/--discovery-token-unsafe-skip-ca-verification/')"
115
+
116
+ _do_join() {
117
+ # Reset any stale state from a previous failed join so kubeadm does not
118
+ # reuse cached config that points at an unreachable endpoint.
119
+ sudo kubeadm reset -f --cri-socket="${CRI_SOCKET}" >/dev/null 2>&1 || true
120
+ sudo rm -rf /etc/kubernetes/* /var/lib/kubelet/pki 2>/dev/null || true
121
+ log "Joining cluster: ${UNSAFE_JOIN%% --token*} ..."
122
+ sudo ${UNSAFE_JOIN} --cri-socket="${CRI_SOCKET}"
123
+ }
124
+
125
+ if _do_join; then
126
+ log "Worker joined the cluster."
127
+ return 0
128
+ fi
129
+ log "WARNING: kubeadm join attempt failed; resetting and retrying once..."
130
+ if _do_join; then
131
+ log "Worker joined the cluster."
132
+ return 0
133
+ fi
134
+ log "FATAL: kubeadm join failed after retry"
135
+ return 1
136
+ }
137
+
138
+ # --join-only: skip all prerequisite/engine/host setup and go straight to the
139
+ # join (used by `baremetal --resume-join` on an already-provisioned node).
140
+ if [ "$JOIN_ONLY" = "1" ]; then
141
+ [ "$ROLE" = "worker" ] || { echo "ERROR: --join-only is only valid with --worker" >&2; exit 1; }
142
+ log "--join-only: skipping prerequisites/engine setup; joining cluster directly"
143
+ worker_join
144
+ log "kubeadm worker (join-only) complete."
145
+ exit 0
146
+ fi
147
+
148
+ # ---------------------------------------------------------------------------
149
+ # 0. Base prerequisites — a minimal @core Rocky install lacks tar/xz/git, which
150
+ # NVM needs to extract Node.js and the engine clone needs. Install them first.
151
+ # ---------------------------------------------------------------------------
152
+ log "Installing base prerequisites (tar, xz, gzip, git, curl)..."
153
+ sudo dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils 2>/dev/null \
154
+ || dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # 1. NVM and Node.js — required for the `node bin ...` entrypoints. Idempotent so
158
+ # a retried run does not reinstall Node from scratch.
159
+ # ---------------------------------------------------------------------------
160
+ if command -v node >/dev/null 2>&1 && node --version 2>/dev/null | grep -q '^v24'; then
161
+ log "Node.js $(node --version) already installed; skipping NVM setup"
162
+ else
163
+ log "Installing NVM and Node.js v24.15.0..."
164
+ curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.40.1/install.sh | bash
165
+
166
+ export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
167
+ # shellcheck disable=SC1090
168
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
169
+
170
+ nvm install 24.15.0
171
+ nvm use 24.15.0
172
+ nvm alias default 24.15.0
173
+ ln -sf "$(command -v node)" /usr/local/bin/node
174
+ ln -sf "$(command -v npm)" /usr/local/bin/npm
175
+ ln -sf "$(command -v npx)" /usr/local/bin/npx
176
+ fi
177
+
178
+ echo "
179
+ ██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗
180
+ ██║░░░██║████╗░██║██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝
181
+ ██║░░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██████╔╝██║░░██║╚█████╗░░░░██║░░░
182
+ ██║░░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██╔═══╝░██║░░██║░╚═══██╗░░░██║░░░
183
+ ╚██████╔╝██║░╚███║██████╔╝███████╗██║░░██║██║░░░░░╚█████╔╝██████╔╝░░░██║░░░
184
+ ░╚═════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚═╝░░░░░░╚════╝░╚═════╝░░░░╚═╝░░░
185
+
186
+ Bringing up underpost kubeadm node (role=$ROLE) from $ENGINE_ROOT
187
+ "
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # 2. Engine source — required by `node bin cluster` (manifests + CLI).
191
+ # Normalizes any (custom-named) repo into the canonical paths, mirroring
192
+ # src/server/start.js: clone into a temp dir then copy the contents into the
193
+ # target, so the working tree is always $ENGINE_ROOT regardless of repo name.
194
+ # ---------------------------------------------------------------------------
195
+ command -v git >/dev/null 2>&1 || sudo dnf install -y git
196
+
197
+ # clone_or_pull <repo-url> <branch> <target-dir> <remote-name> [mask-secret]
198
+ # If <target-dir> exists with a git repo, does a git pull (fetch+reset) to
199
+ # update it. Otherwise clones <repo-url> into a temp dir and copies contents
200
+ # to <target-dir> so a custom repo (e.g. engine-test-test) always lands at
201
+ # the canonical path. [mask-secret] is redacted from any logged output.
202
+ clone_or_pull() {
203
+ local url="${1:-}" branch="${2:-}" target="${3:-}" remote="${4:-origin}" mask="${5:-}"
204
+ if [ -d "$target" ] && git -C "$target" rev-parse --git-dir >/dev/null 2>&1; then
205
+ log "$target already present; pulling latest ..."
206
+ if git -C "$target" remote get-url "$remote" >/dev/null 2>&1; then
207
+ # Temporarily set the authenticated URL so the fetch succeeds.
208
+ # The caller strips the token from the remote URL after this returns.
209
+ local saved_url; saved_url="$(git -C "$target" remote get-url "$remote" 2>/dev/null || true)"
210
+ git -C "$target" remote set-url "$remote" "$url" 2>/dev/null || true
211
+ if [ -n "$mask" ]; then
212
+ git -C "$target" fetch --depth 1 "$remote" 2>&1 | sed "s/${mask}/***/g" || true
213
+ else
214
+ git -C "$target" fetch --depth 1 "$remote" 2>&1 || true
215
+ fi
216
+ # Restore the saved (clean) URL immediately so the token is never persisted.
217
+ if [ -n "$saved_url" ]; then
218
+ git -C "$target" remote set-url "$remote" "$saved_url" 2>/dev/null || true
219
+ fi
220
+ if [ -n "$branch" ]; then
221
+ git -C "$target" reset --hard "$remote/$branch" 2>/dev/null || true
222
+ else
223
+ # No branch specified: pull whichever branch is HEAD on remote
224
+ git -C "$target" reset --hard "$remote/$(git -C "$target" rev-parse --abbrev-ref HEAD 2>/dev/null)" 2>/dev/null || true
225
+ fi
226
+ # Clean untracked files so stale artifacts don't accumulate
227
+ git -C "$target" clean -fd 2>/dev/null || true
228
+ log "$target updated via pull"
229
+ return 0
230
+ fi
231
+ fi
232
+ # Full clone + normalize path
233
+ log "Cloning + normalizing $url -> $target ..."
234
+ local tmp; tmp="$(mktemp -d)"
235
+ local ok=0
236
+ if [ -n "$branch" ]; then
237
+ if [ -n "$mask" ]; then git clone --depth 1 --branch "$branch" "$url" "$tmp/repo" 2>&1 | sed "s/${mask}/***/g"; ok=${PIPESTATUS[0]}; else git clone --depth 1 --branch "$branch" "$url" "$tmp/repo"; ok=$?; fi
238
+ else
239
+ if [ -n "$mask" ]; then git clone --depth 1 "$url" "$tmp/repo" 2>&1 | sed "s/${mask}/***/g"; ok=${PIPESTATUS[0]}; else git clone --depth 1 "$url" "$tmp/repo"; ok=$?; fi
240
+ fi
241
+ if [ "$ok" -ne 0 ] || [ ! -d "$tmp/repo" ]; then
242
+ rm -rf "$tmp"
243
+ return 1
244
+ fi
245
+ mkdir -p "$target"
246
+ cp -a "$tmp/repo/." "$target/"
247
+ rm -rf "$tmp"
248
+ }
249
+
250
+ if [ -n "$GITHUB_TOKEN" ]; then
251
+ ENGINE_REPO_URL="https://${GITHUB_USERNAME:-x-access-token}:${GITHUB_TOKEN}@${ENGINE_REPO#https://}"
252
+ # Mask the token in log output by routing through clone_or_pull with mask arg
253
+ clone_or_pull "$ENGINE_REPO_URL" "$ENGINE_BRANCH" "$ENGINE_ROOT" "origin" "$GITHUB_TOKEN" || { log "FATAL: engine clone failed"; exit 1; }
254
+ # Drop the token from the remote URL so it is not persisted on disk.
255
+ git -C "$ENGINE_ROOT" remote set-url origin "$ENGINE_REPO" 2>/dev/null || true
256
+ else
257
+ clone_or_pull "$ENGINE_REPO" "$ENGINE_BRANCH" "$ENGINE_ROOT" "origin" "" || { log "FATAL: engine clone failed"; exit 1; }
258
+ fi
259
+
260
+ cd "$ENGINE_ROOT"
261
+
262
+ # Clone + normalize the private secrets repo into $ENGINE_ROOT/engine-private
263
+ # (where `node bin run secret` reads engine-private/conf/.../.env.production),
264
+ # regardless of the private repo's name. The token is masked in logs and then
265
+ # stripped from the saved remote URL.
266
+ if [ -n "$GITHUB_TOKEN" ]; then
267
+ PRIV_URL="https://${GITHUB_USERNAME:-x-access-token}:${GITHUB_TOKEN}@${ENGINE_PRIVATE_REPO#https://}"
268
+ if clone_or_pull "$PRIV_URL" "$ENGINE_PRIVATE_BRANCH" "$ENGINE_ROOT/engine-private" "origin" "$GITHUB_TOKEN"; then
269
+ # Drop the token from the remote URL so it is not persisted on disk.
270
+ git -C "$ENGINE_ROOT/engine-private" remote set-url origin "$ENGINE_PRIVATE_REPO" 2>/dev/null || true
271
+ else
272
+ log "WARNING: engine-private clone failed — secrets will be unavailable"
273
+ fi
274
+ else
275
+ log "WARNING: GITHUB_TOKEN not provided; engine-private not cloned — secrets will be unavailable"
276
+ fi
277
+
278
+ # Install JS deps and generate secrets using the local engine entrypoint.
279
+ npm install
280
+ npm install -g underpost
281
+ node bin run secret
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # 3. Host prerequisites (Docker, CRI-O, kubelet/kubeadm/kubectl, ...) via cluster.js
285
+ # ---------------------------------------------------------------------------
286
+ log "Installing Kubernetes host prerequisites (underpost cluster --init-host)..."
287
+ # CRI-O is already installed by this point (idempotent dnf). The install-crio
288
+ # runner also tries to install cri-tools (crictl) which is not in Rocky 9 repos.
289
+ # That failure is non-fatal; CRI-O itself works fine without crictl.
290
+ node bin cluster --dev --init-host || log "WARNING: init-host had minor errors (CRI-O is already installed)"
291
+
292
+ # ---------------------------------------------------------------------------
293
+ # 4. Role-specific bring-up, fully delegated to src/cli/cluster.js
294
+ # ---------------------------------------------------------------------------
295
+ if [ "$ROLE" = "control" ]; then
296
+ if [ "$INSTALL_CONTOUR" = "1" ]; then
297
+ log "Initializing kubeadm control-plane + Contour (underpost cluster --kubeadm --contour)..."
298
+ node bin cluster --dev --kubeadm --contour
299
+ else
300
+ log "Initializing kubeadm control-plane (underpost cluster --kubeadm)..."
301
+ node bin cluster --dev --kubeadm
302
+ fi
303
+
304
+ echo ""
305
+ log "Control-plane ready. Join command for workers:"
306
+ sudo kubeadm token create --print-join-command
307
+
308
+ elif [ "$ROLE" = "worker" ]; then
309
+ # Apply the same host configuration cluster.js uses (SELinux, swap, sysctl)
310
+ # before joining; worker_join then handles endpoint mapping + the join itself
311
+ # and fails the script (set -e) if the join does not succeed.
312
+ log "Applying host configuration (underpost cluster --config)..."
313
+ node bin cluster --dev --config
314
+ worker_join
315
+ fi
316
+
317
+ log "kubeadm ${ROLE} setup complete."