@underpostnet/cyberia 3.2.90 → 3.3.73

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 (490) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +16 -0
  3. package/.github/workflows/coverall.cyberia.ci.yml +107 -0
  4. package/.github/workflows/cyberia-client.cd.yml +6 -18
  5. package/.github/workflows/cyberia-server.cd.yml +6 -18
  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 +6 -78
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +42 -21
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +2 -9
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +47 -0
  22. package/CHANGELOG.md +581 -2417
  23. package/CLI-HELP.md +416 -127
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +4 -4
  27. package/README.md +21 -1
  28. package/bin/build.js +70 -43
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +372 -135
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +372 -135
  33. package/compose.env +16 -0
  34. package/conf.js +93 -4
  35. package/deploy/cyberia-client/deploy.sh +47 -0
  36. package/deploy/cyberia-client/package.sh +18 -0
  37. package/deploy/cyberia-client/state.sh +14 -0
  38. package/deploy/cyberia-server/deploy.sh +47 -0
  39. package/deploy/cyberia-server/package.sh +18 -0
  40. package/deploy/cyberia-server/state.sh +14 -0
  41. package/deploy/dd-cyberia/deploy.sh +22 -0
  42. package/deploy/dd-cyberia/init.sh +55 -0
  43. package/deploy/dd-cyberia/package.sh +17 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +116 -0
  46. package/deploy/lib/github-actions-logging.sh +594 -0
  47. package/deploy/lib/host.sh +185 -0
  48. package/deploy/lib/state.sh +62 -0
  49. package/deploy/pwa-microservices-template/deploy.sh +45 -0
  50. package/deploy/release/deploy.sh +32 -0
  51. package/deployment.yaml +7 -2
  52. package/docker-compose.yml +31 -37
  53. package/examples/static-page/README.md +19 -19
  54. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  55. package/hardhat/hardhat.config.js +1 -6
  56. package/hardhat/package-lock.json +104 -43
  57. package/hardhat/package.json +3 -3
  58. package/manifests/alertmanager/deployment.yaml +81 -0
  59. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  60. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
  61. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
  62. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  63. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  64. package/manifests/grafana/deployment.yaml +26 -7
  65. package/manifests/grafana/kustomization.yaml +1 -0
  66. package/manifests/grafana/service.yaml +6 -1
  67. package/manifests/mariadb/pv.yaml +1 -0
  68. package/manifests/mongodb/statefulset.yaml +9 -2
  69. package/manifests/mysql/pv-pvc.yaml +14 -1
  70. package/manifests/postgresql/pv.yaml +12 -1
  71. package/manifests/postgresql/pvc.yaml +1 -0
  72. package/manifests/prometheus/deployment.yaml +18 -22
  73. package/package.json +83 -10
  74. package/scripts/audit-selinux.sh +64 -0
  75. package/scripts/event-monitor.sh +56 -0
  76. package/scripts/gpu-diag.sh +0 -0
  77. package/scripts/ip-info.sh +0 -0
  78. package/scripts/k3s-node-setup.sh +30 -14
  79. package/scripts/kubeadm-node-setup.sh +40 -31
  80. package/scripts/lxd-vm-setup.sh +0 -0
  81. package/scripts/maas-nat-firewalld.sh +0 -0
  82. package/scripts/maas-setup.sh +8 -8
  83. package/scripts/nat-iptables.sh +2 -0
  84. package/scripts/rhel-grpc-setup.sh +0 -0
  85. package/scripts/rocky-kickstart.sh +25 -9
  86. package/scripts/selinux-normalize.sh +907 -0
  87. package/scripts/shutdown-machine.sh +843 -0
  88. package/scripts/test-monitor.sh +3 -3
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
  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 +1 -1
  99. package/src/api/cyberia-action/cyberia-action.service.js +2 -2
  100. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
  101. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
  102. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
  103. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  104. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  105. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  106. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  107. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  108. package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
  109. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
  110. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
  111. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
  112. package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
  113. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  114. package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
  115. package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
  116. package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
  117. package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
  118. package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
  119. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  120. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  121. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  122. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
  123. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  124. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  125. package/src/api/cyberia-map/cyberia-map.service.js +2 -2
  126. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  127. package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
  128. package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
  129. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  130. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  131. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  132. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  133. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  134. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  135. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -29
  136. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  137. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  138. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  139. package/src/api/default/default.controller.js +1 -1
  140. package/src/api/default/default.router.js +1 -1
  141. package/src/api/default/default.service.js +2 -2
  142. package/src/api/document/document.controller.js +1 -1
  143. package/src/api/document/document.router.js +1 -1
  144. package/src/api/document/document.service.js +2 -2
  145. package/src/api/file/file.controller.js +1 -1
  146. package/src/api/file/file.router.js +1 -1
  147. package/src/api/file/file.service.js +2 -2
  148. package/src/api/instance/instance.controller.js +1 -1
  149. package/src/api/instance/instance.router.js +1 -1
  150. package/src/api/instance/instance.service.js +2 -2
  151. package/src/api/ipfs/ipfs.controller.js +1 -1
  152. package/src/api/ipfs/ipfs.router.js +2 -2
  153. package/src/api/ipfs/ipfs.service.js +2 -2
  154. package/src/api/object-layer/object-layer.controller.js +1 -1
  155. package/src/api/object-layer/object-layer.model.js +204 -3
  156. package/src/api/object-layer/object-layer.router.js +2 -2
  157. package/src/api/object-layer/object-layer.service.js +4 -17
  158. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  159. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  160. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  161. package/src/api/test/test.controller.js +1 -1
  162. package/src/api/test/test.service.js +1 -1
  163. package/src/api/user/guest.service.js +3 -3
  164. package/src/api/user/user.controller.js +1 -1
  165. package/src/api/user/user.router.js +2 -2
  166. package/src/api/user/user.service.js +4 -4
  167. package/src/api.js +5 -9
  168. package/src/cli/app.js +391 -0
  169. package/src/cli/baremetal.js +30 -36
  170. package/src/cli/client.js +306 -0
  171. package/src/cli/cloud-init.js +3 -3
  172. package/src/cli/cluster.js +242 -135
  173. package/src/cli/db.js +126 -67
  174. package/src/cli/deploy.js +158 -60
  175. package/src/cli/docker-compose.js +5 -5
  176. package/src/cli/domains.js +184 -0
  177. package/src/cli/dotenv-store.js +143 -0
  178. package/src/cli/event.js +2281 -0
  179. package/src/cli/fs.js +3 -3
  180. package/src/cli/host.js +672 -0
  181. package/src/cli/image.js +4 -7
  182. package/src/cli/index.js +426 -134
  183. package/src/cli/ipfs.js +34 -30
  184. package/src/cli/kickstart.js +1 -1
  185. package/src/cli/kubectl.js +218 -41
  186. package/src/cli/lxd.js +3 -3
  187. package/src/cli/monitor.js +1359 -26
  188. package/src/cli/package.js +90 -0
  189. package/src/cli/release.js +73 -34
  190. package/src/cli/repository.js +564 -367
  191. package/src/cli/run.js +362 -654
  192. package/src/cli/secrets.js +2158 -964
  193. package/src/cli/ssh.js +651 -331
  194. package/src/cli/state.js +394 -0
  195. package/src/cli/static.js +2 -2
  196. package/src/cli/system.js +26 -13
  197. package/src/cli/test.js +199 -125
  198. package/src/cli/vultr.js +623 -0
  199. package/src/cli/wireguard.js +2930 -0
  200. package/src/client/components/core/CalendarCore.js +53 -96
  201. package/src/client/components/core/Docs.js +258 -211
  202. package/src/client/components/core/FileExplorer.js +0 -21
  203. package/src/client/components/core/PanelForm.js +4 -4
  204. package/src/client/components/core/Repository.js +56 -0
  205. package/src/client/components/core/Translate.js +7 -0
  206. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  207. package/src/client/components/core/Wallet.js +0 -11
  208. package/src/client/components/core/Worker.js +7 -1
  209. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  210. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  211. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
  212. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  213. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  214. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  215. package/src/client/components/default/AppShellDefault.js +1 -1
  216. package/src/client/components/default/SettingsDefault.js +2 -2
  217. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  218. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  219. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  220. package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
  221. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
  222. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  223. package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
  224. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  225. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  226. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  227. package/src/client/public/default/android-chrome-144x144.png +0 -0
  228. package/src/client/public/default/android-chrome-192x192.png +0 -0
  229. package/src/client/public/default/android-chrome-256x256.png +0 -0
  230. package/src/client/public/default/android-chrome-36x36.png +0 -0
  231. package/src/client/public/default/android-chrome-384x384.png +0 -0
  232. package/src/client/public/default/android-chrome-48x48.png +0 -0
  233. package/src/client/public/default/android-chrome-512x512.png +0 -0
  234. package/src/client/public/default/android-chrome-72x72.png +0 -0
  235. package/src/client/public/default/android-chrome-96x96.png +0 -0
  236. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  237. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  238. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  239. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  240. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  241. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  242. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  243. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  244. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  245. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  246. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  247. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  248. package/src/client/public/default/apple-touch-icon.png +0 -0
  249. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  250. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  251. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  252. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  253. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  254. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  255. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  256. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  257. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  258. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  259. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  260. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  261. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  262. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  263. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  264. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  265. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  266. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  267. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  268. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  269. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  270. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  271. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  272. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  273. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  274. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  275. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  276. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  277. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  278. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  279. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  280. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  281. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  282. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  283. package/src/client/public/default/assets/background/white.jpg +0 -0
  284. package/src/client/public/default/browserconfig.xml +1 -1
  285. package/src/client/public/default/favicon-16x16.png +0 -0
  286. package/src/client/public/default/favicon-32x32.png +0 -0
  287. package/src/client/public/default/favicon-48x48.png +0 -0
  288. package/src/client/public/default/favicon.ico +0 -0
  289. package/src/client/public/default/manifest.webmanifest +5 -5
  290. package/src/client/public/default/mstile-144x144.png +0 -0
  291. package/src/client/public/default/mstile-150x150.png +0 -0
  292. package/src/client/public/default/mstile-310x150.png +0 -0
  293. package/src/client/public/default/mstile-310x310.png +0 -0
  294. package/src/client/public/default/mstile-70x70.png +0 -0
  295. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  296. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  297. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  298. package/src/client/services/object-layer/object-layer.management.js +4 -4
  299. package/src/client/ssr/RootDocument.js +1 -1
  300. package/src/client/ssr/body/404.js +1 -1
  301. package/src/client/ssr/body/500.js +1 -1
  302. package/src/client/ssr/body/CacheControl.js +1 -1
  303. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  304. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  305. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  306. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  307. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  308. package/src/client/ssr/head/Css.js +1 -1
  309. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  310. package/src/client/ssr/head/DefaultScripts.js +1 -1
  311. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  312. package/src/client/ssr/head/Microdata.js +1 -1
  313. package/src/client/ssr/head/Production.js +1 -1
  314. package/src/client/ssr/head/Pwa.js +1 -1
  315. package/src/client/ssr/head/PwaItemledger.js +1 -1
  316. package/src/client/ssr/head/Seo.js +1 -1
  317. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  318. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  319. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  320. package/src/client/ssr/views/Cyberia404.js +7 -5
  321. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  322. package/src/client/ssr/views/Maintenance.js +1 -1
  323. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  324. package/src/client/ssr/views/Test.js +1 -1
  325. package/src/client/sw/core.sw.js +20 -14
  326. package/src/client-builder/client-build-docs.js +61 -46
  327. package/src/client-builder/client-build-live.js +2 -2
  328. package/src/client-builder/client-build.js +58 -44
  329. package/src/client-builder/client-dev-server.js +3 -3
  330. package/src/client-builder/client-icons.js +2 -2
  331. package/src/client-builder/ssr.js +3 -3
  332. package/src/client.build.js +3 -6
  333. package/src/client.dev.js +3 -6
  334. package/src/db/DataBaseProvider.js +186 -3
  335. package/src/db/mariadb/MariaDB.js +3 -2
  336. package/src/db/mongo/MongoBootstrap.js +273 -101
  337. package/src/db/mongo/MongooseDB.js +11 -8
  338. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  339. package/src/grpc/cyberia/grpc-server.js +1 -1
  340. package/src/index.js +103 -23
  341. package/src/mailer/EmailRender.js +2 -2
  342. package/src/mailer/MailerInterceptor.js +87 -0
  343. package/src/mailer/MailerProvider.js +50 -6
  344. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
  345. package/src/projects/cyberia/besu-genesis-generator.js +3 -3
  346. package/src/projects/cyberia/catalog-cyberia.js +54 -6
  347. package/src/projects/cyberia/gemini-client.js +1 -1
  348. package/src/projects/cyberia/generate-saga.js +15 -24
  349. package/src/projects/cyberia/hot-reload-trigger.js +1 -1
  350. package/src/projects/cyberia/instance-data.js +22 -3
  351. package/src/projects/cyberia/ipfs-client.js +4 -4
  352. package/src/projects/cyberia/map-preview-generator.js +1 -1
  353. package/src/projects/cyberia/object-layer.js +12 -22
  354. package/src/projects/cyberia/semantic-layer-generator.js +1 -1
  355. package/src/projects/underpost/catalog-underpost.js +13 -2
  356. package/src/proxy.js +4 -7
  357. package/src/runtime/cyberia-client/Dockerfile +9 -4
  358. package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
  359. package/src/runtime/cyberia-server/Dockerfile +9 -4
  360. package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
  361. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  362. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  363. package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
  364. package/src/runtime/engine-cyberia/compose.env +16 -0
  365. package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
  366. package/src/runtime/express/Express.js +32 -27
  367. package/src/runtime/lampp/Lampp.js +22 -70
  368. package/src/runtime/nginx/Nginx.js +1 -1
  369. package/src/runtime/wp/Wp.js +16 -48
  370. package/src/server/{catalog.js → build/catalog.js} +15 -5
  371. package/src/server/build/coverage.js +117 -0
  372. package/src/server/build/execution.js +402 -0
  373. package/src/server/build/package.js +465 -0
  374. package/src/server/build/testing.js +528 -0
  375. package/src/server/{dns.js → network/dns.js} +241 -14
  376. package/src/server/network/forward-proxy.js +541 -0
  377. package/src/server/{middlewares.js → network/middlewares.js} +59 -4
  378. package/src/server/network/node-capability.js +98 -0
  379. package/src/server/{peer.js → network/peer.js} +3 -3
  380. package/src/server/{proxy.js → network/proxy.js} +7 -13
  381. package/src/server/network/router.js +356 -0
  382. package/src/server/{tls.js → network/tls.js} +2 -2
  383. package/src/server/network/underpost-compression.js +186 -0
  384. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
  385. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
  386. package/src/server/{cri.js → ops/cri.js} +2 -2
  387. package/src/server/ops/cron.js +949 -0
  388. package/src/server/ops/event-notification.js +284 -0
  389. package/src/server/{logger.js → ops/logger.js} +108 -23
  390. package/src/server/ops/monitoring.js +1724 -0
  391. package/src/server/ops/systemd.js +259 -0
  392. package/src/server/{conf.js → runtime/conf.js} +521 -520
  393. package/src/server/runtime/config-scope.js +215 -0
  394. package/src/server/runtime/environment.js +149 -0
  395. package/src/server/{process.js → runtime/process.js} +153 -13
  396. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  397. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  398. package/src/server/{start.js → runtime/start.js} +128 -39
  399. package/src/server/{auth.js → security/auth.js} +7 -8
  400. package/src/server/security/container-storage.js +382 -0
  401. package/src/server/{crypto.js → security/crypto.js} +1 -1
  402. package/src/server/security/selinux.js +185 -0
  403. package/src/server/{backup.js → storage/backup.js} +17 -8
  404. package/src/server/{data-query.js → storage/data-query.js} +1 -1
  405. package/src/server/{downloader.js → storage/downloader.js} +2 -2
  406. package/src/server/storage/repository.js +68 -0
  407. package/src/server.js +4 -7
  408. package/src/ws/IoInterface.js +1 -1
  409. package/src/ws/IoServer.js +1 -1
  410. package/src/ws/core/core.ws.connection.js +1 -1
  411. package/src/ws/core/core.ws.emit.js +1 -1
  412. package/src/ws/core/core.ws.server.js +1 -1
  413. package/src/ws/default/default.ws.connection.js +1 -1
  414. package/src/ws/default/default.ws.emit.js +1 -1
  415. package/src/ws/default/default.ws.server.js +1 -1
  416. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  417. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  418. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  419. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  420. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
  421. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
  422. package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
  423. package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
  424. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  425. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  426. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  427. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  428. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  429. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  430. package/test/integration/infra/1-security/selinux.test.js +69 -0
  431. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  432. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  433. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  434. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  435. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  436. package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
  437. package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
  438. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  439. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  440. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  441. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  442. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  443. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  444. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +47 -6
  445. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -4
  446. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  447. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  448. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  449. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  450. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  451. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  452. package/test/support/shell-harness.js +73 -0
  453. package/test/unit/build-template.test.js +97 -0
  454. package/test/unit/build-workflow-offline.test.js +132 -0
  455. package/test/unit/catalog.test.js +71 -0
  456. package/test/unit/client-build-docs.test.js +334 -0
  457. package/test/unit/conf-loading.test.js +303 -0
  458. package/test/unit/conf-resolution.test.js +979 -0
  459. package/test/unit/coverage-artifact.test.js +130 -0
  460. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  461. package/test/unit/deploy-env-resolution.test.js +38 -0
  462. package/test/unit/deploy-log-table.test.js +447 -0
  463. package/test/unit/execution-profiles.test.js +219 -0
  464. package/test/unit/fleet-sync-source.test.js +149 -0
  465. package/test/unit/logger-redaction.test.js +112 -0
  466. package/test/unit/package.test.js +403 -0
  467. package/test/unit/prepare-host.test.js +299 -0
  468. package/test/unit/process-environment.test.js +18 -0
  469. package/test/unit/propagation-message.test.js +78 -0
  470. package/test/unit/redeploy-plan.test.js +24 -0
  471. package/test/unit/release-bump.test.js +474 -0
  472. package/test/unit/ssh-output-redaction.test.js +28 -0
  473. package/test/unit/start-options.test.js +46 -0
  474. package/test/unit/test-tiers.test.js +193 -0
  475. package/vitest.config.js +58 -0
  476. package/.nycrc +0 -9
  477. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  478. package/manifests/mongodb/pv-pvc.yaml +0 -59
  479. package/scripts/link-local-underpost-cli.sh +0 -6
  480. package/src/api/user/user.build.js +0 -16
  481. package/src/cli/env.js +0 -177
  482. package/src/client/components/core/Blockchain.js +0 -41
  483. package/src/client/components/core/Blog.js +0 -9
  484. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  485. package/src/client/public/default/assets/background/dark.jpg +0 -0
  486. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  487. package/src/client/public/default/assets/background/white0.jpg +0 -0
  488. package/src/client/public/doc/favicon.ico +0 -0
  489. package/src/client/public/doc/sitemap +0 -148
  490. package/src/server/cron.js +0 -476
