@underpostnet/cyberia 3.3.0 → 3.3.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (568) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +17 -5
  3. package/.github/workflows/coverall.cyberia.ci.yml +39 -14
  4. package/.github/workflows/cyberia-client.cd.yml +5 -0
  5. package/.github/workflows/cyberia-server.cd.yml +5 -0
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +35 -20
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +1 -0
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +96 -0
  22. package/CHANGELOG.md +653 -2513
  23. package/CLI-HELP.md +323 -157
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +5 -5
  27. package/README.md +48 -28
  28. package/bin/build.js +86 -41
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +1494 -1403
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +1494 -1403
  33. package/compose.env +17 -5
  34. package/conf.js +98 -7
  35. package/deploy/cyberia-client/deploy.sh +46 -0
  36. package/deploy/cyberia-client/package.sh +17 -0
  37. package/deploy/cyberia-client/state.sh +15 -0
  38. package/deploy/cyberia-server/deploy.sh +46 -0
  39. package/deploy/cyberia-server/package.sh +17 -0
  40. package/deploy/cyberia-server/state.sh +15 -0
  41. package/deploy/dd-cyberia/deploy.sh +9 -29
  42. package/deploy/dd-cyberia/init.sh +38 -45
  43. package/deploy/dd-cyberia/package.sh +16 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +133 -147
  46. package/deploy/lib/config.sh +29 -0
  47. package/deploy/lib/github-actions-logging.sh +610 -0
  48. package/deploy/lib/host.sh +194 -0
  49. package/deploy/lib/state.sh +65 -0
  50. package/deploy/pwa-microservices-template/deploy.sh +17 -42
  51. package/deploy/release/deploy.sh +9 -41
  52. package/deployment.yaml +7 -2
  53. package/docker-compose.yml +23 -17
  54. package/examples/static-page/README.md +19 -19
  55. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  56. package/hardhat/hardhat.config.js +1 -6
  57. package/hardhat/package-lock.json +101 -40
  58. package/hardhat/package.json +2 -2
  59. package/manifests/alertmanager/deployment.yaml +81 -0
  60. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  61. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  62. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  63. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  64. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  65. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  66. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  67. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  68. package/manifests/deployment/mongo-express/service.yaml +16 -0
  69. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  70. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  71. package/manifests/grafana/deployment.yaml +26 -7
  72. package/manifests/grafana/kustomization.yaml +1 -0
  73. package/manifests/grafana/service.yaml +6 -1
  74. package/manifests/mariadb/pv.yaml +1 -0
  75. package/manifests/mongodb/statefulset.yaml +9 -2
  76. package/manifests/mysql/pv-pvc.yaml +14 -1
  77. package/manifests/postgresql/pv.yaml +12 -1
  78. package/manifests/postgresql/pvc.yaml +1 -0
  79. package/manifests/prometheus/deployment.yaml +18 -22
  80. package/package.json +87 -12
  81. package/scripts/event-monitor.sh +56 -0
  82. package/scripts/k3s-node-setup.sh +27 -14
  83. package/scripts/kubeadm-node-setup.sh +31 -11
  84. package/scripts/maas-setup.sh +8 -8
  85. package/scripts/selinux-normalize.sh +907 -0
  86. package/scripts/shutdown-machine.sh +843 -0
  87. package/scripts/test-monitor.sh +5 -5
  88. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +3 -3
  99. package/src/api/cyberia-action/cyberia-action.service.js +5 -6
  100. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  101. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  102. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  103. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  104. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
  105. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  106. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
  107. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
  108. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  109. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  110. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  111. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  112. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  113. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  114. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  115. package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
  116. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  117. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  118. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
  119. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
  120. package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
  121. package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
  122. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  123. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  124. package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
  125. package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
  126. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  127. package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
  128. package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
  129. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  130. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  131. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  132. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  133. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
  134. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  135. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  136. package/src/api/cyberia-map/cyberia-map.service.js +8 -4
  137. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  138. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  139. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  140. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  141. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  142. package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
  143. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  144. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  145. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  146. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  147. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  148. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  149. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  150. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  151. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
  152. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  153. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  154. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  155. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  156. package/src/api/default/default.controller.js +1 -1
  157. package/src/api/default/default.router.js +1 -1
  158. package/src/api/default/default.service.js +2 -2
  159. package/src/api/document/document.controller.js +1 -1
  160. package/src/api/document/document.router.js +1 -1
  161. package/src/api/document/document.service.js +2 -2
  162. package/src/api/file/file.controller.js +1 -1
  163. package/src/api/file/file.ref.js +153 -0
  164. package/src/api/file/file.ref.json +11 -2
  165. package/src/api/file/file.router.js +1 -1
  166. package/src/api/file/file.service.js +2 -2
  167. package/src/api/instance/instance.controller.js +1 -1
  168. package/src/api/instance/instance.router.js +1 -1
  169. package/src/api/instance/instance.service.js +2 -2
  170. package/src/api/ipfs/ipfs.controller.js +1 -1
  171. package/src/api/ipfs/ipfs.router.js +2 -2
  172. package/src/api/ipfs/ipfs.service.js +2 -2
  173. package/src/api/object-layer/object-layer.controller.js +1 -1
  174. package/src/api/object-layer/object-layer.model.js +47 -12
  175. package/src/api/object-layer/object-layer.router.js +12 -5
  176. package/src/api/object-layer/object-layer.service.js +116 -123
  177. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  178. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  179. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  180. package/src/api/test/test.controller.js +1 -1
  181. package/src/api/test/test.service.js +1 -1
  182. package/src/api/user/guest.service.js +3 -3
  183. package/src/api/user/user.controller.js +1 -1
  184. package/src/api/user/user.router.js +2 -2
  185. package/src/api/user/user.service.js +4 -4
  186. package/src/api.js +5 -9
  187. package/src/cli/app.js +402 -0
  188. package/src/cli/baremetal.js +30 -35
  189. package/src/cli/client.js +306 -0
  190. package/src/cli/cloud-init.js +3 -3
  191. package/src/cli/cluster.js +191 -82
  192. package/src/cli/db.js +120 -68
  193. package/src/cli/deploy.js +157 -75
  194. package/src/cli/docker-compose.js +5 -5
  195. package/src/cli/domains.js +184 -0
  196. package/src/cli/dotenv-store.js +143 -0
  197. package/src/cli/event.js +2281 -0
  198. package/src/cli/fs-selection.js +115 -0
  199. package/src/cli/fs.js +159 -343
  200. package/src/cli/host.js +672 -0
  201. package/src/cli/image.js +4 -6
  202. package/src/cli/index.js +343 -151
  203. package/src/cli/ipfs.js +34 -30
  204. package/src/cli/kickstart.js +1 -1
  205. package/src/cli/kubectl.js +218 -41
  206. package/src/cli/lxd.js +3 -3
  207. package/src/cli/monitor.js +1359 -26
  208. package/src/cli/package.js +90 -0
  209. package/src/cli/release.js +16 -12
  210. package/src/cli/repository.js +586 -363
  211. package/src/cli/run.js +398 -843
  212. package/src/cli/secrets.js +2158 -964
  213. package/src/cli/ssh.js +478 -279
  214. package/src/cli/state.js +394 -0
  215. package/src/cli/static.js +2 -2
  216. package/src/cli/system.js +1 -1
  217. package/src/cli/test.js +203 -125
  218. package/src/cli/vultr.js +61 -21
  219. package/src/cli/wireguard.js +1112 -306
  220. package/src/client/components/core/CalendarCore.js +53 -96
  221. package/src/client/components/core/Docs.js +327 -303
  222. package/src/client/components/core/FileExplorer.js +0 -21
  223. package/src/client/components/core/Modal.js +56 -1
  224. package/src/client/components/core/PanelForm.js +4 -4
  225. package/src/client/components/core/Repository.js +56 -0
  226. package/src/client/components/core/Translate.js +7 -0
  227. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  228. package/src/client/components/core/Wallet.js +0 -11
  229. package/src/client/components/core/Worker.js +7 -1
  230. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  231. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  232. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  233. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  234. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  235. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  236. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  237. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  238. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  239. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  240. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
  241. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  242. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  243. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  244. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  245. package/src/client/components/default/AppShellDefault.js +1 -1
  246. package/src/client/components/default/SettingsDefault.js +2 -2
  247. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  248. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  249. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  250. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  251. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
  252. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  253. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  254. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  255. package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
  256. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  257. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  258. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  259. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  260. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  261. package/src/client/public/default/android-chrome-144x144.png +0 -0
  262. package/src/client/public/default/android-chrome-192x192.png +0 -0
  263. package/src/client/public/default/android-chrome-256x256.png +0 -0
  264. package/src/client/public/default/android-chrome-36x36.png +0 -0
  265. package/src/client/public/default/android-chrome-384x384.png +0 -0
  266. package/src/client/public/default/android-chrome-48x48.png +0 -0
  267. package/src/client/public/default/android-chrome-512x512.png +0 -0
  268. package/src/client/public/default/android-chrome-72x72.png +0 -0
  269. package/src/client/public/default/android-chrome-96x96.png +0 -0
  270. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  271. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  272. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  273. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  274. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  275. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  276. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  277. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  278. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  279. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  280. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  281. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  282. package/src/client/public/default/apple-touch-icon.png +0 -0
  283. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  284. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  285. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  286. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  287. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  288. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  289. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  290. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  291. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  292. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  293. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  294. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  295. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  296. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  297. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  298. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  299. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  300. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  301. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  302. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  303. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  304. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  305. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  306. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  307. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  308. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  309. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  310. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  311. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  312. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  313. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  314. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  315. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  316. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  317. package/src/client/public/default/assets/background/white.jpg +0 -0
  318. package/src/client/public/default/browserconfig.xml +1 -1
  319. package/src/client/public/default/favicon-16x16.png +0 -0
  320. package/src/client/public/default/favicon-32x32.png +0 -0
  321. package/src/client/public/default/favicon-48x48.png +0 -0
  322. package/src/client/public/default/favicon.ico +0 -0
  323. package/src/client/public/default/manifest.webmanifest +5 -5
  324. package/src/client/public/default/mstile-144x144.png +0 -0
  325. package/src/client/public/default/mstile-150x150.png +0 -0
  326. package/src/client/public/default/mstile-310x150.png +0 -0
  327. package/src/client/public/default/mstile-310x310.png +0 -0
  328. package/src/client/public/default/mstile-70x70.png +0 -0
  329. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  330. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  331. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  332. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  333. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  334. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  335. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  336. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  337. package/src/client/services/object-layer/object-layer.management.js +6 -10
  338. package/src/client/services/object-layer/object-layer.service.js +3 -1
  339. package/src/client/ssr/RootDocument.js +1 -1
  340. package/src/client/ssr/body/404.js +1 -1
  341. package/src/client/ssr/body/500.js +1 -1
  342. package/src/client/ssr/body/CacheControl.js +1 -1
  343. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  344. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  345. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  346. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  347. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  348. package/src/client/ssr/head/Css.js +1 -1
  349. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  350. package/src/client/ssr/head/DefaultScripts.js +1 -1
  351. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  352. package/src/client/ssr/head/Microdata.js +1 -1
  353. package/src/client/ssr/head/Production.js +1 -1
  354. package/src/client/ssr/head/Pwa.js +1 -1
  355. package/src/client/ssr/head/PwaItemledger.js +1 -1
  356. package/src/client/ssr/head/Seo.js +1 -1
  357. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  358. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  359. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  360. package/src/client/ssr/views/Cyberia404.js +7 -5
  361. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  362. package/src/client/ssr/views/Maintenance.js +1 -1
  363. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  364. package/src/client/ssr/views/Test.js +1 -1
  365. package/src/client/sw/core.sw.js +20 -14
  366. package/src/client-builder/client-build-docs.js +92 -77
  367. package/src/client-builder/client-build-live.js +2 -2
  368. package/src/client-builder/client-build.js +105 -74
  369. package/src/client-builder/client-bundle.js +347 -0
  370. package/src/client-builder/client-dev-server.js +3 -3
  371. package/src/client-builder/client-icons.js +2 -2
  372. package/src/client-builder/ssr.js +3 -3
  373. package/src/client.build.js +3 -6
  374. package/src/client.dev.js +3 -6
  375. package/src/db/DataBaseProvider.js +186 -3
  376. package/src/db/mariadb/MariaDB.js +3 -2
  377. package/src/db/mongo/MongoBootstrap.js +273 -101
  378. package/src/db/mongo/MongoExpress.js +187 -0
  379. package/src/db/mongo/MongooseDB.js +7 -8
  380. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  381. package/src/grpc/cyberia/grpc-server.js +6 -8
  382. package/src/index.js +79 -23
  383. package/src/mailer/EmailRender.js +2 -2
  384. package/src/mailer/MailerInterceptor.js +87 -0
  385. package/src/mailer/MailerProvider.js +50 -6
  386. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
  387. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  388. package/src/projects/cyberia/besu-genesis-generator.js +8 -17
  389. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  390. package/src/projects/cyberia/catalog-cyberia.js +54 -10
  391. package/src/projects/cyberia/gemini-client.js +4 -4
  392. package/src/projects/cyberia/generate-saga.js +5 -12
  393. package/src/projects/cyberia/hot-reload-trigger.js +18 -29
  394. package/src/projects/cyberia/instance-backup.js +210 -0
  395. package/src/projects/cyberia/instance-data.js +178 -258
  396. package/src/projects/cyberia/ipfs-client.js +4 -4
  397. package/src/projects/cyberia/map-preview-generator.js +33 -69
  398. package/src/projects/cyberia/object-layer.js +153 -320
  399. package/src/projects/cyberia/seed-audio.js +139 -0
  400. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  401. package/src/projects/cyberia/semantic-layer-generator.js +3 -19
  402. package/src/projects/cyberia/stat-balance.js +81 -0
  403. package/src/projects/cyberia/stat-commands.js +23 -0
  404. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  405. package/src/projects/underpost/catalog-underpost.js +9 -1
  406. package/src/proxy.js +4 -7
  407. package/src/runtime/cyberia-client/Dockerfile +17 -8
  408. package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
  409. package/src/runtime/cyberia-server/Dockerfile +18 -6
  410. package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
  411. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  412. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  413. package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
  414. package/src/runtime/engine-cyberia/compose.env +17 -5
  415. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  416. package/src/runtime/express/Express.js +32 -27
  417. package/src/runtime/lampp/Lampp.js +22 -70
  418. package/src/runtime/nginx/Nginx.js +1 -1
  419. package/src/runtime/wp/Wp.js +16 -48
  420. package/src/server/{catalog.js → build/catalog.js} +15 -5
  421. package/src/server/build/coverage.js +188 -0
  422. package/src/server/build/execution.js +402 -0
  423. package/src/server/build/package.js +508 -0
  424. package/src/server/build/testing.js +578 -0
  425. package/src/server/{dns.js → network/dns.js} +144 -11
  426. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  427. package/src/server/{middlewares.js → network/middlewares.js} +11 -5
  428. package/src/server/network/node-capability.js +98 -0
  429. package/src/server/{peer.js → network/peer.js} +3 -3
  430. package/src/server/{proxy.js → network/proxy.js} +7 -13
  431. package/src/server/network/router.js +356 -0
  432. package/src/server/{tls.js → network/tls.js} +2 -2
  433. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  434. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  435. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  436. package/src/server/{cri.js → ops/cri.js} +2 -2
  437. package/src/server/{cron.js → ops/cron.js} +349 -74
  438. package/src/server/ops/event-notification.js +284 -0
  439. package/src/server/{logger.js → ops/logger.js} +143 -23
  440. package/src/server/ops/monitoring.js +1724 -0
  441. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  442. package/src/server/{conf.js → runtime/conf.js} +516 -423
  443. package/src/server/runtime/config-scope.js +215 -0
  444. package/src/server/runtime/environment.js +149 -0
  445. package/src/server/{process.js → runtime/process.js} +153 -12
  446. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  447. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  448. package/src/server/{start.js → runtime/start.js} +128 -39
  449. package/src/server/{auth.js → security/auth.js} +7 -8
  450. package/src/server/security/container-storage.js +382 -0
  451. package/src/server/{crypto.js → security/crypto.js} +1 -1
  452. package/src/server/{selinux.js → security/selinux.js} +1 -1
  453. package/src/server/{backup.js → storage/backup.js} +17 -8
  454. package/src/server/{data-query.js → storage/data-query.js} +17 -1
  455. package/src/server/storage/downloader.js +121 -0
  456. package/src/server/storage/repository.js +68 -0
  457. package/src/server/storage/zip.js +153 -0
  458. package/src/server.js +4 -7
  459. package/src/ws/IoInterface.js +1 -1
  460. package/src/ws/IoServer.js +1 -1
  461. package/src/ws/core/core.ws.connection.js +1 -1
  462. package/src/ws/core/core.ws.emit.js +1 -1
  463. package/src/ws/core/core.ws.server.js +1 -1
  464. package/src/ws/default/default.ws.connection.js +1 -1
  465. package/src/ws/default/default.ws.emit.js +1 -1
  466. package/src/ws/default/default.ws.server.js +1 -1
  467. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  468. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  469. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  470. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  471. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  472. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  473. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
  474. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  475. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  476. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
  477. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  478. package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
  479. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  480. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  481. package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
  482. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  483. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  484. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  485. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  486. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  487. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  488. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  489. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  490. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  491. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  492. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  493. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  494. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  495. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  496. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  497. package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
  498. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
  499. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  500. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  501. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  502. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  503. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  504. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  505. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  506. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  507. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  508. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  509. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  510. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  511. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  512. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  513. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  514. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  515. package/test/support/shell-harness.js +73 -0
  516. package/test/unit/build-template.test.js +97 -0
  517. package/test/unit/build-workflow-offline.test.js +132 -0
  518. package/test/unit/catalog.test.js +71 -0
  519. package/test/unit/client-build-docs.test.js +361 -0
  520. package/test/unit/client-bundle.test.js +313 -0
  521. package/test/unit/conf-loading.test.js +303 -0
  522. package/test/unit/conf-resolution.test.js +996 -0
  523. package/test/unit/coverage-artifact.test.js +221 -0
  524. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  525. package/test/unit/cyberia/instance-backup.test.js +183 -0
  526. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  527. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  528. package/test/unit/cyberia/stat-balance.test.js +69 -0
  529. package/test/unit/deploy-env-resolution.test.js +38 -0
  530. package/test/unit/deploy-log-table.test.js +518 -0
  531. package/test/unit/downloader-integrity.test.js +98 -0
  532. package/test/unit/execution-profiles.test.js +219 -0
  533. package/test/unit/file-reference-registry.test.js +141 -0
  534. package/test/unit/fleet-sync-source.test.js +149 -0
  535. package/test/unit/fs-storage-paths.test.js +596 -0
  536. package/test/unit/logger-redaction.test.js +141 -0
  537. package/test/unit/package.test.js +457 -0
  538. package/test/unit/prepare-host.test.js +382 -0
  539. package/test/unit/process-environment.test.js +18 -0
  540. package/test/unit/propagation-message.test.js +78 -0
  541. package/test/unit/redeploy-plan.test.js +24 -0
  542. package/test/unit/release-bump.test.js +474 -0
  543. package/test/unit/ssh-output-redaction.test.js +28 -0
  544. package/test/unit/start-options.test.js +46 -0
  545. package/test/unit/test-tiers.test.js +220 -0
  546. package/test/unit/zip-archive.test.js +142 -0
  547. package/vitest.config.js +66 -0
  548. package/.nycrc +0 -9
  549. package/deploy/lib/logging.sh +0 -96
  550. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  551. package/manifests/mongodb/pv-pvc.yaml +0 -59
  552. package/scripts/coverall-test-cyberia.sh +0 -24
  553. package/scripts/coverall-test.sh +0 -24
  554. package/scripts/link-local-underpost-cli.sh +0 -6
  555. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  556. package/src/api/user/user.build.js +0 -16
  557. package/src/cli/env.js +0 -177
  558. package/src/client/components/core/Blockchain.js +0 -41
  559. package/src/client/components/core/Blog.js +0 -9
  560. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  561. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
  562. package/src/client/public/default/assets/background/dark.jpg +0 -0
  563. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  564. package/src/client/public/default/assets/background/white0.jpg +0 -0
  565. package/src/client/public/doc/favicon.ico +0 -0
  566. package/src/client/public/doc/sitemap +0 -148
  567. package/src/server/downloader.js +0 -73
  568. package/src/server/environment.js +0 -98
