cyberia 3.2.5 → 3.2.12

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 (381) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +8 -2
  2. package/.github/workflows/npmpkg.ci.yml +1 -0
  3. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  4. package/.github/workflows/release.cd.yml +2 -2
  5. package/.vscode/extensions.json +9 -9
  6. package/.vscode/settings.json +20 -4
  7. package/CHANGELOG.md +563 -1
  8. package/CLI-HELP.md +130 -34
  9. package/Dockerfile +0 -4
  10. package/README.md +194 -607
  11. package/bin/build.js +42 -12
  12. package/bin/build.template.js +187 -0
  13. package/bin/cyberia.js +1367 -281
  14. package/bin/deploy.js +582 -3
  15. package/bin/index.js +1367 -281
  16. package/bump.config.js +26 -0
  17. package/conf.js +195 -111
  18. package/deployment.yaml +6 -222
  19. package/hardhat/package-lock.json +118 -149
  20. package/hardhat/package.json +5 -4
  21. package/jsconfig.json +1 -1
  22. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  23. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  24. package/manifests/deployment/dd-cyberia-development/deployment.yaml +6 -222
  25. package/manifests/deployment/dd-cyberia-development/proxy.yaml +10 -118
  26. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  27. package/manifests/deployment/dd-test-development/deployment.yaml +138 -66
  28. package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
  29. package/manifests/kind-config-dev.yaml +8 -0
  30. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  31. package/manifests/mongodb/pv-pvc.yaml +44 -8
  32. package/manifests/mongodb/statefulset.yaml +55 -68
  33. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  34. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  35. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  36. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  37. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  38. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  39. package/manifests/valkey/statefulset.yaml +1 -1
  40. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  41. package/package.json +45 -24
  42. package/proxy.yaml +10 -118
  43. package/scripts/ipxe-setup.sh +52 -49
  44. package/scripts/k3s-node-setup.sh +83 -48
  45. package/scripts/lxd-vm-setup.sh +193 -8
  46. package/scripts/maas-nat-firewalld.sh +145 -0
  47. package/scripts/nat-iptables.sh +103 -18
  48. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +18 -18
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -14
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  51. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +91 -36
  52. package/src/api/core/core.controller.js +10 -10
  53. package/src/api/core/core.router.js +19 -14
  54. package/src/api/core/core.service.js +15 -15
  55. package/src/api/crypto/crypto.controller.js +8 -8
  56. package/src/api/crypto/crypto.router.js +18 -12
  57. package/src/api/crypto/crypto.service.js +11 -11
  58. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  59. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  60. package/src/api/cyberia-action/cyberia-action.router.js +31 -0
  61. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  62. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  63. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  64. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  66. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +13 -13
  67. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +11 -11
  68. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  69. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +22 -22
  70. package/src/api/cyberia-entity/cyberia-entity.controller.js +10 -10
  71. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  72. package/src/api/cyberia-entity/cyberia-entity.service.js +15 -15
  73. package/src/api/cyberia-instance/cyberia-fallback-world.js +83 -198
  74. package/src/api/cyberia-instance/cyberia-instance.controller.js +14 -14
  75. package/src/api/cyberia-instance/cyberia-instance.model.js +3 -0
  76. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  77. package/src/api/cyberia-instance/cyberia-instance.service.js +32 -67
  78. package/src/api/cyberia-instance/cyberia-portal-connector.js +20 -246
  79. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  80. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +10 -10
  81. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +18 -49
  82. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  83. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +19 -15
  84. package/src/api/cyberia-map/cyberia-map.controller.js +10 -10
  85. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  86. package/src/api/cyberia-map/cyberia-map.service.js +17 -17
  87. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  88. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  89. package/src/api/cyberia-quest/cyberia-quest.router.js +31 -0
  90. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  91. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  92. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  93. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +31 -0
  94. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  95. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +451 -0
  96. package/src/api/default/default.controller.js +10 -10
  97. package/src/api/default/default.router.js +22 -18
  98. package/src/api/default/default.service.js +15 -15
  99. package/src/api/document/document.controller.js +12 -12
  100. package/src/api/document/document.model.js +10 -16
  101. package/src/api/document/document.router.js +28 -23
  102. package/src/api/document/document.service.js +100 -23
  103. package/src/api/file/file.controller.js +8 -8
  104. package/src/api/file/file.model.js +10 -10
  105. package/src/api/file/file.router.js +19 -13
  106. package/src/api/file/file.service.js +45 -43
  107. package/src/api/instance/instance.controller.js +10 -10
  108. package/src/api/instance/instance.model.js +4 -10
  109. package/src/api/instance/instance.router.js +29 -24
  110. package/src/api/instance/instance.service.js +16 -16
  111. package/src/api/ipfs/ipfs.controller.js +12 -12
  112. package/src/api/ipfs/ipfs.model.js +4 -13
  113. package/src/api/ipfs/ipfs.router.js +21 -16
  114. package/src/api/ipfs/ipfs.service.js +22 -36
  115. package/src/api/object-layer/object-layer.controller.js +12 -12
  116. package/src/api/object-layer/object-layer.model.js +4 -17
  117. package/src/api/object-layer/object-layer.router.js +512 -507
  118. package/src/api/object-layer/object-layer.service.js +29 -26
  119. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  120. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  121. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  122. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +19 -15
  123. package/src/api/test/test.controller.js +8 -8
  124. package/src/api/test/test.router.js +17 -12
  125. package/src/api/test/test.service.js +8 -8
  126. package/src/api/types.js +24 -0
  127. package/src/api/user/guest.service.js +100 -0
  128. package/src/api/user/user.controller.js +6 -6
  129. package/src/api/user/user.model.js +8 -13
  130. package/src/api/user/user.router.js +297 -288
  131. package/src/api/user/user.service.js +103 -55
  132. package/src/cli/baremetal.js +132 -101
  133. package/src/cli/cluster.js +732 -217
  134. package/src/cli/db.js +106 -62
  135. package/src/cli/deploy.js +260 -149
  136. package/src/cli/fs.js +90 -9
  137. package/src/cli/image.js +43 -1
  138. package/src/cli/index.js +106 -16
  139. package/src/cli/ipfs.js +4 -6
  140. package/src/cli/kubectl.js +4 -1
  141. package/src/cli/lxd.js +1099 -223
  142. package/src/cli/monitor.js +9 -3
  143. package/src/cli/release.js +336 -86
  144. package/src/cli/repository.js +136 -53
  145. package/src/cli/run.js +599 -76
  146. package/src/cli/secrets.js +11 -2
  147. package/src/cli/ssh.js +1 -1
  148. package/src/cli/static.js +43 -115
  149. package/src/cli/test.js +9 -3
  150. package/src/client/Cryptokoyn.index.js +18 -21
  151. package/src/client/CyberiaPortal.index.js +19 -23
  152. package/src/client/Default.index.js +30 -36
  153. package/src/client/Itemledger.index.js +20 -26
  154. package/src/client/Underpost.index.js +19 -23
  155. package/src/client/components/core/404.js +4 -4
  156. package/src/client/components/core/500.js +4 -4
  157. package/src/client/components/core/Account.js +73 -60
  158. package/src/client/components/core/AgGrid.js +23 -33
  159. package/src/client/components/core/Alert.js +12 -13
  160. package/src/client/components/core/AppStore.js +1 -1
  161. package/src/client/components/core/Auth.js +40 -37
  162. package/src/client/components/core/Badge.js +7 -13
  163. package/src/client/components/core/BtnIcon.js +15 -17
  164. package/src/client/components/core/CalendarCore.js +42 -63
  165. package/src/client/components/core/Chat.js +13 -15
  166. package/src/client/components/core/ClientEvents.js +163 -0
  167. package/src/client/components/core/ColorPaletteElement.js +309 -0
  168. package/src/client/components/core/Content.js +17 -14
  169. package/src/client/components/core/Css.js +15 -71
  170. package/src/client/components/core/CssCore.js +12 -16
  171. package/src/client/components/core/D3Chart.js +4 -4
  172. package/src/client/components/core/Docs.js +64 -91
  173. package/src/client/components/core/DropDown.js +69 -91
  174. package/src/client/components/core/EventBus.js +96 -0
  175. package/src/client/components/core/EventsUI.js +14 -17
  176. package/src/client/components/core/FileExplorer.js +96 -228
  177. package/src/client/components/core/FullScreen.js +47 -75
  178. package/src/client/components/core/Input.js +24 -69
  179. package/src/client/components/core/Keyboard.js +25 -18
  180. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  181. package/src/client/components/core/LoadingAnimation.js +25 -31
  182. package/src/client/components/core/LogIn.js +41 -41
  183. package/src/client/components/core/LogOut.js +23 -14
  184. package/src/client/components/core/Modal.js +544 -219
  185. package/src/client/components/core/NotificationManager.js +14 -18
  186. package/src/client/components/core/Panel.js +54 -50
  187. package/src/client/components/core/PanelForm.js +81 -177
  188. package/src/client/components/core/Polyhedron.js +110 -214
  189. package/src/client/components/core/PublicProfile.js +39 -32
  190. package/src/client/components/core/Recover.js +48 -44
  191. package/src/client/components/core/Responsive.js +88 -32
  192. package/src/client/components/core/RichText.js +9 -18
  193. package/src/client/components/core/Router.js +24 -3
  194. package/src/client/components/core/SearchBox.js +37 -37
  195. package/src/client/components/core/SignUp.js +39 -30
  196. package/src/client/components/core/SocketIo.js +31 -2
  197. package/src/client/components/core/SocketIoHandler.js +6 -6
  198. package/src/client/components/core/ToggleSwitch.js +8 -20
  199. package/src/client/components/core/ToolTip.js +5 -17
  200. package/src/client/components/core/Translate.js +56 -59
  201. package/src/client/components/core/Validator.js +26 -16
  202. package/src/client/components/core/Wallet.js +15 -26
  203. package/src/client/components/core/Worker.js +211 -276
  204. package/src/client/components/core/windowGetDimensions.js +7 -7
  205. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +57 -57
  206. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  207. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +6 -4
  208. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +6 -4
  209. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  210. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  211. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  212. package/src/client/components/cyberia/InstanceEngineCyberia.js +141 -60
  213. package/src/client/components/cyberia/MapEngineCyberia.js +691 -214
  214. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  215. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1204 -94
  216. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +196 -298
  217. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  218. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +102 -102
  219. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  220. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +6 -4
  221. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +6 -4
  222. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  223. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  224. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  225. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  226. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -4
  227. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
  228. package/src/client/components/default/CssDefault.js +12 -12
  229. package/src/client/components/default/LogInDefault.js +6 -4
  230. package/src/client/components/default/LogOutDefault.js +6 -4
  231. package/src/client/components/default/RouterDefault.js +47 -0
  232. package/src/client/components/default/SettingsDefault.js +4 -4
  233. package/src/client/components/default/SignUpDefault.js +6 -4
  234. package/src/client/components/default/TranslateDefault.js +3 -3
  235. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +57 -57
  236. package/src/client/components/itemledger/CssItemledger.js +15 -15
  237. package/src/client/components/itemledger/LogInItemledger.js +6 -4
  238. package/src/client/components/itemledger/LogOutItemledger.js +6 -4
  239. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  240. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  241. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  242. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  243. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +88 -88
  244. package/src/client/components/underpost/CssUnderpost.js +14 -14
  245. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  246. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  247. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  248. package/src/client/components/underpost/LogInUnderpost.js +6 -4
  249. package/src/client/components/underpost/LogOutUnderpost.js +6 -4
  250. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  251. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  252. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  253. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  254. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  255. package/src/client/public/cyberia-docs/ARCHITECTURE.md +83 -0
  256. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +204 -0
  257. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +291 -0
  258. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +278 -0
  259. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  260. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  261. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  262. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  263. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  264. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  265. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  266. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  267. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  268. package/src/client/services/core/core.service.js +17 -49
  269. package/src/client/services/crypto/crypto.service.js +8 -13
  270. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  271. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  272. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +10 -16
  273. package/src/client/services/cyberia-entity/cyberia-entity.management.js +5 -5
  274. package/src/client/services/cyberia-entity/cyberia-entity.service.js +10 -16
  275. package/src/client/services/cyberia-instance/cyberia-instance.management.js +6 -6
  276. package/src/client/services/cyberia-instance/cyberia-instance.service.js +12 -18
  277. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +10 -16
  278. package/src/client/services/cyberia-map/cyberia-map.management.js +6 -6
  279. package/src/client/services/cyberia-map/cyberia-map.service.js +12 -18
  280. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  281. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  282. package/src/client/services/default/default.management.js +159 -267
  283. package/src/client/services/default/default.service.js +10 -16
  284. package/src/client/services/document/document.service.js +14 -19
  285. package/src/client/services/file/file.service.js +8 -13
  286. package/src/client/services/instance/instance.management.js +5 -5
  287. package/src/client/services/instance/instance.service.js +10 -15
  288. package/src/client/services/ipfs/ipfs.service.js +12 -18
  289. package/src/client/services/object-layer/object-layer.management.js +12 -12
  290. package/src/client/services/object-layer/object-layer.service.js +20 -26
  291. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  292. package/src/client/services/test/test.service.js +8 -13
  293. package/src/client/services/user/guest.service.js +86 -0
  294. package/src/client/services/user/user.management.js +5 -5
  295. package/src/client/services/user/user.service.js +14 -20
  296. package/src/client/ssr/body/404.js +3 -3
  297. package/src/client/ssr/body/500.js +3 -3
  298. package/src/client/ssr/body/CacheControl.js +5 -2
  299. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  300. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  301. package/src/client/ssr/head/PwaItemledger.js +197 -60
  302. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  303. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  304. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  305. package/src/client/ssr/{offline → views}/Maintenance.js +12 -11
  306. package/src/client/ssr/{offline → views}/NoNetworkConnection.js +3 -3
  307. package/src/client/ssr/{pages → views}/Test.js +2 -2
  308. package/src/client/sw/core.sw.js +274 -0
  309. package/src/db/DataBaseProvider.js +115 -15
  310. package/src/db/mariadb/MariaDB.js +2 -1
  311. package/src/db/mongo/MongoBootstrap.js +657 -0
  312. package/src/db/mongo/MongooseDB.js +129 -21
  313. package/src/grpc/cyberia/grpc-server.js +185 -105
  314. package/src/index.js +1 -1
  315. package/src/runtime/cyberia-client/Dockerfile +101 -0
  316. package/src/runtime/cyberia-client/Dockerfile.dev +82 -0
  317. package/src/runtime/cyberia-server/Dockerfile +62 -0
  318. package/src/runtime/cyberia-server/Dockerfile.dev +71 -0
  319. package/src/runtime/express/Dockerfile +4 -4
  320. package/src/runtime/express/Express.js +2 -2
  321. package/src/runtime/lampp/Dockerfile +8 -7
  322. package/src/runtime/wp/Dockerfile +11 -17
  323. package/src/runtime/wp/Wp.js +8 -5
  324. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  325. package/src/server/auth.js +2 -2
  326. package/src/server/client-build-docs.js +46 -47
  327. package/src/server/client-build.js +371 -132
  328. package/src/server/client-formatted.js +47 -16
  329. package/src/server/conf.js +91 -87
  330. package/src/server/data-query.js +32 -20
  331. package/src/server/dns.js +22 -0
  332. package/src/server/ipfs-client.js +232 -91
  333. package/src/server/object-layer.js +1 -6
  334. package/src/server/process.js +192 -45
  335. package/src/server/proxy.js +9 -2
  336. package/src/server/runtime.js +1 -1
  337. package/src/server/semantic-layer-generator-floor.js +11 -51
  338. package/src/server/semantic-layer-generator-resource.js +259 -0
  339. package/src/server/semantic-layer-generator-skin.js +41 -171
  340. package/src/server/semantic-layer-generator.js +122 -14
  341. package/src/server/shape-generator.js +108 -0
  342. package/src/server/start.js +34 -8
  343. package/src/server/valkey.js +143 -235
  344. package/src/ws/IoInterface.js +16 -16
  345. package/src/ws/core/channels/core.ws.chat.js +11 -11
  346. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  347. package/src/ws/core/channels/core.ws.stream.js +19 -19
  348. package/src/ws/core/core.ws.connection.js +8 -8
  349. package/src/ws/core/core.ws.server.js +6 -5
  350. package/src/ws/default/channels/default.ws.main.js +10 -10
  351. package/src/ws/default/default.ws.connection.js +4 -4
  352. package/src/ws/default/default.ws.server.js +4 -3
  353. package/tsconfig.docs.json +15 -0
  354. package/typedoc.dd-cyberia.json +29 -0
  355. package/typedoc.json +29 -0
  356. package/WHITE-PAPER.md +0 -1540
  357. package/bin/file.js +0 -196
  358. package/bin/vs.js +0 -74
  359. package/bin/zed.js +0 -84
  360. package/hardhat/README.md +0 -531
  361. package/hardhat/WHITE-PAPER.md +0 -1540
  362. package/jsdoc.dd-cyberia.json +0 -68
  363. package/jsdoc.json +0 -68
  364. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -413
  365. package/src/api/object-layer/README.md +0 -672
  366. package/src/client/components/core/ColorPalette.js +0 -5267
  367. package/src/client/components/core/JoyStick.js +0 -80
  368. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  369. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -223
  370. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -62
  371. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  372. package/src/client/components/default/RoutesDefault.js +0 -49
  373. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  374. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  375. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  376. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  377. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  378. package/src/client/sw/default.sw.js +0 -127
  379. package/src/client/sw/template.sw.js +0 -84
  380. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +0 -305
  381. package/src/grpc/cyberia/README.md +0 -326
