@underpostnet/cyberia 3.2.90 → 3.3.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +16 -0
  3. package/.github/workflows/coverall.cyberia.ci.yml +107 -0
  4. package/.github/workflows/cyberia-client.cd.yml +6 -18
  5. package/.github/workflows/cyberia-server.cd.yml +6 -18
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +6 -78
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +42 -21
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +2 -9
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +47 -0
  22. package/CHANGELOG.md +581 -2417
  23. package/CLI-HELP.md +416 -127
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +4 -4
  27. package/README.md +21 -1
  28. package/bin/build.js +70 -43
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +372 -135
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +372 -135
  33. package/compose.env +16 -0
  34. package/conf.js +93 -4
  35. package/deploy/cyberia-client/deploy.sh +47 -0
  36. package/deploy/cyberia-client/package.sh +18 -0
  37. package/deploy/cyberia-client/state.sh +14 -0
  38. package/deploy/cyberia-server/deploy.sh +47 -0
  39. package/deploy/cyberia-server/package.sh +18 -0
  40. package/deploy/cyberia-server/state.sh +14 -0
  41. package/deploy/dd-cyberia/deploy.sh +22 -0
  42. package/deploy/dd-cyberia/init.sh +55 -0
  43. package/deploy/dd-cyberia/package.sh +17 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +116 -0
  46. package/deploy/lib/github-actions-logging.sh +594 -0
  47. package/deploy/lib/host.sh +185 -0
  48. package/deploy/lib/state.sh +62 -0
  49. package/deploy/pwa-microservices-template/deploy.sh +45 -0
  50. package/deploy/release/deploy.sh +32 -0
  51. package/deployment.yaml +7 -2
  52. package/docker-compose.yml +31 -37
  53. package/examples/static-page/README.md +19 -19
  54. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  55. package/hardhat/hardhat.config.js +1 -6
  56. package/hardhat/package-lock.json +104 -43
  57. package/hardhat/package.json +3 -3
  58. package/manifests/alertmanager/deployment.yaml +81 -0
  59. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  60. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
  61. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
  62. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  63. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  64. package/manifests/grafana/deployment.yaml +26 -7
  65. package/manifests/grafana/kustomization.yaml +1 -0
  66. package/manifests/grafana/service.yaml +6 -1
  67. package/manifests/mariadb/pv.yaml +1 -0
  68. package/manifests/mongodb/statefulset.yaml +9 -2
  69. package/manifests/mysql/pv-pvc.yaml +14 -1
  70. package/manifests/postgresql/pv.yaml +12 -1
  71. package/manifests/postgresql/pvc.yaml +1 -0
  72. package/manifests/prometheus/deployment.yaml +18 -22
  73. package/package.json +83 -10
  74. package/scripts/audit-selinux.sh +64 -0
  75. package/scripts/event-monitor.sh +56 -0
  76. package/scripts/gpu-diag.sh +0 -0
  77. package/scripts/ip-info.sh +0 -0
  78. package/scripts/k3s-node-setup.sh +30 -14
  79. package/scripts/kubeadm-node-setup.sh +40 -31
  80. package/scripts/lxd-vm-setup.sh +0 -0
  81. package/scripts/maas-nat-firewalld.sh +0 -0
  82. package/scripts/maas-setup.sh +8 -8
  83. package/scripts/nat-iptables.sh +2 -0
  84. package/scripts/rhel-grpc-setup.sh +0 -0
  85. package/scripts/rocky-kickstart.sh +25 -9
  86. package/scripts/selinux-normalize.sh +907 -0
  87. package/scripts/shutdown-machine.sh +843 -0
  88. package/scripts/test-monitor.sh +3 -3
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +1 -1
  99. package/src/api/cyberia-action/cyberia-action.service.js +2 -2
  100. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
  101. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
  102. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
  103. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  104. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  105. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  106. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  107. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  108. package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
  109. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
  110. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
  111. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
  112. package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
  113. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  114. package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
  115. package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
  116. package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
  117. package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
  118. package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
  119. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  120. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  121. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  122. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
  123. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  124. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  125. package/src/api/cyberia-map/cyberia-map.service.js +2 -2
  126. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  127. package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
  128. package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
  129. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  130. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  131. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  132. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  133. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  134. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  135. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -29
  136. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  137. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  138. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  139. package/src/api/default/default.controller.js +1 -1
  140. package/src/api/default/default.router.js +1 -1
  141. package/src/api/default/default.service.js +2 -2
  142. package/src/api/document/document.controller.js +1 -1
  143. package/src/api/document/document.router.js +1 -1
  144. package/src/api/document/document.service.js +2 -2
  145. package/src/api/file/file.controller.js +1 -1
  146. package/src/api/file/file.router.js +1 -1
  147. package/src/api/file/file.service.js +2 -2
  148. package/src/api/instance/instance.controller.js +1 -1
  149. package/src/api/instance/instance.router.js +1 -1
  150. package/src/api/instance/instance.service.js +2 -2
  151. package/src/api/ipfs/ipfs.controller.js +1 -1
  152. package/src/api/ipfs/ipfs.router.js +2 -2
  153. package/src/api/ipfs/ipfs.service.js +2 -2
  154. package/src/api/object-layer/object-layer.controller.js +1 -1
  155. package/src/api/object-layer/object-layer.model.js +204 -3
  156. package/src/api/object-layer/object-layer.router.js +2 -2
  157. package/src/api/object-layer/object-layer.service.js +4 -17
  158. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  159. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  160. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  161. package/src/api/test/test.controller.js +1 -1
  162. package/src/api/test/test.service.js +1 -1
  163. package/src/api/user/guest.service.js +3 -3
  164. package/src/api/user/user.controller.js +1 -1
  165. package/src/api/user/user.router.js +2 -2
  166. package/src/api/user/user.service.js +4 -4
  167. package/src/api.js +5 -9
  168. package/src/cli/app.js +391 -0
  169. package/src/cli/baremetal.js +30 -36
  170. package/src/cli/client.js +306 -0
  171. package/src/cli/cloud-init.js +3 -3
  172. package/src/cli/cluster.js +242 -135
  173. package/src/cli/db.js +126 -67
  174. package/src/cli/deploy.js +158 -60
  175. package/src/cli/docker-compose.js +5 -5
  176. package/src/cli/domains.js +184 -0
  177. package/src/cli/dotenv-store.js +143 -0
  178. package/src/cli/event.js +2281 -0
  179. package/src/cli/fs.js +3 -3
  180. package/src/cli/host.js +672 -0
  181. package/src/cli/image.js +4 -7
  182. package/src/cli/index.js +426 -134
  183. package/src/cli/ipfs.js +34 -30
  184. package/src/cli/kickstart.js +1 -1
  185. package/src/cli/kubectl.js +218 -41
  186. package/src/cli/lxd.js +3 -3
  187. package/src/cli/monitor.js +1359 -26
  188. package/src/cli/package.js +90 -0
  189. package/src/cli/release.js +73 -34
  190. package/src/cli/repository.js +564 -367
  191. package/src/cli/run.js +362 -654
  192. package/src/cli/secrets.js +2158 -964
  193. package/src/cli/ssh.js +651 -331
  194. package/src/cli/state.js +394 -0
  195. package/src/cli/static.js +2 -2
  196. package/src/cli/system.js +26 -13
  197. package/src/cli/test.js +199 -125
  198. package/src/cli/vultr.js +623 -0
  199. package/src/cli/wireguard.js +2930 -0
  200. package/src/client/components/core/CalendarCore.js +53 -96
  201. package/src/client/components/core/Docs.js +258 -211
  202. package/src/client/components/core/FileExplorer.js +0 -21
  203. package/src/client/components/core/PanelForm.js +4 -4
  204. package/src/client/components/core/Repository.js +56 -0
  205. package/src/client/components/core/Translate.js +7 -0
  206. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  207. package/src/client/components/core/Wallet.js +0 -11
  208. package/src/client/components/core/Worker.js +7 -1
  209. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  210. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  211. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
  212. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  213. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  214. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  215. package/src/client/components/default/AppShellDefault.js +1 -1
  216. package/src/client/components/default/SettingsDefault.js +2 -2
  217. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  218. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  219. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  220. package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
  221. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
  222. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  223. package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
  224. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  225. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  226. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  227. package/src/client/public/default/android-chrome-144x144.png +0 -0
  228. package/src/client/public/default/android-chrome-192x192.png +0 -0
  229. package/src/client/public/default/android-chrome-256x256.png +0 -0
  230. package/src/client/public/default/android-chrome-36x36.png +0 -0
  231. package/src/client/public/default/android-chrome-384x384.png +0 -0
  232. package/src/client/public/default/android-chrome-48x48.png +0 -0
  233. package/src/client/public/default/android-chrome-512x512.png +0 -0
  234. package/src/client/public/default/android-chrome-72x72.png +0 -0
  235. package/src/client/public/default/android-chrome-96x96.png +0 -0
  236. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  237. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  238. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  239. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  240. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  241. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  242. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  243. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  244. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  245. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  246. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  247. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  248. package/src/client/public/default/apple-touch-icon.png +0 -0
  249. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  250. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  251. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  252. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  253. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  254. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  255. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  256. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  257. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  258. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  259. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  260. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  261. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  262. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  263. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  264. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  265. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  266. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  267. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  268. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  269. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  270. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  271. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  272. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  273. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  274. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  275. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  276. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  277. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  278. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  279. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  280. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  281. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  282. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  283. package/src/client/public/default/assets/background/white.jpg +0 -0
  284. package/src/client/public/default/browserconfig.xml +1 -1
  285. package/src/client/public/default/favicon-16x16.png +0 -0
  286. package/src/client/public/default/favicon-32x32.png +0 -0
  287. package/src/client/public/default/favicon-48x48.png +0 -0
  288. package/src/client/public/default/favicon.ico +0 -0
  289. package/src/client/public/default/manifest.webmanifest +5 -5
  290. package/src/client/public/default/mstile-144x144.png +0 -0
  291. package/src/client/public/default/mstile-150x150.png +0 -0
  292. package/src/client/public/default/mstile-310x150.png +0 -0
  293. package/src/client/public/default/mstile-310x310.png +0 -0
  294. package/src/client/public/default/mstile-70x70.png +0 -0
  295. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  296. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  297. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  298. package/src/client/services/object-layer/object-layer.management.js +4 -4
  299. package/src/client/ssr/RootDocument.js +1 -1
  300. package/src/client/ssr/body/404.js +1 -1
  301. package/src/client/ssr/body/500.js +1 -1
  302. package/src/client/ssr/body/CacheControl.js +1 -1
  303. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  304. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  305. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  306. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  307. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  308. package/src/client/ssr/head/Css.js +1 -1
  309. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  310. package/src/client/ssr/head/DefaultScripts.js +1 -1
  311. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  312. package/src/client/ssr/head/Microdata.js +1 -1
  313. package/src/client/ssr/head/Production.js +1 -1
  314. package/src/client/ssr/head/Pwa.js +1 -1
  315. package/src/client/ssr/head/PwaItemledger.js +1 -1
  316. package/src/client/ssr/head/Seo.js +1 -1
  317. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  318. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  319. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  320. package/src/client/ssr/views/Cyberia404.js +7 -5
  321. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  322. package/src/client/ssr/views/Maintenance.js +1 -1
  323. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  324. package/src/client/ssr/views/Test.js +1 -1
  325. package/src/client/sw/core.sw.js +20 -14
  326. package/src/client-builder/client-build-docs.js +61 -46
  327. package/src/client-builder/client-build-live.js +2 -2
  328. package/src/client-builder/client-build.js +58 -44
  329. package/src/client-builder/client-dev-server.js +3 -3
  330. package/src/client-builder/client-icons.js +2 -2
  331. package/src/client-builder/ssr.js +3 -3
  332. package/src/client.build.js +3 -6
  333. package/src/client.dev.js +3 -6
  334. package/src/db/DataBaseProvider.js +186 -3
  335. package/src/db/mariadb/MariaDB.js +3 -2
  336. package/src/db/mongo/MongoBootstrap.js +273 -101
  337. package/src/db/mongo/MongooseDB.js +11 -8
  338. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  339. package/src/grpc/cyberia/grpc-server.js +1 -1
  340. package/src/index.js +103 -23
  341. package/src/mailer/EmailRender.js +2 -2
  342. package/src/mailer/MailerInterceptor.js +87 -0
  343. package/src/mailer/MailerProvider.js +50 -6
  344. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
  345. package/src/projects/cyberia/besu-genesis-generator.js +3 -3
  346. package/src/projects/cyberia/catalog-cyberia.js +54 -6
  347. package/src/projects/cyberia/gemini-client.js +1 -1
  348. package/src/projects/cyberia/generate-saga.js +15 -24
  349. package/src/projects/cyberia/hot-reload-trigger.js +1 -1
  350. package/src/projects/cyberia/instance-data.js +22 -3
  351. package/src/projects/cyberia/ipfs-client.js +4 -4
  352. package/src/projects/cyberia/map-preview-generator.js +1 -1
  353. package/src/projects/cyberia/object-layer.js +12 -22
  354. package/src/projects/cyberia/semantic-layer-generator.js +1 -1
  355. package/src/projects/underpost/catalog-underpost.js +13 -2
  356. package/src/proxy.js +4 -7
  357. package/src/runtime/cyberia-client/Dockerfile +9 -4
  358. package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
  359. package/src/runtime/cyberia-server/Dockerfile +9 -4
  360. package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
  361. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  362. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  363. package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
  364. package/src/runtime/engine-cyberia/compose.env +16 -0
  365. package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
  366. package/src/runtime/express/Express.js +32 -27
  367. package/src/runtime/lampp/Lampp.js +22 -70
  368. package/src/runtime/nginx/Nginx.js +1 -1
  369. package/src/runtime/wp/Wp.js +16 -48
  370. package/src/server/{catalog.js → build/catalog.js} +15 -5
  371. package/src/server/build/coverage.js +117 -0
  372. package/src/server/build/execution.js +402 -0
  373. package/src/server/build/package.js +465 -0
  374. package/src/server/build/testing.js +528 -0
  375. package/src/server/{dns.js → network/dns.js} +241 -14
  376. package/src/server/network/forward-proxy.js +541 -0
  377. package/src/server/{middlewares.js → network/middlewares.js} +59 -4
  378. package/src/server/network/node-capability.js +98 -0
  379. package/src/server/{peer.js → network/peer.js} +3 -3
  380. package/src/server/{proxy.js → network/proxy.js} +7 -13
  381. package/src/server/network/router.js +356 -0
  382. package/src/server/{tls.js → network/tls.js} +2 -2
  383. package/src/server/network/underpost-compression.js +186 -0
  384. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
  385. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
  386. package/src/server/{cri.js → ops/cri.js} +2 -2
  387. package/src/server/ops/cron.js +949 -0
  388. package/src/server/ops/event-notification.js +284 -0
  389. package/src/server/{logger.js → ops/logger.js} +108 -23
  390. package/src/server/ops/monitoring.js +1724 -0
  391. package/src/server/ops/systemd.js +259 -0
  392. package/src/server/{conf.js → runtime/conf.js} +521 -520
  393. package/src/server/runtime/config-scope.js +215 -0
  394. package/src/server/runtime/environment.js +149 -0
  395. package/src/server/{process.js → runtime/process.js} +153 -13
  396. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  397. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  398. package/src/server/{start.js → runtime/start.js} +128 -39
  399. package/src/server/{auth.js → security/auth.js} +7 -8
  400. package/src/server/security/container-storage.js +382 -0
  401. package/src/server/{crypto.js → security/crypto.js} +1 -1
  402. package/src/server/security/selinux.js +185 -0
  403. package/src/server/{backup.js → storage/backup.js} +17 -8
  404. package/src/server/{data-query.js → storage/data-query.js} +1 -1
  405. package/src/server/{downloader.js → storage/downloader.js} +2 -2
  406. package/src/server/storage/repository.js +68 -0
  407. package/src/server.js +4 -7
  408. package/src/ws/IoInterface.js +1 -1
  409. package/src/ws/IoServer.js +1 -1
  410. package/src/ws/core/core.ws.connection.js +1 -1
  411. package/src/ws/core/core.ws.emit.js +1 -1
  412. package/src/ws/core/core.ws.server.js +1 -1
  413. package/src/ws/default/default.ws.connection.js +1 -1
  414. package/src/ws/default/default.ws.emit.js +1 -1
  415. package/src/ws/default/default.ws.server.js +1 -1
  416. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  417. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  418. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  419. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  420. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
  421. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
  422. package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
  423. package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
  424. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  425. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  426. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  427. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  428. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  429. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  430. package/test/integration/infra/1-security/selinux.test.js +69 -0
  431. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  432. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  433. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  434. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  435. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  436. package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
  437. package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
  438. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  439. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  440. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  441. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  442. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  443. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  444. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +47 -6
  445. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -4
  446. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  447. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  448. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  449. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  450. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  451. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  452. package/test/support/shell-harness.js +73 -0
  453. package/test/unit/build-template.test.js +97 -0
  454. package/test/unit/build-workflow-offline.test.js +132 -0
  455. package/test/unit/catalog.test.js +71 -0
  456. package/test/unit/client-build-docs.test.js +334 -0
  457. package/test/unit/conf-loading.test.js +303 -0
  458. package/test/unit/conf-resolution.test.js +979 -0
  459. package/test/unit/coverage-artifact.test.js +130 -0
  460. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  461. package/test/unit/deploy-env-resolution.test.js +38 -0
  462. package/test/unit/deploy-log-table.test.js +447 -0
  463. package/test/unit/execution-profiles.test.js +219 -0
  464. package/test/unit/fleet-sync-source.test.js +149 -0
  465. package/test/unit/logger-redaction.test.js +112 -0
  466. package/test/unit/package.test.js +403 -0
  467. package/test/unit/prepare-host.test.js +299 -0
  468. package/test/unit/process-environment.test.js +18 -0
  469. package/test/unit/propagation-message.test.js +78 -0
  470. package/test/unit/redeploy-plan.test.js +24 -0
  471. package/test/unit/release-bump.test.js +474 -0
  472. package/test/unit/ssh-output-redaction.test.js +28 -0
  473. package/test/unit/start-options.test.js +46 -0
  474. package/test/unit/test-tiers.test.js +193 -0
  475. package/vitest.config.js +58 -0
  476. package/.nycrc +0 -9
  477. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  478. package/manifests/mongodb/pv-pvc.yaml +0 -59
  479. package/scripts/link-local-underpost-cli.sh +0 -6
  480. package/src/api/user/user.build.js +0 -16
  481. package/src/cli/env.js +0 -177
  482. package/src/client/components/core/Blockchain.js +0 -41
  483. package/src/client/components/core/Blog.js +0 -9
  484. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  485. package/src/client/public/default/assets/background/dark.jpg +0 -0
  486. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  487. package/src/client/public/default/assets/background/white0.jpg +0 -0
  488. package/src/client/public/doc/favicon.ico +0 -0
  489. package/src/client/public/doc/sitemap +0 -148
  490. package/src/server/cron.js +0 -476
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Host filesystem preparation for Kubernetes storage that unprivileged containers mount.
3
+ *
4
+ * Every node-local path a pod reaches through a `hostPath` PersistentVolume — or through a
5
+ * dynamic provisioner that is itself backed by a node directory — must carry a type the
6
+ * `container_t` domain can use. The base RHEL/Rocky policy has no mapping for the trees this
7
+ * platform writes into, so a directory created there falls back to the policy default
8
+ * (`default_t` under `/data`, `usr_t` under `/opt`, `user_home_t` under the operator's home) and
9
+ * every read and write from a pod becomes an AVC denial.
10
+ *
11
+ * The mapping is registered with `semanage fcontext` rather than applied with `chcon` so it
12
+ * survives a relabel, a policy update and `/.autorelabel`; the paths are then restored. Both
13
+ * operations are idempotent, which is what lets deploy, redeploy and recovery flows all call
14
+ * this without special-casing.
15
+ *
16
+ * @module src/server/security/container-storage.js
17
+ * @namespace ContainerStorageService
18
+ */
19
+ 'use strict';
20
+
21
+ import SELinuxService, { runSELinuxCommands, shellArgumentFactory } from './selinux.js';
22
+ import { HOST_VOLUME_ROOT } from '../runtime/environment.js';
23
+
24
+ /**
25
+ * Host storage preparation and SELinux labeling for container-mounted paths.
26
+ * @class ContainerStorageService
27
+ * @memberof ContainerStorageService
28
+ */
29
+ class ContainerStorageService {
30
+ /** Label every unprivileged container domain can read and write, at the empty category set. */
31
+ static SHARED_CONTAINER_TYPE = SELinuxService.SHARED_CONTAINER_TYPE;
32
+
33
+ /**
34
+ * Mode used when this module is the one creating the directory. Existing directories keep the
35
+ * mode, ownership and ACLs they already have — the workload's own init container owns that,
36
+ * and resetting it would rewrite live data trees.
37
+ */
38
+ static DEFAULT_MODE = '0755';
39
+
40
+ /** Upstream rancher local-path-provisioner defaults, used only when discovery finds nothing. */
41
+ static LOCAL_PATH_DEFAULT_ROOT = '/opt/local-path-provisioner';
42
+ static LOCAL_PATH_CONFIG_MAP = 'local-path-config';
43
+ static LOCAL_PATH_NAMESPACES = ['local-path-storage', 'kube-system'];
44
+
45
+ /**
46
+ * Node directories this platform owns and hands to containers. `/data` covers the MongoDB
47
+ * replica volumes, MariaDB, MySQL and PostgreSQL trees; `HOST_VOLUME_ROOT` covers every
48
+ * `hostPath` PersistentVolume the deploy flow materializes, including the gateway's static tree.
49
+ */
50
+ static PLATFORM_STORAGE_ROOTS = ['/data', HOST_VOLUME_ROOT];
51
+
52
+ /**
53
+ * Kubernetes control-plane state bind-mounted into containers by kubeadm.
54
+ */
55
+ static CONTROL_PLANE_MOUNT_PATHS = ['/etc/kubernetes', '/var/lib/etcd', '/var/lib/calico'];
56
+
57
+ /**
58
+ * Directories that are a system location in their own right. A storage tree may live *under*
59
+ * one of these (`/opt/local-path-provisioner`, `/var/lib/etcd`), but the directory itself never
60
+ * becomes container storage.
61
+ */
62
+ static PROTECTED_EXACT_PATHS = [
63
+ '/',
64
+ '/boot',
65
+ '/dev',
66
+ '/etc',
67
+ '/home',
68
+ '/mnt',
69
+ '/opt',
70
+ '/proc',
71
+ '/root',
72
+ '/run',
73
+ '/srv',
74
+ '/sys',
75
+ '/tmp',
76
+ '/usr',
77
+ '/var',
78
+ '/var/lib',
79
+ '/var/log',
80
+ '/var/run',
81
+ ];
82
+
83
+ /**
84
+ * Trees no container storage may live in at all.
85
+ *
86
+ * A host directory being mounted into a pod is not evidence that it holds workload data: the
87
+ * CNI plugin directory, the runtime socket and `/proc` are all mounted into pods, and each has
88
+ * a policy type that exists for a reason. Relabeling `/opt/cni/bin` would strip `bin_t` from
89
+ * the calico executables the kubelet runs, which is a worse outcome than the denial it set out
90
+ * to fix.
91
+ */
92
+ static PROTECTED_PATH_PREFIXES = [
93
+ '/bin',
94
+ '/boot',
95
+ '/dev',
96
+ '/etc',
97
+ '/lib',
98
+ '/lib64',
99
+ '/opt/cni',
100
+ '/proc',
101
+ '/run',
102
+ '/sbin',
103
+ '/sys',
104
+ '/usr',
105
+ ];
106
+
107
+ /**
108
+ * The one set of paths allowed to break {@link PROTECTED_PATH_PREFIXES}: kubeadm bind-mounts
109
+ * these into its control-plane containers, so they genuinely are container-visible state. They
110
+ * are a hardcoded constant, never something discovery or an operator flag can widen.
111
+ */
112
+ static PROTECTED_PATH_EXCEPTIONS = ContainerStorageService.CONTROL_PLANE_MOUNT_PATHS;
113
+
114
+ /**
115
+ * Reports whether a path may be given the shared container label.
116
+ * @param {string} path - Absolute path.
117
+ * @returns {boolean}
118
+ */
119
+ static isSafeStoragePath(path) {
120
+ if (typeof path !== 'string' || !path.startsWith('/')) return false;
121
+ const normalized = path.replace(/\/+$/, '') || '/';
122
+ if (ContainerStorageService.PROTECTED_PATH_EXCEPTIONS.includes(normalized)) return true;
123
+ if (ContainerStorageService.PROTECTED_EXACT_PATHS.includes(normalized)) return false;
124
+ return !ContainerStorageService.PROTECTED_PATH_PREFIXES.some(
125
+ (prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Throws unless every path may be labeled. Applied at the command-building boundary so no
131
+ * caller — a discovery result, a CLI `extraPaths`, a future one — can route around it.
132
+ * @param {string[]} paths - Absolute paths.
133
+ * @returns {string[]} The same paths.
134
+ */
135
+ static assertSafeStoragePaths(paths = []) {
136
+ const refused = paths.filter((path) => !ContainerStorageService.isSafeStoragePath(path));
137
+ if (refused.length > 0)
138
+ throw new RangeError(
139
+ `refusing to apply the container storage label to a system path: ${refused.join(', ')}. ` +
140
+ `A host directory mounted into a pod is not necessarily workload data; give the volume ` +
141
+ `its own directory under a declared storage root instead.`,
142
+ );
143
+ return paths;
144
+ }
145
+
146
+ /**
147
+ * Deduplicates and sorts a path list, keeping only absolute entries.
148
+ * @param {string[]} [paths] - Candidate paths.
149
+ * @returns {string[]} Absolute paths, parents before their descendants.
150
+ */
151
+ static absoluteStoragePaths(paths = []) {
152
+ return [
153
+ ...new Set(
154
+ paths
155
+ .filter((path) => typeof path === 'string')
156
+ .map((path) => path.trim().replace(/\/+$/, ''))
157
+ .filter((path) => path.startsWith('/') && path !== '/'),
158
+ ),
159
+ ].sort();
160
+ }
161
+
162
+ /**
163
+ * Reduces a path list to the smallest set of roots that still covers every entry.
164
+ *
165
+ * A `semanage fcontext` mapping is the regular expression `<root>(/.*)?`, so a root already
166
+ * covers all of its descendants; keeping both `/data` and `/data/mongodb` would add a redundant
167
+ * rule and a second full `restorecon` pass over the same tree on every deploy.
168
+ * @param {string[]} [paths] - Candidate absolute paths.
169
+ * @returns {string[]} Non-overlapping, sorted roots.
170
+ */
171
+ static normalizeStorageRoots(paths = []) {
172
+ const absolute = ContainerStorageService.absoluteStoragePaths(paths);
173
+ return absolute.filter(
174
+ (path) => !absolute.some((candidate) => candidate !== path && path.startsWith(`${candidate}/`)),
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Extracts the node directories a local-path provisioner writes into, from its `config.json`.
180
+ *
181
+ * Read rather than assumed: the provisioner's root is operator-configurable, and every
182
+ * dynamically provisioned claim in this platform (Grafana, Prometheus, Alertmanager, IPFS,
183
+ * MariaDB) becomes a subdirectory of it. Labeling the wrong root leaves those claims with the
184
+ * `/opt` policy default (`usr_t`), which is exactly the shape of the Grafana denials.
185
+ * @param {string} [configJson] - Contents of the provisioner ConfigMap's `config.json` key.
186
+ * @returns {string[]} Configured node paths; empty when the config is absent or unparsable.
187
+ */
188
+ static localPathRootsFactory(configJson = '') {
189
+ if (!configJson || !`${configJson}`.trim()) return [];
190
+ let config;
191
+ try {
192
+ config = JSON.parse(configJson);
193
+ } catch {
194
+ return [];
195
+ }
196
+ const paths = [];
197
+ if (Array.isArray(config?.nodePathMap))
198
+ for (const entry of config.nodePathMap) if (Array.isArray(entry?.paths)) paths.push(...entry.paths);
199
+ if (typeof config?.sharedFileSystemPath === 'string') paths.push(config.sharedFileSystemPath);
200
+ return ContainerStorageService.normalizeStorageRoots(paths);
201
+ }
202
+
203
+ /**
204
+ * Builds the command that creates a storage directory only when it is missing.
205
+ *
206
+ * Guarded rather than unconditional: `install -d -m` on an existing tree would rewrite the
207
+ * mode a workload's init container set (MongoDB's `chown 999:999`, MariaDB's data dir), and
208
+ * this runs on every redeploy.
209
+ * @param {string} path - Absolute directory path.
210
+ * @param {{mode?: string, owner?: string, sudo?: boolean}} [options]
211
+ * @returns {string} Shell command.
212
+ */
213
+ static storageDirectoryCommandFactory(
214
+ path,
215
+ { mode = ContainerStorageService.DEFAULT_MODE, owner = '', sudo = true } = {},
216
+ ) {
217
+ if (!path) throw new TypeError('storageDirectoryCommandFactory requires a path');
218
+ const prefix = sudo ? 'sudo ' : '';
219
+ const quoted = shellArgumentFactory(path);
220
+ const ownerFlag = owner
221
+ ? ` -o ${shellArgumentFactory(`${owner}`.split(':')[0])}${
222
+ `${owner}`.includes(':') ? ` -g ${shellArgumentFactory(`${owner}`.split(':')[1])}` : ''
223
+ }`
224
+ : '';
225
+ return `[ -d ${quoted} ] || ${prefix}install -d -m ${mode}${ownerFlag} ${quoted}`;
226
+ }
227
+
228
+ /**
229
+ * Builds the full idempotent preparation for one or more container-mounted storage roots:
230
+ * create when missing, register the persistent `container_file_t` mapping, restore the tree.
231
+ * @param {string|string[]} paths - Absolute directories.
232
+ * @param {{mode?: string, owner?: string, sudo?: boolean}} [options]
233
+ * @returns {string[]} Commands, in execution order.
234
+ */
235
+ static containerStorageCommandsFactory(paths, options = {}) {
236
+ const requested = ContainerStorageService.absoluteStoragePaths(Array.isArray(paths) ? paths : [paths]);
237
+ if (requested.length === 0)
238
+ throw new TypeError('containerStorageCommandsFactory requires at least one absolute path');
239
+ ContainerStorageService.assertSafeStoragePaths(requested);
240
+ // Every requested path is created — a nested one (`/data/mysql`) is a real directory a PV
241
+ // mounts. Mapping and restoring collapse to the covering roots: an fcontext expression is
242
+ // `<root>(/.*)?`, so a nested entry would add a redundant rule to the local policy store and
243
+ // a second full relabel pass over the same tree on every deploy.
244
+ return [
245
+ ...requested.map((path) => ContainerStorageService.storageDirectoryCommandFactory(path, options)),
246
+ ...ContainerStorageService.containerStorageSubtreeCommandsFactory(requested, {
247
+ sudo: options.sudo !== false,
248
+ }),
249
+ ];
250
+ }
251
+
252
+ /**
253
+ * Resolves which declared storage root covers a path.
254
+ * @param {string} path - Absolute path.
255
+ * @param {string[]} [roots] - Candidate roots.
256
+ * @returns {string} The covering root, or the path itself when none covers it.
257
+ */
258
+ static coveringRootFactory(path, roots = ContainerStorageService.PLATFORM_STORAGE_ROOTS) {
259
+ const covering = ContainerStorageService.absoluteStoragePaths(roots)
260
+ .filter((root) => path === root || path.startsWith(`${root}/`))
261
+ .sort((a, b) => b.length - a.length)[0];
262
+ return covering || path;
263
+ }
264
+
265
+ /**
266
+ * Builds the preparation for one subtree of an already-declared storage root: the persistent
267
+ * mapping is registered on the covering root, and only the subtree is restored.
268
+ *
269
+ * A per-volume mapping would grow the local policy store by one `semanage fcontext` rule for
270
+ * every PersistentVolume the deploy flow has ever materialized, and every one of those rules is
271
+ * then evaluated on each relabel. One rule on the root already covers all of them, and it is
272
+ * what makes a volume created later — by kubelet's `DirectoryOrCreate`, or by the local-path
273
+ * helper pod — inherit the right label without another pass.
274
+ * @param {string|string[]} paths - Absolute subtrees to restore.
275
+ * @param {{roots?: string[], mode?: string, owner?: string, sudo?: boolean}} [options]
276
+ * @returns {string[]} Commands, in execution order.
277
+ */
278
+ static containerStorageSubtreeCommandsFactory(paths, { roots, ...options } = {}) {
279
+ const subtrees = ContainerStorageService.absoluteStoragePaths(Array.isArray(paths) ? paths : [paths]);
280
+ if (subtrees.length === 0)
281
+ throw new TypeError('containerStorageSubtreeCommandsFactory requires at least one absolute path');
282
+ const sudo = options.sudo !== false;
283
+ ContainerStorageService.assertSafeStoragePaths(subtrees);
284
+ const mapped = ContainerStorageService.normalizeStorageRoots(
285
+ subtrees.map((path) => ContainerStorageService.coveringRootFactory(path, roots)),
286
+ );
287
+ // The mapped root is what the fcontext rule covers, so it is checked too: a subtree that is
288
+ // itself safe must never resolve up into a root that is not.
289
+ ContainerStorageService.assertSafeStoragePaths(mapped);
290
+ return [
291
+ ...mapped.map((root) =>
292
+ SELinuxService.selinuxFileContextCommandFactory(root, {
293
+ type: ContainerStorageService.SHARED_CONTAINER_TYPE,
294
+ sudo,
295
+ }),
296
+ ),
297
+ SELinuxService.selinuxRestoreconCommandFactory(ContainerStorageService.normalizeStorageRoots(subtrees), {
298
+ sudo,
299
+ }),
300
+ ];
301
+ }
302
+
303
+ /**
304
+ * Executes {@link containerStorageSubtreeCommandsFactory}.
305
+ * @param {string|string[]} paths - Absolute subtrees.
306
+ * @param {{execute: Function, roots?: string[], sudo?: boolean}} options
307
+ * @returns {*[]} Executor results.
308
+ */
309
+ static ensureContainerStorageSubtree(paths, { execute, ...options } = {}) {
310
+ return runSELinuxCommands(ContainerStorageService.containerStorageSubtreeCommandsFactory(paths, options), {
311
+ execute,
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Executes {@link containerStorageCommandsFactory}.
317
+ * @param {string|string[]} paths - Absolute directories.
318
+ * @param {{execute: Function, mode?: string, owner?: string, sudo?: boolean}} options
319
+ * @returns {*[]} Executor results.
320
+ */
321
+ static ensureContainerStorage(paths, { execute, ...options } = {}) {
322
+ return runSELinuxCommands(ContainerStorageService.containerStorageCommandsFactory(paths, options), { execute });
323
+ }
324
+
325
+ /**
326
+ * Command that prints the local-path provisioner `config.json`, or nothing when absent.
327
+ * @param {{namespace: string, configMap?: string}} options
328
+ * @returns {string} Shell command.
329
+ */
330
+ static localPathConfigCommandFactory({ namespace, configMap = ContainerStorageService.LOCAL_PATH_CONFIG_MAP } = {}) {
331
+ if (!namespace) throw new TypeError('localPathConfigCommandFactory requires a namespace');
332
+ return `kubectl get configmap ${configMap} -n ${namespace} -o jsonpath='{.data.config\\.json}' 2>/dev/null || true`;
333
+ }
334
+
335
+ /**
336
+ * Resolves every node directory that must carry the shared container label on this host.
337
+ * @param {{localPathRoots?: string[], extraPaths?: string[], controlPlane?: boolean}} [options]
338
+ * @returns {string[]} Non-overlapping roots.
339
+ */
340
+ static containerStorageRootsFactory({ localPathRoots = [], extraPaths = [], controlPlane = false } = {}) {
341
+ return ContainerStorageService.normalizeStorageRoots([
342
+ ...ContainerStorageService.PLATFORM_STORAGE_ROOTS,
343
+ ...(controlPlane ? ContainerStorageService.CONTROL_PLANE_MOUNT_PATHS : []),
344
+ ...(localPathRoots.length > 0 ? localPathRoots : [ContainerStorageService.LOCAL_PATH_DEFAULT_ROOT]),
345
+ ...extraPaths,
346
+ ]);
347
+ }
348
+ }
349
+
350
+ const {
351
+ absoluteStoragePaths,
352
+ isSafeStoragePath,
353
+ assertSafeStoragePaths,
354
+ containerStorageCommandsFactory,
355
+ containerStorageRootsFactory,
356
+ containerStorageSubtreeCommandsFactory,
357
+ coveringRootFactory,
358
+ ensureContainerStorage,
359
+ ensureContainerStorageSubtree,
360
+ localPathConfigCommandFactory,
361
+ localPathRootsFactory,
362
+ normalizeStorageRoots,
363
+ storageDirectoryCommandFactory,
364
+ } = ContainerStorageService;
365
+
366
+ export default ContainerStorageService;
367
+
368
+ export {
369
+ absoluteStoragePaths,
370
+ isSafeStoragePath,
371
+ assertSafeStoragePaths,
372
+ containerStorageCommandsFactory,
373
+ containerStorageRootsFactory,
374
+ containerStorageSubtreeCommandsFactory,
375
+ coveringRootFactory,
376
+ ensureContainerStorage,
377
+ ensureContainerStorageSubtree,
378
+ localPathConfigCommandFactory,
379
+ localPathRootsFactory,
380
+ normalizeStorageRoots,
381
+ storageDirectoryCommandFactory,
382
+ };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Module for managing crypto operations
3
- * @module src/server/crypto.js
3
+ * @module src/server/security/crypto.js
4
4
  * @namespace Crypto
5
5
  */
6
6
 
@@ -0,0 +1,185 @@
1
+ /**
2
+ * SELinux policy, labeling, and enforcement command helpers.
3
+ *
4
+ * @module src/server/security/selinux.js
5
+ * @namespace SELinuxService
6
+ */
7
+ 'use strict';
8
+
9
+ /**
10
+ * Main SELinux utility.
11
+ * @class SELinuxService
12
+ * @memberof SELinuxService
13
+ */
14
+ class SELinuxService {
15
+ /** Shared container label every unprivileged container domain can read and write. */
16
+ static SHARED_CONTAINER_TYPE = 'container_file_t';
17
+
18
+ /**
19
+ * Quotes one shell argument used by generated SELinux commands.
20
+ * @param {*} value - Value to quote.
21
+ * @returns {string}
22
+ */
23
+ static shellArgumentFactory(value) {
24
+ return `'${`${value ?? ''}`.replaceAll("'", `'"'"'`)}'`;
25
+ }
26
+
27
+ /**
28
+ * Builds the Rocky/RHEL SELinux userspace installation command.
29
+ * @param {{sudo?: boolean}} [options]
30
+ * @returns {string}
31
+ */
32
+ static selinuxPackagesCommandFactory({ sudo = true } = {}) {
33
+ return `${sudo ? 'sudo ' : ''}dnf install -y policycoreutils policycoreutils-python-utils selinux-policy-targeted audit`;
34
+ }
35
+
36
+ /**
37
+ * Builds commands that make Enforcing mode persistent and active.
38
+ * @param {{sudo?: boolean, restorePaths?: string[]}} [options]
39
+ * @returns {string[]}
40
+ */
41
+ static selinuxEnforcingCommandsFactory({ sudo = true, restorePaths = [] } = {}) {
42
+ const prefix = sudo ? 'sudo ' : '';
43
+ return [
44
+ `if [ -f /etc/selinux/config ]; then ${prefix}sed -i -E 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config; fi`,
45
+ // A host running with SELinux Disabled has an unlabeled filesystem, so the
46
+ // config flip alone would boot it into Enforcing with nothing labeled.
47
+ // `setenforce` cannot activate the mode from Disabled either: the switch
48
+ // completes on the next boot, and only after this relabel pass.
49
+ `if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Disabled" ]; then ${prefix}touch /.autorelabel; fi`,
50
+ ...(restorePaths.length > 0
51
+ ? [SELinuxService.selinuxRestoreconCommandFactory(restorePaths, { sudo })]
52
+ : []),
53
+ `if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then ${prefix}setenforce 1; fi`,
54
+ ];
55
+ }
56
+
57
+ /**
58
+ * Builds a command that restores policy-defined file contexts.
59
+ * @param {string|string[]} paths - Files or directories to label.
60
+ * @param {{recursive?: boolean, sudo?: boolean}} [options]
61
+ * @returns {string}
62
+ */
63
+ static selinuxRestoreconCommandFactory(paths, { recursive = true, sudo = true } = {}) {
64
+ const values = (Array.isArray(paths) ? paths : [paths]).filter(Boolean);
65
+ if (values.length === 0) throw new TypeError('selinuxRestoreconCommandFactory requires at least one path');
66
+ const operations = values
67
+ .map(SELinuxService.shellArgumentFactory)
68
+ .map(
69
+ (path) =>
70
+ `{ [ ! -e ${path} ] || ${sudo ? 'sudo ' : ''}restorecon ${recursive ? '-RF ' : ''}${path}; }`,
71
+ )
72
+ .join(' && ');
73
+ return `if command -v restorecon >/dev/null 2>&1; then ${operations}; fi`;
74
+ }
75
+
76
+ /**
77
+ * Builds an idempotent persistent file context mapping.
78
+ * @param {string} path - Directory or file prefix to map.
79
+ * @param {{type: string, sudo?: boolean}} options
80
+ * @returns {string}
81
+ */
82
+ static selinuxFileContextCommandFactory(path, { type, sudo = true } = {}) {
83
+ if (!path) throw new TypeError('selinuxFileContextCommandFactory requires a path');
84
+ if (!type) throw new TypeError('selinuxFileContextCommandFactory requires a type');
85
+ const prefix = sudo ? 'sudo ' : '';
86
+ const expression = SELinuxService.shellArgumentFactory(`${path}(/.*)?`);
87
+ return `if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then command -v semanage >/dev/null 2>&1 || { echo 'semanage is required for persistent file contexts' >&2; exit 1; }; ${prefix}semanage fcontext -a -t ${type} ${expression} 2>/dev/null || ${prefix}semanage fcontext -m -t ${type} ${expression}; fi`;
88
+ }
89
+
90
+ /**
91
+ * Builds persistent labeling commands for host paths bind-mounted into
92
+ * unprivileged containers. `container_t` cannot read the policy defaults of
93
+ * those trees (`kubernetes_file_t`, `var_lib_t`), and the mapping is
94
+ * registered before the files exist so entries created later inherit the
95
+ * shared label instead of requiring another relabel pass.
96
+ * @param {string|string[]} paths - Files or directories to share.
97
+ * @param {{sudo?: boolean}} [options]
98
+ * @returns {string[]}
99
+ */
100
+ static selinuxContainerSharedContextCommandsFactory(paths, { sudo = true } = {}) {
101
+ const values = (Array.isArray(paths) ? paths : [paths]).filter(Boolean);
102
+ if (values.length === 0)
103
+ throw new TypeError('selinuxContainerSharedContextCommandsFactory requires at least one path');
104
+ return [
105
+ ...values.map((path) =>
106
+ SELinuxService.selinuxFileContextCommandFactory(path, { type: SELinuxService.SHARED_CONTAINER_TYPE, sudo }),
107
+ ),
108
+ SELinuxService.selinuxRestoreconCommandFactory(values, { sudo }),
109
+ ];
110
+ }
111
+
112
+ /**
113
+ * Builds persistent labeling commands for an SSH directory.
114
+ * Standard /root and /home locations already have policy mappings; custom
115
+ * home locations receive an explicit ssh_home_t mapping.
116
+ * @param {{sshDirectory: string, sudo?: boolean}} options
117
+ * @returns {string[]}
118
+ */
119
+ static selinuxSshContextCommandsFactory({ sshDirectory, sudo = true } = {}) {
120
+ if (!sshDirectory) throw new TypeError('selinuxSshContextCommandsFactory requires sshDirectory');
121
+ const prefix = sudo ? 'sudo ' : '';
122
+ const standard = sshDirectory === '/root/.ssh' || /^\/home\/[^/]+\/\.ssh$/.test(sshDirectory);
123
+ const commands = [];
124
+ if (!standard) {
125
+ const expression = SELinuxService.shellArgumentFactory(`${sshDirectory}(/.*)?`);
126
+ commands.push(
127
+ `if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then command -v semanage >/dev/null 2>&1 || { echo 'semanage is required for a custom SSH home' >&2; exit 1; }; ${prefix}semanage fcontext -a -t ssh_home_t ${expression} 2>/dev/null || ${prefix}semanage fcontext -m -t ssh_home_t ${expression}; fi`,
128
+ );
129
+ }
130
+ commands.push(SELinuxService.selinuxRestoreconCommandFactory(sshDirectory, { sudo }));
131
+ return commands;
132
+ }
133
+
134
+ /**
135
+ * Builds an idempotent ssh_port_t assignment for a custom SSH port.
136
+ * @param {{port?: number|string, sudo?: boolean}} [options]
137
+ * @returns {string[]}
138
+ */
139
+ static selinuxSshPortCommandsFactory({ port = 22, sudo = true } = {}) {
140
+ const value = Number(port);
141
+ if (!Number.isInteger(value) || value < 1 || value > 65535) throw new RangeError('SSH port must be 1-65535');
142
+ if (value === 22) return [];
143
+ const prefix = sudo ? 'sudo ' : '';
144
+ return [
145
+ `if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then command -v semanage >/dev/null 2>&1 || { echo 'semanage is required for a custom SSH port' >&2; exit 1; }; ${prefix}semanage port -a -t ssh_port_t -p tcp ${value} 2>/dev/null || ${prefix}semanage port -m -t ssh_port_t -p tcp ${value}; fi`,
146
+ ];
147
+ }
148
+
149
+ /**
150
+ * Executes a generated command list.
151
+ * @param {string[]} [commands]
152
+ * @param {{execute: Function}} options
153
+ * @returns {*[]}
154
+ */
155
+ static runSELinuxCommands(commands = [], { execute } = {}) {
156
+ if (typeof execute !== 'function') throw new TypeError('runSELinuxCommands requires an executor');
157
+ return commands.map((command) => execute(command));
158
+ }
159
+ }
160
+
161
+ const {
162
+ runSELinuxCommands,
163
+ selinuxContainerSharedContextCommandsFactory,
164
+ selinuxEnforcingCommandsFactory,
165
+ selinuxFileContextCommandFactory,
166
+ selinuxPackagesCommandFactory,
167
+ selinuxRestoreconCommandFactory,
168
+ selinuxSshContextCommandsFactory,
169
+ selinuxSshPortCommandsFactory,
170
+ shellArgumentFactory,
171
+ } = SELinuxService;
172
+
173
+ export default SELinuxService;
174
+
175
+ export {
176
+ runSELinuxCommands,
177
+ selinuxContainerSharedContextCommandsFactory,
178
+ selinuxEnforcingCommandsFactory,
179
+ selinuxFileContextCommandFactory,
180
+ selinuxPackagesCommandFactory,
181
+ selinuxRestoreconCommandFactory,
182
+ selinuxSshContextCommandsFactory,
183
+ selinuxSshPortCommandsFactory,
184
+ shellArgumentFactory,
185
+ };
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Manages backup operations for deployments.
3
- * @module src/server/backup.js
3
+ * @module src/server/storage/backup.js
4
4
  * @namespace UnderpostBakcUp
5
5
  */
6
6
 
7
7
  import fs from 'fs-extra';
8
- import { loggerFactory } from './logger.js';
9
- import Underpost from '../index.js';
10
- import { loadCronDeployEnv } from './conf.js';
8
+ import { loggerFactory } from '../ops/logger.js';
9
+ import Underpost from '../../index.js';
10
+ import { loadCronDeployEnv } from '../ops/cron.js';
11
+ import { readDeployRoutes } from '../network/router.js';
11
12
 
12
13
  const logger = loggerFactory(import.meta);
13
14
 
@@ -39,13 +40,13 @@ class BackUp {
39
40
  static callback = async function (deployList, options = { git: false }) {
40
41
  const firstDeployId = deployList && deployList !== 'dd' ? deployList.split(',')[0].trim() : '';
41
42
  loadCronDeployEnv();
42
- if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`))
43
- deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim();
43
+ if (!deployList || deployList === 'dd') deployList = readDeployRoutes().join(',');
44
44
 
45
45
  logger.info('init backups callback', deployList);
46
46
  await logger.setUpInfo();
47
47
 
48
48
  const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : options.kubeadm ? ' --kubeadm' : '';
49
+ const failures = [];
49
50
 
50
51
  for (const _deployId of deployList.split(',')) {
51
52
  const deployId = _deployId.trim();
@@ -71,7 +72,8 @@ class BackUp {
71
72
  cd: '/home/dd/engine',
72
73
  });
73
74
  } catch (err) {
74
- logger.error(`Error during database export for ${deployId}:`, err);
75
+ failures.push({ deployId, phase: 'database-export', error: err.message });
76
+ logger.error(`Error during database export for ${deployId}`, { error: err.message });
75
77
  }
76
78
 
77
79
  // Repository backup: Cron container → SSH to host → host finds pod → kubectl exec git backup
@@ -83,9 +85,16 @@ class BackUp {
83
85
  cd: '/home/dd/engine',
84
86
  });
85
87
  } catch (err) {
86
- logger.error(`Error during repository backup for ${deployId}:`, err);
88
+ failures.push({ deployId, phase: 'repository-backup', error: err.message });
89
+ logger.error(`Error during repository backup for ${deployId}`, { error: err.message });
87
90
  }
88
91
  }
92
+
93
+ // One deploy-id failing must not abort the remaining ones, so the loop swallows
94
+ // per-phase errors — this summary is the only place the run's real outcome shows up.
95
+ // The failing `shellExec` has already latched `container-status=error` for monitoring.
96
+ if (failures.length > 0) logger.error('Backup completed with failures', { count: failures.length, failures });
97
+ else logger.info('Backup completed for all deploy-ids', { deployList });
89
98
  };
90
99
  }
91
100
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Module for parsing data query parameters into Mongoose query options.
3
3
  * Supports AG Grid filterModel/sortModel as well as simple legacy parameters.
4
- * @module src/server/data-query.js
4
+ * @module src/server/storage/data-query.js
5
5
  * @namespace DataQuery
6
6
  */
7
7
 
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Provides a utility class for downloading files from a URL and saving them to the local filesystem.
3
- * @module src/server/downloader.js
3
+ * @module src/server/storage/downloader.js
4
4
  * @namespace Downloader
5
5
  */
6
6
 
7
7
  import axios from 'axios';
8
8
  import fs from 'fs';
9
- import { loggerFactory } from './logger.js';
9
+ import { loggerFactory } from '../ops/logger.js';
10
10
 
11
11
  const logger = loggerFactory(import.meta);
12
12