@@ -1,25 +1,75 @@
1
1
  /**
2
2
  * UnderpostCron server module
3
- * @module src/server/cron.js
3
+ *
4
+ * ## Host mounts and SELinux
5
+ *
6
+ * Generated CronJob pods run as `container_t`, so every host path they mount has to carry a type
7
+ * that domain can use, and no host path carries a credential:
8
+ *
9
+ * - **the global underpost directory** (`<npm root>/underpost`, i.e. root's nvm tree) was mounted
10
+ * over the image's own copy of the CLI purely so the container could read `.env` and `.state`
11
+ * from it. It carried `admin_home_t`, which `container_t` can neither read nor write, and it
12
+ * exposed the whole of root's npm tree to get at two variables. Replaced by an optional
13
+ * `envFrom` Secret carrying exactly those values.
14
+ * - **the node-exporter textfile directory** is written by the job bodies. Created by the
15
+ * collector's installer under `/var/lib`, so it inherits a type `container_t` may read but not
16
+ * write; {@link prepareCronHostStorage} gives it the shared container label.
17
+ * - **the engine mirror** at {@link ENGINE_MIRROR_PATH}. The job body is `node bin ...` — it
18
+ * executes *this repository*, so it needs the source tree, its `node_modules` and the deploy
19
+ * configuration it resolves — {@link engineMirrorContentsFactory} is that set, and nothing wider.
20
+ * The checkout itself is never mounted. It lives in the operator's home tree, where
21
+ * `container_t` is denied every read, and the fix cannot be to relabel it: it is a live working
22
+ * tree, and the shared container label on it would make everything ever written there
23
+ * container-readable. So the pods mount a dedicated copy outside `/home` instead, refreshed
24
+ * from the checkout and labeled by {@link prepareCronHostStorage}. The mount lands on the
25
+ * checkout's own path inside the container, so nothing the job body runs knows the difference.
26
+ * - **the connection key**, a projected Secret rather than a host path. `ssh` authenticates with a
27
+ * file, so it cannot travel in the `envFrom` Secret beside the tokens; and it must not travel in
28
+ * the mirror, which every `container_t` process on the node can read.
29
+ * {@link UnderpostSSH.keyPathFactory} prefers the mount, so a pod and the host CLI take the same
30
+ * code path to different keys.
31
+ *
32
+ * @module src/server/ops/cron.js
4
33
  * @namespace UnderpostCron
5
34
  */