package/src/cli/run.js CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  awaitDeployMonitor,
11
11
  buildKindPorts,
12
12
  Config,
13
+ cronDeployIdResolve,
14
+ etcHostFactory,
13
15
  getNpmRootPath,
14
16
  isDeployRunnerContext,
15
17
  loadConfServerJson,
@@ -24,6 +26,7 @@ import { range, setPad, timer } from '../client/components/core/CommonJs.js';
24
26
  import os from 'os';
25
27
  import Underpost from '../index.js';
26
28
  import dotenv from 'dotenv';
29
+ import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
27
30
 
28
31
  const waitForPort = (port, host = '127.0.0.1', { maxAttempts = 30, interval = 2000 } = {}) =>
29
32
  new Promise((resolve, reject) => {
@@ -93,11 +96,11 @@ const logger = loggerFactory(import.meta);
93
96
  * @property {boolean} kubeadm - Whether to run in kubeadm mode.
94
97
  * @property {boolean} kind - Whether to run in kind mode.
95
98
  * @property {boolean} k3s - Whether to run in k3s mode.
96
- * @property {string} logType - The type of log to generate.
97
99
  * @property {string} hosts - The hosts to use.
98
100
  * @property {string} deployId - The deployment ID.
99
101
  * @property {string} instanceId - The instance ID.
100
102
  * @property {string} user - The user to run as.
103
+ * @property {string} group - The group to use.
101
104
  * @property {string} pid - The process ID.
102
105
  * @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
103
106
  * @property {string} monitorStatus - The monitor status option.
@@ -111,6 +114,11 @@ const logger = loggerFactory(import.meta);
111
114
  * @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
112
115
  * As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
113
116
  * As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
117
+ * @property {boolean} gitClean - Whether to perform a `git clean` before running.
118
+ * @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
119
+ * @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
120
+ * @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
121
+ * @property {boolean} remove - Whether to remove/teardown resources instead of creating them (e.g. delete-expose for k3s proxy devices in dev-cluster).
114
122
  * @memberof UnderpostRun
115
123
  */
116
124
  const DEFAULT_OPTION = {
@@ -158,11 +166,11 @@ const DEFAULT_OPTION = {
158
166
  kubeadm: false,
159
167
  kind: false,
160
168
  k3s: false,
161
- logType: '',
162
169
  hosts: '',
163
170
  deployId: '',
164
171
  instanceId: '',
165
172
  user: '',
173
+ group: '',
166
174
  pid: '',
167
175
  disablePrivateConfUpdate: false,
168
176
  monitorStatus: '',
@@ -176,6 +184,9 @@ const DEFAULT_OPTION = {
176
184
  hostAliases: '',
177
185
  gitClean: false,
178
186
  copy: false,
187
+ skipFullBuild: false,
188
+ pullBundle: false,
189
+ remove: false,
179
190
  };
180
191
 
181
192
  /**
@@ -205,28 +216,39 @@ class UnderpostRun {
205
216
  'dev-cluster': (path, options = DEFAULT_OPTION) => {
206
217
  const baseCommand = options.dev ? 'node bin' : 'underpost';
207
218
  const mongoHosts = ['mongodb-0.mongodb-service'];
219
+ let primaryMongoHost = 'mongodb-0.mongodb-service';
208
220
  if (!options.expose) {
209
221
  shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --reset`);
210
222
  shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''}`);
211
223
 
212
224
  shellExec(
213
- `${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb --mongo-db-host ${mongoHosts.join(
225
+ `${baseCommand} cluster${options.dev ? ' --dev' : ''} --mongodb4 --service-host ${mongoHosts.join(
214
226
  ',',
215
227
  )} --pull-image`,
216
228
  );
217
229
  shellExec(`${baseCommand} cluster${options.dev ? ' --dev' : ''} --valkey --pull-image`);
218
230
  }
219
-
220
- {
221
- // Detect MongoDB primary pod using method
222
- let primaryMongoHost = 'mongodb-0.mongodb-service';
231
+ if (options.k3s) {
232
+ if (options.remove) {
233
+ shellExec(`${baseCommand} lxd --delete-expose k3s-control:27017`);
234
+ shellExec(`${baseCommand} lxd --delete-expose k3s-control:6379`);
235
+ } else {
236
+ shellExec(`${baseCommand} lxd --expose k3s-control:27017 --node-port 32017`);
237
+ shellExec(`${baseCommand} lxd --expose k3s-control:6379 --node-port 32079`);
238
+ }
239
+ shellExec(`lxc config device show k3s-control`);
240
+ } else {
223
241
  try {
224
- const primaryPodName = Underpost.db.getMongoPrimaryPodName({
225
- namespace: options.namespace,
226
- podName: 'mongodb-0',
227
- });
228
- // shellExec(`${baseCommand} deploy --expose --disable-update-underpost-config mongo`, { async: true });
229
- shellExec(`kubectl port-forward -n ${options.namespace} pod/${primaryPodName} 27017:27017`, { async: true });
242
+ const primaryPodName =
243
+ MongoBootstrap.getPrimaryPodName({
244
+ namespace: options.namespace,
245
+ podName: 'mongodb-0',
246
+ disableAuth: options.dev,
247
+ }) || 'mongodb-0';
248
+ shellExec(
249
+ `${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config mongo`,
250
+ { async: true },
251
+ );
230
252
  shellExec(
231
253
  `${baseCommand} deploy --expose --namespace ${options.namespace} --disable-update-underpost-config valkey`,
232
254
  { async: true },
@@ -237,10 +259,9 @@ class UnderpostRun {
237
259
  default: primaryMongoHost,
238
260
  });
239
261
  }
240
-
241
- const hostListenResult = Underpost.deploy.etcHostFactory([primaryMongoHost]);
242
- logger.info(hostListenResult.renderHosts);
243
262
  }
263
+ const hostListenResult = etcHostFactory([primaryMongoHost]);
264
+ logger.info(hostListenResult.renderHosts);
244
265
  },
245
266
 
246
267
  /**
@@ -437,6 +458,15 @@ class UnderpostRun {
437
458
  deployType = 'init';
438
459
  }
439
460
 
461
+ // If --build is set and path is a sync-engine-* target, push the pre-built client bundle
462
+ // to Cloudinary so the remote container can pull it instead of rebuilding from source.
463
+ if (options.build && deployConfId && deployConfId.startsWith('engine-')) {
464
+ const confName = deployConfId.replace(/^engine-/, '');
465
+ const pushDeployId = options.deployId || `dd-${confName}`;
466
+ logger.info(`[template-deploy] Running push-bundle for deployId: ${pushDeployId}`);
467
+ shellExec(`${baseCommand} run push-bundle --deploy-id ${pushDeployId}`);
468
+ }
469
+
440
470
  // Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
441
471
  // If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
442
472
  // with sync=true after template build completes. The engine CI then dispatches
@@ -490,32 +520,19 @@ class UnderpostRun {
490
520
  : await Underpost.release.pwa(sanitizedMessage, options);
491
521
  pbcopy(triggerCmd + ' && cd /home/dd/engine');
492
522
  },
493
- /**
494
- * @method template-deploy-image
495
- * @description Dispatches the Docker image CI workflow for the `engine` repository.
496
- * @param {string} path - The input value, identifier, or path for the operation.
497
- * @param {Object} options - The default underpost runner options for customizing workflow
498
- * @memberof UnderpostRun
499
- */
500
- 'template-deploy-image': (path, options = DEFAULT_OPTION) => {
501
- Underpost.repo.dispatchWorkflow({
502
- repo: `${process.env.GITHUB_USERNAME}/engine`,
503
- workflowFile: 'docker-image.ci.yml',
504
- ref: 'master',
505
- inputs: {},
506
- });
507
- },
508
523
  /**
509
524
  * @method docker-image
510
- * @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`) for the `engine` repository via `workflow_dispatch`.
511
- * @param {string} path - The input value, identifier, or path for the operation.
525
+ * @description Dispatches the Docker image CI workflow (`docker-image[.<runtime>].ci.yml`) via `workflow_dispatch`.
526
+ * Repository resolution is delegated to `Underpost.repo.resolveInstanceRepo(path)`.
527
+ * @param {string} path - Optional runtime / workflow suffix (e.g. `cyberia-server`, `cyberia-client`).
512
528
  * @param {Object} options - The default underpost runner options for customizing workflow
513
529
  * @memberof UnderpostRun
514
530
  */
515
531
  'docker-image': (path, options = DEFAULT_OPTION) => {
532
+ const repo = Underpost.repo.resolveInstanceRepo(path);
516
533
  Underpost.repo.dispatchWorkflow({
517
- repo: `${process.env.GITHUB_USERNAME}/engine`,
518
- workflowFile: 'docker-image.ci.yml',
534
+ repo,
535
+ workflowFile: `docker-image${path ? `.${path}` : ''}.ci.yml`,
519
536
  ref: 'master',
520
537
  inputs: {},
521
538
  });
@@ -529,6 +546,7 @@ class UnderpostRun {
529
546
  */
530
547
  clean: (path = '', options = DEFAULT_OPTION) => {
531
548
  Underpost.repo.clean({ paths: path ? path.split(',') : ['/home/dd/engine', '/home/dd/engine/engine-private'] });
549
+ if (options.dev) shellExec(`node bin run shared-dir ${path ? path : '/home/dd/engine'}`);
532
550
  },
533
551
  /**
534
552
  * @method pull
@@ -538,16 +556,14 @@ class UnderpostRun {
538
556
  * @memberof UnderpostRun
539
557
  */
540
558
  pull: (path, options = DEFAULT_OPTION) => {
559
+ // shellExec is fail-fast by default — any non-zero exit throws and
560
+ // propagates up to the workflow step. No per-call flag required.
541
561
  if (!fs.existsSync(`/home/dd`) || !fs.existsSync(`/home/dd/engine`)) {
542
562
  fs.mkdirSync(`/home/dd`, { recursive: true });
543
- shellExec(`cd /home/dd && underpost clone ${process.env.GITHUB_USERNAME}/engine`, {
544
- silent: true,
545
- });
563
+ shellExec(`cd /home/dd && underpost clone ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
546
564
  } else {
547
565
  shellExec(`underpost run clean`);
548
- shellExec(`cd /home/dd/engine && underpost pull . ${process.env.GITHUB_USERNAME}/engine`, {
549
- silent: true,
550
- });
566
+ shellExec(`cd /home/dd/engine && underpost pull . ${process.env.GITHUB_USERNAME}/engine`, { silent: true });
551
567
  }
552
568
  if (!fs.existsSync(`/home/dd/engine/engine-private`))
553
569
  shellExec(`cd /home/dd/engine && underpost clone ${process.env.GITHUB_USERNAME}/engine-private`, {
@@ -556,9 +572,7 @@ class UnderpostRun {
556
572
  else
557
573
  shellExec(
558
574
  `cd /home/dd/engine/engine-private && underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`,
559
- {
560
- silent: true,
561
- },
575
+ { silent: true },
562
576
  );
563
577
  },
564
578
  /**
@@ -645,6 +659,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
645
659
  /**
646
660
  * @method sync
647
661
  * @description Cleans up, and then runs a deployment synchronization command (`underpost deploy --kubeadm --build-manifest --sync...`) using parameters parsed from `path` (deployId, replicas, versions, image, node).
662
+ *
663
+ * Forwards `--image-pull-policy <policy>` to the underlying `deploy --build-manifest` invocation when `options.imagePullPolicy` is set,
664
+ * which then plumbs through `buildManifest` and `deploymentYamlPartsFactory` to override the container `imagePullPolicy` in the generated
665
+ * `deployment.yaml`. Useful when you want to force `Always` so the kubelet re-pulls a mutable tag on every rollout. Example:
666
+ * `node bin run sync dd-core --kubeadm --image-pull-policy Always`
648
667
  * @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string containing deploy parameters).
649
668
  * @param {Object} options - The default underpost runner options for customizing workflow
650
669
  * @memberof UnderpostRun
@@ -652,7 +671,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
652
671
  sync: async (path, options = DEFAULT_OPTION) => {
653
672
  // Dev usage: node bin run --dev --build sync dd-default
654
673
  const env = options.dev ? 'development' : 'production';
655
- const baseCommand = options.dev ? 'node bin' : 'underpost';
674
+ const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
656
675
  const baseClusterCommand = options.dev ? ' --dev' : '';
657
676
  const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
658
677
  const defaultPath = [
@@ -669,6 +688,15 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
669
688
  image = image ? image : defaultPath[3];
670
689
  node = node ? node : defaultPath[4];
671
690
  shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
691
+
692
+ if (image && !image.startsWith('localhost'))
693
+ Underpost.image.pullDockerHubImage({
694
+ dockerhubImage: image,
695
+ kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
696
+ kubeadm: options.nodeName || options.kubeadm,
697
+ k3s: options.k3s,
698
+ });
699
+
672
700
  if (isDeployRunnerContext(path, options)) {
673
701
  if (!options.disablePrivateConfUpdate) {
674
702
  const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
@@ -691,12 +719,16 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
691
719
  : '';
692
720
  const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
693
721
 
722
+ const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
723
+ const pullBundleFlag = options.pullBundle ? ' --pull-bundle' : '';
724
+ const imagePullPolicyFlag = options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : '';
725
+
694
726
  shellExec(
695
727
  `${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
696
728
  image ? ` --image ${image}` : ''
697
729
  }${versions ? ` --versions ${versions}` : ''}${
698
730
  options.namespace ? ` --namespace ${options.namespace}` : ''
699
- }${timeoutFlags}${cmdString}${gitCleanFlag} ${deployId} ${env}`,
731
+ }${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag}${imagePullPolicyFlag} ${deployId} ${env}`,
700
732
  );
701
733
 
702
734
  if (isDeployRunnerContext(path, options)) {
@@ -707,7 +739,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
707
739
  shellExec(
708
740
  `${baseCommand} deploy${clusterFlag}${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
709
741
  options.namespace ? ` --namespace ${options.namespace}` : ''
710
- }${timeoutFlags}${gitCleanFlag}`,
742
+ }${timeoutFlags}${gitCleanFlag}${imagePullPolicyFlag}`,
711
743
  );
712
744
  if (!targetTraffic)
713
745
  targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
@@ -928,12 +960,17 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
928
960
  image: _image,
929
961
  fromPort: _fromPort,
930
962
  toPort: _toPort,
963
+ fromDebugPort: _fromDebugPort,
964
+ toDebugPort: _toDebugPort,
931
965
  cmd: _cmd,
932
966
  volumes: _volumes,
933
967
  metadata: _metadata,
934
968
  } = instance;
935
969
  if (id !== _id) continue;
936
970
  const _deployId = `${deployId}-${_id}`;
971
+ // Use debug ports in development when defined, fall back to production ports.
972
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
973
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
937
974
  const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
938
975
  hostTest: _host,
939
976
  namespace: options.namespace,
@@ -1003,12 +1040,20 @@ EOF
1003
1040
  image: _image,
1004
1041
  fromPort: _fromPort,
1005
1042
  toPort: _toPort,
1043
+ fromDebugPort: _fromDebugPort,
1044
+ toDebugPort: _toDebugPort,
1006
1045
  cmd: _cmd,
1007
1046
  volumes: _volumes,
1008
1047
  metadata: _metadata,
1048
+ lifecycle: _lifecycle,
1049
+ readinessProbe: _readinessProbe,
1050
+ livenessProbe: _livenessProbe,
1009
1051
  } = instance;
1010
1052
  if (id !== _id) continue;
1011
1053
  const _deployId = `${deployId}-${_id}`;
1054
+ // Use debug ports in development when defined, fall back to production ports.
1055
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
1056
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
1012
1057
  etcHosts.push(_host);
1013
1058
  if (options.expose) continue;
1014
1059
  // Examples images:
@@ -1016,12 +1061,13 @@ EOF
1016
1061
  // `localhost/rockylinux9-underpost:${Underpost.version}`
1017
1062
  if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
1018
1063
 
1019
- Underpost.image.pullDockerHubImage({
1020
- dockerhubImage: _image,
1021
- kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
1022
- kubeadm: options.nodeName || options.kubeadm,
1023
- k3s: options.k3s,
1024
- });
1064
+ if (_image && !_image.startsWith('localhost'))
1065
+ Underpost.image.pullDockerHubImage({
1066
+ dockerhubImage: _image,
1067
+ kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
1068
+ kubeadm: options.nodeName || options.kubeadm,
1069
+ k3s: options.k3s,
1070
+ });
1025
1071
 
1026
1072
  const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
1027
1073
  hostTest: _host,
@@ -1066,6 +1112,20 @@ EOF
1066
1112
  ),
1067
1113
  );
1068
1114
 
1115
+ // Resolve env-scoped lifecycle/probe blocks: each can be either
1116
+ // { ...envObj } // shared shape
1117
+ // { development: {...}, production: {...} } // env-specific
1118
+ const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
1119
+
1120
+ // Convention: an instance config may place `imagePullPolicy` inside
1121
+ // the env-scoped lifecycle block (alongside postStart/preStop).
1122
+ // Extract it onto the container spec (where K8S expects it) and
1123
+ // strip it from the lifecycle hash so the rendered YAML stays valid.
1124
+ // CLI override (`--image-pull-policy`) wins over the conf value.
1125
+ const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
1126
+ Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
1127
+ const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
1128
+
1069
1129
  let deploymentYaml = `---
1070
1130
  ${Underpost.deploy
1071
1131
  .deploymentYamlPartsFactory({
@@ -1078,6 +1138,11 @@ ${Underpost.deploy
1078
1138
  namespace: options.namespace,
1079
1139
  volumes: _volumes,
1080
1140
  cmd: resolvedCmd,
1141
+ lifecycle: lifecycleForManifest,
1142
+ readinessProbe: pickEnv(_readinessProbe),
1143
+ livenessProbe: pickEnv(_livenessProbe),
1144
+ containerPort: _toPort,
1145
+ imagePullPolicy: instanceImagePullPolicy,
1081
1146
  })
1082
1147
  .replace('{{ports}}', buildKindPorts(_fromPort, _toPort))}
1083
1148
  `;
@@ -1095,7 +1160,6 @@ EOF
1095
1160
  targetTraffic,
1096
1161
  ignorePods,
1097
1162
  options.namespace,
1098
- options.logType,
1099
1163
  );
1100
1164
 
1101
1165
  if (!ready) {
@@ -1110,11 +1174,194 @@ EOF
1110
1174
  );
1111
1175
  }
1112
1176
  if (options.etcHosts) {
1113
- const hostListenResult = Underpost.deploy.etcHostFactory(etcHosts);
1177
+ const hostListenResult = etcHostFactory(etcHosts);
1114
1178
  logger.info(hostListenResult.renderHosts);
1115
1179
  }
1116
1180
  },
1117
1181
 
1182
+ /**
1183
+ * @method instance-build-manifest
1184
+ * @description Builds a Kubernetes Deployment + Service manifest for a specific instance entry
1185
+ * from `conf.instances.json` and writes it to a file.
1186
+ * Traffic colour is automatically chosen as the opposite of the current live colour (blue/green),
1187
+ * defaulting to `blue` when no deployment is running yet.
1188
+ *
1189
+ * If `--build` is supplied the image is built from the project Dockerfile and loaded into the
1190
+ * cluster before the manifest is written (kind by default; `--kubeadm` / `--k3s` override).
1191
+ *
1192
+ * @param {string} path - Comma-separated: `deployId,instanceId[,projectPath]`.
1193
+ * `projectPath` is the root directory that contains the `Dockerfile` (e.g. `./cyberia-client`).
1194
+ * Artifacts are written to `<projectPath>/manifests/<env>/Dockerfile` and
1195
+ * `<projectPath>/manifests/<env>/deployment.yaml`.
1196
+ * In production, files are also copied to `<projectPath>/Dockerfile` and
1197
+ * `<projectPath>/deployment.yaml`.
1198
+ * @param {Object} options - The default underpost runner options for customizing workflow
1199
+ * @memberof UnderpostRun
1200
+ */
1201
+ 'instance-build-manifest': (path, options = DEFAULT_OPTION) => {
1202
+ const env = options.dev ? 'development' : 'production';
1203
+ let [deployId, id, projectPath] = path.split(',');
1204
+ const rootPath = projectPath ? projectPath : '.';
1205
+ const envManifestPath = `${rootPath}/manifests/deployments/${id}-${env}`;
1206
+ const outputPath = `${envManifestPath}/deployment.yaml`;
1207
+ const dockerfileManifestPath = `${envManifestPath}/Dockerfile`;
1208
+
1209
+ fs.mkdirpSync(envManifestPath);
1210
+
1211
+ const confInstances = JSON.parse(
1212
+ fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
1213
+ );
1214
+
1215
+ const instance = confInstances.find((i) => i.id === id);
1216
+ if (!instance) {
1217
+ logger.error(`Instance with id '${id}' not found in conf.instances.json for deployId '${deployId}'`);
1218
+ return;
1219
+ }
1220
+
1221
+ let {
1222
+ id: _id,
1223
+ host: _host,
1224
+ path: _path,
1225
+ image: _image,
1226
+ fromPort: _fromPort,
1227
+ toPort: _toPort,
1228
+ fromDebugPort: _fromDebugPort,
1229
+ toDebugPort: _toDebugPort,
1230
+ cmd: _cmd,
1231
+ volumes: _volumes,
1232
+ metadata: _metadata,
1233
+ runtime: _runtime,
1234
+ lifecycle: _lifecycle,
1235
+ readinessProbe: _readinessProbe,
1236
+ livenessProbe: _livenessProbe,
1237
+ } = instance;
1238
+
1239
+ // Resolve Dockerfile source. Dev/prod variant rules:
1240
+ // - When the instance defines a `runtime`, look under
1241
+ // `src/runtime/<runtime>/`. In `--dev` mode prefer `Dockerfile.dev`
1242
+ // when it exists, falling back to `Dockerfile`.
1243
+ // - When `runtime` is not set, look in the project root with the
1244
+ // same `.dev` → no-suffix precedence.
1245
+ // Dockerfile.dev is a full Dockerfile (not an overlay) — each runtime
1246
+ // owns the contract between its dev image and its prod image (debug
1247
+ // build flags, extra tooling, default ports, etc.).
1248
+ const dockerfileBase = _runtime ? `src/runtime/${_runtime}` : rootPath;
1249
+ const dockerfileCandidates = options.dev
1250
+ ? [`${dockerfileBase}/Dockerfile.dev`, `${dockerfileBase}/Dockerfile`]
1251
+ : [`${dockerfileBase}/Dockerfile`];
1252
+ const dockerfileSourcePath = dockerfileCandidates.find((p) => fs.existsSync(p));
1253
+ if (dockerfileSourcePath) {
1254
+ if (options.dev && !dockerfileSourcePath.endsWith('.dev')) {
1255
+ logger.warn(
1256
+ `[instance-build-manifest] --dev requested but no Dockerfile.dev present; falling back to ${dockerfileSourcePath}`,
1257
+ );
1258
+ }
1259
+ fs.copyFileSync(dockerfileSourcePath, dockerfileManifestPath);
1260
+ } else {
1261
+ logger.warn(`[instance-build-manifest] Dockerfile not found; tried: ${dockerfileCandidates.join(', ')}`);
1262
+ }
1263
+
1264
+ const _deployId = `${deployId}-${_id}`;
1265
+ if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
1266
+ // Use debug ports in development when defined, fall back to production ports.
1267
+ if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
1268
+ if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
1269
+
1270
+ // Build image from projectPath Dockerfile and load into cluster when --build is set.
1271
+ if (options.build && projectPath) {
1272
+ const isKind = !options.kubeadm && !options.k3s;
1273
+ Underpost.image.build({
1274
+ path: projectPath,
1275
+ imageName: _image,
1276
+ podmanSave: true,
1277
+ imagePath: projectPath,
1278
+ kind: isKind,
1279
+ kubeadm: !!options.kubeadm,
1280
+ k3s: !!options.k3s,
1281
+ reset: !!options.reset,
1282
+ dev: options.dev,
1283
+ });
1284
+ logger.info(`[instance-build-manifest] Image built and loaded`, {
1285
+ image: _image,
1286
+ cluster: isKind ? 'kind' : options.kubeadm ? 'kubeadm' : 'k3s',
1287
+ });
1288
+ }
1289
+
1290
+ // Determine target traffic: opposite of current, or 'blue' if nothing is running yet.
1291
+ const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
1292
+ hostTest: _host,
1293
+ namespace: options.namespace,
1294
+ });
1295
+ const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
1296
+
1297
+ // Resolve {{grpc-service-dns}} using the parent deploy's current (or default) traffic.
1298
+ const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
1299
+ const resolvedCmd = _cmd[env].map((c) =>
1300
+ c.replaceAll(
1301
+ '{{grpc-service-dns}}',
1302
+ `${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
1303
+ ),
1304
+ );
1305
+
1306
+ // Env-aware lifecycle / probe selection. Each block may either be
1307
+ // a single object (shared across envs) or `{ development, production }`.
1308
+ const pickEnv = (v) => (v && (v.development || v.production) ? v[env] : v);
1309
+
1310
+ // Convention: an instance config may place `imagePullPolicy` inside
1311
+ // the env-scoped lifecycle block (alongside postStart/preStop).
1312
+ // Extract it onto the container spec and strip it from the lifecycle hash.
1313
+ const { lifecycle: lifecycleForManifest, imagePullPolicy: lifecycleImagePullPolicy } =
1314
+ Underpost.deploy.extractInstanceImagePullPolicy(pickEnv(_lifecycle));
1315
+ const instanceImagePullPolicy = options.imagePullPolicy || lifecycleImagePullPolicy;
1316
+
1317
+ const deploymentYaml =
1318
+ `---\n` +
1319
+ Underpost.deploy
1320
+ .deploymentYamlPartsFactory({
1321
+ deployId: _deployId,
1322
+ env,
1323
+ suffix: targetTraffic,
1324
+ resources: Underpost.deploy.resourcesFactory(options),
1325
+ replicas: options.replicas,
1326
+ image: _image,
1327
+ namespace: options.namespace,
1328
+ volumes: _volumes,
1329
+ cmd: resolvedCmd,
1330
+ lifecycle: lifecycleForManifest,
1331
+ readinessProbe: pickEnv(_readinessProbe),
1332
+ livenessProbe: pickEnv(_livenessProbe),
1333
+ containerPort: _toPort,
1334
+ imagePullPolicy: instanceImagePullPolicy,
1335
+ })
1336
+ .replace('{{ports}}', buildKindPorts(_fromPort, _toPort));
1337
+
1338
+ fs.writeFileSync(outputPath, deploymentYaml, 'utf8');
1339
+ logger.info(`[instance-build-manifest] Manifest written to ${outputPath}`, {
1340
+ deployId: _deployId,
1341
+ env,
1342
+ traffic: targetTraffic,
1343
+ image: _image,
1344
+ });
1345
+
1346
+ if (env === 'production') {
1347
+ if (fs.existsSync(dockerfileManifestPath)) {
1348
+ fs.copyFileSync(dockerfileManifestPath, `${rootPath}/Dockerfile`);
1349
+ }
1350
+ fs.copyFileSync(outputPath, `${rootPath}/deployment.yaml`);
1351
+ logger.info('[instance-build-manifest] Production artifacts copied to project root', {
1352
+ rootPath,
1353
+ dockerfile: `${rootPath}/Dockerfile`,
1354
+ deployment: `${rootPath}/deployment.yaml`,
1355
+ });
1356
+ const ciSrc = `./.github/workflows/docker-image.${_runtime}.ci.yml`;
1357
+ if (fs.existsSync(ciSrc)) {
1358
+ if (!fs.existsSync(`${rootPath}/.github/workflows`)) fs.mkdirpSync(`${rootPath}/.github/workflows`);
1359
+ fs.copyFileSync(ciSrc, `${rootPath}/.github/workflows/docker-image.${_runtime}.ci.yml`);
1360
+ logger.info(`[instance-build-manifest] CI workflow copied`, { src: ciSrc });
1361
+ }
1362
+ }
1363
+ },
1364
+
1118
1365
  /**
1119
1366
  * @method ls-deployments
1120
1367
  * @description Retrieves and logs a table of Kubernetes deployments using `Underpost.deploy.get`.
@@ -1139,6 +1386,44 @@ EOF
1139
1386
  shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
1140
1387
  },
1141
1388
 
1389
+ /**
1390
+ * @method install-crio
1391
+ * @description Installs and configures CRI-O as the container runtime for kubeadm clusters.
1392
+ * Adds the stable v1.33 CRI-O yum repository, installs the `cri-o` package, configures
1393
+ * the systemd cgroup driver, enables the `crio` service, and writes `/etc/crictl.yaml`
1394
+ * so that `crictl` targets the CRI-O socket by default.
1395
+ * @param {string} path - Unused.
1396
+ * @param {Object} options - The default underpost runner options for customizing workflow.
1397
+ * @memberof UnderpostRun
1398
+ */
1399
+ 'install-crio': (path, options = DEFAULT_OPTION) => {
1400
+ logger.info('Installing CRI-O...');
1401
+ shellExec(`cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
1402
+ [cri-o]
1403
+ name=CRI-O
1404
+ baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/
1405
+ enabled=1
1406
+ gpgcheck=1
1407
+ gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/repodata/repomd.xml.key
1408
+ EOF`);
1409
+ shellExec(`sudo dnf -y install cri-o`);
1410
+ // crictl is in the kubernetes repo but excluded by default — install it explicitly
1411
+ shellExec(`sudo yum install -y cri-tools --disableexcludes=kubernetes`);
1412
+ // Ensure CRI-O uses systemd cgroup driver (matches kubelet default)
1413
+ shellExec(`sudo sed -i 's/^#\?cgroup_manager =.*/cgroup_manager = "systemd"/' /etc/crio/crio.conf`, {
1414
+ silentOnError: true,
1415
+ });
1416
+ shellExec(`sudo systemctl enable --now crio`);
1417
+ logger.info('CRI-O installed and started.');
1418
+ // Write crictl config so all crictl calls default to the CRI-O socket
1419
+ shellExec(`cat <<EOF | sudo tee /etc/crictl.yaml
1420
+ runtime-endpoint: unix:///var/run/crio/crio.sock
1421
+ image-endpoint: unix:///var/run/crio/crio.sock
1422
+ timeout: 10
1423
+ debug: false
1424
+ EOF`);
1425
+ },
1426
+
1142
1427
  /**
1143
1428
  * @method dd-container
1144
1429
  * @description Deploys a development or debug container tasks jobs, setting up necessary volumes and images, and running specified commands within the container.
@@ -1261,7 +1546,8 @@ EOF
1261
1546
  `git config user.name '${username}' && ` +
1262
1547
  `git config user.email '${email}' && ` +
1263
1548
  `git config credential.interactive always &&` +
1264
- `git config pull.rebase false`,
1549
+ `git config pull.rebase false && ` +
1550
+ `git config core.filemode false`,
1265
1551
  {
1266
1552
  disableLog: true,
1267
1553
  silent: true,
@@ -1281,6 +1567,9 @@ EOF
1281
1567
  /**
1282
1568
  * @method promote
1283
1569
  * @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.router` for 'dd', or a specific ID).
1570
+ * When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
1571
+ * TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
1572
+ * (cert-manager Certificate resources) for each affected deployment.
1284
1573
  * @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string: `deployId,env,replicas`).
1285
1574
  * @param {Object} options - The default underpost runner options for customizing workflow
1286
1575
  * @memberof UnderpostRun
@@ -1289,11 +1578,34 @@ EOF
1289
1578
  let [inputDeployId, inputEnv, inputReplicas] = path.split(',');
1290
1579
  if (!inputEnv) inputEnv = 'production';
1291
1580
  if (!inputReplicas) inputReplicas = 1;
1581
+ // TODO: normalize: --tls maps to --cert for deploy.js isValidTLSContext compatibility
1582
+ if (options.tls) options.cert = true;
1583
+
1584
+ const applyCerts = (deployId, targetTraffic) => {
1585
+ if (!options.tls) return;
1586
+ // Rebuild proxy.yaml with --cert so the HTTPProxy includes TLS virtualhost config
1587
+ shellExec(
1588
+ `node bin deploy --build-manifest --cert --traffic ${targetTraffic} --replicas ${inputReplicas} --namespace ${options.namespace} ${deployId} ${inputEnv}`,
1589
+ );
1590
+ // Delete stale Certificate resources before reapplying
1591
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
1592
+ if (fs.existsSync(confServerPath)) {
1593
+ for (const host of Object.keys(JSON.parse(fs.readFileSync(confServerPath, 'utf8'))))
1594
+ shellExec(`sudo kubectl delete Certificate ${host} -n ${options.namespace} --ignore-not-found`);
1595
+ }
1596
+ shellExec(
1597
+ `sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${inputEnv}/proxy.yaml -n ${options.namespace}`,
1598
+ );
1599
+ const secretPath = `./engine-private/conf/${deployId}/build/${inputEnv}/secret.yaml`;
1600
+ if (fs.existsSync(secretPath)) shellExec(`kubectl apply -f ${secretPath} -n ${options.namespace}`);
1601
+ };
1602
+
1292
1603
  if (inputDeployId === 'dd') {
1293
1604
  for (const deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
1294
1605
  const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
1295
1606
  const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
1296
1607
  Underpost.deploy.switchTraffic(deployId, inputEnv, targetTraffic, inputReplicas, options.namespace, options);
1608
+ applyCerts(deployId, targetTraffic);
1297
1609
  }
1298
1610
  } else {
1299
1611
  const currentTraffic = Underpost.deploy.getCurrentTraffic(inputDeployId, { namespace: options.namespace });
@@ -1306,6 +1618,7 @@ EOF
1306
1618
  options.namespace,
1307
1619
  options,
1308
1620
  );
1621
+ applyCerts(inputDeployId, targetTraffic);
1309
1622
  }
1310
1623
  },
1311
1624
  /**
@@ -1506,7 +1819,7 @@ EOF
1506
1819
  }`;
1507
1820
  shellExec(cmd, { async: true });
1508
1821
  }
1509
- await awaitDeployMonitor(true);
1822
+ await awaitDeployMonitor();
1510
1823
  {
1511
1824
  const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
1512
1825
 
@@ -1514,7 +1827,7 @@ EOF
1514
1827
  async: true,
1515
1828
  });
1516
1829
  }
1517
- await awaitDeployMonitor(true);
1830
+ await awaitDeployMonitor();
1518
1831
  shellExec(
1519
1832
  `NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
1520
1833
  );
@@ -1611,7 +1924,7 @@ EOF
1611
1924
  );
1612
1925
  } else logger.error(`Service pod ${podToMonitor} failed to start in time.`);
1613
1926
  if (options.etcHosts === true) {
1614
- const hostListenResult = Underpost.deploy.etcHostFactory([host]);
1927
+ const hostListenResult = etcHostFactory([host]);
1615
1928
  logger.info(hostListenResult.renderHosts);
1616
1929
  }
1617
1930
  },
@@ -1629,7 +1942,7 @@ EOF
1629
1942
  const confServer = loadConfServerJson(`./engine-private/conf/${options.deployId}/conf.server.json`);
1630
1943
  hosts.push(...Object.keys(confServer));
1631
1944
  }
1632
- const hostListenResult = Underpost.deploy.etcHostFactory(hosts);
1945
+ const hostListenResult = etcHostFactory(hosts);
1633
1946
  logger.info(hostListenResult.renderHosts);
1634
1947
  },
1635
1948
 
@@ -1920,6 +2233,16 @@ EOF
1920
2233
 
1921
2234
  shellCd('/home/dd/engine');
1922
2235
  },
2236
+ /**
2237
+ * @method pull-rocky-image
2238
+ * @description Pulls the base `rockylinux:9` image from Docker Hub via Podman.
2239
+ * @param {string} path - The input value, identifier, or path for the operation.
2240
+ * @param {Object} options - The default underpost runner options for customizing workflow
2241
+ * @memberof UnderpostRun
2242
+ */
2243
+ 'pull-rocky-image': (path, options = DEFAULT_OPTION) => {
2244
+ shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
2245
+ },
1923
2246
  /**
1924
2247
  * @method rmi
1925
2248
  * @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
@@ -1938,24 +2261,28 @@ EOF
1938
2261
  * @memberof UnderpostRun
1939
2262
  */
1940
2263
  kill: (path = '', options = DEFAULT_OPTION) => {
1941
- if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
2264
+ if (options.pid)
2265
+ return shellExec(`sudo kill -9 ${options.pid}`, {
2266
+ silentOnError: true,
2267
+ });
1942
2268
  for (const _path of path.split(',')) {
1943
2269
  if (_path.split('+')[1]) {
1944
2270
  let [port, sumPortOffSet] = _path.split('+');
1945
2271
  port = parseInt(port);
1946
2272
  sumPortOffSet = parseInt(sumPortOffSet);
1947
2273
  for (const sumPort of range(0, sumPortOffSet))
1948
- shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
1949
- } else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
2274
+ shellExec(
2275
+ `PIDS=$(lsof -t -i:${parseInt(port) + parseInt(sumPort)}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`,
2276
+ {
2277
+ silentOnError: true,
2278
+ },
2279
+ );
2280
+ } else
2281
+ shellExec(`PIDS=$(lsof -t -i:${_path}); [ -n "$PIDS" ] && sudo kill -9 $PIDS || true`, {
2282
+ silentOnError: true,
2283
+ });
1950
2284
  }
1951
2285
  },
1952
- /**
1953
- * @method secret
1954
- * @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
1955
- * @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
1956
- * @param {Object} options - The default underpost runner options for customizing workflow
1957
- * @memberof UnderpostRun
1958
- */
1959
2286
  /**
1960
2287
  * @method generate-pass
1961
2288
  * @description Generates a cryptographically secure random password that satisfies all validatePassword
@@ -1992,11 +2319,18 @@ EOF
1992
2319
  if (options.copy) pbcopy(password);
1993
2320
  else console.log(password);
1994
2321
  },
1995
-
2322
+ /**
2323
+ * @method secret
2324
+ * @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
2325
+ * @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
2326
+ * @param {Object} options - The default underpost runner options for customizing workflow
2327
+ * @memberof UnderpostRun
2328
+ */
1996
2329
  secret: (path, options = DEFAULT_OPTION) => {
1997
- const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
1998
- const command = `node bin secret underpost --create-from-file ${secretPath}`;
1999
- shellExec(command);
2330
+ const cronDeployId = cronDeployIdResolve() || 'dd-cron';
2331
+ Underpost.secret.underpost.createFromEnvFile(
2332
+ `/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${options.dev ? 'development' : 'production'}`,
2333
+ );
2000
2334
  },
2001
2335
  /**
2002
2336
  * @method underpost-config
@@ -2166,6 +2500,195 @@ EOF`;
2166
2500
  if (options.logs) shellExec(`kubectl logs -f ${podName} -n ${namespace}`, { async: true });
2167
2501
  }
2168
2502
  },
2503
+
2504
+ /**
2505
+ * @method push-bundle
2506
+ * @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
2507
+ * Steps: set env, build+split zip, switch to cron env, upload parts to storage.
2508
+ * @param {string} path - Optional `fsPath.splitOption` string.
2509
+ * Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
2510
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2511
+ * @param {string} [options.deployId] - Override deploy ID.
2512
+ * @param {boolean} [options.dev] - Use development environment; defaults to production.
2513
+ * @memberof UnderpostRun
2514
+ */
2515
+ 'push-bundle': (path = '', options = DEFAULT_OPTION) => {
2516
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
2517
+ const env = options.dev ? 'development' : 'production';
2518
+ const deployId = options.deployId || 'dd-default';
2519
+ const pathParts = (path || '').split('.');
2520
+ const fsPath = (pathParts[0] || '').trim() || 'build';
2521
+ const splitOption = (pathParts[1] || '').trim();
2522
+
2523
+ let splitFlag = '--split 8';
2524
+ if (splitOption) {
2525
+ if (splitOption === 'none-split') {
2526
+ splitFlag = '';
2527
+ } else {
2528
+ const splitMb = Number(splitOption);
2529
+ if (Number.isFinite(splitMb) && splitMb > 0) {
2530
+ splitFlag = `--split ${splitMb}`;
2531
+ } else {
2532
+ logger.warn('push-bundle: invalid split option, using default split 8', {
2533
+ path,
2534
+ splitOption,
2535
+ });
2536
+ }
2537
+ }
2538
+ }
2539
+
2540
+ shellExec(`${baseCommand} env ${deployId} ${env}`);
2541
+ shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
2542
+ shellExec(
2543
+ `${baseCommand} fs ${fsPath} --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --force`,
2544
+ );
2545
+ },
2546
+
2547
+ /**
2548
+ * @method pull-bundle
2549
+ * @description Downloads split zip parts from file storage, merges and extracts them, and moves the result into the public directory.
2550
+ * Steps: set env, download parts (omit-unzip), merge zip, unzip, remove zip + parts, move to public/<host>[/path].
2551
+ * Iterates over every non-singleReplica, non-redirect, non-disabledRebuild route in conf.server.json
2552
+ * so that multi-path deployments are handled correctly.
2553
+ * @param {string} path - Optional comma-separated host name(s) to restrict processing (e.g. 'underpost.net' or 'a.com,b.com').
2554
+ * If omitted, all hosts from `engine-private/conf/<deployId>/conf.server.json` are used.
2555
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2556
+ * @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
2557
+ * @param {boolean} [options.dev] - Use development environment; defaults to production.
2558
+ * @memberof UnderpostRun
2559
+ */
2560
+ 'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
2561
+ const baseCommand = options.dev ? 'node bin' : 'underpost';
2562
+ const env = options.dev ? 'development' : 'production';
2563
+ const deployId = options.deployId || 'dd-default';
2564
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
2565
+ const confServer = fs.existsSync(confServerPath) ? loadConfServerJson(confServerPath) : {};
2566
+ const hostsArg = path
2567
+ ? path
2568
+ .split(',')
2569
+ .map((h) => h.trim())
2570
+ .filter(Boolean)
2571
+ : Object.keys(confServer);
2572
+
2573
+ if (hostsArg.length === 0) {
2574
+ logger.error('pull-bundle: no hosts resolved', { deployId, path, confServerPath });
2575
+ return;
2576
+ }
2577
+
2578
+ shellExec(`${baseCommand} env ${deployId} ${env}`);
2579
+ if (!fs.existsSync('./build')) fs.mkdirSync('./build', { recursive: true });
2580
+ shellExec(
2581
+ `${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
2582
+ );
2583
+
2584
+ for (const host of hostsArg) {
2585
+ // Gather all routes for this host; fall back to root '/' when host is not in confServer
2586
+ // (e.g. when hosts were provided explicitly via the path argument).
2587
+ const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
2588
+
2589
+ for (const routePath of routePaths) {
2590
+ const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
2591
+ // Skip routes that are not built by buildClient (mirrors buildClient skip conditions)
2592
+ if (routeConf.singleReplica || routeConf.redirect || routeConf.disabledRebuild) continue;
2593
+
2594
+ // buildClient names the zip as "<host>-<path-no-slashes>.zip"
2595
+ // e.g. host="underpost.net", path="/" → buildId="underpost.net-", zip="build/underpost.net-.zip"
2596
+ // e.g. host="app.net", path="/admin" → buildId="app.net-admin", zip="build/app.net-admin.zip"
2597
+ const buildId = `${host}-${routePath.replaceAll('/', '')}`;
2598
+ const zipPath = `build/${buildId}.zip`;
2599
+ const buildDir = './build';
2600
+ const hasZip = fs.existsSync(zipPath);
2601
+ const hasParts =
2602
+ fs.existsSync(buildDir) &&
2603
+ fs
2604
+ .readdirSync(buildDir)
2605
+ .some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
2606
+
2607
+ if (!hasZip && !hasParts) {
2608
+ logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
2609
+ continue;
2610
+ }
2611
+
2612
+ if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
2613
+ shellExec(`${baseCommand} client --unzip ${zipPath}`);
2614
+ shellExec(`sudo rm -rf ${zipPath}`);
2615
+
2616
+ // Clean up downloaded part wrapper zips left by --omit-unzip pull
2617
+ if (fs.existsSync(buildDir)) {
2618
+ fs.readdirSync(buildDir)
2619
+ .filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
2620
+ .forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
2621
+ }
2622
+
2623
+ // unzipClientBuild extracts to buildId with trailing '-' stripped
2624
+ // e.g. "build/underpost.net-" → "build/underpost.net"
2625
+ // e.g. "build/app.net-admin" → "build/app.net-admin" (no trailing dash, no change)
2626
+ const extractedDir = `build/${buildId.replace(/-$/, '')}`;
2627
+ if (!fs.existsSync(extractedDir)) {
2628
+ logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
2629
+ continue;
2630
+ }
2631
+
2632
+ // Destination mirrors the public directory layout used by the server
2633
+ const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
2634
+ if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
2635
+ // Ensure parent directory exists for sub-paths
2636
+ if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
2637
+ shellExec(`sudo mv ${extractedDir} ${publicDestPath}`);
2638
+ }
2639
+ }
2640
+ },
2641
+
2642
+ /**
2643
+ * @method monitor-ui
2644
+ * @description Installs and enables the Cockpit KVM Dashboard (cockpit, cockpit-machines, libvirt)
2645
+ * and opens the cockpit firewall service. With `--remove`, closes the firewall service instead.
2646
+ * @param {string} path - Unused.
2647
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2648
+ * `options.remove` — when true, removes the cockpit firewall rule instead of adding it.
2649
+ * @memberof UnderpostRun
2650
+ */
2651
+ 'monitor-ui': (path, options = DEFAULT_OPTION) => {
2652
+ if (options.remove) {
2653
+ shellExec(`sudo firewall-cmd --zone=public --remove-service=cockpit --permanent`);
2654
+ shellExec(`sudo firewall-cmd --reload`);
2655
+ return;
2656
+ }
2657
+ shellExec(`sudo dnf install -y cockpit cockpit-machines libvirt`);
2658
+ shellExec(`sudo systemctl enable --now cockpit.socket libvirtd`);
2659
+ shellExec(`sudo firewall-cmd --permanent --add-service=cockpit`);
2660
+ shellExec(`sudo firewall-cmd --reload`);
2661
+ },
2662
+
2663
+ /**
2664
+ * @method shared-dir
2665
+ * @description Run once for initial shared-directory setup. Creates the group, adds the user,
2666
+ * creates the directory, sets ownership, applies the SGID bit, and configures default ACLs so
2667
+ * all future files inside the directory automatically inherit group write permissions.
2668
+ * Use `reload-shared-dir` for subsequent permission repairs without recreating the group.
2669
+ * @param {string} path - Target directory to set up (defaults to `/home/dd/engine`).
2670
+ * Customise via the `path` argument or leave empty to use the default.
2671
+ * @param {Object} options - The default underpost runner options for customizing workflow.
2672
+ * Key fields: `options.user` (default `'admin'`), `options.group` (default `'engine-dev'`).
2673
+ * @memberof UnderpostRun
2674
+ */
2675
+ 'shared-dir': (path = '/home/dd/engine', options = DEFAULT_OPTION) => {
2676
+ const dir = path || '/home/dd/engine';
2677
+ const user = options.user || 'admin';
2678
+ const group = options.group || 'engine-dev';
2679
+
2680
+ logger.info(`[setup-shared-dir] dir=${dir} user=${user} group=${group}`);
2681
+
2682
+ shellExec(`sudo groupadd ${group} 2>/dev/null || true`);
2683
+ shellExec(`sudo usermod -aG ${group} ${user}`);
2684
+ shellExec(`sudo mkdir -p ${dir}`);
2685
+ shellExec(`sudo chown -R ${user}:${group} ${dir}`);
2686
+ shellExec(`sudo chmod -R 2775 ${dir}`);
2687
+ shellExec(`sudo setfacl -d -m g:${group}:rwx ${dir}`);
2688
+ shellExec(`sudo setfacl -m g:${group}:rwx ${dir}`);
2689
+
2690
+ logger.info(`[setup-shared-dir] Shared directory setup complete: ${dir}`);
2691
+ },
2169
2692
  };
2170
2693
 
2171
2694
  static API = {
@@ -2222,14 +2745,14 @@ EOF`;
2222
2745
  if (options.replicas === '' || options.replicas === null || options.replicas === undefined)
2223
2746
  options.replicas = 1;
2224
2747
  options.npmRoot = npmRoot;
2225
- logger.info('callback', { path, options });
2748
+ logger.info(`Executing runner`, { runner, namespace: options.namespace });
2226
2749
  if (!Underpost.run.RUNNERS.includes(runner)) throw new Error(`Runner not found: ${runner}`);
2227
2750
  const result = await Underpost.run.CALL(runner, path, options);
2228
2751
  return result;
2229
2752
  } catch (error) {
2230
2753
  console.log(error);
2231
2754
  logger.error(error);
2232
- return null;
2755
+ process.exit(1);
2233
2756
  }
2234
2757
  },
2235
2758
  };