package/src/cli/deploy.js CHANGED
@@ -5,14 +5,9 @@
5
5
  */
6
6
 
7
7
  import {
8
- buildKindPorts,
9
- buildPortProxyRouter,
10
- buildProxyRouter,
11
8
  clusterTypeFactory,
12
9
  Config,
13
- cronDeployIdResolve,
14
10
  deployHostsFactory,
15
- deployRangePortFactory,
16
11
  gatewayApiEnabledFactory,
17
12
  getDataDeploy,
18
13
  instanceStatusPageEntriesFactory,
@@ -20,13 +15,22 @@ import {
20
15
  loadConfServerJson,
21
16
  loadReplicas,
22
17
  nextTrafficFactory,
23
- pathPortAssignmentFactory,
24
18
  schedulableNodeFactory,
25
19
  trafficFromRoutingInfoFactory,
26
- } from '../server/conf.js';
27
- import { loggerFactory } from '../server/logger.js';
28
- import { shellExec } from '../server/process.js';
29
- import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime-status.js';
20
+ } from '../server/runtime/conf.js';
21
+ import {
22
+ buildKindPorts,
23
+ buildPortProxyRouter,
24
+ buildProxyRouter,
25
+ deployRangePortFactory,
26
+ pathPortAssignmentFactory,
27
+ readDeployRoutes,
28
+ } from '../server/network/router.js';
29
+ import { loggerFactory } from '../server/ops/logger.js';
30
+ import { HOST_VOLUME_ROOT } from '../server/runtime/environment.js';
31
+ import { shellExec } from '../server/runtime/process.js';
32
+ import { ensureContainerStorage, ensureContainerStorageSubtree } from '../server/security/container-storage.js';
33
+ import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime/runtime-status.js';
30
34
  import { staticContextRoutesFactory, statusPageRoutesFactory } from '../client-builder/client-build.js';
