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
@@ -7,6 +7,8 @@
7
7
  import { getNpmRootPath } from '../server/conf.js';
8
8
  import { loggerFactory } from '../server/logger.js';
9
9
  import { shellExec } from '../server/process.js';
10
+ import { MONGODB_DEFAULT_REPLICA_COUNT } from '../db/mongo/MongooseDB.js';
11
+ import { MongoBootstrap } from '../db/mongo/MongoBootstrap.js';
10
12
  import os from 'os';
11
13
  import fs from 'fs-extra';
12
14
  import Underpost from '../index.js';
@@ -31,7 +33,7 @@ class UnderpostCluster {
31
33
  * @param {object} [options] - Configuration options for cluster initialization.
32
34
  * @param {boolean} [options.mongodb=false] - Deploy MongoDB.
33
35
  * @param {boolean} [options.mongodb4=false] - Deploy MongoDB 4.4.
34
- * @param {String} [options.mongoDbHost=''] - Set custom mongo db host
36
+ * @param {string} [options.serviceHost=''] - Set a custom host/IP for exposed MongoDB and Valkey clients.
35
37
  * @param {boolean} [options.mariadb=false] - Deploy MariaDB.
36
38
  * @param {boolean} [options.mysql=false] - Deploy MySQL.
37
39
  * @param {boolean} [options.postgresql=false] - Deploy PostgreSQL.
@@ -41,6 +43,7 @@ class UnderpostCluster {
41
43
  * @param {boolean} [options.certManager=false] - Deploy Cert-Manager for certificate management.
42
44
  * @param {boolean} [options.listPods=false] - List Kubernetes pods.
43
45
  * @param {boolean} [options.reset=false] - Perform a comprehensive reset of Kubernetes and container environments.
46
+ * @param {boolean} [options.resetMongodb=false] - Perform a targeted reset of MongoDB components without restarting the entire cluster.
44
47
  * @param {boolean} [options.dev=false] - Run in development mode (adjusts paths).
45
48
  * @param {string} [options.nsUse=''] - Set the current kubectl namespace (creates namespace if it doesn't exist).
46
49
  * @param {string} [options.namespace='default'] - Kubernetes namespace for cluster operations.
@@ -61,6 +64,12 @@ class UnderpostCluster {
61
64
  * @param {boolean} [options.removeVolumeHostPaths=false] - Remove data from host paths used by Persistent Volumes.
62
65
  * @param {string} [options.hosts] - Set custom hosts entries.
63
66
  * @param {string} [options.replicas] - Set the number of replicas for certain deployments.
67
+ * @param {boolean} [options.nodePort=false] - Expose enabled ready services (e.g. MongoDB 4.4, Valkey)
68
+ * to the host/public network via their NodePort Service manifest. The node port value lives directly
69
+ * in each manifest (mongodb-4.4/mongodb-nodeport.yaml, valkey/valkey-nodeport.yaml).
70
+ * @param {string} [options.nodeSelector=''] - Pin the just-deployed StatefulSet (MongoDB 4.4 / Valkey)
71
+ * to a specific Kubernetes node by name (e.g. 'localhost.localdomain'). Applied via a
72
+ * `kubernetes.io/hostname` nodeSelector patch once the workload reports Ready.
64
73
  * @memberof UnderpostCluster
65
74
  */
66
75
  async init(
@@ -68,7 +77,7 @@ class UnderpostCluster {
68
77
  options = {
69
78
  mongodb: false,
70
79
  mongodb4: false,
71
- mongoDbHost: '',
80
+ serviceHost: '',
72
81
  mariadb: false,
73
82
  mysql: false,
74
83
  postgresql: false,
@@ -78,6 +87,7 @@ class UnderpostCluster {
78
87
  certManager: false,
79
88
  listPods: false,
80
89
  reset: false,
90
+ resetMongodb: false,
81
91
  dev: false,
82
92
  nsUse: '',
83
93
  namespace: 'default',
@@ -98,18 +108,22 @@ class UnderpostCluster {
98
108
  removeVolumeHostPaths: false,
99
109
  hosts: '',
100
110
  replicas: '',
111
+ nodePort: false,
112
+ nodeSelector: '',
101
113
  },
102
114
  ) {
103
115
  if (options.initHost) return Underpost.cluster.initHost();
104
116
 
105
117
  if (options.uninstallHost) return Underpost.cluster.uninstallHost();
106
118
 
107
- if (options.config) return Underpost.cluster.config();
119
+ if (options.config) return options.k3s ? Underpost.cluster.configMinimalK3s() : Underpost.cluster.config();
108
120
 
109
- if (options.chown) return Underpost.cluster.chown();
121
+ if (options.chown) return Underpost.cluster.chown(options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind');
110
122
 
111
123
  const npmRoot = getNpmRootPath();
112
124
  const underpostRoot = options.dev ? '.' : `${npmRoot}/underpost`;
125
+ const serviceHostInput = `${options.serviceHost || ''}`.trim();
126
+ const serviceHost = Underpost.cluster.resolveServiceHost(options);
113
127
 
114
128
  if (options.listPods) return console.table(Underpost.kubectl.get(podName ?? undefined));
115
129
  // Set default namespace if not specified
@@ -120,6 +134,7 @@ class UnderpostCluster {
120
134
  const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
121
135
  stdout: true,
122
136
  silent: true,
137
+ silentOnError: true,
123
138
  }).trim();
124
139
 
125
140
  if (!namespaceExists) {
@@ -135,30 +150,57 @@ class UnderpostCluster {
135
150
  return;
136
151
  }
137
152
 
138
- // Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
153
+ // Route reset to the per-type method. Each only touches what its runtime owns.
139
154
  if (options.reset) {
140
- const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
141
- return await Underpost.cluster.safeReset({
155
+ if (options.k3s)
156
+ return await Underpost.cluster.safeResetK3s({ underpostRoot, resetMode: options.resetMode || 'full' });
157
+ if (options.kubeadm)
158
+ return await Underpost.cluster.safeResetKubeadm({
159
+ underpostRoot,
160
+ removeVolumeHostPaths: options.removeVolumeHostPaths,
161
+ });
162
+ return await Underpost.cluster.safeResetKind({
142
163
  underpostRoot,
143
164
  removeVolumeHostPaths: options.removeVolumeHostPaths,
165
+ });
166
+ }
167
+
168
+ // Targeted MongoDB-only reset (does not restart the whole node)
169
+ if (options.resetMongodb) {
170
+ const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
171
+ return await MongoBootstrap.reset({
172
+ namespace: options.namespace,
144
173
  clusterType,
174
+ underpostRoot,
145
175
  });
146
176
  }
147
177
 
148
- // Check if a cluster (Kind, Kubeadm, or K3s) is already initialized
149
- const alreadyKubeadmCluster = Underpost.kubectl.get('calico-kube-controllers')[0];
150
- const alreadyKindCluster = Underpost.kubectl.get('kube-apiserver-kind-control-plane')[0];
151
- // K3s pods often contain 'svclb-traefik' in the kube-system namespace
152
- const alreadyK3sCluster = Underpost.kubectl.get('svclb-traefik')[0];
178
+ // Check if a cluster (Kind, Kubeadm, or K3s) is already initialized by
179
+ // inspecting nodes rather than probing add-on pods. See detectClusterRuntime.
180
+ const runtime = Underpost.cluster.detectClusterRuntime();
181
+ const alreadyCluster = runtime.type !== null;
182
+ if (alreadyCluster) {
183
+ logger.info(
184
+ `Detected existing ${runtime.type} cluster (${runtime.ready ? 'Ready' : 'NotReady'}); skipping initialization.`,
185
+ );
186
+ }
153
187
 
154
188
  // --- Kubeadm/Kind/K3s Cluster Initialization ---
155
- if (!alreadyKubeadmCluster && !alreadyKindCluster && !alreadyK3sCluster) {
156
- Underpost.cluster.config();
189
+ // Host config is applied per cluster type (not shared) so the K3s path can
190
+ // stay minimal and avoid the Docker/containerd/kubelet setup it does not need.
191
+ if (!alreadyCluster) {
157
192
  if (options.k3s) {
193
+ // K3s is self-contained (bundles containerd, kubelet, CNI, CoreDNS).
194
+ // Apply ONLY minimal host config — see configMinimalK3s.
195
+ Underpost.cluster.configMinimalK3s();
158
196
  logger.info('Initializing K3s control plane...');
159
197
  // Install K3s
160
198
  logger.info('Installing K3s...');
161
- shellExec(`curl -sfL https://get.k3s.io | sh -`);
199
+ // Disable the bundled traefik ingress and servicelb (Klipper) load
200
+ // balancer. The platform exposes services explicitly via Project
201
+ // Contour / Envoy and NodePort services (see --node-port); leaving the
202
+ // K3s built-ins enabled would bind the same host ports and conflict.
203
+ shellExec(`curl -sfL https://get.k3s.io | sh -s - --disable=traefik --disable=servicelb`);
162
204
  logger.info('K3s installation completed.');
163
205
 
164
206
  Underpost.cluster.chown('k3s');
@@ -166,15 +208,29 @@ class UnderpostCluster {
166
208
  logger.info('Waiting for K3s to be ready...');
167
209
  shellExec(`sudo systemctl is-active --wait k3s || sudo systemctl wait --for=active k3s.service`);
168
210
  logger.info('K3s service is active.');
211
+ // K3s manages its own CNI (flannel) and iptables rules. nat-iptables.sh
212
+ // is neither needed nor desirable for a single K3s node in a VM.
169
213
  } else if (options.kubeadm) {
214
+ Underpost.cluster.config();
215
+ Underpost.cluster.natSetup({ underpostRoot });
170
216
  logger.info('Initializing Kubeadm control plane...');
171
217
  // Set default values if not provided
172
218
  const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
173
219
  const controlPlaneEndpoint = options.controlPlaneEndpoint || `${os.hostname()}:6443`;
174
220
 
175
- // Initialize kubeadm control plane
221
+ // Initialize kubeadm control plane.
222
+ // Use CRI-O socket when available, otherwise fall back to containerd.
223
+ const crioSocket = 'unix:///var/run/crio/crio.sock';
224
+ const containerdSocket = 'unix:///run/containerd/containerd.sock';
225
+ const criSocket =
226
+ shellExec(`test -S /var/run/crio/crio.sock && echo crio || echo containerd`, {
227
+ stdout: true,
228
+ silent: true,
229
+ }).trim() === 'crio'
230
+ ? crioSocket
231
+ : containerdSocket;
176
232
  shellExec(
177
- `sudo kubeadm init --pod-network-cidr=${podNetworkCidr} --control-plane-endpoint="${controlPlaneEndpoint}"`,
233
+ `sudo kubeadm init --pod-network-cidr=${podNetworkCidr} --control-plane-endpoint="${controlPlaneEndpoint}" --cri-socket=${criSocket}`,
178
234
  );
179
235
  // Configure kubectl for the current user
180
236
  Underpost.cluster.chown('kubeadm'); // Pass 'kubeadm' to chown
@@ -192,19 +248,49 @@ class UnderpostCluster {
192
248
  // Untaint control plane node to allow scheduling pods
193
249
  const nodeName = os.hostname();
194
250
  shellExec(`kubectl taint nodes ${nodeName} node-role.kubernetes.io/control-plane:NoSchedule-`);
195
- // Install local-path-provisioner for dynamic PVCs (optional but recommended)
251
+ // Install local-path-provisioner for dynamic PVCs
196
252
  logger.info('Installing local-path-provisioner...');
197
253
  shellExec(
198
254
  `kubectl apply -f https://cdn.jsdelivr.net/gh/rancher/local-path-provisioner@master/deploy/local-path-storage.yaml`,
199
255
  );
200
256
  } else {
201
- // Kind cluster initialization (if not using kubeadm or k3s)
257
+ Underpost.cluster.config();
258
+ Underpost.cluster.natSetup({ underpostRoot });
259
+ // Kind cluster initialization (default for development)
202
260
  logger.info('Initializing Kind cluster...');
203
- shellExec(
204
- `cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
205
- options.dev ? '-dev' : ''
206
- }.yaml`,
207
- );
261
+ const devReplicaCount = Math.max(Number(options.replicas) || MONGODB_DEFAULT_REPLICA_COUNT, 3);
262
+ shellExec(`sudo mkdir -p /data/mongodb`);
263
+ for (let index = 0; index < devReplicaCount; index++) {
264
+ shellExec(`sudo mkdir -p /data/mongodb/v${index}`);
265
+ }
266
+ const kindCreateCmd = `cd ${underpostRoot}/manifests && kind create cluster --config kind-config-dev.yaml`;
267
+ try {
268
+ shellExec(kindCreateCmd);
269
+ } catch (error) {
270
+ const kindCreateErrText = `${error?.message || ''}\n${error?.stderr || ''}`;
271
+ if (kindCreateErrText.includes('all predefined address pools have been fully subnetted')) {
272
+ logger.warn(
273
+ 'Docker address pool exhausted while creating Kind cluster. Running cleanup and retrying once...',
274
+ );
275
+ Underpost.cluster.recoverKindDockerNetworks();
276
+ try {
277
+ shellExec(kindCreateCmd);
278
+ } catch (retryError) {
279
+ const retryErrText = `${retryError?.message || ''}\n${retryError?.stderr || ''}`;
280
+ if (retryErrText.includes('all predefined address pools have been fully subnetted')) {
281
+ logger.warn(
282
+ 'Kind retry still failed from pool exhaustion. Applying Docker daemon address-pool config and retrying once more...',
283
+ );
284
+ Underpost.cluster.ensureDockerDefaultAddressPools();
285
+ shellExec(kindCreateCmd);
286
+ } else {
287
+ throw retryError;
288
+ }
289
+ }
290
+ } else {
291
+ throw error;
292
+ }
293
+ }
208
294
  Underpost.cluster.chown('kind'); // Pass 'kind' to chown
209
295
  }
210
296
  }
@@ -253,7 +339,23 @@ EOF
253
339
  if (options.pullImage) Underpost.cluster.pullImage('valkey/valkey:latest', options);
254
340
  shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
255
341
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
256
- await Underpost.test.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60 * 10);
342
+ const valkeyReady = await Underpost.test.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60 * 10);
343
+ // Expose valkey to the host/public network only once the pod is ready.
344
+ // The node port (32079) is set directly in the manifest.
345
+ if (valkeyReady && options.nodePort)
346
+ shellExec(`kubectl apply -f ${underpostRoot}/manifests/valkey/valkey-nodeport.yaml -n ${options.namespace}`);
347
+ if (valkeyReady && options.nodeSelector)
348
+ Underpost.cluster.pinToNode({
349
+ name: 'valkey-service',
350
+ namespace: options.namespace,
351
+ node: options.nodeSelector,
352
+ });
353
+ if (valkeyReady && serviceHost)
354
+ Underpost.cluster.syncServiceConnectionEnv({
355
+ serviceHost,
356
+ valkey: true,
357
+ options,
358
+ });
257
359
  }
258
360
  if (options.ipfs) {
259
361
  await Underpost.ipfs.deploy(options, underpostRoot);
@@ -286,54 +388,75 @@ EOF
286
388
  }
287
389
  if (options.mongodb4) {
288
390
  if (options.pullImage) Underpost.cluster.pullImage('mongo:4.4', options);
391
+ shellExec(`kubectl delete statefulset mongodb -n ${options.namespace} --ignore-not-found`);
289
392
  shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
290
393
 
291
- const deploymentName = 'mongodb-deployment';
394
+ const statefulSetName = 'mongodb';
395
+ const podName = 'mongodb-0';
292
396
 
293
- const successInstance = await Underpost.test.statusMonitor(deploymentName);
397
+ const successInstance = await Underpost.test.statusMonitor(podName);
294
398
 
295
399
  if (successInstance) {
296
- if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-service';
297
- const mongoConfig = {
298
- _id: 'rs0',
299
- members: [{ _id: 0, host: `${options.mongoDbHost}:27017` }],
300
- };
301
-
302
- const [pod] = Underpost.kubectl.get(deploymentName);
303
-
304
- shellExec(
305
- `sudo kubectl exec -i ${pod.NAME} -- mongo --quiet \
306
- --eval 'rs.initiate(${JSON.stringify(mongoConfig)})'`,
307
- );
400
+ // mongod only accepts a member host it can recognize as itself (the isSelf check):
401
+ // it must match a local interface IP or be reachable back at that address. A pod-external
402
+ // LAN IP / NodePort address is neither bound in the pod netns nor routable back from it,
403
+ // so reconfiguring to it fails with NodeNotFound ("...maps to this node") even under
404
+ // force. Bootstrap on localhost; only advertise a non-localhost host when the node can
405
+ // self-verify it, and tolerate the failure otherwise so bootstrap stays idempotent.
406
+ // Clients reaching the set through an external IP/NodePort must use directConnection=true,
407
+ // which ignores the advertised member host (see MongooseDB.buildUri).
408
+ const rsHost = serviceHostInput || (options.dev ? '127.0.0.1' : 'mongodb-0.mongodb-service');
409
+ const memberHost = `${rsHost}:27017`;
410
+ const initEval = [
411
+ `try { rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "localhost:27017" }] }); }`,
412
+ `catch (e) { if (!String(e).includes("already initialized") && !String(e).includes("AlreadyInitialized")) throw e; }`,
413
+ `for (var i = 0; i < 30; i++) { if (db.isMaster().ismaster) break; sleep(1000); }`,
414
+ memberHost === 'localhost:27017'
415
+ ? ``
416
+ : `try { var c = rs.conf(); c.members[0].host = "${memberHost}"; rs.reconfig(c, { force: true }); print("RS_HOST_SET ${memberHost}"); } catch (e) { if (String(e).includes("NodeNotFound") || String(e).includes("maps to this node")) { print("RS_HOST_SKIPPED ${memberHost} (not self-reachable from pod; clients must use directConnection=true)"); } else { throw e; } }`,
417
+ ]
418
+ .filter(Boolean)
419
+ .join(' ');
420
+
421
+ shellExec(`sudo kubectl exec -i ${podName} -n ${options.namespace} -- mongo --quiet --eval '${initEval}'`);
422
+
423
+ // Only expose mongos to the host/public network once the instance is
424
+ // confirmed ready and the replica set is initiated. The node port (32017)
425
+ // is set directly in the manifest.
426
+ if (options.nodePort)
427
+ shellExec(
428
+ `kubectl apply -f ${underpostRoot}/manifests/mongodb-4.4/mongodb-nodeport.yaml -n ${options.namespace}`,
429
+ );
430
+ if (options.nodeSelector)
431
+ Underpost.cluster.pinToNode({
432
+ name: statefulSetName,
433
+ namespace: options.namespace,
434
+ node: options.nodeSelector,
435
+ });
436
+ if (serviceHost)
437
+ Underpost.cluster.syncServiceConnectionEnv({
438
+ serviceHost,
439
+ mongodb: true,
440
+ options,
441
+ });
308
442
  }
309
443
  } else if (options.mongodb) {
310
- if (options.pullImage) Underpost.cluster.pullImage('mongo:latest', options);
311
- shellExec(
312
- `sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
313
- );
314
- shellExec(
315
- `sudo kubectl create secret generic mongodb-secret --from-file=username=/home/dd/engine/engine-private/mongodb-username --from-file=password=/home/dd/engine/engine-private/mongodb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
316
- );
317
- shellExec(`kubectl delete statefulset mongodb -n ${options.namespace} --ignore-not-found`);
318
- shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml -n ${options.namespace}`);
319
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb -n ${options.namespace}`);
320
-
321
- const successInstance = await Underpost.test.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
322
-
323
- if (successInstance) {
324
- if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-0.mongodb-service';
325
- const mongoConfig = {
326
- _id: 'rs0',
327
- members: options.mongoDbHost.split(',').map((host, index) => ({ _id: index, host: `${host}:27017` })),
328
- };
329
-
330
- shellExec(
331
- `sudo kubectl exec -i mongodb-0 -- mongosh --quiet --json=relaxed \
332
- --eval 'use admin' \
333
- --eval 'rs.initiate(${JSON.stringify(mongoConfig)})' \
334
- --eval 'rs.status()'`,
335
- );
336
- }
444
+ const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
445
+ await MongoBootstrap.initReplicaSet({
446
+ namespace: options.namespace,
447
+ replicaCount: Number(options.replicas) || MONGODB_DEFAULT_REPLICA_COUNT,
448
+ hostList: serviceHostInput,
449
+ pullImage: options.pullImage,
450
+ reset: options.reset,
451
+ clusterType,
452
+ underpostRoot,
453
+ });
454
+ if (serviceHost)
455
+ Underpost.cluster.syncServiceConnectionEnv({
456
+ serviceHost,
457
+ mongodb: true,
458
+ options,
459
+ });
337
460
  }
338
461
 
339
462
  if (options.contour) {
@@ -368,6 +491,132 @@ EOF
368
491
  }
369
492
  },
370
493
 
494
+ /**
495
+ * @method detectClusterRuntime
496
+ * @description Detects an already-initialized cluster by inspecting Kubernetes
497
+ * nodes, and classifies its runtime. Nodes are authoritative and stable, unlike
498
+ * add-on pods: the previous check keyed off pod names (`calico-kube-controllers`,
499
+ * `kube-apiserver-kind-control-plane`, `svclb-traefik`), whose presence and
500
+ * readiness are timing- and config-dependent. Disabling servicelb removes the
501
+ * `svclb-traefik` pods entirely, and CNI/controller pods report NotReady for a
502
+ * window right after init — both of which made re-runs misdetect the cluster
503
+ * state. Classification relies on stable node attributes:
504
+ * - k3s: the node VERSION carries a `+k3s` build suffix (e.g. v1.30.5+k3s1).
505
+ * - kind: kind names every node `<cluster>-control-plane` / `<cluster>-worker`.
506
+ * - kubeadm: a real control-plane node that is neither of the above.
507
+ * @returns {{ type: ('k3s'|'kubeadm'|'kind'|null), ready: boolean, nodes: Array<object> }}
508
+ * `type` is the detected runtime (null when no cluster exists); `ready` is true
509
+ * when at least one node reports STATUS=Ready.
510
+ * @memberof UnderpostCluster
511
+ */
512
+ detectClusterRuntime() {
513
+ const nodes = Underpost.kubectl.get('', 'nodes');
514
+ if (!nodes.length) return { type: null, ready: false, nodes: [] };
515
+
516
+ // STATUS can be a comma-joined list (e.g. "Ready,SchedulingDisabled").
517
+ const ready = nodes.some((n) => `${n.STATUS || ''}`.split(',').includes('Ready'));
518
+
519
+ let type;
520
+ if (nodes.some((n) => `${n.VERSION || ''}`.includes('+k3s'))) type = 'k3s';
521
+ else if (nodes.some((n) => `${n.NAME || ''}`.includes('-control-plane') || `${n.NAME || ''}`.includes('kind')))
522
+ type = 'kind';
523
+ else type = 'kubeadm';
524
+
525
+ return { type, ready, nodes };
526
+ },
527
+
528
+ /**
529
+ * @method pinToNode
530
+ * @description Pins a workload to a specific Kubernetes node by patching its
531
+ * pod template with a `kubernetes.io/hostname` nodeSelector. General-purpose;
532
+ * currently used to place the MongoDB 4.4 / Valkey StatefulSets on a chosen
533
+ * node (`--node-selector`). The patch triggers a rolling reschedule onto the
534
+ * target node.
535
+ * @param {object} params
536
+ * @param {string} [params.kind='statefulset'] - Workload kind to patch.
537
+ * @param {string} params.name - Workload name.
538
+ * @param {string} params.namespace - Target namespace.
539
+ * @param {string} params.node - Target node name (matches `kubernetes.io/hostname`).
540
+ * @memberof UnderpostCluster
541
+ */
542
+ pinToNode({ kind = 'statefulset', name, namespace, node }) {
543
+ logger.info(`Pinning ${kind}/${name} to node '${node}' (namespace: ${namespace}).`);
544
+ const patch = JSON.stringify({
545
+ spec: { template: { spec: { nodeSelector: { 'kubernetes.io/hostname': node } } } },
546
+ });
547
+ shellExec(`kubectl patch ${kind} ${name} -n ${namespace} --type merge -p '${patch}'`);
548
+ },
549
+
550
+ /**
551
+ * @method resolveServiceHost
552
+ * @description Resolves a shared single-host override used by exposed service clients.
553
+ * @param {object} [options={}] - Cluster options.
554
+ * @returns {string} A single host override, or an empty string when unset / not reusable.
555
+ * @memberof UnderpostCluster
556
+ */
557
+ resolveServiceHost(options = {}) {
558
+ const candidate = `${options.serviceHost || ''}`.trim();
559
+ return candidate && !candidate.includes(',') ? candidate : '';
560
+ },
561
+
562
+ /**
563
+ * @method upsertEnvVar
564
+ * @description Replaces or appends one env var assignment in raw env file text.
565
+ * @param {string} envText - Existing env file contents.
566
+ * @param {string} key - Env var name.
567
+ * @param {string} value - Env var value.
568
+ * @returns {string} Updated env file contents.
569
+ * @memberof UnderpostCluster
570
+ */
571
+ upsertEnvVar(envText, key, value) {
572
+ const nextEntry = `${key}=${value}`;
573
+ const envKeyPattern = new RegExp(`^${key}=.*$`, 'm');
574
+ if (envKeyPattern.test(envText)) return envText.replace(envKeyPattern, nextEntry);
575
+
576
+ const trimmedEnvText = envText.replace(/\s*$/, '');
577
+ return `${trimmedEnvText}${trimmedEnvText ? '\n' : ''}${nextEntry}\n`;
578
+ },
579
+
580
+ /**
581
+ * @method syncServiceConnectionEnv
582
+ * @description Persists exposed service connection hosts to the active deploy env files.
583
+ * Currently applies only to MongoDB (`DB_HOST`) and Valkey (`VALKEY_HOST`).
584
+ * @param {object} params
585
+ * @param {string} params.serviceHost - Shared exposed host/IP.
586
+ * @param {boolean} [params.mongodb=false] - Update MongoDB runtime host.
587
+ * @param {boolean} [params.valkey=false] - Update Valkey runtime host.
588
+ * @param {object} [params.options={}] - Cluster options used to infer the active env.
589
+ * @memberof UnderpostCluster
590
+ */
591
+ syncServiceConnectionEnv({ serviceHost, mongodb = false, valkey = false, options = {} }) {
592
+ if (!serviceHost) return;
593
+
594
+ const updates = {};
595
+ if (mongodb) updates.DB_HOST = `mongodb://${serviceHost}:27017`;
596
+ if (valkey) updates.VALKEY_HOST = serviceHost;
597
+ if (Object.keys(updates).length === 0) return;
598
+
599
+ const deployId = process.env.DEPLOY_ID || process.env.DEFAULT_DEPLOY_ID || 'dd-default';
600
+ const envName = process.env.NODE_ENV || (options.dev ? 'development' : 'production');
601
+ const envPaths = [`./engine-private/conf/${deployId}/.env.${envName}`, `./.env.${envName}`, `./.env`].filter(
602
+ (envPath, index, paths) => fs.existsSync(envPath) && paths.indexOf(envPath) === index,
603
+ );
604
+
605
+ if (envPaths.length === 0) {
606
+ logger.warn(`No env files found to persist service host override for deploy '${deployId}' (${envName}).`);
607
+ return;
608
+ }
609
+
610
+ for (const envPath of envPaths) {
611
+ let envText = fs.readFileSync(envPath, 'utf8');
612
+ for (const [key, value] of Object.entries(updates))
613
+ envText = Underpost.cluster.upsertEnvVar(envText, key, value);
614
+ fs.writeFileSync(envPath, envText, 'utf8');
615
+ }
616
+
617
+ logger.info(`Persisted service host override for ${Object.keys(updates).join(', ')} to ${envPaths.join(', ')}`);
618
+ },
619
+
371
620
  /**
372
621
  * @method pullImage
373
622
  * @description Pulls a container image using the appropriate runtime based on the cluster type.
@@ -388,9 +637,24 @@ EOF
388
637
  `for node in $(kind get nodes); do cat ${tarPath} | docker exec -i $node ctr --namespace=k8s.io images import -; done`,
389
638
  );
390
639
  shellExec(`rm -f ${tarPath}`);
391
- } else if (options.kubeadm || options.k3s) {
392
- // Kubeadm / K3s: use crictl to pull directly into containerd
393
- shellExec(`sudo crictl pull ${image}`);
640
+ } else if (options.k3s) {
641
+ // K3s uses its own embedded containerd socket, not the host-level one
642
+ // used by kubeadm/containerd installations.
643
+ shellExec(
644
+ `sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl --runtime-endpoint unix:///run/k3s/containerd/containerd.sock pull ${image}`,
645
+ );
646
+ } else if (options.kubeadm) {
647
+ // Kubeadm / K3s: use crictl to pull directly into the active CRI runtime.
648
+ // crictl is not in sudo's secure_path; pass full PATH through env.
649
+ // Point crictl at CRI-O when the socket exists, otherwise fall back to containerd.
650
+ const criSock =
651
+ shellExec(`test -S /var/run/crio/crio.sock && echo crio || echo containerd`, {
652
+ stdout: true,
653
+ silent: true,
654
+ }).trim() === 'crio'
655
+ ? 'unix:///var/run/crio/crio.sock'
656
+ : 'unix:///run/containerd/containerd.sock';
657
+ shellExec(`sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl --runtime-endpoint ${criSock} pull ${image}`);
394
658
  }
395
659
  },
396
660
 
@@ -400,15 +664,20 @@ EOF
400
664
  * This method ensures proper SELinux, Docker, Containerd, and Sysctl settings
401
665
  * are applied for a healthy Kubernetes environment. It explicitly avoids
402
666
  * iptables flushing commands to prevent conflicts with Kubernetes' own network management.
403
- * @param {string} underpostRoot - The root directory of the underpost project.
667
+ * @param {object} [options] - Configuration options for host setup.
668
+ * @param {string} [options.underpostRoot] - The root path of the underpost project, used for locating scripts if needed.
404
669
  * @memberof UnderpostCluster
405
670
  */
406
671
  config(options = { underpostRoot: '.' }) {
407
672
  const { underpostRoot } = options;
408
673
  console.log('Applying host configuration: SELinux, Docker, Containerd, and Sysctl settings.');
409
674
  // Disable SELinux (permissive mode)
410
- shellExec(`sudo setenforce 0`);
411
- shellExec(`sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config`);
675
+ shellExec(`sudo setenforce 0`, {
676
+ silentOnError: true,
677
+ });
678
+ shellExec(`sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config`, {
679
+ silentOnError: true,
680
+ });
412
681
 
413
682
  // Enable and start Docker and Kubelet services
414
683
  shellExec(`sudo systemctl enable --now docker`); // Docker might not be needed for K3s
@@ -432,33 +701,89 @@ EOF
432
701
  // Reload systemd daemon to pick up new unit files/changes
433
702
  shellExec(`sudo systemctl daemon-reload`);
434
703
 
435
- // Enable bridge-nf-call-iptables for Kubernetes networking
436
- // This ensures traffic through Linux bridges is processed by iptables (crucial for CNI)
437
- for (const iptableConfPath of [
438
- `/etc/sysctl.d/k8s.conf`,
439
- `/etc/sysctl.d/99-k8s-ipforward.conf`,
440
- `/etc/sysctl.d/99-k8s.conf`,
441
- ])
442
- shellExec(
443
- `echo 'net.bridge.bridge-nf-call-iptables = 1
444
- net.bridge.bridge-nf-call-ip6tables = 1
445
- net.bridge.bridge-nf-call-arptables = 1
446
- net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
447
- { silent: true },
448
- );
449
-
450
704
  // Increase inotify limits
451
705
  shellExec(`sudo sysctl -w fs.inotify.max_user_watches=2099999999`);
452
706
  shellExec(`sudo sysctl -w fs.inotify.max_user_instances=2099999999`);
453
707
  shellExec(`sudo sysctl -w fs.inotify.max_queued_events=2099999999`);
708
+ },
709
+
710
+ /**
711
+ * @method configMinimalK3s
712
+ * @description Minimal host configuration for a K3s node. K3s is fully
713
+ * self-contained — it ships its own containerd, kubelet, CNI (flannel),
714
+ * CoreDNS and traefik. It therefore needs NONE of the Docker /
715
+ * standalone-containerd / standalone-kubelet setup that `config()` applies
716
+ * for kind and kubeadm. In a fresh LXD VM those packages do not exist, so
717
+ * `config()` there is both redundant and a source of errors.
718
+ *
719
+ * This applies only what K3s genuinely requires, and every step is guarded
720
+ * so it is a no-op when the relevant tooling is absent (e.g. minimal images
721
+ * without SELinux userspace):
722
+ * - SELinux → permissive (only if SELinux tooling is present).
723
+ * - swap off (Kubernetes best practice).
724
+ * - br_netfilter + bridge/forward sysctls (pod networking).
725
+ * - inotify limits.
726
+ * @memberof UnderpostCluster
727
+ */
728
+ configMinimalK3s() {
729
+ console.log('Applying minimal K3s host configuration (firewalld, SELinux, swap, sysctl).');
730
+
731
+ // Disable firewalld. K3s manages its own iptables rules; firewalld
732
+ // closes 6443/tcp (supervisor + API) and 8472/udp (flannel VXLAN) by
733
+ // default on RHEL/Rocky, which makes k3s-agent hang on `systemctl start`
734
+ // forever (the upstream unit ships TimeoutStartSec=0).
735
+ shellExec(`if systemctl is-active --quiet firewalld; then sudo systemctl disable --now firewalld; fi`);
736
+
737
+ // SELinux → permissive, but only when the tooling exists. Rocky has it;
738
+ // minimal LXD images may not. K3s also installs k3s-selinux for enforcing
739
+ // mode, so this is a best-effort dev convenience, not a hard requirement.
740
+ shellExec(`if command -v setenforce >/dev/null 2>&1; then sudo setenforce 0; fi`, {
741
+ silentOnError: true,
742
+ });
743
+ shellExec(
744
+ `if [ -f /etc/selinux/config ]; then sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config; fi`,
745
+ { silentOnError: true },
746
+ );
747
+
748
+ // Disable swap. `swapoff -a` is a no-op without swap; the sed only edits
749
+ // fstab when a swap line is present.
750
+ shellExec(`sudo swapoff -a`);
751
+ shellExec(`sudo sed -i '/swap/d' /etc/fstab`);
752
+
753
+ // Pod networking: ensure br_netfilter is loaded and the bridge/forward
754
+ // sysctls are set. K3s + flannel depend on these.
755
+ shellExec(
756
+ `if command -v lsmod >/dev/null 2>&1 && command -v modprobe >/dev/null 2>&1; then if ! lsmod | grep -q '^br_netfilter'; then sudo modprobe br_netfilter || true; fi; fi`,
757
+ );
758
+ shellExec(
759
+ `echo 'net.bridge.bridge-nf-call-iptables = 1
760
+ net.bridge.bridge-nf-call-ip6tables = 1
761
+ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
762
+ );
763
+ shellExec(`sudo sysctl --system`);
454
764
 
455
- // shellExec(`sudo sysctl --system`); // Apply sysctl changes immediately
456
- // Apply NAT iptables rules.
457
- shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
765
+ // inotify limits many pods/watchers. Conservative, sane values.
766
+ shellExec(`sudo sysctl -w fs.inotify.max_user_instances=1024`);
767
+ shellExec(`sudo sysctl -w fs.inotify.max_user_watches=1048576`);
768
+ },
458
769
 
459
- // Disable firewalld (common cause of network issues in Kubernetes)
460
- shellExec(`sudo systemctl stop firewalld`); // Stop if running
461
- shellExec(`sudo systemctl disable firewalld`); // Disable from starting on boot
770
+ /**
771
+ * @method natSetup
772
+ * @description Configures NAT and iptables settings for Kubernetes networking.
773
+ * This method enables necessary sysctl settings for bridge networking and applies iptables rules
774
+ * required for Kubernetes cluster communication. It is designed to work with kubeadm and k3s clusters, ensuring that
775
+ * traffic through Linux bridges is processed by iptables, which is crucial for CNI plugins to function correctly.
776
+ * The method also applies NAT iptables rules and configures firewalld for Kubernetes, which is required for multi-machine kubeadm inter-node communication.
777
+ * Note: This method must be called before kubeadm init / kind create so that br_netfilter is loaded and kernel networking is ready when the control plane starts.
778
+ * @param {object} [options] - Configuration options for NAT setup.
779
+ * @param {string} [options.underpostRoot] - The root path of the underpost project, used to locate the nat-iptables.sh script.
780
+ * @memberof UnderpostCluster
781
+ */
782
+ natSetup(options = { underpostRoot: '.' }) {
783
+ const { underpostRoot } = options;
784
+ // Loads br_netfilter, applies bridge/forward sysctls, opens firewall ports, enables masquerade.
785
+ // Must run before kubeadm init / kind create so kernel networking is ready.
786
+ shellExec(`${underpostRoot}/scripts/nat-iptables.sh`);
462
787
  },
463
788
 
464
789
  /**
@@ -495,139 +820,231 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
495
820
  console.log('kubectl config set up successfully.');
496
821
  },
497
822
 
823
+ // Shared reset helpers (internal — used by safeResetKind / safeResetKubeadm).
824
+
498
825
  /**
499
- * @method safeReset
500
- * @description Performs a complete reset of the Kubernetes cluster and its container environments.
501
- * This version focuses on correcting persistent permission errors (such as 'permission denied'
502
- * in coredns) by restoring SELinux security contexts and safely cleaning up cluster artifacts.
503
- * Only the uninstall/delete commands specific to the given clusterType are executed; all other
504
- * cleanup steps (log truncation, filesystem, network) are always run as generic k8s resets.
505
- * @param {object} [options] - Configuration options for the reset.
506
- * @param {string} [options.underpostRoot] - The root path of the underpost project.
507
- * @param {boolean} [options.removeVolumeHostPaths=false] - Whether to remove data from host paths used by Persistent Volumes.
508
- * @param {string} [options.clusterType='kind'] - The type of cluster to reset: 'kind', 'kubeadm', or 'k3s'.
509
- * @memberof UnderpostCluster
826
+ * @method _truncateLargeLogs
827
+ * @description Removes files >1 GiB under /var/log. Best-effort.
828
+ * @private
510
829
  */
511
- async safeReset(options = { underpostRoot: '.', removeVolumeHostPaths: false, clusterType: 'kind' }) {
512
- logger.info('Starting a safe and comprehensive reset of Kubernetes and container environments...');
513
-
830
+ _truncateLargeLogs() {
514
831
  try {
515
- // Phase 0: Truncate large logs under /var/log to free up immediate space
516
- logger.info('Phase 0/7: Truncating large log files under /var/log...');
517
- try {
518
- const cleanPath = `/var/log/`;
519
- const largeLogsFiles = shellExec(
520
- `sudo du -sh ${cleanPath}* | awk '{if ($1 ~ /G$/ && ($1+0) > 1) print}' | sort -rh`,
521
- {
522
- stdout: true,
523
- },
524
- );
525
- for (const pathLog of largeLogsFiles
526
- .split(`\n`)
527
- .map((p) => p.split(cleanPath)[1])
528
- .filter((p) => p)) {
529
- shellExec(`sudo rm -rf ${cleanPath}${pathLog}`);
530
- }
531
- } catch (err) {
532
- logger.warn(` -> Error truncating log files: ${err.message}. Continuing with reset.`);
832
+ const cleanPath = `/var/log/`;
833
+ const largeLogsFiles = shellExec(
834
+ `sudo du -sh ${cleanPath}* | awk '{if ($1 ~ /G$/ && ($1+0) > 1) print}' | sort -rh`,
835
+ { stdout: true },
836
+ );
837
+ for (const pathLog of largeLogsFiles
838
+ .split('\n')
839
+ .map((p) => p.split(cleanPath)[1])
840
+ .filter((p) => p)) {
841
+ shellExec(`sudo rm -rf ${cleanPath}${pathLog}`);
533
842
  }
843
+ } catch (err) {
844
+ logger.warn(` -> Skipped log truncation: ${err.message}`);
845
+ }
846
+ },
534
847
 
535
- // Phase 1: Clean up Persistent Volumes with hostPath
536
- // This targets data created by Kubernetes Persistent Volumes that use hostPath.
537
- logger.info('Phase 1/7: Cleaning Kubernetes hostPath volumes...');
538
- if (options.removeVolumeHostPaths)
539
- try {
540
- const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, {
541
- stdout: true,
542
- silent: true,
543
- });
544
- const pvList = JSON.parse(pvListJson);
545
-
546
- if (pvList.items && pvList.items.length > 0) {
547
- for (const pv of pvList.items) {
548
- // Check if the PV uses hostPath and delete its contents
549
- if (pv.spec.hostPath && pv.spec.hostPath.path) {
550
- const hostPath = pv.spec.hostPath.path;
551
- logger.info(`Removing data from host path for PV '${pv.metadata.name}': ${hostPath}`);
552
- shellExec(`sudo rm -rf ${hostPath}/*`);
553
- }
554
- }
555
- } else {
556
- logger.info('No Persistent Volumes found with hostPath to clean up.');
557
- }
558
- } catch (error) {
559
- logger.error('Failed to clean up Persistent Volumes:', error);
848
+ /**
849
+ * @method _cleanHostPathPvs
850
+ * @description Wipes contents of every hostPath PV. Destroys live data —
851
+ * only call when `--remove-volume-host-paths` is set.
852
+ * @private
853
+ */
854
+ _cleanHostPathPvs() {
855
+ try {
856
+ const pvListJson = shellExec(`kubectl get pv -o json || echo '{"items":[]}'`, {
857
+ stdout: true,
858
+ silent: true,
859
+ });
860
+ const pvList = JSON.parse(pvListJson);
861
+ if (!pvList.items || pvList.items.length === 0) {
862
+ logger.info(' -> No PersistentVolumes with hostPath found.');
863
+ return;
864
+ }
865
+ for (const pv of pvList.items) {
866
+ if (pv.spec.hostPath && pv.spec.hostPath.path) {
867
+ const hostPath = pv.spec.hostPath.path;
868
+ logger.info(` -> Removing PV '${pv.metadata.name}' hostPath: ${hostPath}`);
869
+ shellExec(`sudo rm -rf ${hostPath}/*`);
560
870
  }
561
- else logger.info(' -> Skipping hostPath volume cleanup as per configuration.');
562
- // Phase 2: Restore SELinux and stop services
563
- // This is critical for fixing the 'permission denied' error you experienced.
564
- // Enable SELinux permissive mode and restore file contexts.
565
- logger.info('Phase 2/7: Stopping services and fixing SELinux...');
566
- logger.info(' -> Ensuring SELinux is in permissive mode...');
567
- shellExec(`sudo setenforce 0`);
568
- shellExec(`sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config`);
569
- logger.info(' -> Restoring SELinux contexts for container data directories...');
570
- // The 'restorecon' command corrects file system security contexts.
571
- shellExec(`sudo restorecon -Rv /var/lib/containerd`);
572
- shellExec(`sudo restorecon -Rv /var/lib/kubelet`);
573
-
574
- logger.info(' -> Stopping kubelet, docker, and podman services...');
575
- shellExec('sudo systemctl stop kubelet');
576
- shellExec('sudo systemctl stop docker');
577
- shellExec('sudo systemctl stop podman');
578
- // Safely unmount pod filesystems to avoid errors.
579
- shellExec('sudo umount -f /var/lib/kubelet/pods/*/*');
580
-
581
- // Phase 3: Execute official uninstallation commands (type-specific)
582
- const clusterType = options.clusterType || 'kind';
583
- logger.info(
584
- `Phase 3/7: Executing official reset/uninstallation commands for cluster type: '${clusterType}'...`,
585
- );
586
- if (clusterType === 'kubeadm') {
587
- logger.info(' -> Executing kubeadm reset...');
588
- shellExec('sudo kubeadm reset --force');
589
- } else if (clusterType === 'k3s') {
590
- logger.info(' -> Executing K3s uninstallation script if it exists...');
591
- shellExec('sudo /usr/local/bin/k3s-uninstall.sh');
592
- } else {
593
- // Default: kind
594
- logger.info(' -> Deleting Kind clusters...');
595
- shellExec('kind get clusters | xargs -r -t -n1 kind delete cluster');
596
871
  }
597
-
598
- // Phase 4: File system cleanup
599
- logger.info('Phase 4/7: Cleaning up remaining file system artifacts...');
600
- // Remove any leftover configurations and data.
601
- shellExec('sudo rm -rf /etc/kubernetes/*');
602
- shellExec('sudo rm -rf /etc/cni/net.d/*');
603
- shellExec('sudo rm -rf /var/lib/kubelet/*');
604
- shellExec('sudo rm -rf /var/lib/cni/*');
605
- shellExec('sudo rm -rf /var/lib/docker/*');
606
- shellExec('sudo rm -rf /var/lib/containerd/*');
607
- shellExec('sudo rm -rf /var/lib/containers/storage/*');
608
- // Clean up the current user's kubeconfig.
609
- shellExec('rm -rf $HOME/.kube');
610
-
611
- // Phase 5: Host network cleanup
612
- logger.info('Phase 5/7: Cleaning up host network configurations...');
613
- // Remove iptables rules and CNI network interfaces.
614
- shellExec('sudo iptables -F');
615
- shellExec('sudo iptables -t nat -F');
616
- shellExec('sudo ip link del cni0');
617
- shellExec('sudo ip link del flannel.1');
618
-
619
- logger.info('Phase 6/7: Clean up images');
620
- shellExec(`podman rmi $(podman images -qa) --force`);
621
-
622
- // Phase 6: Reload daemon and finalize
623
- logger.info('Phase 7/7: Reloading the system daemon and finalizing...');
624
- // shellExec('sudo systemctl daemon-reload');
625
- Underpost.cluster.config();
626
- logger.info('Safe and complete reset finished. The system is ready for a new cluster initialization.');
627
872
  } catch (error) {
628
- logger.error(`Error during reset: ${error.message}`);
629
- console.error(error);
873
+ logger.error(` -> Failed cleaning hostPath PVs: ${error.message}`);
874
+ }
875
+ },
876
+
877
+ /**
878
+ * @method _lazyUmountKubeletMounts
879
+ * @description Lazy-unmounts every mount under /var/lib/kubelet so a
880
+ * subsequent `rm -rf` does not hit 'Device or resource busy'. Best-effort.
881
+ * @private
882
+ */
883
+ _lazyUmountKubeletMounts() {
884
+ shellExec(
885
+ `sudo sh -c 'findmnt --raw --noheadings -o TARGET | grep /var/lib/kubelet | sort -r | xargs -r umount -l'`,
886
+ { silentOnError: true },
887
+ );
888
+ },
889
+
890
+ // Per-type reset methods. Each only touches what its own runtime owns.
891
+
892
+ /**
893
+ * @method safeResetKind
894
+ * @description Kind (Kubernetes in Docker) reset — Docker-scoped only.
895
+ * Does not touch host kubelet / containerd / iptables / SELinux.
896
+ * @param {object} [options]
897
+ * @param {string} [options.underpostRoot='.']
898
+ * @param {boolean} [options.removeVolumeHostPaths=false]
899
+ * @memberof UnderpostCluster
900
+ */
901
+ async safeResetKind(options = { underpostRoot: '.', removeVolumeHostPaths: false }) {
902
+ logger.info('=== KIND SAFE RESET (development) ===');
903
+
904
+ logger.info('Phase 1/5: Cleaning Kind node-local MongoDB hostPath directories...');
905
+ Underpost.cluster.cleanKindMongoHostPaths({ basePath: '/data/mongodb', replicaCount: 3 });
906
+
907
+ logger.info('Phase 2/5: PersistentVolume hostPath cleanup...');
908
+ if (options.removeVolumeHostPaths) Underpost.cluster._cleanHostPathPvs();
909
+ else logger.info(' -> Skipping (pass --remove-volume-host-paths to enable).');
910
+
911
+ logger.info('Phase 3/5: Deleting all Kind clusters...');
912
+ shellExec(`clusters=$(kind get clusters)
913
+ if [ -n "$clusters" ]; then
914
+ for c in $clusters; do
915
+ echo "Deleting cluster: $c"
916
+ kind delete cluster --name "$c"
917
+ done
918
+ fi`);
919
+
920
+ logger.info('Phase 4/5: Cleaning kubeconfig and Kind Docker networks...');
921
+ shellExec(`rm -rf "$HOME/.kube"`);
922
+ Underpost.cluster.recoverKindDockerNetworks();
923
+
924
+ logger.info('Phase 5/5: Re-applying host configuration (Docker, containerd, sysctl).');
925
+ Underpost.cluster.config();
926
+
927
+ logger.info('=== KIND SAFE RESET COMPLETE ===');
928
+ },
929
+
930
+ /**
931
+ * @method safeResetKubeadm
932
+ * @description Kubeadm reset on the host: stop kubelet + runtime, kill
933
+ * control-plane ports (6443 / 2379 / 2380 / 10257 / 10259), run
934
+ * `kubeadm reset --force`, wipe kubeadm-managed FS + network state.
935
+ * Does not touch K3s or Kind state.
936
+ * @param {object} [options]
937
+ * @param {string} [options.underpostRoot='.']
938
+ * @param {boolean} [options.removeVolumeHostPaths=false]
939
+ * @memberof UnderpostCluster
940
+ */
941
+ async safeResetKubeadm(options = { underpostRoot: '.', removeVolumeHostPaths: false }) {
942
+ logger.info('=== KUBEADM SAFE RESET ===');
943
+
944
+ logger.info('Phase 0/7: Truncating large /var/log files...');
945
+ Underpost.cluster._truncateLargeLogs();
946
+
947
+ logger.info('Phase 1/7: PersistentVolume hostPath cleanup...');
948
+ if (options.removeVolumeHostPaths) Underpost.cluster._cleanHostPathPvs();
949
+ else logger.info(' -> Skipping (pass --remove-volume-host-paths to enable).');
950
+
951
+ logger.info('Phase 2/7: SELinux permissive + restore contexts (when present)...');
952
+ shellExec(`if command -v setenforce >/dev/null 2>&1; then sudo setenforce 0; fi`);
953
+ shellExec(
954
+ `if [ -f /etc/selinux/config ]; then sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config; fi`,
955
+ );
956
+ shellExec(
957
+ `if command -v restorecon >/dev/null 2>&1 && [ -d /var/lib/kubelet ]; then sudo restorecon -Rv /var/lib/kubelet; fi`,
958
+ );
959
+ shellExec(
960
+ `if command -v restorecon >/dev/null 2>&1 && [ -d /var/lib/containerd ]; then sudo restorecon -Rv /var/lib/containerd; fi`,
961
+ );
962
+
963
+ logger.info('Phase 3/7: Stopping host kubelet and container runtimes (kubeadm-scope only)...');
964
+ shellExec(`if systemctl is-active --quiet kubelet; then sudo systemctl stop kubelet; fi`);
965
+ shellExec(`if systemctl is-active --quiet docker; then sudo systemctl stop docker; fi`);
966
+ shellExec(`if systemctl is-active --quiet crio; then sudo systemctl stop crio; fi`);
967
+ Underpost.cluster._lazyUmountKubeletMounts();
968
+
969
+ logger.info('Phase 4/7: Killing control-plane processes and running kubeadm reset...');
970
+ shellExec(`if command -v crictl >/dev/null 2>&1; then sudo crictl rm -a -f; fi`, { silentOnError: true });
971
+ // Remove CNI config before stopping sandboxes so Calico's CNI delete hook is
972
+ // not invoked (the API server is already down and the hook would fail).
973
+ shellExec(`sudo rm -rf /etc/cni/net.d/*`);
974
+ shellExec(`if command -v crictl >/dev/null 2>&1; then sudo crictl rmp -a -f; fi`, { silentOnError: true });
975
+ shellExec(`if systemctl is-active --quiet etcd; then sudo systemctl stop etcd; fi`);
976
+ for (const port of [6443, 10259, 10257, 2379, 2380]) {
977
+ shellExec(`if sudo fuser ${port}/tcp >/dev/null 2>&1; then sudo fuser -k ${port}/tcp; fi`);
630
978
  }
979
+ shellExec(`if command -v kubeadm >/dev/null 2>&1; then sudo kubeadm reset --force; fi`);
980
+
981
+ logger.info('Phase 5/7: Filesystem cleanup (kubeadm-managed paths only)...');
982
+ shellExec(`sudo rm -rf /etc/kubernetes/*`);
983
+ Underpost.cluster._lazyUmountKubeletMounts();
984
+ shellExec(`sudo rm -rf /var/lib/kubelet/*`);
985
+ shellExec(`sudo rm -rf /var/lib/etcd`);
986
+ shellExec(`sudo rm -rf /var/lib/cni/*`);
987
+ shellExec(`sudo rm -rf /var/lib/containerd/*`);
988
+ shellExec(`rm -rf "$HOME/.kube"`);
989
+
990
+ logger.info('Phase 6/7: Network cleanup (Calico interfaces + host iptables)...');
991
+ shellExec(`if ip link show cni0 >/dev/null 2>&1; then sudo ip link del cni0; fi`);
992
+ shellExec(`if ip link show vxlan.calico >/dev/null 2>&1; then sudo ip link del vxlan.calico; fi`);
993
+ shellExec(`if ip link show tunl0 >/dev/null 2>&1; then sudo ip link del tunl0; fi`);
994
+ shellExec(`sudo iptables -F`);
995
+ shellExec(`sudo iptables -t nat -F`);
996
+ shellExec(`if command -v crictl >/dev/null 2>&1; then sudo crictl rmi --prune; fi`);
997
+
998
+ logger.info('Phase 7/7: Re-applying host configuration (Docker, containerd, sysctl).');
999
+ Underpost.cluster.config();
1000
+
1001
+ logger.info('=== KUBEADM SAFE RESET COMPLETE ===');
1002
+ },
1003
+
1004
+ /**
1005
+ * @method safeResetK3s
1006
+ * @description Centralized K3s teardown. Runs the same way on a physical
1007
+ * host (`node bin cluster --dev --reset --k3s`) or inside an LXD VM via
1008
+ * `lxc exec` (driven by `_resetK3sInVm` in src/cli/lxd.js).
1009
+ * @param {object} [options]
1010
+ * @param {string} [options.underpostRoot='.']
1011
+ * @param {'drain'|'full'} [options.resetMode='full'] - `drain` stops
1012
+ * services + runs `k3s-killall.sh` (K3s persists, returns on next boot).
1013
+ * `full` also runs `k3s-uninstall.sh` and cleans residual state.
1014
+ * @memberof UnderpostCluster
1015
+ */
1016
+ async safeResetK3s(options = { underpostRoot: '.', resetMode: 'full' }) {
1017
+ const resetMode = options.resetMode === 'drain' ? 'drain' : 'full';
1018
+ logger.info(`=== K3s SAFE RESET (resetMode=${resetMode}) ===`);
1019
+
1020
+ logger.info('Phase 1/5: Stopping K3s systemd units...');
1021
+ shellExec(`if systemctl list-unit-files | grep -q '^k3s\\.service'; then sudo systemctl stop k3s; fi`);
1022
+ shellExec(
1023
+ `if systemctl list-unit-files | grep -q '^k3s-agent\\.service'; then sudo systemctl stop k3s-agent; fi`,
1024
+ );
1025
+
1026
+ logger.info('Phase 2/5: Running k3s-killall.sh (unmount pod overlays, tear down CNI)...');
1027
+ shellExec(`if [ -x /usr/local/bin/k3s-killall.sh ]; then sudo /usr/local/bin/k3s-killall.sh; fi`);
1028
+
1029
+ if (resetMode === 'drain') {
1030
+ logger.info('=== K3s DRAIN COMPLETE (K3s remains installed; will start on next boot) ===');
1031
+ return;
1032
+ }
1033
+
1034
+ logger.info('Phase 3/5: Running k3s-uninstall.sh...');
1035
+ shellExec(`if [ -x /usr/local/bin/k3s-uninstall.sh ]; then sudo /usr/local/bin/k3s-uninstall.sh; fi`);
1036
+ shellExec(`if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then sudo /usr/local/bin/k3s-agent-uninstall.sh; fi`);
1037
+
1038
+ logger.info('Phase 4/5: Removing residual K3s state...');
1039
+ shellExec(`rm -rf "$HOME/.kube"`);
1040
+ shellExec(`if [ -d /etc/rancher/k3s ]; then sudo rm -rf /etc/rancher/k3s; fi`);
1041
+ shellExec(`if ip link show flannel.1 >/dev/null 2>&1; then sudo ip link del flannel.1; fi`);
1042
+ shellExec(`if ip link show cni0 >/dev/null 2>&1; then sudo ip link del cni0; fi`);
1043
+
1044
+ logger.info('Phase 5/5: Re-applying minimal K3s host config.');
1045
+ Underpost.cluster.configMinimalK3s();
1046
+
1047
+ logger.info('=== K3s SAFE RESET COMPLETE (full) ===');
631
1048
  },
632
1049
 
633
1050
  /**
@@ -687,6 +1104,9 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
687
1104
  // Install Podman
688
1105
  shellExec(`sudo dnf -y install podman`);
689
1106
 
1107
+ // Install CRI-O (required for kubeadm with CRI-O socket)
1108
+ shellExec(`node bin run install-crio`);
1109
+
690
1110
  // Install Kind (Kubernetes in Docker)
691
1111
  shellExec(`[ $(uname -m) = ${archData.name} ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.29.0/kind-linux-${archData.alias}
692
1112
  chmod +x ./kind
@@ -744,6 +1164,14 @@ EOF`);
744
1164
  console.log('Removing Podman...');
745
1165
  shellExec(`sudo dnf -y remove podman`);
746
1166
 
1167
+ // Remove CRI-O
1168
+ console.log('Removing CRI-O...');
1169
+ shellExec('sudo systemctl stop crio', { silentOnError: true });
1170
+ shellExec('sudo systemctl disable crio', { silentOnError: true });
1171
+ shellExec(`sudo dnf -y remove cri-o`);
1172
+ shellExec(`sudo rm -f /etc/yum.repos.d/cri-o.repo`);
1173
+ shellExec(`sudo rm -f /etc/crictl.yaml`);
1174
+
747
1175
  // Remove Kubeadm, Kubelet, and Kubectl
748
1176
  console.log('Removing Kubernetes tools...');
749
1177
  shellExec(`sudo yum remove -y kubelet kubeadm kubectl`);
@@ -780,6 +1208,93 @@ EOF`);
780
1208
 
781
1209
  console.log('Uninstall process completed.');
782
1210
  },
1211
+
1212
+ /**
1213
+ * @method cleanKindMongoHostPaths
1214
+ * @description Best-effort cleanup of MongoDB hostPath directories inside Kind node containers.
1215
+ * This prevents stale replica/auth state when hostPath data lives in node-local container filesystems.
1216
+ * @param {object} [options]
1217
+ * @param {string} [options.basePath='/data/mongodb'] - Node-internal base path for MongoDB data.
1218
+ * @param {number} [options.replicaCount=3] - Number of replica ordinal directories (v0..vN-1).
1219
+ * @memberof UnderpostCluster
1220
+ */
1221
+ cleanKindMongoHostPaths(options = { basePath: '/data/mongodb', replicaCount: 3 }) {
1222
+ const basePath = options.basePath || '/data/mongodb';
1223
+ const replicaCount = Math.max(Number(options.replicaCount) || 3, 1);
1224
+ const nodesRaw = shellExec('kind get nodes', {
1225
+ stdout: true,
1226
+ silent: true,
1227
+ silentOnError: true,
1228
+ });
1229
+ const nodes = nodesRaw
1230
+ .split('\n')
1231
+ .map((node) => node.trim())
1232
+ .filter((node) => !!node);
1233
+
1234
+ if (nodes.length === 0) {
1235
+ logger.info('No Kind nodes detected for node-local MongoDB hostPath cleanup.');
1236
+ return;
1237
+ }
1238
+
1239
+ for (const node of nodes) {
1240
+ logger.info(
1241
+ `Cleaning Kind node-local MongoDB paths '${basePath}/v0..v${replicaCount - 1}' on node '${node}'...`,
1242
+ );
1243
+ const prepareReplicaDirsCmd = Array.from(
1244
+ { length: replicaCount },
1245
+ (_, index) => `mkdir -p ${basePath}/v${index}; rm -rf ${basePath}/v${index}/*;`,
1246
+ ).join(' ');
1247
+ const verifyReplicaDirsCmd = Array.from(
1248
+ { length: replicaCount },
1249
+ (_, index) => `test -d ${basePath}/v${index};`,
1250
+ ).join(' ');
1251
+ shellExec(`sudo docker exec ${node} sh -lc 'mkdir -p ${basePath}; ${prepareReplicaDirsCmd}'`, {
1252
+ silentOnError: true,
1253
+ });
1254
+ shellExec(`sudo docker exec ${node} sh -lc '${verifyReplicaDirsCmd}'`);
1255
+ }
1256
+ },
1257
+
1258
+ /**
1259
+ * @method recoverKindDockerNetworks
1260
+ * @description Best-effort cleanup of stale Kind Docker resources when Docker bridge
1261
+ * address pools are exhausted and new networks cannot be allocated.
1262
+ * @memberof UnderpostCluster
1263
+ */
1264
+ recoverKindDockerNetworks() {
1265
+ logger.warn('Attempting Docker network recovery for Kind (address pool exhaustion detected)...');
1266
+ shellExec(`sudo docker ps -aq --filter label=io.x-k8s.kind.cluster | xargs -r sudo docker rm -f`, {
1267
+ silentOnError: true,
1268
+ });
1269
+ shellExec(`sudo docker network ls -q --filter label=io.x-k8s.kind.cluster | xargs -r sudo docker network rm`, {
1270
+ silentOnError: true,
1271
+ });
1272
+ shellExec(`sudo docker network rm kind`, { silentOnError: true });
1273
+ shellExec(`sudo docker network prune -f`, { silentOnError: true });
1274
+ },
1275
+
1276
+ /**
1277
+ * @method ensureDockerDefaultAddressPools
1278
+ * @description Writes a sane Docker default-address-pools config to reduce
1279
+ * Kind network allocation failures on hosts with exhausted predefined pools.
1280
+ * @memberof UnderpostCluster
1281
+ */
1282
+ ensureDockerDefaultAddressPools() {
1283
+ logger.warn('Applying Docker default-address-pools workaround for Kind network creation...');
1284
+ shellExec(`cat <<'EOF' | sudo tee /etc/docker/daemon.json
1285
+ {
1286
+ "default-address-pools": [
1287
+ {"base": "172.17.0.0/16", "size": 24},
1288
+ {"base": "172.18.0.0/16", "size": 24},
1289
+ {"base": "172.19.0.0/16", "size": 24},
1290
+ {"base": "172.20.0.0/14", "size": 24},
1291
+ {"base": "172.24.0.0/14", "size": 24}
1292
+ ]
1293
+ }
1294
+ EOF`);
1295
+ shellExec('sudo systemctl restart docker');
1296
+ shellExec('sudo docker network prune -f', { silentOnError: true });
1297
+ },
783
1298
  };
784
1299
  }
785
1300
  export default UnderpostCluster;