6
35
 
7
36
  import { loggerFactory } from './logger.js';
8
- import { shellExec } from './process.js';
37
+ import { shellArgumentFactory, shellExec } from '../runtime/process.js';
9
38
  import fs from 'fs-extra';
39
+ import os from 'node:os';
10
40
  import dotenv from 'dotenv';
11
- import Underpost from '../index.js';
12
- import { getUnderpostRootPath } from './environment.js';
41
+ import Underpost from '../../index.js';
42
+ import { DEPLOY_ROUTES_PATH, readDeployRoutes } from '../network/router.js';
43
+ import { UNDERPOST_MONITORING } from './monitoring.js';
44
+ import ContainerStorageService, { ensureContainerStorage } from '../security/container-storage.js';
45
+ import { assertRoleCapability } from '../network/node-capability.js';
13
46
 
14
47
  const logger = loggerFactory(import.meta);
15
48
 
16
- const volumeHostPath = '/home/dd';
49
+ // Where the job body runs, inside the container. The image is built around this path and every
50
+ // relative path the CLI resolves hangs off it, so the mount lands here whatever its host source.
17
51
  const enginePath = '/home/dd/engine';
52
+ // The host source that mount reads: a copy of the checkout outside every home tree, which is the
53
+ // only way an unprivileged pod can read this platform's own source. See the module header.
54
+ const ENGINE_MIRROR_PATH = '/opt/engine';
18
55
  const cronVolumeName = 'underpost-cron-container-volume';