31
35
  import {
32
36
  UNDERPOST_GATEWAY,
@@ -39,13 +43,15 @@ import {
39
43
  syncStaticAssetFromPod,
40
44
  writeHostServerConf,
41
45
  writeStaticAsset,
42
- } from '../server/underpost-gateway.js';
46
+ } from '../server/network/underpost-gateway.js';
43
47
  import { getCapVariableName } from '../client/components/core/CommonJs.js';
44
48
  import fs from 'fs-extra';
45
49
  import nodePath from 'node:path';
46
50
  import dotenv from 'dotenv';
47
51
  import os from 'node:os';
48
52
  import crypto from 'node:crypto';
53
+ import { appSecretName } from './app.js';
54
+ import { domainContextFactory } from './domains.js';
49
55
  import Underpost from '../index.js';
50
56
 
51
57
  /**
@@ -77,7 +83,7 @@ const GATEWAY_CONTROLLER_NAME = `${GATEWAY_EXTENSION_GROUP}/gatewayclass-control
77
83
  // Gateway references: one name, resolved through gatewayApiConfigFactory, so an
78
84
  // override reaches the installer and the manifests together.
79
85
  const GATEWAY_CLASS_DEFAULT = 'eg';
80
- // Where `bin client <deployId> <env>` writes each host's bundle, including the
86
+ // Where `bin client <deployId> --env <env>` writes each host's bundle, including the
81
87
  // SSR status views declared in conf.ssr.json (`<host><path>/<status>/index.html`).
82
88
  // Engine root inside the workload container; the built PWA artifacts live under
83
89
  // its `public/` tree, which is where the static edge documents are sourced from.
@@ -87,6 +93,17 @@ const UPSTREAM_FAILURE_STATUSES = [502, 503, 504];
87
93
 
88
94
  const CONTAINER_ENGINE_ROOT = '/home/dd/engine';
89
95
 
96
+ // The manifest set `deploy --build-manifest` produces for one environment.
97
+ const DEFAULT_MANIFEST_FILES = [
98
+ 'deployment.yaml',
99
+ 'proxy.yaml',
100
+ 'gateway.yaml',
101
+ 'httproute.yaml',
102
+ 'pv-pvc.yaml',
103
+ 'traffic-service.yaml',
104
+ 'grpc-service.yaml',
105
+ ];
106
+
90
107
  /**
91
108
  * Maps a host/path's edge-served views onto the statuses the gateway intercepts
92
109
  * for it, and the context directory each status is answered from.
@@ -152,6 +169,13 @@ const gatewayDurationFactory = (value) => {
152
169
 
153
170
  const logger = loggerFactory(import.meta);
154
171
 
172
+ // hostPath volume trees are written under the operator's home directory, which carries a label no
173
+ // unprivileged container can read. This registers the persistent mapping and restores the tree,
174
+ // rather than only restoring it: a deploy must not depend on cluster bring-up having run on this
175
+ // host, and a plain `restorecon` against an unmapped path re-applies the very default that makes
176
+ // the volume unreadable. A no-op where SELinux or its userspace is absent.
177
+ const prepareContainerStorage = (path) => ensureContainerStorageSubtree(path, { execute: shellExec });
178
+
155
179
  /**
156
180
  * @class UnderpostDeploy
157
181
  * @description Manages the deployment of applications and services.
@@ -440,20 +464,15 @@ EOF
440
464
  }) {
441
465
  if (!readinessProbe) throw new Error(`Refusing to build ${deployId}-${env}-${suffix} without a readiness probe`);
442
466
  if (!cmd)
443
- cmd =
444
- pullBundle || skipFullBuild
445
- ? [
446
- // When pullBundle (or skipFullBuild) is set the container pulls the pre-built client
447
- // bundle from Cloudinary (push-bundle must have been run on the dev machine beforehand).
448
- `underpost secret underpost --create-from-env`,
449
- `underpost start --build --run --pull-bundle --skip-full-build ${deployId} ${env}`,
450
- ]
451
- : [
452
- // `npm install -g npm@11.2.0`,
453
- // `npm install -g underpost`,
454
- `underpost secret underpost --create-from-env`,
455
- `underpost start --build --run ${deployId} ${env}`,
456
- ];
467
+ // One command, and deliberately only `start`. This string is executed by whatever
468
+ // underpost the image happens to ship, which is as new as the last npm publish and no
469
+ // newer — so it may name only CLI surface that has always existed. Everything the
470
+ // deployment needs beyond that, host configuration included, is done inside the start
471
+ // lifecycle, where the code is the freshly pulled source rather than the image snapshot.
472
+ // `--pull-bundle` fetches the pre-built client bundle instead of building it in-pod.
473
+ cmd = [
474
+ `underpost start --build --run${pullBundle || skipFullBuild ? ' --pull-bundle --skip-full-build' : ''} ${deployId} ${env}`,
475
+ ];
457
476
  const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
458
477
  if (!volumes) volumes = [];
459
478
  const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
@@ -494,6 +513,12 @@ ${
494
513
  envFrom:
495
514
  - secretRef:
496
515
  name: underpost-config
516
+ # The deployment's own environment, with its .env.<env>.oci overlay already
517
+ # applied by 'app apply'. Optional so a deployment predating that Secret still
518
+ # starts; the in-pod 'app load' resolves the same overlay as a second path.
519
+ - secretRef:
520
+ name: ${appSecretName(deployId, env)}
521
+ optional: true
497
522
  ${
498
523
  internalStatusPort
499
524
  ? ` env:
@@ -724,7 +749,7 @@ ${Underpost.deploy
724
749
  if (!volume.claimName) continue;
725
750
  const pvcId = `${volume.claimName}-${deployId}-${env}-${deploymentVersion}`;
726
751
  const pvId = pvcId.replace(/^pvc-/, 'pv-');
727
- const hostPath = `/home/dd/engine/volume/${pvId}`;
752
+ const hostPath = `${HOST_VOLUME_ROOT}/${pvId}`;
728
753
  volumeYaml += `---\n${Underpost.deploy.persistentVolumeFactory({
729
754
  pvcId,
730
755
  namespace: options.namespace,
@@ -1087,6 +1112,41 @@ spec:
1087
1112
  kind: ClusterIssuer
1088
1113
  secretName: ${host}`;
1089
1114
  },
1115
+ /**
1116
+ * Mirrors each deploy's built development manifests into the project tree, and refreshes the
1117
+ * public default configuration they belong to.
1118
+ *
1119
+ * The project repository carries the development manifests of every routed deploy; the
1120
+ * private build directory is where they are produced. A manifest the build stopped producing
1121
+ * is removed here rather than left behind, so the two cannot disagree about what a deploy
1122
+ * ships.
1123
+ * @param {string[]} [deployIds] - Deploy ids to mirror; the route table plus `dd-cron` otherwise.
1124
+ * @returns {Array<{deployId: string, copied: string[], removed: string[]}>} What was mirrored.
1125
+ * @memberof UnderpostDeploy
1126
+ */
1127
+ mirrorDefaultManifests(deployIds = readDeployRoutes().concat(['dd-cron'])) {
1128
+ const mirrored = [];
1129
+ for (const deployId of deployIds) {
1130
+ const copied = [];
1131
+ const removed = [];
1132
+ for (const file of DEFAULT_MANIFEST_FILES) {
1133
+ const source = `./engine-private/conf/${deployId}/build/development/${file}`;
1134
+ const target = `./manifests/deployment/${deployId}-development/${file}`;
1135
+ if (fs.existsSync(source)) {
1136
+ fs.copySync(source, target);
1137
+ copied.push(file);
1138
+ } else if (fs.existsSync(target)) {
1139
+ fs.removeSync(target);
1140
+ removed.push(file);
1141
+ }
1142
+ }
1143
+ shellExec(`node bin new --dev --default-conf --deploy-id ${deployId}`);
1144
+ mirrored.push({ deployId, copied, removed });
1145
+ }
1146
+ logger.info('Default deployment manifests mirrored', { deployIds: mirrored.map(({ deployId }) => deployId) });
1147
+ return mirrored;
1148
+ },
1149
+
1090
1150
  /**
1091
1151
  * Retrieves the current traffic status for a deployment.
1092
1152
  * @param {string} deployId - Deployment ID for which the traffic status is being retrieved.
@@ -1697,13 +1757,13 @@ ${rules}`;
1697
1757
 
1698
1758
  /**
1699
1759
  * Node directory backing the static utility's volume, following the same
1700
- * `/home/dd/engine/volume/<pv>` convention as every other hostPath volume.
1760
+ * `HOST_VOLUME_ROOT/<pv>` convention as every other hostPath volume.
1701
1761
  * @param {object} [options] - Deploy/run options.
1702
1762
  * @returns {string} Absolute host path.
1703
1763
  * @memberof UnderpostDeploy
1704
1764
  */
