@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/ipfs.js CHANGED
@@ -4,14 +4,19 @@
4
4
  * @namespace UnderpostIPFS
5
5
  */
6
6
 
7
- import { loggerFactory } from '../server/logger.js';
8
- import { shellExec } from '../server/process.js';
9
- import { resolveReplicaCount } from '../server/conf.js';
7
+ import { loggerFactory } from '../server/ops/logger.js';
8
+ import { shellExec } from '../server/runtime/process.js';
9
+ import { resolveReplicaCount } from '../server/runtime/conf.js';
10
10
  import fs from 'fs-extra';
11
+ import dir from 'node:path';
11
12
  import Underpost from '../index.js';
12
13
 
13
14
  const logger = loggerFactory(import.meta);
14
15
 
16
+ // The managed secret this module owns; its origin, its encrypted manifest and its Kubernetes
17
+ // projection all resolve from this one name.
18
+ const IPFS_SECRET_NAME = 'ipfs-cluster-secret';
19
+
15
20
  const IPFS_DEFAULT_REPLICA_COUNT = 3;
16
21
 
17
22
  /**
@@ -25,27 +30,32 @@ class UnderpostIPFS {
25
30
  static API = {
26
31
  /**
27
32
  * @method resolveCredentials
28
- * @description Resolves the IPFS cluster credentials from engine-private/ if they
29
- * already exist, otherwise generates new ones (hex cluster secret + peer identity
30
- * via ipfs-cluster-service init) and persists them with mode 0o600.
33
+ * @description Resolves the IPFS cluster origin credentials, generating them when absent
34
+ * (hex cluster secret + peer identity via ipfs-cluster-service init) and persisting them
35
+ * with mode 0o600.
36
+ *
37
+ * Paths come from the workload secret domain, so this module names no credential path of its
38
+ * own and the pair sits in the deploy secret area with every other origin credential.
39
+ *
31
40
  * Idempotent and self-healing: an existing pair is reused untouched, and a missing or
32
41
  * unreadable one is regenerated. Both files are always rewritten together — the peer id and
33
42
  * the private key are two halves of one identity, so reusing one with a freshly minted other
34
43
  * would advertise a peer id that does not match the key.
35
- * @param {string} privateDir - Absolute path to the engine-private directory.
36
44
  * @returns {{ CLUSTER_SECRET: string, IDENTITY_JSON: { id: string, private_key: string }, generated: boolean }}
37
45
  * @memberof UnderpostIPFS
38
46
  */