19
- const shareEnvVolumeName = 'underpost-share-env';
20
- const underpostContainerEnvDir = '/usr/lib/node_modules/underpost';
56
+ const textfileVolumeName = 'underpost-node-exporter-textfile';
57
+ // The credentials the job bodies read out of the environment (`GITHUB_TOKEN`, `GITHUB_USERNAME`).
58
+ // They used to arrive by bind-mounting the operator's global underpost directory out of root's
59
+ // home over the image's own copy of the CLI; a Secret delivers exactly those values, is labeled
60
+ // for container consumption by kubelet, and exposes nothing else of that tree.
61
+ const cronEnvSecretName = 'underpost-cron-env';
62
+ // The connection key, projected as a volume: ssh authenticates with a file, and the mirror the
63
+ // pods mount deliberately carries no key material. `UnderpostSSH.keyPathFactory` reads it here.
64
+ const cronSshSecretName = 'underpost-ssh-key';
65
+ const sshSecretMountPath = '/etc/underpost/secrets/ssh';
66
+ const sshVolumeName = 'underpost-ssh-key';
21
67
  const DEFAULT_CRON_ID = 'dd-cron';
22
68
 
69
+ /** The environment a generated pod runs. Rendered into the manifest, where it wins over the
70
+ * projected Secret, so the resolved value and the running value cannot drift. */
71
+ const cronDeployEnvFactory = (dev) => (dev === true ? 'development' : 'production');
72
+
23
73
  /**
24
74
  * Resolves the deploy ID stored in `engine-private/deploy/dd.cron`.
25
75
  * @returns {string|null}
@@ -45,10 +95,7 @@ const loadCronDeployEnv = () => {
45
95
  if (fs.existsSync(path)) process.env = { ...process.env, ...dotenv.parse(fs.readFileSync(path, 'utf8')) };
46
96
  }
47
97
 
48
- const routerPath = './engine-private/deploy/dd.router';
49
- if (!fs.existsSync(routerPath)) return;
50
- for (const deployId of fs.readFileSync(routerPath, 'utf8').trim().split(',')) {
51
- const id = deployId.trim();
98
+ for (const id of readDeployRoutes()) {
52
99
  const path = `./engine-private/conf/${id}/.env.${envName}`;
53
100
  if (!id || !fs.existsSync(path)) continue;
54
101
  const env = dotenv.parse(fs.readFileSync(path, 'utf8'));
@@ -109,11 +156,13 @@ const cronJobYamlFactory = ({
109
156
 
110
157
  const cronDeployId = cronDeployIdResolve();
111
158
 
112
- const cronBin = 'node bin'; // dev ? 'node bin' : 'underpost';
113
159
  const flags = `${git ? '--git ' : ''}${dev ? '--dev ' : ''}${dryRun ? '--dry-run ' : ''}${k3s ? '--k3s ' : ''}${kind ? '--kind ' : ''}${kubeadm ? '--kubeadm ' : ''}`;
114
- const commands = [`cd ${enginePath}`, `node bin env ${cronDeployId} ${dev ? `development` : `production`}`]; // `node bin run secret`
160
+ // No `app load`: it materializes a deployment's environment from its env file, and the pod has
161
+ // neither. Its environment arrives injected, scoped to what the jobs consume; the `conf.*.json`
162
+ // the jobs read carry `env:` references that resolve against exactly that.
163
+ const commands = [`cd ${enginePath}`];
115
164
  if (cmd) commands.push(cmd);
116
- commands.push(`${cronBin} cron ${deployList} ${jobList} ${flags}`);
165
+ commands.push(`node bin cron ${deployList} ${jobList} ${flags}`);
117
166
  const fullCommand = commands.join(' &&\n ');
118
167
 
119
168
  return `apiVersion: batch/v1
@@ -157,24 +206,84 @@ ${
157
206
  - -c
158
207
  - >
159
208
  ${fullCommand}
209
+ # Explicit, so the environment the pod runs is the one this manifest resolved:
210
+ # a container env entry wins over envFrom, whatever the projected Secret carries.
211
+ env:
212
+ - name: NODE_ENV
213
+ value: ${cronDeployEnvFactory(dev)}
214
+ envFrom:
215
+ - secretRef:
216
+ name: ${cronEnvSecretName}
217
+ optional: true
160
218
  volumeMounts:
161
219
  - mountPath: ${enginePath}
162
220
  name: ${cronVolumeName}
163
- - mountPath: ${underpostContainerEnvDir}
164
- name: ${shareEnvVolumeName}
221
+ - mountPath: ${UNDERPOST_MONITORING.nodeExporter.textfileDirectory}
222
+ name: ${textfileVolumeName}
223
+ - mountPath: ${sshSecretMountPath}
224
+ name: ${sshVolumeName}
225
+ readOnly: true
165
226
  volumes:
227
+ # The engine mirror: a copy of the checkout kept outside every home tree, because
228
+ # \`container_t\` is denied every read under one. The job body runs this repository's CLI
229
+ # (\`node bin ...\`), so it needs the source tree, its node_modules and engine-private's
230
+ # deploy configuration — the mount cannot be narrowed below that. Refreshed and labeled
231
+ # by prepareCronHostStorage(); see src/server/ops/cron.js's module header.
166
232
  - hostPath:
167
- path: ${enginePath}
233
+ path: ${ENGINE_MIRROR_PATH}
168
234
  type: Directory
169
235
  name: ${cronVolumeName}
236
+ # Metrics the cluster cannot scrape for itself are written here and read by the
237
+ # node-exporter collector. Prepared and labeled by prepareCronHostStorage().
170
238
  - hostPath:
171
- path: ${getUnderpostRootPath()}
239
+ path: ${UNDERPOST_MONITORING.nodeExporter.textfileDirectory}
172
240
  type: DirectoryOrCreate
173
- name: ${shareEnvVolumeName}
241
+ name: ${textfileVolumeName}
242
+ # Optional for the same reason the credential Secret is: a cluster that has onboarded
243
+ # no key still schedules, and the job that needs one reports its own failure.
244
+ - secret:
245
+ secretName: ${cronSshSecretName}
246
+ optional: true
247
+ defaultMode: 0400
248
+ name: ${sshVolumeName}
174
249
  restartPolicy: OnFailure
175
250
  `;
176
251
  };
177
252
 
253
+ /**
254
+ * Prepares the node directories the generated CronJobs mount, and gives the one they write to a
255
+ * label an unprivileged container can actually use.
256
+ *
257
+ * The node-exporter textfile directory is created by the collector's own installer under
258
+ * `/var/lib`, so it inherits that tree's policy type — which `container_t` may read but never
259
+ * write. The cron pods write their metrics there, so without this the first Enforcing run turns
260
+ * every `*.prom` write into a denial.
261
+ *
262
+ * The engine mirror is refreshed from the checkout first, then labeled with the rest: the pods
263
+ * execute it, so a stale or unlabeled mirror is the same outage. The checkout itself is never
264
+ * prepared — it is the operator's working tree, and the shared container label on it would make
265
+ * everything written there container-readable.
266
+ * @returns {string[]} Prepared paths.
267
+ * @memberof UnderpostCron
268
+ */
269
+ const prepareCronHostStorage = () => {
270
+ syncEngineMirror();
271
+
272
+ const paths = [UNDERPOST_MONITORING.nodeExporter.textfileDirectory, ENGINE_MIRROR_PATH];
273
+ ensureContainerStorage(paths, { execute: shellExec });
274
+
275
+ const mirror = cronCheckoutContextFactory();
276
+ if (!mirror.readable)
277
+ logger.warn('CronJob pods cannot read the engine mirror they mount', {
278
+ path: ENGINE_MIRROR_PATH,
279
+ type: mirror.type,
280
+ effect: `every job body fails with Cannot find module '${enginePath}/bin'`,
281
+ remedy: `label it ${ContainerStorageService.SHARED_CONTAINER_TYPE}: sudo restorecon -RF ${ENGINE_MIRROR_PATH}`,
282
+ });
283
+
284
+ return paths;
285
+ };
286
+
178
287
  /**
179
288
  * Syncs the engine directory into the kind-worker container node.
180
289
  * Required for kind clusters where worker nodes don't share the host filesystem.
@@ -182,12 +291,11 @@ ${
182
291
  * @memberof UnderpostCron
183
292
  */