1705
1765
  underpostGatewayRootFactory(options = {}) {
1706
- return options.underpostGatewayRoot || `/home/dd/engine/volume/${UNDERPOST_GATEWAY.volumeName}`;
1766
+ return options.underpostGatewayRoot || `${HOST_VOLUME_ROOT}/${UNDERPOST_GATEWAY.volumeName}`;
1707
1767
  },
1708
1768
 
1709
1769
  /**
@@ -1726,6 +1786,45 @@ ${rules}`;
1726
1786
  return `${ready}`.includes('true');
1727
1787
  },
1728
1788
 
1789
+ /**
1790
+ * Reports whether a Deployment object exists at all.
1791
+ *
1792
+ * The inactive colour of a blue/green pair is routinely absent, so callers
1793
+ * that act on it must distinguish "not deployed yet" from a failed lookup.
1794
+ * @param {string} deployment - Deployment name.
1795
+ * @param {string} [namespace] - Kubernetes namespace.
1796
+ * @returns {boolean} True when the object is present.
1797
+ */
1798
+ deploymentExists({ deployment, namespace = 'default' }) {
1799
+ return !!`${
1800
+ shellExec(`kubectl get deployment ${deployment} -n ${namespace} --ignore-not-found -o name`, {
1801
+ stdout: true,
1802
+ silent: true,
1803
+ silentOnError: true,
1804
+ }) || ''
1805
+ }`.trim();
1806
+ },
1807
+
1808
+ /**
1809
+ * Node a live Deployment is pinned to, read from its `nodeSelector`.
1810
+ *
1811
+ * A colour created next to a live one must inherit that placement: its
1812
+ * hostPath volumes exist on that node only.
1813
+ * @param {string} deployment - Deployment name.
1814
+ * @param {string} [namespace] - Kubernetes namespace.
1815
+ * @returns {string} Node hostname, or empty when the workload is unpinned or absent.
1816
+ */
1817
+ deploymentNode({ deployment, namespace = 'default' }) {
1818
+ if (!deployment) return '';
1819
+ return `${
1820
+ shellExec(
1821
+ `kubectl get deployment ${deployment} -n ${namespace} ` +
1822
+ `-o jsonpath='{.spec.template.spec.nodeSelector.kubernetes\.io/hostname}'`,
1823
+ { stdout: true, silent: true, silentOnError: true },
1824
+ ) || ''
1825
+ }`.trim();
1826
+ },
1827
+
1729
1828
  /**
1730
1829
  * Reports whether the Deployment controller has observed the current
1731
1830
  * generation and every desired replica is updated, Ready, and Available.
@@ -1881,6 +1980,11 @@ ${rules}`;
1881
1980
  ? 'project'
1882
1981
  : null,
1883
1982
  });
1983
+ // The documents were just written under the operator's home tree, whose policy label
1984
+ // (user_home_t) the unprivileged gateway container cannot read — it would answer 403 for
1985
+ // every one of them. Register the mapping for the volume root and restore this subtree, so
1986
+ // the files this pass created carry the shared container label.
1987
+ prepareContainerStorage(hostRoot);
1884
1988
  logger.info('Static edge documents placed', {
1885
1989
  deployId,
1886
1990
  podName: podName || '(no running workload; placed from this checkout)',
@@ -2142,8 +2246,7 @@ ${Underpost.deploy.buildCertManagerCertificate({ host, namespace })}
2142
2246
  EOF`);
2143
2247
  }
2144
2248
  return;
2145
- } else if (!deployList || deployList === 'dd')
2146
- deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
2249
+ } else if (!deployList || deployList === 'dd') deployList = readDeployRoutes().join(',');
2147
2250
  const deployIds = deployList
2148
2251
  .split(',')
2149
2252
  .map((id) => id.trim())
@@ -2187,11 +2290,19 @@ EOF`);
2187
2290
  Underpost.deploy.syncStaticAssets(deployId, env, options);
2188
2291
  return;
2189
2292
  }
2293
+ // Before the `--build-manifest` return: a manifest build is when the Secrets it mounts
2294
+ // must exist, and `run sync` reaches this command with `--build-manifest --info-router`.
2295
+ if (!options.disableUpdateUnderpostConfig) {
2296
+ Underpost.host.apply(domainContextFactory({ env, namespace }));
2297
+ // Each deployment's own env, OCI overlay applied. Without it the overlay reaches a pod
2298
+ // only through in-pod `app load`, so a stale image keeps the base file's 127.0.0.1.
2299
+ for (const _deployId of deployIds)
2300
+ Underpost.app.apply(domainContextFactory({ env, namespace, args: { 'deploy-id': _deployId } }));
2301
+ }
2190
2302
  if (options.infoRouter === true || options.buildManifest === true) {
2191
2303
  logger.info('router', await Underpost.deploy.routerFactory(deployList, env));
2192
2304
  return;
2193
2305
  }
2194
- if (!options.disableUpdateUnderpostConfig) Underpost.deploy.configMap(env);
2195
2306
 
2196
2307
  for (const _deployId of deployList.split(',')) {
2197
2308
  const deployId = _deployId.trim();
@@ -2377,27 +2488,6 @@ EOF`);
2377
2488
  }
2378
2489
  }
2379
2490
  },
2380
- /**
2381
- * Creates a Kubernetes Secret for a deployment (replaces configMap for secret data).
2382
- * Secrets are mounted as tmpfs (never written to node disk) and support RBAC restrictions.
2383
- * @param {string} env - Environment for which the secret is being created.
2384
- * @param {string} [namespace='default'] - Kubernetes namespace for the secret.
2385
- * @memberof UnderpostDeploy
2386
- */
2387
- configMap(env, namespace = 'default') {
2388
- const cronDeployId = cronDeployIdResolve() || 'dd-cron';
2389
- const envFilePath = `/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${env}`;
2390
- // `--from-env-file` turns every KEY=VALUE into a secret key that the Deployment injects via
2391
- // `envFrom`. Strip shell/runtime-critical keys (notably PATH) first — an injected PATH
2392
- // overrides the image's own and breaks coreutils/sudo resolution inside the pod.
2393
- const sanitizedEnvPath = `${envFilePath}.secret`;
2394
- fs.writeFileSync(sanitizedEnvPath, Underpost.secret.sanitizeSecretEnvFile(fs.readFileSync(envFilePath, 'utf8')));
2395
- shellExec(`kubectl delete secret underpost-config -n ${namespace} --ignore-not-found`);
2396
- shellExec(
2397
- `kubectl create secret generic underpost-config --from-env-file=${sanitizedEnvPath} --dry-run=client -o yaml | kubectl apply -f - -n ${namespace}`,
2398
- );
2399
- fs.removeSync(sanitizedEnvPath);
2400
- },
2401
2491
  /**
2402
2492
  * Switches the traffic for a deployment.
2403
2493
  *
@@ -2537,7 +2627,7 @@ EOF`);
2537
2627
  const grpcServicePath = `./engine-private/conf/${deployId}/build/${env}/grpc-service.yaml`;
2538
2628
  if (fs.existsSync(grpcServicePath)) shellExec(`kubectl apply -f ${grpcServicePath} -n ${namespace}`);
2539
2629
 
2540
- Underpost.env.set(`${deployId}-${env}-traffic`, targetTraffic);
2630
+ Underpost.host.store.set(`${deployId}-${env}-traffic`, targetTraffic);
2541
2631
  },
2542
2632
 
2543
2633
  /**
@@ -2547,9 +2637,8 @@ EOF`);
2547
2637
  * behaves identically everywhere:
2548
2638
  *
2549
2639
  * 1. **Explicit node** — `node` (the resolved `--node` value). Upstream
2550
- * runners derive it from the comma-path field or `--node-name`
2551
- * (`run sync`: `path.split(',')[4]` > `--node-name` > default) and from
2552
- * `--node-name` directly (`run instance`).
2640
+ * runners derive it from `--node-name` (`run sync`: `--node-name` >
2641
+ * cluster-type default; `run instance`: `--node-name` directly).
2553
2642
  * 2. **`UNDERPOST_DEPLOY_NODE` env** — for kubeadm / k3s, the configured
2554
2643
  * target node name. This makes hostPath PV `nodeAffinity` deterministic
2555
2644
  * regardless of where the manifest is *built*: building inside a
@@ -2632,7 +2721,7 @@ EOF`);
2632
2721
  const clusterContext = options.clusterContext || 'kind';
2633
2722
  const pvcId = `${volume.claimName}-${deployId}-${env}-${version}`;
2634
2723
  const pvId = `${volume.claimName.replace('pvc-', 'pv-')}-${deployId}-${env}-${version}`;
2635
- const rootVolumeHostPath = `/home/dd/engine/volume/${pvId}`;
2724
+ const rootVolumeHostPath = `${HOST_VOLUME_ROOT}/${pvId}`;
2636
2725
  if (options.gitClean && volume.volumeMountPath) {
2637
2726
  Underpost.repo.clean({ paths: [volume.volumeMountPath] });
2638
2727
  }
@@ -2653,9 +2742,15 @@ EOF`);
2653
2742
  const localHost = os.hostname();
2654
2743
  dataNode = options.nodeName || localHost;
2655
2744
  if (dataNode === localHost) {
2656
- // Target node is the control plane / current host: write directly.
2657
- if (!fs.existsSync(rootVolumeHostPath)) fs.mkdirSync(rootVolumeHostPath, { recursive: true });
2745
+ // Target node is the control plane / current host: write directly. The directory is
2746
+ // created owned by the invoking user — the root above it may be root-owned, and the copy
2747
+ // that follows is unprivileged — and labeled before as well as after the copy, so the
2748
+ // files land already carrying the shared container label instead of inheriting the
2749
+ // operator home tree's.
2750
+ const { uid, gid } = os.userInfo();
2751
+ ensureContainerStorage(rootVolumeHostPath, { execute: shellExec, owner: `${uid}:${gid}` });
2658
2752
  fs.copySync(volume.volumeMountPath, rootVolumeHostPath);
2753
+ prepareContainerStorage(rootVolumeHostPath);
2659
2754
  } else {
2660
2755
  // Target node is remote: fs.copySync would only write the control-plane
2661
2756
  // filesystem, leaving the real node's hostPath empty. Ship the folder to
@@ -2675,6 +2770,9 @@ EOF`);
2675
2770
  host: nodeHost,
2676
2771
  localDir: volume.volumeMountPath,
2677
2772
  remoteDir: rootVolumeHostPath,
2773
+ // The volume is mounted on that node, so the label has to exist there. Labeling the
2774
+ // control plane's copy of the path would leave the pod reading an unlabeled tree.
2775
+ containerStorage: true,
2678
2776
  ...(options.sshKeyPath ? { keyPath: options.sshKeyPath } : {}),
2679
2777
  });
2680
2778
  }
@@ -10,8 +10,8 @@
10
10
 
11
11
  import fs from 'fs-extra';
12
12
  import nodePath from 'path';
13
- import { getRootDirectory, shellExec } from '../server/process.js';
14
- import { loggerFactory } from '../server/logger.js';
13
+ import { getRootDirectory, shellExec } from '../server/runtime/process.js';
14
+ import { loggerFactory } from '../server/ops/logger.js';
15
15
  import Nginx from '../runtime/nginx/Nginx.js';
16
16
 
17
17
  const logger = loggerFactory(import.meta);
@@ -143,7 +143,7 @@ class UnderpostDockerCompose {
143
143
  // Point the template .env.example at the Docker service-discovery hosts
144
144
  // before `new engine` copies it. The generated dd-engine/.env.development
145
145
  // is seeded from this file and is applied over the process env by
146
- // loadConf (src/server/conf.js), so without this the engine would fall
146
+ // loadConf (src/server/runtime/conf.js), so without this the engine would fall
147
147
  // back to the .env.example localhost defaults for DB_HOST/VALKEY_HOST.
148
148
  'sed -i "s#^DB_HOST=.*#DB_HOST=$${DB_HOST}#" .env.example',
149
149
  'sed -i "s#^VALKEY_HOST=.*#VALKEY_HOST=$${VALKEY_HOST}#" .env.example',
@@ -151,7 +151,7 @@ class UnderpostDockerCompose {
151
151
  'cd /home/dd',
152
152
  'underpost new engine',
153
153
  ];
154
- return ['underpost secret underpost --create-from-env', `underpost start --build --run ${deployId} ${env}`];
154
+ return [`underpost start --build --run ${deployId} ${env}`];
155
155
  }
156
156
 
157
157
  /**
@@ -192,7 +192,7 @@ services:
192
192
  MONGO_IMAGE=mongo:latest
193
193
  VALKEY_IMAGE=valkey/valkey:latest
194
194
  APP_IMAGE=underpost/underpost-engine
195
- APP_TAG=v3.2.90
195
+ APP_TAG=v3.3.73
196
196
  PROXY_IMAGE=nginx:stable-alpine
197
197
  PROMETHEUS_IMAGE=prom/prometheus:latest
198
198
  GRAFANA_IMAGE=grafana/grafana:latest
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Shared command surface for the four domains.
3
+ *
4
+ * `secret`, `host`, `app` and `state` are registered from one factory, so the action list and the
5
+ * option list exist exactly once. Symmetry is not a convention the four commands agree to follow
6
+ * — it is structural: a verb or a flag added here appears on all four, and one that is not here
7
+ * cannot appear on any of them.
8
+ *
9
+ * The three configuration domains carry a durable source; `state` is the runtime monitoring /
10
+ * telemetry layer and carries none, so its actions read and export a live observation instead.
11
+ * The verbs are the same either way — see {@link UnderpostState} for what each means there.
12
+ * @module src/cli/domains.js
13
+ * @namespace UnderpostDomains
14
+ */
15
+
16
+ /**
17
+ * The canonical action set. Every domain implements all of these with the same meaning:
18
+ *
19
+ * | action | direction |
20
+ * |---------|-----------------------------------|
21
+ * | setup | onboard the domain, idempotently |
22
+ * | load | durable store -> local runtime |
23
+ * | publish | local runtime -> durable store |
24
+ * | apply | durable store -> live cluster |
25
+ * | status | read-only report |
26
+ * | rotate | replace the current projection |
27
+ * | clean | withdraw local traces |
28
+ * @constant {Array<{name: string, summary: string}>}
29
+ * @memberof UnderpostDomains
30
+ */
31
+ const DOMAIN_ACTIONS = [
32
+ { name: 'setup', summary: 'Onboards the domain: provisions whatever it needs, then converges it. Idempotent.' },
33
+ { name: 'load', summary: 'Loads the durable source into the local runtime environment.' },
34
+ { name: 'publish', summary: 'Writes the local runtime environment into the durable source.' },
35
+ { name: 'apply', summary: 'Projects the durable source into the live cluster.' },
36
+ { name: 'status', summary: 'Read-only report of the domain: sources, keys, and drift from the cluster.' },
37
+ { name: 'rotate', summary: 'Replaces the current projection or encryption identity.' },
38
+ { name: 'clean', summary: 'Withdraws the domain traces from the local filesystem.' },
39
+ ];
40
+
41
+ /**
42
+ * The canonical option set, identical on every action of every domain.
43
+ *
44
+ * Deliberately five. Everything a single domain used to carry its own flag for — an Age
45
+ * recipient, a secret name list, a sub-configuration — passes through `--args` instead, so the
46
+ * visible surface does not grow when one domain gains a parameter.
47
+ * @constant {Array<{flags: string, description: string}>}
48
+ * @memberof UnderpostDomains
49
+ */
50
+ const DOMAIN_OPTIONS = [
51
+ { flags: '--env <env>', description: 'Target environment: development | production | test (default: production).' },
52
+ { flags: '--namespace <namespace>', description: 'Kubernetes namespace to act on (default: default).' },
53
+ {
54
+ flags: '--args <key=value-list>',
55
+ description:
56
+ 'Comma-separated domain parameters, e.g. `names=postgres-secret`, `recipient=age1...`, `sub-conf=nexodev`.',
57
+ },
58
+ { flags: '--dry-run', description: 'Reports what the action would change without changing anything.' },
59
+ { flags: '--force', description: 'Confirms the irreversible variant of the action.' },
60
+ ];
61
+
62
+ /**
63
+ * Key-level operators a domain may register alongside the canonical seven.
64
+ *
65
+ * They are not part of {@link DOMAIN_ACTIONS} on purpose: the canonical set is a bulk-lifecycle
66
+ * verb set, symmetric across every domain, with no place for per-key reads and writes. A domain
67
+ * that owns a key-value store opts into these instead of getting a command of its own — the host
68
+ * configuration store is the host domain's, so `underpost host get KEY` is where it is read.
69
+ * @constant {Array<string>}
70
+ * @memberof UnderpostDomains
71
+ */
72
+ const DOMAIN_STORE_OPERATORS = ['get', 'set', 'delete', 'list'];
73
+
74
+ const DOMAIN_STORE_OPTIONS = [
75
+ { flags: '--plain', description: 'Prints the value in plain text (get).' },
76
+ { flags: '--filter <keyword>', description: 'Filters by matching key or value (list).' },
77
+ { flags: '--copy', description: 'Copies the value to the clipboard (get).' },
78
+ ];
79
+
80
+ /**
81
+ * Parses `--args key=value,key=value` into an object.
82
+ *
83
+ * Values may contain `=` (an Age recipient does not, a password can), so only the first
84
+ * separator splits. A bare `key` is the boolean shorthand for `key=true`.
85
+ * @param {string} [value] - Raw `--args` string.
86
+ * @returns {Object<string, string|boolean>} Parsed parameters.
87
+ * @memberof UnderpostDomains
88
+ */
89
+ const parseDomainArgs = (value) => {
90
+ // Idempotent: an already-parsed object passes through, so normalizing a context twice — once
91
+ // at the CLI boundary, once inside the action a programmatic caller invoked directly — is safe.
92
+ if (value && typeof value === 'object') return { ...value };
93
+ return Object.fromEntries(
94
+ `${value ?? ''}`
95
+ .split(',')
96
+ .map((entry) => entry.trim())
97
+ .filter(Boolean)
98
+ .map((entry) => {
99
+ const index = entry.indexOf('=');
100
+ return index === -1 ? [entry, true] : [entry.slice(0, index).trim(), entry.slice(index + 1)];
101
+ }),
102
+ );
103
+ };
104
+
105
+ /**
106
+ * Normalizes the parsed options handed to a domain action, so every domain receives the same
107
+ * shape and none of them re-derives a default of its own.
108
+ * @param {object} [options] - Commander options for the invocation.
109
+ * @returns {{env: string, namespace: string, args: object, dryRun: boolean, force: boolean}} Normalized context.
110
+ * @memberof UnderpostDomains
111
+ */
112
+ const domainContextFactory = (options = {}) => ({
113
+ env: `${options.env ?? ''}`.trim() || 'production',
114
+ namespace: `${options.namespace ?? ''}`.trim() || 'default',
115
+ args: parseDomainArgs(options.args),
116
+ dryRun: options.dryRun === true,
117
+ force: options.force === true,
118
+ });
119
+
120
+ /**
121
+ * Registers one domain as a top-level command carrying the full canonical surface.
122
+ *
123
+ * The action is a required argument rather than a flag so the three commands read as
124
+ * `underpost <domain> <action>`, and an unknown action fails against the shared list instead of
125
+ * silently doing nothing.
126
+ * @param {object} program - The commander program.
127
+ * @param {object} domain - Domain registration.
128
+ * @param {string} domain.name - Command name (`secret`, `host`, `app`, `state`).
129
+ * @param {boolean} [domain.store] - Whether this domain owns a key-value store, and so carries
130
+ * the key-level operators alongside the canonical actions.
131
+ * @param {string} domain.description - One-line command description.
132
+ * @param {Function} domain.api - Returns the domain API implementing every canonical action.
133
+ * @returns {object} The registered commander command.
134
+ * @memberof UnderpostDomains
135
+ */
136
+ const registerDomainCommand = (program, { name, description, api, store = false }) => {
137
+ const actions = DOMAIN_ACTIONS.map((action) => action.name);
138
+ const operators = store ? DOMAIN_STORE_OPERATORS : [];
139
+ const command = program
140
+ .command(name)
141
+ .argument(
142
+ '<action>',
143
+ `Action to run. One of: ${[...actions, ...operators].join(', ')}.\n` +
144
+ DOMAIN_ACTIONS.map((action) => ` ${action.name.padEnd(8)} ${action.summary}`).join('\n') +
145
+ (store ? `\n ${'get|set|delete|list'.padEnd(8)} Key-level access to the store this domain owns.` : ''),
146
+ )
147
+ .description(description);
148
+ // Only a store-owning domain takes a key and a value; the others keep the bare action surface.
149
+ if (store) {
150
+ command.argument('[key]', 'Key to act on, for the key-level operators.');
151
+ command.argument('[value]', 'Value to write, for the `set` operator.');
152
+ }
153
+ // Commander hands the action `(...arguments, options, command)`, so the options object is
154
+ // second from the end no matter how many positionals this domain declared.
155
+ command.action((...received) => {
156
+ const options = received[received.length - 2];
157
+ const [action, key, value] = received;
158
+ if (operators.includes(action)) {
159
+ if (action !== 'list' && !key) throw new Error(`${name} ${action} requires a key`);
160
+ if (action === 'set' && value === undefined) throw new Error(`${name} set requires a value`);
161
+ return api()[action](key, value, options);
162
+ }
163
+ if (!actions.includes(action))
164
+ throw new Error(`Unknown ${name} action: ${action} (expected ${[...actions, ...operators].join(', ')})`);
165
+ const implementation = api()[action];
166
+ // Guards the contract at the boundary: a domain that has not implemented the full
167
+ // canonical set fails here by name, rather than as `undefined is not a function`.
168
+ if (typeof implementation !== 'function')
169
+ throw new Error(`Domain '${name}' does not implement the '${action}' action`);
170
+ return implementation(domainContextFactory(options));
171
+ });
172
+ for (const option of DOMAIN_OPTIONS) command.option(option.flags, option.description);
173
+ if (store) for (const option of DOMAIN_STORE_OPTIONS) command.option(option.flags, option.description);
174
+ return command;
175
+ };
176
+
177
+ export {
178
+ DOMAIN_ACTIONS,
179
+ DOMAIN_OPTIONS,
180
+ DOMAIN_STORE_OPERATORS,
181
+ domainContextFactory,
182
+ parseDomainArgs,
183
+ registerDomainCommand,
184
+ };