39
- resolveCredentials(privateDir) {
40
- const secretPath = `${privateDir}/ipfs-cluster-secret`;
41
- const identityPath = `${privateDir}/ipfs-cluster-identity.json`;
47
+ resolveCredentials() {
48
+ const sources = Underpost.secret.seedSources(IPFS_SECRET_NAME);
49
+ const secretPath = sources['cluster-secret'];
50
+ const identityPath = sources['bootstrap-peer-priv-key'];
51
+ const privateDir = dir.dirname(secretPath);
42
52
 
43
53
  if (fs.existsSync(secretPath) && fs.existsSync(identityPath)) {
44
54
  try {
45
55
  const CLUSTER_SECRET = fs.readFileSync(secretPath, 'utf8').trim();
46
56
  const IDENTITY_JSON = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
47
57
  if (CLUSTER_SECRET && IDENTITY_JSON?.id && IDENTITY_JSON?.private_key) {
48
- logger.info('Reusing existing IPFS cluster credentials from engine-private/');
58
+ logger.info('Reusing existing IPFS cluster credentials', { secretPath, identityPath });
49
59
  return { CLUSTER_SECRET, IDENTITY_JSON, generated: false };
50
60
  }
51
61
  logger.warn('Existing IPFS cluster credentials are incomplete; regenerating');
@@ -54,7 +64,7 @@ class UnderpostIPFS {
54
64
  }
55
65
  }
56
66
 
57
- logger.info('Generating new IPFS cluster credentials and persisting to engine-private/');
67
+ logger.info('Generating new IPFS cluster credentials', { secretPath, identityPath });
58
68
 
59
69
  // ipfs-cluster-service requires CLUSTER_SECRET as a 64-char hex string.
60
70
  // base64 (openssl rand -base64 32) contains '/', '+', '=' which are invalid hex bytes.
@@ -115,7 +125,7 @@ class UnderpostIPFS {
115
125
  );
116
126
  fs.chmodSync(stagePath, 0o600);
117
127
  // encrypt() stages, validates, moves into place, and shreds the plaintext source.
118
- Underpost.secret.sops.encrypt(stagePath, options.namespace, { force: true });
128
+ Underpost.secret.encrypt(stagePath, options.namespace, { force: true });
119
129
  logger.info('Re-encrypted regenerated IPFS credentials into the SOPS store');
120
130
  } finally {
121
131
  fs.removeSync(stageDir);
@@ -148,28 +158,22 @@ class UnderpostIPFS {
148
158
  * @method applySecrets
149
159
  * @description Creates (or idempotently updates) the Kubernetes Secret and env ConfigMap
150
160
  * that the StatefulSet pods read at startup.
151
- * - Secret `ipfs-cluster-secret`: cluster-secret + bootstrap-peer-priv-key
161
+ * - Secret `ipfs-cluster-secret`: cluster-secret + bootstrap-peer-priv-key, projected by the
162
+ * secret domain from the encrypted store or, failing that, from the origin credentials
152
163
  * - ConfigMap `env-config`: bootstrap-peer-id + CLUSTER_SVC_NAME
153
- * @param {{ CLUSTER_SECRET: string, IDENTITY_JSON: { id: string, private_key: string } }} credentials
164
+ * @param {{ IDENTITY_JSON: { id: string } }} credentials - Only the public peer id is read here; the Secret keys are projected by the secret domain.
154
165
  * @param {object} options
155
166
  * @param {string} options.namespace - Kubernetes namespace.
156
167
  * @memberof UnderpostIPFS
157
168
  */
158
- applySecrets({ CLUSTER_SECRET, IDENTITY_JSON }, options) {
169
+ applySecrets({ IDENTITY_JSON }, options) {
159
170
  logger.info('Applying IPFS cluster Kubernetes Secret and env ConfigMap');
160
171
 
161
- // Encrypted store first, origin generate/read logic only when no manifest is stored.
162
- // `--from-literal` places the cluster secret and the peer private key in the command
163
- // string, so the seed path is kept out of the command log; the encrypted path never
164
- // exposes them at all.
165
- if (!Underpost.secret.sops.applyIfPresent('ipfs-cluster-secret', options.namespace))
166
- shellExec(
167
- `kubectl create secret generic ipfs-cluster-secret \
168
- --from-literal=cluster-secret=${CLUSTER_SECRET} \
169
- --from-literal=bootstrap-peer-priv-key=${IDENTITY_JSON.private_key} \
170
- --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
171
- { disableLog: true },
172
- );
172
+ // Encrypted store first; the origin projection runs only when no manifest is stored. Both
173
+ // paths keep the credentials out of the command string the store streams a decrypt into
174
+ // kubectl, and the origin projection stages the values on tmpfs.
175
+ if (!Underpost.secret.applyIfPresent(IPFS_SECRET_NAME, options.namespace))
176
+ Underpost.secret.applyFromOriginSeed(IPFS_SECRET_NAME, options.namespace);
173
177
 
174
178
  shellExec(
175
179
  `kubectl create configmap env-config \
@@ -227,13 +231,13 @@ sudo sysctl -w net.core.wmem_max=7500000`,
227
231
  Underpost.cluster.pullImage('ipfs/ipfs-cluster:latest', options);
228
232
  }
229
233
 
230
- const credentials = Underpost.ipfs.resolveCredentials(`${underpostRoot}/engine-private`);
234
+ const credentials = Underpost.ipfs.resolveCredentials();
231
235
 
232
236
  // `env-config` advertises `bootstrap-peer-id` from the local identity file — the peer id is
233
237
  // not carried in the Secret. So a regeneration invalidates any stored manifest: it would
234
238
  // pair the previous private key with the new id and the cluster would never form. Re-encrypt
235
239
  // the fresh pair so the store and the ConfigMap stay one identity.
236
- if (credentials.generated && Underpost.secret.sops.has('ipfs-cluster-secret', options.namespace))
240
+ if (credentials.generated && Underpost.secret.has(IPFS_SECRET_NAME, options.namespace))
237
241
  Underpost.ipfs.storeCredentials(credentials, options);
238
242
 
239
243
  const ipfsReplicas = resolveReplicaCount(options.replicas, IPFS_DEFAULT_REPLICA_COUNT);
@@ -7,7 +7,7 @@
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import { fileURLToPath } from 'url';
10
- import { loggerFactory } from '../server/logger.js';
10
+ import { loggerFactory } from '../server/ops/logger.js';
11
11
 
12
12
  const logger = loggerFactory(import.meta);
13
13
  const __filename = fileURLToPath(import.meta.url);
@@ -6,26 +6,37 @@
6
6
  * @namespace UnderpostKubectl
7
7
  */
8
8
 
9
- import { loggerFactory } from '../server/logger.js';
10
- import { shellExec } from '../server/process.js';
9
+ import { loggerFactory, redactSensitiveText } from '../server/ops/logger.js';
10
+ import { shellExec, sleepSync } from '../server/runtime/process.js';
11
+ import { timer } from '../client/components/core/CommonJs.js';
11
12
  import Underpost from '../index.js';
12
13
 
13
14
  const logger = loggerFactory(import.meta);
14
15
 
16
+ const DEFAULT_POD_RETRIES = 2;
17
+ const RETRY_BASE_DELAY_MS = 2000;
18
+ const DEFAULT_EXEC_READY_ATTEMPTS = 5;
19
+ const DEFAULT_CREATION_TIMEOUT_SECONDS = 120;
20
+ const DEFAULT_READY_TIMEOUT_SECONDS = 180;
21
+
22
+ /**
23
+ * Exponential backoff for attempt `n`: 2s, 4s, 8s, …
24
+ * @param {number} attempt - 1-based attempt that just failed.
25
+ * @param {number} [baseDelayMs=2000] - Delay after the first failure.
26
+ * @returns {number} Milliseconds to wait before the next attempt.
27
+ * @memberof UnderpostKubectl
28
+ */
29
+ const backoffDelayMs = (attempt, baseDelayMs = RETRY_BASE_DELAY_MS) => baseDelayMs * 2 ** (attempt - 1);
30
+
15
31
  /**
16
- * Redacts credentials from shell command strings before logging.
17
- * Masks passwords in `-p<password>`, `--password=<password>`, and `-P <password>` patterns.
18
- * @param {string} cmd - The raw command string.
19
- * @returns {string} The command with credentials replaced by `***`.
32
+ * Wraps a command in `timeout(1)` so a stalled API-server stream cannot hang a job forever.
33
+ * @param {string} command - Command to bound.
34
+ * @param {number} timeoutSeconds - Wall-clock budget; `0`/falsy returns the command unchanged.
35
+ * @returns {string} The bounded command.
20
36
  * @memberof UnderpostKubectl
21
37
  */
22
- const sanitizeCommand = (cmd) => {
23
- if (typeof cmd !== 'string') return cmd;
24
- return cmd
25
- .replace(/-p['"]?[^\s'"]+/g, '-p***')
26
- .replace(/--password=['"]?[^\s'"]+/g, '--password=***')
27
- .replace(/-P\s+['"]?[^\s'"]+/g, '-P ***');
28
- };
38
+ const withTimeout = (command, timeoutSeconds) =>
39
+ timeoutSeconds > 0 ? `timeout --kill-after=10s ${timeoutSeconds}s ${command}` : command;
29
40
 
30
41
  /**
31
42
  * @class UnderpostKubectl
@@ -80,22 +91,187 @@ class UnderpostKubectl {
80
91
 
81
92
  /**
82
93
  * Executes a kubectl command with credential-safe logging and error propagation.
94
+ * Retries any failure up to `retries` times with exponential backoff; callers whose
95
+ * command is not safe to replay pass `retries: 0`.
83
96
  * @param {string} command - Full kubectl command string.
84
97
  * @param {object} [options={}] - Execution options.
85
98
  * @param {string} [options.context=''] - Human-readable label for log messages.
99
+ * @param {number} [options.retries=0] - Extra attempts allowed after a failure.
100
+ * @param {number} [options.baseDelayMs=2000] - Delay after the first failure; doubles per attempt.
101
+ * @param {number} [options.timeoutSeconds=0] - Wall-clock budget per attempt; `0` disables the bound.
102
+ * @param {string} [options.podName=''] - Pod the command targets; retries wait for it to accept exec again.
103
+ * @param {string} [options.namespace='default'] - Namespace of `podName`.
86
104
  * @returns {string} stdout output from the command.
87
- * @throws {Error} Re-throws any execution error after logging.
105
+ * @throws {Error} Re-throws the last execution error after logging.
88
106
  * @memberof UnderpostKubectl
89
107
  */
90
108
  run(command, options = {}) {
91
- const { context = '' } = options;
92
- try {
93
- logger.info(`Executing kubectl command`, { command: sanitizeCommand(command), context });
94
- return shellExec(command, { stdout: true, disableLog: true });
95
- } catch (error) {
96
- logger.error(`kubectl command failed`, { command: sanitizeCommand(command), error: error.message, context });
97
- throw error;
109
+ const {
110
+ context = '',
111
+ retries = 0,
112
+ baseDelayMs = RETRY_BASE_DELAY_MS,
113
+ timeoutSeconds = 0,
114
+ podName = '',
115
+ namespace = 'default',
116
+ } = options;
117
+ const attempts = Math.max(0, retries) + 1;
118
+ const boundedCommand = withTimeout(command, timeoutSeconds);
119
+
120
+ for (let attempt = 1; ; attempt++) {
121
+ try {
122
+ logger.info(`Executing kubectl command`, {
123
+ command: redactSensitiveText(command),
124
+ context,
125
+ ...(attempts > 1 ? { attempt, attempts } : {}),
126
+ });
127
+ return shellExec(boundedCommand, { stdout: true, disableLog: true });
128
+ } catch (error) {
129
+ if (attempt === attempts) {
130
+ logger.error(`kubectl command failed`, {
131
+ command: redactSensitiveText(command),
132
+ error: error.message,
133
+ context,
134
+ attempts,
135
+ });
136
+ throw error;
137
+ }
138
+ const delayMs = backoffDelayMs(attempt, baseDelayMs);
139
+ logger.warn(`kubectl command failed, retrying`, {
140
+ command: redactSensitiveText(command),
141
+ context,
142
+ attempt,
143
+ attempts,
144
+ delayMs,
145
+ error: error.message,
146
+ });
147
+ sleepSync(delayMs);
148
+ // An OOM kill takes the container with it (exit 137), so the replay would land on
149
+ // a container that is still coming back up. Wait for it rather than burning an attempt.
150
+ if (podName) Underpost.kubectl.ensureExecReady({ podName, namespace });
151
+ }
152
+ }
153
+ },
154
+
155
+ /**
156
+ * Polls until the pod can actually accept an exec stream.
157
+ *
158
+ * `kubectl wait --for=condition=Ready` reads the pod status the API server last
159
+ * recorded, which stays `Ready` for a window after the container underneath is
160
+ * gone — the exact gap that makes a dump fail with `container not found`. This
161
+ * probes the real thing: a no-op exec into the container.
162
+ *
163
+ * @param {object} params
164
+ * @param {string} params.podName - Pod to gate on.
165
+ * @param {string} [params.namespace='default'] - Pod namespace.
166
+ * @param {number} [params.attempts=5] - Probe attempts before giving up.
167
+ * @param {number} [params.baseDelayMs=2000] - Delay after the first probe; doubles per attempt.
168
+ * @param {number} [params.readyTimeoutSeconds=30] - Budget for the readiness condition wait.
169
+ * @param {number} [params.probeTimeoutSeconds=15] - Budget for the no-op exec probe.
170
+ * @returns {boolean} `true` when the pod accepted an exec stream.
171
+ * @memberof UnderpostKubectl
172
+ */
173
+ ensureExecReady({
174
+ podName,
175
+ namespace = 'default',
176
+ attempts = DEFAULT_EXEC_READY_ATTEMPTS,
177
+ baseDelayMs = RETRY_BASE_DELAY_MS,
178
+ readyTimeoutSeconds = 30,
179
+ probeTimeoutSeconds = 15,
180
+ }) {
181
+ const probeOptions = { silent: true, silentOnError: true, disableLog: true };
182
+ for (let attempt = 1; attempt <= attempts; attempt++) {
183
+ shellExec(
184
+ `kubectl wait --for=condition=Ready pod/${podName} -n ${namespace} --timeout=${readyTimeoutSeconds}s`,
185
+ probeOptions,
186
+ );
187
+ const probe = shellExec(
188
+ withTimeout(`sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "exit 0"`, probeTimeoutSeconds),
189
+ probeOptions,
190
+ );
191
+ if (probe && probe.code === 0) return true;
192
+ if (attempt < attempts) sleepSync(backoffDelayMs(attempt, baseDelayMs));
193
+ }
194
+ logger.error('Pod never became exec-ready', { podName, namespace, attempts });
195
+ return false;
196
+ },
197
+
198
+ /**
199
+ * Waits for a pod object to exist.
200
+ *
201
+ * `kubectl wait` fails on a missing object rather than waiting for one to appear, so any
202
+ * readiness wait issued while the controller is still creating the pod returns
203
+ * `Error from server (NotFound)`. A StatefulSet with `podManagementPolicy: OrderedReady`
204
+ * hits this every rollout: member N+1 is created only once N reports Ready.
205
+ *
206
+ * @param {object} params
207
+ * @param {string} params.podName - Pod to wait for.
208
+ * @param {string} [params.namespace='default'] - Pod namespace.
209
+ * @param {number} [params.timeoutSeconds=120] - Maximum wait window.
210
+ * @param {number} [params.intervalMs=2000] - Poll interval.
211
+ * @returns {Promise<boolean>} `true` once the pod object exists.
212
+ * @memberof UnderpostKubectl
213
+ */
214
+ async waitForPodCreation({
215
+ podName,
216
+ namespace = 'default',
217
+ timeoutSeconds = DEFAULT_CREATION_TIMEOUT_SECONDS,
218
+ intervalMs = 2000,
219
+ }) {
220
+ const deadline = Date.now() + timeoutSeconds * 1000;
221
+ while (Date.now() < deadline) {
222
+ const found = shellExec(`kubectl get pod ${podName} -n ${namespace} --ignore-not-found -o name`, {
223
+ stdout: true,
224
+ silent: true,
225
+ silentOnError: true,
226
+ disableLog: true,
227
+ });
228
+ if (`${found || ''}`.trim()) return true;
229
+ await timer(intervalMs);
98
230
  }
231
+ return false;
232
+ },
233
+
234
+ /**
235
+ * Waits for each named pod to be created and then to report Ready.
236
+ *
237
+ * Never throws: a pod that never arrives or never becomes Ready is returned to the caller,
238
+ * which owns the diagnosis — a stalled rollout usually needs surrounding context (volume
239
+ * bindings, events) that a bare non-zero exit code cannot carry.
240
+ *
241
+ * @param {object} params
242
+ * @param {string[]} params.podNames - Pods to wait for, in order.
243
+ * @param {string} [params.namespace='default'] - Pod namespace.
244
+ * @param {number} [params.creationTimeoutSeconds=120] - Per-pod budget for the pod to appear.
245
+ * @param {number} [params.readyTimeoutSeconds=180] - Per-pod budget for the Ready condition.
246
+ * @returns {Promise<string[]>} Names of the pods that did not become ready (empty = all good).
247
+ * @memberof UnderpostKubectl
248
+ */
249
+ async waitForPodsReady({
250
+ podNames,
251
+ namespace = 'default',
252
+ creationTimeoutSeconds = DEFAULT_CREATION_TIMEOUT_SECONDS,
253
+ readyTimeoutSeconds = DEFAULT_READY_TIMEOUT_SECONDS,
254
+ }) {
255
+ const failed = [];
256
+ for (const podName of podNames) {
257
+ if (
258
+ !(await Underpost.kubectl.waitForPodCreation({ podName, namespace, timeoutSeconds: creationTimeoutSeconds }))
259
+ ) {
260
+ logger.error('Pod was never created', { podName, namespace, timeoutSeconds: creationTimeoutSeconds });
261
+ failed.push(podName);
262
+ continue;
263
+ }
264
+
265
+ const result = shellExec(
266
+ `kubectl wait --for=condition=Ready pod/${podName} -n ${namespace} --timeout=${readyTimeoutSeconds}s`,
267
+ { silentOnError: true },
268
+ );
269
+ if (result.code !== 0) {
270
+ logger.error('Pod did not become ready', { podName, namespace, timeoutSeconds: readyTimeoutSeconds });
271
+ failed.push(podName);
272
+ }
273
+ }
274
+ return failed;
99
275
  },
100
276
 
101
277
  /**
@@ -104,22 +280,21 @@ class UnderpostKubectl {
104
280
  * @param {string} params.podName - Target pod name.
105
281
  * @param {string} params.namespace - Pod namespace.
106
282
  * @param {string} params.command - Shell command to run inside the container.
283
+ * @param {number} [params.retries=2] - Extra attempts allowed after a failure, with exponential backoff.
284
+ * @param {number} [params.timeoutSeconds=0] - Wall-clock budget per attempt; `0` disables the bound.
107
285
  * @returns {string} stdout output from the in-pod command.
108
286
  * @throws {Error} Re-throws any execution error after logging.
109
287
  * @memberof UnderpostKubectl
110
288
  */
111
- exec({ podName, namespace, command }) {
112
- try {
113
- const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
114
- return Underpost.kubectl.run(kubectlCmd, { context: `exec in pod ${podName}` });
115
- } catch (error) {
116
- logger.error('Failed to execute command in pod', {
117
- podName,
118
- command: sanitizeCommand(command),
119
- error: error.message,
120
- });
121
- throw error;
122
- }
289
+ exec({ podName, namespace, command, retries = DEFAULT_POD_RETRIES, timeoutSeconds = 0 }) {
290
+ const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
291
+ return Underpost.kubectl.run(kubectlCmd, {
292
+ context: `exec in pod ${podName}`,
293
+ retries,
294
+ timeoutSeconds,
295
+ podName,
296
+ namespace,
297
+ });
123
298
  },
124
299
 
125
300
  /**
@@ -129,16 +304,16 @@ class UnderpostKubectl {
129
304
  * @param {string} params.podName - Target pod name.
130
305
  * @param {string} params.namespace - Pod namespace.
131
306
  * @param {string} params.destPath - Destination path inside the container.
307
+ * @param {number} [params.retries=2] - Extra attempts allowed after a failure, with exponential backoff.
132
308
  * @returns {boolean} `true` on success, `false` on error.
133
309
  * @memberof UnderpostKubectl
134
310
  */
135
- cpTo({ sourcePath, podName, namespace, destPath }) {
311
+ cpTo({ sourcePath, podName, namespace, destPath, retries = DEFAULT_POD_RETRIES }) {
136
312
  try {
137
313
  const command = `sudo kubectl cp ${sourcePath} ${namespace}/${podName}:${destPath}`;
138
- Underpost.kubectl.run(command, { context: `copy to pod ${podName}` });
314
+ Underpost.kubectl.run(command, { context: `copy to pod ${podName}`, retries, podName, namespace });
139
315
  return true;
140
- } catch (error) {
141
- logger.error('Failed to copy file to pod', { sourcePath, podName, destPath, error: error.message });
316
+ } catch {
142
317
  return false;
143
318
  }
144
319
  },
@@ -150,16 +325,16 @@ class UnderpostKubectl {
150
325
  * @param {string} params.namespace - Pod namespace.
151
326
  * @param {string} params.sourcePath - Source path inside the container.
152
327
  * @param {string} params.destPath - Local destination path.
328
+ * @param {number} [params.retries=2] - Extra attempts allowed after a failure, with exponential backoff.
153
329
  * @returns {boolean} `true` on success, `false` on error.
154
330
  * @memberof UnderpostKubectl
155
331
  */
156
- cpFrom({ podName, namespace, sourcePath, destPath }) {
332
+ cpFrom({ podName, namespace, sourcePath, destPath, retries = DEFAULT_POD_RETRIES }) {
157
333
  try {
158
334
  const command = `sudo kubectl cp ${namespace}/${podName}:${sourcePath} ${destPath}`;
159
- Underpost.kubectl.run(command, { context: `copy from pod ${podName}` });
335
+ Underpost.kubectl.run(command, { context: `copy from pod ${podName}`, retries, podName, namespace });
160
336
  return true;
161
- } catch (error) {
162
- logger.error('Failed to copy file from pod', { podName, sourcePath, destPath, error: error.message });
337
+ } catch {
163
338
  return false;
164
339
  }
165
340
  },
@@ -212,3 +387,5 @@ class UnderpostKubectl {
212
387
  }
213
388
 
214
389
  export default UnderpostKubectl;
390
+
391
+ export { backoffDelayMs, withTimeout };
package/src/cli/lxd.js CHANGED
@@ -5,11 +5,11 @@
5
5
  *
6
6
  */
7
7
 
8
- import { getNpmRootPath } from '../server/conf.js';
9
- import { pbcopy, shellExec } from '../server/process.js';
8
+ import { getNpmRootPath } from '../server/runtime/environment.js';
9
+ import { pbcopy, shellExec } from '../server/runtime/process.js';
10
10
  import walk from 'ignore-walk';
11
11
  import fs from 'fs-extra';
12
- import { loggerFactory } from '../server/logger.js';
12
+ import { loggerFactory } from '../server/ops/logger.js';
13
13
  import Underpost from '../index.js';
14
14
 
15
15
  const logger = loggerFactory(import.meta);