184
293
  const syncEngineToKindWorker = () => {
185
- logger.info('Syncing engine volume to kind-worker node');
186
- shellExec(`docker exec -i kind-worker bash -c "rm -rf ${volumeHostPath}"`);
187
- shellExec(`docker exec -i kind-worker bash -c "mkdir -p ${volumeHostPath}"`);
188
- shellExec(`docker cp ${volumeHostPath}/engine kind-worker:${volumeHostPath}/engine`);
294
+ logger.info('Syncing engine mirror to kind-worker node', { path: ENGINE_MIRROR_PATH });
295
+ shellExec(`docker exec -i kind-worker bash -c "rm -rf ${ENGINE_MIRROR_PATH}"`);
296
+ shellExec(`docker cp ${ENGINE_MIRROR_PATH} kind-worker:${ENGINE_MIRROR_PATH}`);
189
297
  shellExec(
190
- `docker exec -i kind-worker bash -c "chown -R 1000:1000 ${volumeHostPath}; chmod -R 755 ${volumeHostPath}"`,
298
+ `docker exec -i kind-worker bash -c "chown -R 1000:1000 ${ENGINE_MIRROR_PATH}; chmod -R 755 ${ENGINE_MIRROR_PATH}"`,
191
299
  );
192
300
  };
193
301
 
@@ -199,7 +307,7 @@ const syncEngineToKindWorker = () => {
199
307
  * @memberof UnderpostCron
200
308
  * @returns {string|null} Resolved deploy-id or null if not found
201
309
  */
202
- const resolveDeployId = (deployId) => deployId || cronDeployIdResolve();
310
+ const resolveDeployId = (deployId) => (deployId && deployId !== 'dd' ? deployId : cronDeployIdResolve());
203
311
 
204
312
  /**
205
313
  * Parses a comma-separated CLI list into a trimmed, non-empty array.
@@ -218,18 +326,19 @@ const parseList = (value) => {
218
326
  };
219
327
 
220
328
  /**
221
- * Normalizes and validates a `--node-name` value before it reaches a manifest or a shell.
329
+ * Resolves the deploy-id list a job callback receives.
222
330
  *
223
- * @param {string} [nodeName] - Raw CLI value
331
+ * Always a comma-separated string: every job callback splits it on `,`, and
332
+ * `getRelatedDeployIdList` already returns that shape, so an explicit list is
333
+ * normalized to match rather than handed over as a parsed array.
334
+ *
335
+ * @param {string} jobId - Job identifier, used to pick the default source file
336
+ * @param {string|string[]} [deployList] - Explicit deploy-list CLI value
224
337
  * @memberof UnderpostCron
225
- * @returns {string} Trimmed node name, or '' when unset
226
- * @throws {Error} When the value is not a valid Kubernetes node name
338
+ * @returns {string} Comma-separated deploy IDs
227
339
  */
228
- const resolveNodeName = (nodeName) => {
229
- const node = `${nodeName || ''}`.trim();
230
- if (node && !/^[a-zA-Z0-9._-]+$/.test(node)) throw new Error(`Invalid Kubernetes node name: ${node}`);
231
- return node;
232
- };
340
+ const resolveJobDeployList = (jobId, deployList) =>
341
+ !deployList || deployList === 'dd' ? Underpost.cron.getRelatedDeployIdList(jobId) : parseList(deployList).join(',');
233
342
 
234
343
  /**
235
344
  * Checks whether a node is registered on the cluster.
@@ -248,6 +357,147 @@ const nodeExists = (nodeName) => {
248
357
  return `${stdout || ''}`.trim().length > 0;
249
358
  };
250
359
 
360
+ /**
361
+ * Whether the pods can read the mirror they mount, and the type denying them when they cannot.
362
+ *
363
+ * The job body is the engine's own CLI, so a mount `container_t` may not read surfaces as
364
+ * `Cannot find module '<engine>/bin'` — a Node error naming neither the mount nor the policy that
365
+ * denied it. {@link prepareCronHostStorage} labels the mirror, so this is the post-condition on
366
+ * that: a mirror that still denies means the labeling did not take, not that the mount is wrong.
367
+ * Off an Enforcing host nothing denies, so nothing is claimed.
368
+ *
369
+ * @memberof UnderpostCron
370
+ * @returns {{readable: boolean, type: string}} Whether a pod can read the mount, and its SELinux type.
371
+ */
372
+ const cronCheckoutContextFactory = () => {
373
+ const read = (command) =>
374
+ `${shellExec(command, { stdout: true, silent: true, silentOnError: true, disableLog: true }) || ''}`.trim();
375
+
376
+ if (read('command -v getenforce >/dev/null 2>&1 && getenforce') !== 'Enforcing') return { readable: true, type: '' };
377
+
378
+ const type = (read(`ls -Zd ${ENGINE_MIRROR_PATH}`).split(/\s+/)[0] || '').split(':')[2] || '';
379
+ return { readable: type === ContainerStorageService.SHARED_CONTAINER_TYPE, type };
380
+ };
381
+
382
+ /**
383
+ * @method engineMirrorContentsFactory
384
+ * @description Exactly what a job body resolves, as rsync filter rules over the checkout.
385
+ *
386
+ * An allowlist, not a denylist, for two reasons that both bite in production. The mirror carries
387
+ * the shared container label, so every `container_t` process on the node can read it: a denylist
388
+ * leaks whatever the repository gains next, and the thing it gains may be a key. And the mirror is
389
+ * copied on every apply, so a denylist grows without anyone deciding to grow it.
390
+ *
391
+ * `engine-private` is narrowed rather than dropped — the pod cannot run without it. `app load`
392
+ * reads `conf/<deploy-id>/.env.<env>` and that deploy's `conf.*.json`; `cron` reads
393
+ * `deploy/dd.cron`, `deploy/dd.routes` and the env of every routed deploy. That is the whole set.
394
+ * No key material is mirrored: the connection key arrives as a projected Secret volume.
395
+ *
396
+ * `node_modules` is present and `.git` is not, which is why the repository's own ignore files
397
+ * cannot express this set: they describe what is not source, and this is what is executed.
398
+ *
399
+ * No `.env.*` at any environment. Each one is a deployment's entire credential set, and the
400
+ * mirror is readable by every `container_t` process on the node; the pods receive the keys their
401
+ * scope entitles them to as an injected environment instead. `conf.*.json` stay because they hold
402
+ * `env:` references rather than values.
403
+ * @returns {string[]} rsync filter rules, in precedence order.
404
+ * @memberof UnderpostCron
405
+ */
406
+ const engineMirrorContentsFactory = () => [
407
+ // rsync takes the first rule that matches, so the asset trees are subtracted before `/src/**`
408
+ // adds their parents back. They are data the CLI never opens, and the bulk of the checkout.
409
+ '--exclude=/src/client/public/**',
410
+ // The CLI, its modules, and the manifest that makes them resolvable. `/src` is taken whole
411
+ // apart from those two subtrees: it is one import graph, and the entrypoint pulls from every
412
+ // corner of it — `src/runtime/nginx` among them.
413
+ '--include=/bin/',
414
+ '--include=/bin/**',
415
+ '--include=/src/',
416
+ '--include=/src/**',
417
+ '--include=/package.json',
418
+ '--include=/conf.js',
419
+ '--include=/node_modules/',
420
+ '--include=/node_modules/**',
421
+ // The deploy configuration the job bodies resolve, named file-shape by file-shape rather than
422
+ // globbed: `conf/<id>` also holds per-deploy key pairs and `deploy/` holds the fleet's own, and
423
+ // an allowlist is only worth having if it stops at the files that are actually read.
424
+ '--include=/engine-private/',
425
+ '--include=/engine-private/conf/',
426
+ '--include=/engine-private/conf/*/',
427
+ '--include=/engine-private/conf/*/conf.*.json',
428
+ '--include=/engine-private/conf/*/package.json',
429
+ '--include=/engine-private/deploy/',
430
+ '--include=/engine-private/deploy/dd.cron',
431
+ '--include=/engine-private/deploy/dd.routes',
432
+ '--exclude=*',
433
+ ];
434
+
435
+ /**
436
+ * Refreshes the engine mirror the CronJob pods mount from the checkout this command runs in.
437
+ *
438
+ * `--delete` because the mirror is an output, not a second working tree: a file the checkout no
439
+ * longer has must not survive in the tree the pods execute.
440
+ *
441
+ * @memberof UnderpostCron
442
+ * @returns {{source: string, target: string}} What was mirrored, and where.
443
+ */
444
+ const syncEngineMirror = () => {
445
+ const source = process.cwd();
446
+ const filters = engineMirrorContentsFactory().map(shellArgumentFactory).join(' ');
447
+
448
+ logger.info('Refreshing the engine mirror the CronJob pods mount', { source, target: ENGINE_MIRROR_PATH });
449
+ shellExec(
450
+ `sudo rsync -a --delete ${filters} ${shellArgumentFactory(`${source}/`)} ${shellArgumentFactory(`${ENGINE_MIRROR_PATH}/`)}`,
451
+ );
452
+ return { source, target: ENGINE_MIRROR_PATH };
453
+ };
454
+
455
+ /**
456
+ * Resolves the node a generated CronJob's pods are pinned to.
457
+ *
458
+ * Placement is not optional for these manifests. The job body runs the engine checkout they
459
+ * hostPath-mount, and a hostPath is node-local: an unpinned pod is free to land on a node whose
460
+ * `/home/dd/engine` is some other directory of that name, where the body dies on
461
+ * `Cannot find module '/home/dd/engine/bin'` before it can report anything of its own. The node
462
+ * generating the manifest is the one holding the checkout it mounts, so it is the default —
463
+ * claimed only when the cluster actually knows it under that name, so a workstation that is not
464
+ * a node keeps producing the portable manifest it produced before.
465
+ *
466
+ * @param {string} [nodeName] - Explicit `--node-name`, which always wins.
467
+ * @memberof UnderpostCron
468
+ * @returns {string} Node to pin to, or `''` to leave the pods unpinned.
469
+ */
470
+ const cronNodeNameFactory = (nodeName) => {
471
+ const requested = `${nodeName || ''}`.trim();
472
+ if (requested) return requested;
473
+
474
+ const local = os.hostname();
475
+ if (!local || !nodeExists(local)) return '';
476
+ logger.info('Pinning CronJob pods to the node holding the mounted checkout', { nodeName: local });
477
+ return local;
478
+ };
479
+
480
+ /**
481
+ * Resolves the cluster context a manifest run applies under.
482
+ *
483
+ * The context is not decoration: it selects how the image reaches the cluster and is rendered
484
+ * into the pod's own command, so a run that names none published a CronJob whose body addressed
485
+ * no cluster at all. A node can be asked which runtime it belongs to, so an unnamed context is
486
+ * read from the cluster rather than left blank.
487
+ *
488
+ * @param {object} [options] - CLI options carrying `k3s`, `kind` or `kubeadm`.
489
+ * @memberof UnderpostCron
490
+ * @returns {{k3s: boolean, kind: boolean, kubeadm: boolean}} Mutually exclusive context flags.
491
+ */
492
+ const cronClusterContextFactory = (options = {}) => {
493
+ if (options.k3s || options.kind || options.kubeadm)
494
+ return { k3s: !!options.k3s, kind: !!options.kind, kubeadm: !!options.kubeadm };
495
+
496
+ const { type } = Underpost.cluster.detectClusterRuntime();
497
+ if (type) logger.info('Resolved the cluster context from the cluster itself', { clusterType: type });
498
+ return { k3s: type === 'k3s', kind: type === 'kind', kubeadm: type === 'kubeadm' };
499
+ };
500
+
251
501
  /**
252
502
  * Checks whether a CronJob is already published on the cluster.
253
503
  *
@@ -266,19 +516,6 @@ const cronJobExists = (cronJobName, namespace) => {
266
516
  return `${stdout || ''}`.trim().length > 0;
267
517
  };
268
518
 
269
- /**
270
- * Resolves the manifest owner deploy-id from the `deploy-list` positional argument.
271
- * The `default` sentinel means "not provided", deferring to the dd.cron file.
272
- *
273
- * @param {string} [deployList] - Comma-separated deploy IDs from the CLI
274
- * @memberof UnderpostCron
275
- * @returns {string|undefined} Owner deploy-id, or undefined when unspecified
276
- */
277
- const deployIdFromList = (deployList) => {
278
- const [deployId] = parseList(deployList);
279
- return !deployId || deployId === 'default' ? undefined : deployId;
280
- };
281
-
282
519
  /**
283
520
  * UnderpostCron main module methods
284
521
  * @class UnderpostCron
@@ -331,25 +568,24 @@ class UnderpostCron {
331
568
  if (options.generateK8sCronjobs || options.apply || options.createJobNow)
332
569
  return await Underpost.cron.generateK8sCronJobs({
333
570
  ...options,
334
- deployId: deployIdFromList(deployList),
571
+ deployId: deployList,
335
572
  jobFilter,
336
573
  });
337
574
 
338
- const resolvedDeployList = deployList || 'default';
339
- const resolvedJobList = jobFilter.length > 0 ? jobFilter : Object.keys(Underpost.cron.JOB);
340
-
341
575
  if (options.nodeName)
342
576
  logger.warn(`--node-name is a manifest placement flag and has no effect on direct execution`, {
343
577
  nodeName: options.nodeName,
344
578
  });
345
579
 
346
- for (const jobId of resolvedJobList) {
580
+ for (const jobId of jobFilter) {
581
+ const resolvedDeployIdList = resolveJobDeployList(jobId, deployList);
582
+
347
583
  if (Underpost.cron.JOB[jobId]) {
348
584
  if (options.dryRun) {
349
- logger.info(`[dry-run] Would execute cron job`, { jobId, deployList: resolvedDeployList, options });
585
+ logger.info(`[dry-run] Would execute cron job`, { jobId, deployList: resolvedDeployIdList, options });
350
586
  } else {
351
- logger.info(`Executing cron job`, { jobId, deployList: resolvedDeployList, options });
352
- await Underpost.cron.JOB[jobId].callback(resolvedDeployList, options);
587
+ logger.info(`Executing cron job`, { jobId, deployList: resolvedDeployIdList, options });
588
+ await Underpost.cron.JOB[jobId].callback(resolvedDeployIdList, options);
353
589
  }
354
590
  } else {
355
591
  logger.warn(`Unknown cron job: ${jobId}`);
@@ -379,8 +615,8 @@ class UnderpostCron {
379
615
  */
380
616
  setupDeployStart: async function (deployList, options = {}) {
381
617
  // Validated up front: an invalid node name must not leave a rewritten package.json behind.
382
- const nodeName = resolveNodeName(options.nodeName);
383
- const requestedDeployId = deployIdFromList(deployList);
618
+ const nodeName = options.nodeName;
619
+ const requestedDeployId = deployList;
384
620
  const deployId = resolveDeployId(requestedDeployId);
385
621
  if (!deployId) {
386
622
  logger.warn(
@@ -464,17 +700,17 @@ class UnderpostCron {
464
700
  * @param {boolean} [options.apply=false] - kubectl apply generated manifests
465
701
  * @param {string} [options.namespace='default'] - Target Kubernetes namespace
466
702
  * @param {string} [options.image] - Custom container image override
467
- * @param {boolean} [options.k3s=false] - k3s cluster context (apply directly on host)
468
- * @param {boolean} [options.kind=false] - kind cluster context (apply via kind-worker container)
469
- * @param {boolean} [options.kubeadm=false] - kubeadm cluster context (apply directly on host)
703
+ * @param {boolean} [options.k3s] - k3s cluster context (apply directly on host); read from the cluster when no context is named
704
+ * @param {boolean} [options.kind] - kind cluster context (apply via kind-worker container); read from the cluster when no context is named
705
+ * @param {boolean} [options.kubeadm] - kubeadm cluster context (apply directly on host); read from the cluster when no context is named
470
706
  * @param {boolean} [options.createJobNow=false] - After applying, create a Job from each CronJob immediately
471
707
  * @param {boolean} [options.dryRun=false] - Pass --dry-run=client to kubectl commands
472
- * @param {string} [options.nodeName] - Pin every generated CronJob's pod to this node
708
+ * @param {string} [options.nodeName] - Pin every generated CronJob's pod to this node; defaults to this node
709
+ * when the cluster knows it, because the pods mount its checkout
473
710
  * @memberof UnderpostCron
474
711
  */
475
712
  generateK8sCronJobs: async function (options = {}) {
476
713
  const namespace = options.namespace || 'default';
477
- const nodeName = resolveNodeName(options.nodeName);
478
714
  const jobDeployId = resolveDeployId(options.deployId);
479
715
 
480
716
  if (!jobDeployId) {
@@ -512,6 +748,16 @@ class UnderpostCron {
512
748
  return;
513
749
  }
514
750
 
751
+ // Placement and cluster context are read from the cluster this run publishes to, so a run
752
+ // that only writes manifests stays offline and portable — the same reason `--apply` is what
753
+ // makes a manifest node-specific: it is the run that knows which node's checkout the pods
754
+ // will mount.
755
+ const nodeName = options.apply === true ? cronNodeNameFactory(options.nodeName) : options.nodeName || '';
756
+ const cluster =
757
+ options.apply === true
758
+ ? cronClusterContextFactory(options)
759
+ : { k3s: !!options.k3s, kind: !!options.kind, kubeadm: !!options.kubeadm };
760
+
515
761
  const outputDir = `./manifests/cronjobs/${jobDeployId}`;
516
762
  fs.mkdirSync(outputDir, { recursive: true });
517
763
 
@@ -521,6 +767,14 @@ class UnderpostCron {
521
767
  const jobConfig = confCronConfig.jobs[job];
522
768
 
523
769
  if (jobConfig.enabled === false) {
770
+ // The manifest is an output, not a source: leaving the previous one on disk keeps a
771
+ // stale spec around that a later `kubectl apply -f` would happily deploy — which is how
772
+ // a mount or image this generator has since dropped comes back.
773
+ const disabledPath = `${outputDir}/${jobDeployId}-${job}.yaml`;
774
+ if (fs.existsSync(disabledPath)) {
775
+ fs.removeSync(disabledPath);
776
+ logger.info(`Removed manifest for disabled job: ${disabledPath}`);
777
+ }
524
778
  logger.info(`Skipping disabled job: ${job}`);
525
779
  continue;
526
780
  }
@@ -541,9 +795,7 @@ class UnderpostCron {
541
795
  cmd: options.cmd,
542
796
  suspend: false,
543
797
  dryRun: !!options.dryRun,
544
- k3s: !!options.k3s,
545
- kind: !!options.kind,
546
- kubeadm: !!options.kubeadm,
798
+ ...cluster,
547
799
  nodeName,
548
800
  });
549
801
 
@@ -560,11 +812,27 @@ class UnderpostCron {
560
812
  }
561
813
 
562
814
  if (options.apply) {
815
+ // Publishing is the privileged half: it stages credentials on the node and writes cluster
816
+ // resources. Generating manifests is neither, which is why the gate is here and not around
817
+ // the whole command — a workstation or CI runner has no node role and keeps working.
818
+ const role = Underpost.wireguard.localRole();
819
+ if (role) assertRoleCapability({ role, capability: 'cron-publication', operation: 'cron --apply' });
820
+
563
821
  // A nodeSelector naming a node that is not registered leaves every Job Pending at its
564
822
  // next fire, silently. Warn rather than throw: the node may join before the schedule.
565
823
  if (nodeName && !nodeExists(nodeName))
566
824
  logger.warn(`Target node not found on the cluster; pods will stay Pending until it joins`, { nodeName });
567
825
 
826
+ // The node directory the pods write metrics into, labeled for container access.
827
+ prepareCronHostStorage();
828
+
829
+ // The credentials the job bodies read from the environment. Optional by design: the
830
+ // `envFrom` reference is `optional: true`, so a cluster with neither seed files nor the
831
+ // values in its environment still schedules — the jobs that need a GitHub token simply
832
+ // report their own failure instead of the pod refusing to start.
833
+ for (const name of [cronEnvSecretName, cronSshSecretName])
834
+ if (!Underpost.secret.applyIfPresent(name, namespace)) Underpost.secret.applyFromOriginSeed(name, namespace);
835
+
568
836
  // Delete existing CronJobs before applying new ones
569
837
  for (const job of targetJobs) {
570
838
  const cronJobName = `${jobDeployId}-${job}`;
@@ -576,15 +844,13 @@ class UnderpostCron {
576
844
  logger.info('Ensuring default image is loaded on cluster');
577
845
  Underpost.image.pullDockerHubImage({
578
846
  dockerhubImage: 'underpost',
579
- kind: !!options.kind,
580
- k3s: !!options.k3s,
581
- kubeadm: !!options.kubeadm,
847
+ ...cluster,
582
848
  dev: !!options.dev,
583
849
  });
584
850
  }
585
851
 
586
852
  // Sync engine volume to kind-worker node if using kind cluster
587
- if (options.kind) {
853
+ if (cluster.kind) {
588
854
  syncEngineToKindWorker();
589
855
  }
590
856
 
@@ -629,7 +895,7 @@ class UnderpostCron {
629
895
 
630
896
  /**
631
897
  * Resolve the deploy-id list associated with a given job.
632
- * Backup jobs read from dd.router (multiple deploy-ids); others from dd.cron.
898
+ * Backup jobs read from dd.routes (multiple deploy-ids); others from dd.cron.
633
899
  *
634
900
  * @param {string} jobId - Job identifier (e.g., 'dns', 'backup')
635
901
  * @returns {string} Comma-separated deploy IDs
@@ -637,9 +903,9 @@ class UnderpostCron {
637
903
  */
638
904
  getRelatedDeployIdList(jobId) {
639
905
  if (jobId === 'backup') {
640
- const routerFilePath = './engine-private/deploy/dd.router';
641
- if (fs.existsSync(routerFilePath)) return fs.readFileSync(routerFilePath, 'utf8').trim();
642
- logger.warn(`Deploy file not found: ${routerFilePath}, falling back to the cron deploy-id`);
906
+ const routes = readDeployRoutes();
907
+ if (routes.length > 0) return routes.join(',');
908
+ logger.warn(`Deploy route table not found: ${DEPLOY_ROUTES_PATH}, falling back to the cron deploy-id`);
643
909
  }
644
910
 
645
911
  const cronDeployId = cronDeployIdResolve();
@@ -671,4 +937,13 @@ class UnderpostCron {
671
937
 
672
938
  export default UnderpostCron;
673
939
 
674
- export { cronDeployIdResolve, cronJobYamlFactory, loadCronDeployEnv, resolveDeployId };
940
+ export {
941
+ cronCheckoutContextFactory,
942
+ cronDeployIdResolve,
943
+ cronJobYamlFactory,
944
+ engineMirrorContentsFactory,
945
+ loadCronDeployEnv,
946
+ parseList,
947
+ resolveDeployId,
948
+ resolveJobDeployList,
949
+ };