@underpostnet/cyberia 3.3.0 → 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 (471) hide show
  1. package/.dockerignore +1 -0
  2. package/.github/workflows/coverall.cyberia.ci.yml +34 -14
  3. package/.github/workflows/cyberia-client.cd.yml +5 -0
  4. package/.github/workflows/cyberia-server.cd.yml +5 -0
  5. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  6. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  7. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  8. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  9. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  10. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  11. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  12. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  13. package/.github/workflows/ghpkg.ci.yml +35 -20
  14. package/.github/workflows/hardhat.ci.yml +12 -11
  15. package/.github/workflows/npmpkg.ci.yml +17 -15
  16. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  17. package/.github/workflows/release.cd.yml +1 -0
  18. package/.prettierignore +1 -0
  19. package/AGENTS.md +47 -0
  20. package/CHANGELOG.md +565 -2510
  21. package/CLI-HELP.md +306 -147
  22. package/Dockerfile +25 -9
  23. package/Dockerfile.dev +25 -9
  24. package/Dockerfile.test +4 -4
  25. package/README.md +21 -1
  26. package/bin/build.js +66 -41
  27. package/bin/build.template.js +8 -3
  28. package/bin/cyberia.js +308 -91
  29. package/bin/deploy.js +18 -104
  30. package/bin/index.js +308 -91
  31. package/conf.js +91 -4
  32. package/deploy/cyberia-client/deploy.sh +47 -0
  33. package/deploy/cyberia-client/package.sh +18 -0
  34. package/deploy/cyberia-client/state.sh +14 -0
  35. package/deploy/cyberia-server/deploy.sh +47 -0
  36. package/deploy/cyberia-server/package.sh +18 -0
  37. package/deploy/cyberia-server/state.sh +14 -0
  38. package/deploy/dd-cyberia/deploy.sh +6 -26
  39. package/deploy/dd-cyberia/init.sh +35 -43
  40. package/deploy/dd-cyberia/package.sh +17 -0
  41. package/deploy/dd-cyberia/state.sh +14 -0
  42. package/deploy/dd-cyberia/sync-deploy.sh +92 -170
  43. package/deploy/lib/github-actions-logging.sh +594 -0
  44. package/deploy/lib/host.sh +185 -0
  45. package/deploy/lib/state.sh +62 -0
  46. package/deploy/pwa-microservices-template/deploy.sh +12 -39
  47. package/deploy/release/deploy.sh +9 -39
  48. package/deployment.yaml +7 -2
  49. package/docker-compose.yml +7 -7
  50. package/examples/static-page/README.md +19 -19
  51. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  52. package/hardhat/hardhat.config.js +1 -6
  53. package/hardhat/package-lock.json +101 -40
  54. package/hardhat/package.json +2 -2
  55. package/manifests/alertmanager/deployment.yaml +81 -0
  56. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  57. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  58. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  59. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  60. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  61. package/manifests/grafana/deployment.yaml +26 -7
  62. package/manifests/grafana/kustomization.yaml +1 -0
  63. package/manifests/grafana/service.yaml +6 -1
  64. package/manifests/mariadb/pv.yaml +1 -0
  65. package/manifests/mongodb/statefulset.yaml +9 -2
  66. package/manifests/mysql/pv-pvc.yaml +14 -1
  67. package/manifests/postgresql/pv.yaml +12 -1
  68. package/manifests/postgresql/pvc.yaml +1 -0
  69. package/manifests/prometheus/deployment.yaml +18 -22
  70. package/package.json +83 -10
  71. package/scripts/event-monitor.sh +56 -0
  72. package/scripts/k3s-node-setup.sh +27 -14
  73. package/scripts/kubeadm-node-setup.sh +31 -11
  74. package/scripts/maas-setup.sh +8 -8
  75. package/scripts/selinux-normalize.sh +907 -0
  76. package/scripts/shutdown-machine.sh +843 -0
  77. package/scripts/test-monitor.sh +3 -3
  78. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
  79. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
  80. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
  81. package/src/api/core/core.controller.js +1 -1
  82. package/src/api/core/core.router.js +2 -2
  83. package/src/api/core/core.service.js +2 -2
  84. package/src/api/crypto/crypto.controller.js +1 -1
  85. package/src/api/crypto/crypto.service.js +1 -1
  86. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  87. package/src/api/cyberia-action/cyberia-action.router.js +1 -1
  88. package/src/api/cyberia-action/cyberia-action.service.js +2 -2
  89. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
  90. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
  91. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
  92. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  93. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  94. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  95. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  96. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  97. package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
  98. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
  99. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
  100. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
  101. package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
  102. package/src/api/cyberia-instance/cyberia-fallback-world.js +8 -0
  103. package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
  104. package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -1
  105. package/src/api/cyberia-instance/cyberia-instance.router.js +2 -2
  106. package/src/api/cyberia-instance/cyberia-instance.service.js +2 -2
  107. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  108. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  109. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
  110. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  111. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  112. package/src/api/cyberia-map/cyberia-map.service.js +2 -2
  113. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  114. package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
  115. package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
  116. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  117. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  118. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  119. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  120. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  121. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  122. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +0 -26
  123. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  124. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  125. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  126. package/src/api/default/default.controller.js +1 -1
  127. package/src/api/default/default.router.js +1 -1
  128. package/src/api/default/default.service.js +2 -2
  129. package/src/api/document/document.controller.js +1 -1
  130. package/src/api/document/document.router.js +1 -1
  131. package/src/api/document/document.service.js +2 -2
  132. package/src/api/file/file.controller.js +1 -1
  133. package/src/api/file/file.router.js +1 -1
  134. package/src/api/file/file.service.js +2 -2
  135. package/src/api/instance/instance.controller.js +1 -1
  136. package/src/api/instance/instance.router.js +1 -1
  137. package/src/api/instance/instance.service.js +2 -2
  138. package/src/api/ipfs/ipfs.controller.js +1 -1
  139. package/src/api/ipfs/ipfs.router.js +2 -2
  140. package/src/api/ipfs/ipfs.service.js +2 -2
  141. package/src/api/object-layer/object-layer.controller.js +1 -1
  142. package/src/api/object-layer/object-layer.router.js +2 -2
  143. package/src/api/object-layer/object-layer.service.js +3 -3
  144. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  145. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  146. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  147. package/src/api/test/test.controller.js +1 -1
  148. package/src/api/test/test.service.js +1 -1
  149. package/src/api/user/guest.service.js +3 -3
  150. package/src/api/user/user.controller.js +1 -1
  151. package/src/api/user/user.router.js +2 -2
  152. package/src/api/user/user.service.js +4 -4
  153. package/src/api.js +5 -9
  154. package/src/cli/app.js +391 -0
  155. package/src/cli/baremetal.js +30 -35
  156. package/src/cli/client.js +306 -0
  157. package/src/cli/cloud-init.js +3 -3
  158. package/src/cli/cluster.js +184 -82
  159. package/src/cli/db.js +120 -68
  160. package/src/cli/deploy.js +153 -71
  161. package/src/cli/docker-compose.js +5 -5
  162. package/src/cli/domains.js +184 -0
  163. package/src/cli/dotenv-store.js +143 -0
  164. package/src/cli/event.js +2281 -0
  165. package/src/cli/fs.js +3 -3
  166. package/src/cli/host.js +672 -0
  167. package/src/cli/image.js +4 -6
  168. package/src/cli/index.js +322 -142
  169. package/src/cli/ipfs.js +34 -30
  170. package/src/cli/kickstart.js +1 -1
  171. package/src/cli/kubectl.js +218 -41
  172. package/src/cli/lxd.js +3 -3
  173. package/src/cli/monitor.js +1359 -26
  174. package/src/cli/package.js +90 -0
  175. package/src/cli/release.js +16 -12
  176. package/src/cli/repository.js +558 -363
  177. package/src/cli/run.js +331 -650
  178. package/src/cli/secrets.js +2158 -964
  179. package/src/cli/ssh.js +478 -279
  180. package/src/cli/state.js +394 -0
  181. package/src/cli/static.js +2 -2
  182. package/src/cli/system.js +1 -1
  183. package/src/cli/test.js +199 -125
  184. package/src/cli/vultr.js +61 -21
  185. package/src/cli/wireguard.js +1111 -306
  186. package/src/client/components/core/CalendarCore.js +53 -96
  187. package/src/client/components/core/Docs.js +258 -211
  188. package/src/client/components/core/FileExplorer.js +0 -21
  189. package/src/client/components/core/PanelForm.js +4 -4
  190. package/src/client/components/core/Repository.js +56 -0
  191. package/src/client/components/core/Translate.js +7 -0
  192. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  193. package/src/client/components/core/Wallet.js +0 -11
  194. package/src/client/components/core/Worker.js +7 -1
  195. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  196. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -1
  197. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  198. package/src/client/components/default/AppShellDefault.js +1 -1
  199. package/src/client/components/default/SettingsDefault.js +2 -2
  200. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  201. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  202. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  203. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
  204. package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
  205. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  206. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  207. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  208. package/src/client/public/default/android-chrome-144x144.png +0 -0
  209. package/src/client/public/default/android-chrome-192x192.png +0 -0
  210. package/src/client/public/default/android-chrome-256x256.png +0 -0
  211. package/src/client/public/default/android-chrome-36x36.png +0 -0
  212. package/src/client/public/default/android-chrome-384x384.png +0 -0
  213. package/src/client/public/default/android-chrome-48x48.png +0 -0
  214. package/src/client/public/default/android-chrome-512x512.png +0 -0
  215. package/src/client/public/default/android-chrome-72x72.png +0 -0
  216. package/src/client/public/default/android-chrome-96x96.png +0 -0
  217. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  218. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  219. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  220. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  221. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  222. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  223. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  224. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  225. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  226. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  227. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  228. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  229. package/src/client/public/default/apple-touch-icon.png +0 -0
  230. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  231. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  232. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  233. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  234. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  235. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  236. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  237. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  238. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  239. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  240. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  241. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  242. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  243. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  244. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  245. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  246. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  247. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  248. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  249. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  250. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  251. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  252. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  253. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  254. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  255. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  256. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  257. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  258. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  259. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  260. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  261. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  262. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  263. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  264. package/src/client/public/default/assets/background/white.jpg +0 -0
  265. package/src/client/public/default/browserconfig.xml +1 -1
  266. package/src/client/public/default/favicon-16x16.png +0 -0
  267. package/src/client/public/default/favicon-32x32.png +0 -0
  268. package/src/client/public/default/favicon-48x48.png +0 -0
  269. package/src/client/public/default/favicon.ico +0 -0
  270. package/src/client/public/default/manifest.webmanifest +5 -5
  271. package/src/client/public/default/mstile-144x144.png +0 -0
  272. package/src/client/public/default/mstile-150x150.png +0 -0
  273. package/src/client/public/default/mstile-310x150.png +0 -0
  274. package/src/client/public/default/mstile-310x310.png +0 -0
  275. package/src/client/public/default/mstile-70x70.png +0 -0
  276. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  277. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  278. package/src/client/ssr/RootDocument.js +1 -1
  279. package/src/client/ssr/body/404.js +1 -1
  280. package/src/client/ssr/body/500.js +1 -1
  281. package/src/client/ssr/body/CacheControl.js +1 -1
  282. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  283. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  284. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  285. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  286. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  287. package/src/client/ssr/head/Css.js +1 -1
  288. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  289. package/src/client/ssr/head/DefaultScripts.js +1 -1
  290. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  291. package/src/client/ssr/head/Microdata.js +1 -1
  292. package/src/client/ssr/head/Production.js +1 -1
  293. package/src/client/ssr/head/Pwa.js +1 -1
  294. package/src/client/ssr/head/PwaItemledger.js +1 -1
  295. package/src/client/ssr/head/Seo.js +1 -1
  296. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  297. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  298. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  299. package/src/client/ssr/views/Cyberia404.js +7 -5
  300. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  301. package/src/client/ssr/views/Maintenance.js +1 -1
  302. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  303. package/src/client/ssr/views/Test.js +1 -1
  304. package/src/client/sw/core.sw.js +20 -14
  305. package/src/client-builder/client-build-docs.js +61 -46
  306. package/src/client-builder/client-build-live.js +2 -2
  307. package/src/client-builder/client-build.js +38 -30
  308. package/src/client-builder/client-dev-server.js +3 -3
  309. package/src/client-builder/client-icons.js +2 -2
  310. package/src/client-builder/ssr.js +3 -3
  311. package/src/client.build.js +3 -6
  312. package/src/client.dev.js +3 -6
  313. package/src/db/DataBaseProvider.js +186 -3
  314. package/src/db/mariadb/MariaDB.js +3 -2
  315. package/src/db/mongo/MongoBootstrap.js +273 -101
  316. package/src/db/mongo/MongooseDB.js +7 -8
  317. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  318. package/src/grpc/cyberia/grpc-server.js +1 -1
  319. package/src/index.js +79 -23
  320. package/src/mailer/EmailRender.js +2 -2
  321. package/src/mailer/MailerInterceptor.js +87 -0
  322. package/src/mailer/MailerProvider.js +50 -6
  323. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
  324. package/src/projects/cyberia/besu-genesis-generator.js +3 -3
  325. package/src/projects/cyberia/catalog-cyberia.js +53 -10
  326. package/src/projects/cyberia/gemini-client.js +1 -1
  327. package/src/projects/cyberia/generate-saga.js +1 -1
  328. package/src/projects/cyberia/hot-reload-trigger.js +1 -1
  329. package/src/projects/cyberia/instance-data.js +1 -1
  330. package/src/projects/cyberia/ipfs-client.js +4 -4
  331. package/src/projects/cyberia/map-preview-generator.js +1 -1
  332. package/src/projects/cyberia/object-layer.js +1 -1
  333. package/src/projects/cyberia/semantic-layer-generator.js +1 -1
  334. package/src/projects/underpost/catalog-underpost.js +9 -1
  335. package/src/proxy.js +4 -7
  336. package/src/runtime/cyberia-client/Dockerfile +9 -4
  337. package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
  338. package/src/runtime/cyberia-server/Dockerfile +9 -4
  339. package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
  340. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  341. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  342. package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
  343. package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
  344. package/src/runtime/express/Express.js +32 -27
  345. package/src/runtime/lampp/Lampp.js +22 -70
  346. package/src/runtime/nginx/Nginx.js +1 -1
  347. package/src/runtime/wp/Wp.js +16 -48
  348. package/src/server/{catalog.js → build/catalog.js} +15 -5
  349. package/src/server/build/coverage.js +117 -0
  350. package/src/server/build/execution.js +402 -0
  351. package/src/server/build/package.js +465 -0
  352. package/src/server/build/testing.js +528 -0
  353. package/src/server/{dns.js → network/dns.js} +144 -11
  354. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  355. package/src/server/{middlewares.js → network/middlewares.js} +3 -3
  356. package/src/server/network/node-capability.js +98 -0
  357. package/src/server/{peer.js → network/peer.js} +3 -3
  358. package/src/server/{proxy.js → network/proxy.js} +7 -13
  359. package/src/server/network/router.js +356 -0
  360. package/src/server/{tls.js → network/tls.js} +2 -2
  361. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  362. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  363. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  364. package/src/server/{cri.js → ops/cri.js} +2 -2
  365. package/src/server/{cron.js → ops/cron.js} +349 -74
  366. package/src/server/ops/event-notification.js +284 -0
  367. package/src/server/{logger.js → ops/logger.js} +108 -23
  368. package/src/server/ops/monitoring.js +1724 -0
  369. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  370. package/src/server/{conf.js → runtime/conf.js} +513 -422
  371. package/src/server/runtime/config-scope.js +215 -0
  372. package/src/server/runtime/environment.js +149 -0
  373. package/src/server/{process.js → runtime/process.js} +153 -12
  374. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  375. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  376. package/src/server/{start.js → runtime/start.js} +128 -39
  377. package/src/server/{auth.js → security/auth.js} +7 -8
  378. package/src/server/security/container-storage.js +382 -0
  379. package/src/server/{crypto.js → security/crypto.js} +1 -1
  380. package/src/server/{selinux.js → security/selinux.js} +1 -1
  381. package/src/server/{backup.js → storage/backup.js} +17 -8
  382. package/src/server/{data-query.js → storage/data-query.js} +1 -1
  383. package/src/server/{downloader.js → storage/downloader.js} +2 -2
  384. package/src/server/storage/repository.js +68 -0
  385. package/src/server.js +4 -7
  386. package/src/ws/IoInterface.js +1 -1
  387. package/src/ws/IoServer.js +1 -1
  388. package/src/ws/core/core.ws.connection.js +1 -1
  389. package/src/ws/core/core.ws.emit.js +1 -1
  390. package/src/ws/core/core.ws.server.js +1 -1
  391. package/src/ws/default/default.ws.connection.js +1 -1
  392. package/src/ws/default/default.ws.emit.js +1 -1
  393. package/src/ws/default/default.ws.server.js +1 -1
  394. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  395. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  396. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  397. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  398. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
  399. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +1 -1
  400. package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
  401. package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
  402. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  403. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  404. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  405. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  406. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  407. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  408. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  409. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  410. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  411. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  412. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  413. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  414. package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
  415. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +609 -55
  416. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  417. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  418. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  419. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  420. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  421. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  422. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  423. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  424. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  425. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  426. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  427. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  428. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  429. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  430. package/test/support/shell-harness.js +73 -0
  431. package/test/unit/build-template.test.js +97 -0
  432. package/test/unit/build-workflow-offline.test.js +132 -0
  433. package/test/unit/catalog.test.js +71 -0
  434. package/test/unit/client-build-docs.test.js +334 -0
  435. package/test/unit/conf-loading.test.js +303 -0
  436. package/test/unit/conf-resolution.test.js +979 -0
  437. package/test/unit/coverage-artifact.test.js +130 -0
  438. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  439. package/test/unit/deploy-env-resolution.test.js +38 -0
  440. package/test/unit/deploy-log-table.test.js +447 -0
  441. package/test/unit/execution-profiles.test.js +219 -0
  442. package/test/unit/fleet-sync-source.test.js +149 -0
  443. package/test/unit/logger-redaction.test.js +112 -0
  444. package/test/unit/package.test.js +403 -0
  445. package/test/unit/prepare-host.test.js +299 -0
  446. package/test/unit/process-environment.test.js +18 -0
  447. package/test/unit/propagation-message.test.js +78 -0
  448. package/test/unit/redeploy-plan.test.js +24 -0
  449. package/test/unit/release-bump.test.js +474 -0
  450. package/test/unit/ssh-output-redaction.test.js +28 -0
  451. package/test/unit/start-options.test.js +46 -0
  452. package/test/unit/test-tiers.test.js +193 -0
  453. package/vitest.config.js +58 -0
  454. package/.nycrc +0 -9
  455. package/deploy/lib/logging.sh +0 -96
  456. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  457. package/manifests/mongodb/pv-pvc.yaml +0 -59
  458. package/scripts/coverall-test-cyberia.sh +0 -24
  459. package/scripts/coverall-test.sh +0 -24
  460. package/scripts/link-local-underpost-cli.sh +0 -6
  461. package/src/api/user/user.build.js +0 -16
  462. package/src/cli/env.js +0 -177
  463. package/src/client/components/core/Blockchain.js +0 -41
  464. package/src/client/components/core/Blog.js +0 -9
  465. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  466. package/src/client/public/default/assets/background/dark.jpg +0 -0
  467. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  468. package/src/client/public/default/assets/background/white0.jpg +0 -0
  469. package/src/client/public/doc/favicon.ico +0 -0
  470. package/src/client/public/doc/sitemap +0 -148
  471. package/src/server/environment.js +0 -98
@@ -13,8 +13,8 @@
13
13
 
14
14
  import fs from 'fs-extra';
15
15
  import path from 'path';
16
- import { shellExec } from '../../server/process.js';
17
- import { loggerFactory } from '../../server/logger.js';
16
+ import { shellExec } from '../../server/runtime/process.js';
17
+ import { loggerFactory } from '../../server/ops/logger.js';
18
18
  import { Lampp } from '../lampp/Lampp.js';
19
19
  import Underpost from '../../index.js';
20
20
 
@@ -141,14 +141,7 @@ class WpService {
141
141
  ({ freshInstall } = WpService.provisionFresh({ host, siteRoot: wpDir, db, wp, subDir }));
142
142
  }
143
143
 
144
- // Ensure git is initialized and linked to the backup repository.
145
- // Mark the directory as safe before git operations — the site root is owned
146
- // by daemon:daemon (Apache) and git 2.35+ refuses to run in directories owned
147
- // by a different user unless explicitly declared safe.
148
- if (repository) {
149
- shellExec(`git config --global --add safe.directory "${wpDir}"`);
150
- Underpost.repo.initLocalRepo({ path: wpDir, origin: repository });
151
- }
144
+ if (repository) Underpost.repo.initLocalRepo({ path: wpDir, origin: repository });
152
145
 
153
146
  // Write a root .htaccess that rewrites / → /subDir/ when running in subdirectory mode
154
147
  if (subDir) {
@@ -187,9 +180,9 @@ class WpService {
187
180
  }
188
181
 
189
182
  /**
190
- * Clone mode — clones `repository` into `siteRoot` if not present, then
191
- * verifies `wp-config.php` exists. If it is missing the site root is wiped
192
- * and a fresh WordPress install is performed (requires `db` config).
183
+ * Clone mode — materializes `siteRoot` from `repository` via
184
+ * {@link UnderpostRepository.provisionSiteRoot}, then verifies `wp-config.php` exists.
185
+ * An unreachable remote or a checkout without `wp-config.php` falls back to a fresh install.
193
186
  * @param {object} opts
194
187
  * @param {string} opts.host - Virtual-host name (for logging).
195
188
  * @param {string} opts.siteRoot - Absolute path where the site should live.
@@ -197,37 +190,11 @@ class WpService {
197
190
  * @param {object|null} [opts.db] - MariaDB config used as fallback for fresh install.
198
191
  */
199
192
  static provisionClone({ host, siteRoot, repository, db, wp, subDir = '' }) {
200
- if (!process.env.GITHUB_TOKEN && repository && repository.startsWith('https://github.com/')) {
201
- logger.warn(`${host}: GITHUB_TOKEN not set git operations will fail for private repositories`);
202
- }
203
-
204
- // Step 0 — verify the remote repository is reachable; fall back to fresh install if not.
205
- // repository.js isRemoteRepo handles token injection internally.
206
- const repoAccessible = Underpost.repo.isRemoteRepo(repository);
207
- logger.info(`${host}: remote accessible = ${repoAccessible} (${repository})`);
208
- if (!repoAccessible) {
209
- logger.warn(`${host}: remote repository not accessible (${repository}) — running fresh install`);
210
- return WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
211
- }
193
+ const { accessible } = Underpost.repo.provisionSiteRoot({ host, siteRoot, repository });
194
+ if (!accessible) return WpService.provisionFresh({ host, siteRoot, db, wp, subDir });
212
195
 
213
- // Step 1 clone if the directory does not exist yet
214
- if (!fs.existsSync(siteRoot)) {
215
- logger.info(`${host}: cloning ${repository} → ${siteRoot}`);
216
- const cloneUrl = Underpost.repo.resolveAuthUrl(repository);
217
- const tmp = `${siteRoot}.tmp`;
218
- if (fs.existsSync(tmp)) shellExec(`sudo rm -rf "${tmp}"`);
219
- shellExec(`git clone "${cloneUrl}" "${tmp}"`);
220
- shellExec(`sudo mv "${tmp}" "${siteRoot}"`);
221
- shellExec(`sudo chmod -R 755 "${siteRoot}"`);
222
- shellExec(`sudo chown -R daemon:daemon "${siteRoot}"`);
223
- } else {
224
- logger.info(`${host}: repo already present at ${siteRoot}`);
225
- }
226
-
227
- // Step 2 — verify wp-config.php; if missing, wipe and do a fresh install.
228
- // initLocalRepo is NOT called here — createApp always calls it after provisionClone
229
- // returns (whether clone or fallback path) so we avoid a double-init and ensure
230
- // safe.directory is declared before git runs.
196
+ // A checkout without wp-config.php is not a WordPress site: discard it and install fresh.
197
+ // initLocalRepo is left to createApp so both provisioning paths converge on one call.
231
198
  if (!fs.existsSync(path.join(siteRoot, 'wp-config.php'))) {
232
199
  logger.warn(`${host}: wp-config.php not found — wiping site root and running fresh install`);
233
200
  shellExec(`sudo rm -rf "${siteRoot}"`);
@@ -515,10 +482,9 @@ Thumbs.db
515
482
  const repoName = repository.split('/').pop().split('.')[0];
516
483
 
517
484
  logger.info(`${host}: persisting site to repository`);
518
- shellExec(
519
- `cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"`,
520
- { silentOnError: true },
521
- );
485
+ shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, {
486
+ silentOnError: true,
487
+ });
522
488
  shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repoName} -f`);
523
489
  logger.info(`${host}: initial commit pushed to ${githubOrg}/${repoName}`);
524
490
  }
@@ -630,7 +596,9 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
630
596
 
631
597
  // MariaDB export is handled by the shared db.js backup flow — no duplicate dump here.
632
598
  if (fs.existsSync(path.join(siteRoot, '.git'))) {
633
- shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, { silentOnError: true });
599
+ shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, {
600
+ silentOnError: true,
601
+ });
634
602
  shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repository.split('/').pop().split('.')[0]}`);
635
603
  logger.info(`backup: git push done for ${siteRoot}`);
636
604
  } else {
@@ -8,9 +8,9 @@
8
8
  * deploy id resolve to the empty catalog — nothing else breaks.
9
9
  *
10
10
  * Each product catalog default-exports the uniform shape documented in
11
- * {@link module:src/server/catalog-cyberia}.
11
+ * {@link module:src/projects/cyberia/catalog-cyberia.js}.
12
12
  *
13
- * @module src/server/catalog.js
13
+ * @module src/server/build/catalog.js
14
14
  * @namespace Catalog
15
15
  */
16
16
 
@@ -18,7 +18,13 @@ import fs from 'fs-extra';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import * as path from 'node:path';
20
20
 
21
- /** Empty product catalog returned for deploy ids without a dedicated module. */
21
+ /**
22
+ * Empty product catalog returned for deploy ids without a dedicated module.
23
+ *
24
+ * The `package*` fields are the deploy's package manifest contract, read by
25
+ * {@link module:src/server/build/package.js}: a deploy id with no catalog gets the engine
26
+ * manifest under its own identity, and one with a catalog adds exactly what it declares here.
27
+ */
22
28
  const EMPTY_CATALOG = {
23
29
  sourceMoves: [],
24
30
  privateConfPaths: [],
@@ -28,6 +34,10 @@ const EMPTY_CATALOG = {
28
34
  copies: [],
29
35
  keywords: [],
30
36
  description: '',
37
+ packageName: '',
38
+ packageBin: {},
39
+ packageDependencies: {},
40
+ packageScripts: {},
31
41
  };
32
42
 
33
43
  /**
@@ -44,7 +54,7 @@ const loadDeployCatalog = async (deployId) => {
44
54
  const suffix = (deployId ?? '').split('dd-')[1];
45
55
  if (!suffix) return EMPTY_CATALOG;
46
56
  if (fs.existsSync(`./src/projects/${suffix}/catalog-${suffix}.js`)) {
47
- const mod = await import(`../projects/${suffix}/catalog-${suffix}.js`);
57
+ const mod = await import(`../../projects/${suffix}/catalog-${suffix}.js`);
48
58
  return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
49
59
  }
50
60
  return EMPTY_CATALOG;
@@ -63,7 +73,7 @@ const loadProductCatalogs = async () => {
63
73
  const catalogs = [];
64
74
  for (const file of await fs.readdir('./src/projects')) {
65
75
  if (file === 'underpost') continue;
66
- const mod = await import(`../projects/${file}/catalog-${file}.js`);
76
+ const mod = await import(`../../projects/${file}/catalog-${file}.js`);
67
77
  if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
68
78
  }
69
79
  return catalogs;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The coverage HTML report as a build artifact: where a run leaves it, where an assembled
3
+ * deploy artifact carries it, and what is published when nothing was carried.
4
+ *
5
+ * The report is produced by the test stage (`npm run test:coverage`) and bundled into the
6
+ * deploy artifact at {@link COVERAGE_BUNDLE_DIRECTORY}. A workload container only ever
7
+ * copies it: running a test runner inside a pod costs minutes of the build phase, needs the
8
+ * dev dependency tree, and — because a suite's expected non-zero exits latch the runtime
9
+ * status contract — publishes a false `error` to the deployment monitor.
10
+ *
11
+ * @module src/server/build/coverage.js
12
+ * @namespace UnderpostCoverage
13
+ */
14
+
15
+ import fs from 'fs-extra';
16
+ import { UNDERPOST_TESTING } from './testing.js';
17
+
18
+ /**
19
+ * @constant COVERAGE_BUNDLE_DIRECTORY
20
+ * @description Path, relative to a source tree root, where an assembled deploy artifact
21
+ * carries the report. Deliberately not under `coverage/`, which every engine `.gitignore`
22
+ * excludes, so the bundled artifact is publishable source rather than a local run's output.
23
+ * @memberof UnderpostCoverage
24
+ */
25
+ const COVERAGE_BUNDLE_DIRECTORY = 'docs/coverage';
26
+
27
+ const normalizeRoot = (root) => `${root ?? ''}`.replace(/\/+$/, '');
28
+
29
+ /**
30
+ * @method coverageReportCandidates
31
+ * @description Every place a coverage HTML report can live in a source tree, most local
32
+ * first: the reporters a run writes, then the bundled artifact a deploy artifact carries.
33
+ * @param {string} root - Source tree root.
34
+ * @returns {string[]} Candidate report directories.
35
+ * @memberof UnderpostCoverage
36
+ */
37
+ const coverageReportCandidates = (root) => {
38
+ const base = normalizeRoot(root);
39
+ if (!base) return [];
40
+ const coverage = `${base}/${UNDERPOST_TESTING.coverageDirectory}`;
41
+ return [`${coverage}/html`, `${coverage}/lcov-report`, coverage, `${base}/${COVERAGE_BUNDLE_DIRECTORY}`];
42
+ };
43
+
44
+ /**
45
+ * @method resolveCoverageReportPath
46
+ * @description First candidate that actually carries an HTML index. A directory holding
47
+ * only `lcov.info` is not a report and is not published.
48
+ * @param {string} root - Source tree root.
49
+ * @returns {string|undefined} Report directory, or undefined when none is available.
50
+ * @memberof UnderpostCoverage
51
+ */
52
+ const resolveCoverageReportPath = (root) =>
53
+ coverageReportCandidates(root).find((candidate) => fs.existsSync(`${candidate}/index.html`));
54
+
55
+ /**
56
+ * @method bundleCoverageReport
57
+ * @description Copies the report of `fromRoot` into `toRoot`'s bundle directory, so the
58
+ * assembled artifact carries it into every container started from that source.
59
+ * @param {string} fromRoot - Tree the report was generated in.
60
+ * @param {string} toRoot - Assembled artifact root.
61
+ * @returns {{bundled: boolean, from?: string, to: string}} What was bundled, and where.
62
+ * @memberof UnderpostCoverage
63
+ */
64
+ const bundleCoverageReport = (fromRoot, toRoot) => {
65
+ const to = `${normalizeRoot(toRoot)}/${COVERAGE_BUNDLE_DIRECTORY}`;
66
+ const from = resolveCoverageReportPath(fromRoot);
67
+ if (!from) return { bundled: false, to };
68
+ fs.emptyDirSync(to);
69
+ fs.copySync(from, to);
70
+ return { bundled: true, from, to };
71
+ };
72
+
73
+ /**
74
+ * @method coverageUnavailablePage
75
+ * @description The page served where a report was expected but none was bundled. A static
76
+ * document, never an invitation to generate one at runtime.
77
+ * @param {object} [options]
78
+ * @param {string} [options.command] - Command that produces the report.
79
+ * @returns {string} HTML document.
80
+ * @memberof UnderpostCoverage
81
+ */
82
+ const coverageUnavailablePage = ({ command = 'npm run test:coverage' } = {}) => `<!doctype html>
83
+ <html lang="en">
84
+ <head>
85
+ <meta charset="utf-8" />
86
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
87
+ <meta name="robots" content="noindex" />
88
+ <title>Coverage report unavailable</title>
89
+ <style>
90
+ :root { color-scheme: light dark; }
91
+ body { margin: 0; display: grid; place-items: center; min-height: 100vh;
92
+ font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
93
+ main { max-width: 34rem; padding: 2rem; }
94
+ h1 { font-size: 1.25rem; margin: 0 0 .75rem; }
95
+ p { margin: 0 0 .75rem; opacity: .8; }
96
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <main>
101
+ <h1>Coverage report unavailable</h1>
102
+ <p>This build carried no coverage report. The report is generated by the test stage and
103
+ bundled into the deploy artifact under <code>${COVERAGE_BUNDLE_DIRECTORY}</code>.</p>
104
+ <p>Run <code>${command}</code> in the build environment, then assemble and publish the
105
+ artifact again.</p>
106
+ </main>
107
+ </body>
108
+ </html>
109
+ `;
110
+
111
+ export {
112
+ COVERAGE_BUNDLE_DIRECTORY,
113
+ bundleCoverageReport,
114
+ coverageReportCandidates,
115
+ coverageUnavailablePage,
116
+ resolveCoverageReportPath,
117
+ };
@@ -0,0 +1,402 @@
1
+ /**
2
+ * This module is pure with respect to the environment: it classifies, resolves and
3
+ * decides, but never executes. Enforcement belongs to `src/server/runtime/process.js`,
4
+ * which owns the subprocess boundary.
5
+ *
6
+ * @module src/server/build/execution.js
7
+ * @namespace UnderpostExecution
8
+ */
9
+
10
+ import { execFileSync } from 'node:child_process';
11
+ import nodePath from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const packageRoot = nodePath.resolve(nodePath.dirname(fileURLToPath(import.meta.url)), '../../..');
15
+
16
+ /**
17
+ * @constant EXECUTION_PROFILE_ENV_KEY
18
+ * @description Environment key the active profile rides on into child processes. Reading it
19
+ * back is what makes a nested `underpost` invocation stay inside its parent's profile.
20
+ * @memberof UnderpostExecution
21
+ */
22
+ const EXECUTION_PROFILE_ENV_KEY = 'UNDERPOST_EXECUTION_PROFILE';
23
+
24
+ /**
25
+ * @constant CAPABILITIES
26
+ * @description The side-effect classes a command can exercise, ordered least to most
27
+ * privileged. The order is load-bearing: a compound command takes the capability of its
28
+ * most privileged segment, so `kubectl create … | kubectl apply -f -` is a cluster write
29
+ * rather than a cluster read.
30
+ * @memberof UnderpostExecution
31
+ */
32
+ const CAPABILITIES = ['fs', 'git', 'net', 'cluster:read', 'host:write', 'cluster:write'];
33
+
34
+ const capabilityRank = (capability) => CAPABILITIES.indexOf(capability);
35
+
36
+ /**
37
+ * @constant EXECUTION_PROFILES
38
+ * @description The profile table. `permits` is the closed set of capabilities a run may
39
+ * exercise; everything else is elided.
40
+ *
41
+ * `reads: 'neutral'` is what lets a denied read stay invisible to its caller: a blocked
42
+ * `cluster:read` returns the empty result that 200+ `silentOnError` call sites already
43
+ * treat as "not present", so no caller needs to learn a new failure mode.
44
+ * @memberof UnderpostExecution
45
+ */
46
+ const EXECUTION_PROFILES = {
47
+ // Full access. The default, and the only profile that may mutate a cluster or a host.
48
+ LIVE_CLUSTER: {
49
+ name: 'LIVE_CLUSTER',
50
+ permits: [...CAPABILITIES],
51
+ reads: 'live',
52
+ description: 'Full access: cluster and host mutation permitted.',
53
+ },
54
+ // Manifest and artifact generation. Filesystem and git are build outputs, not
55
+ // environment mutation, so they stay permitted — a build that regenerates manifests
56
+ // and commits them is doing its job.
57
+ HERMETIC_BUILD: {
58
+ name: 'HERMETIC_BUILD',
59
+ permits: ['fs', 'git'],
60
+ reads: 'neutral',
61
+ description: 'Build outputs only: no cluster, host or network side effects.',
62
+ },
63
+ // Nothing executes. Every effect is reported as the intent it would have carried out.
64
+ OFFLINE_DRY_RUN: {
65
+ name: 'OFFLINE_DRY_RUN',
66
+ permits: [],
67
+ reads: 'neutral',
68
+ description: 'Nothing executes; every command is reported as intent.',
69
+ },
70
+ };
71
+
72
+ const DEFAULT_PROFILE = 'LIVE_CLUSTER';
73
+
74
+ /**
75
+ * @constant PROFILE_ALIAS_FLAGS
76
+ * @description Legacy bypass flags mapped onto the profile that subsumes them, so an
77
+ * operator who already knows the old flag lands in the right profile without learning a
78
+ * new one. The flags stay on the CLI surface — they are published entrypoints.
79
+ *
80
+ * Each alias is guarded by `when`, because a bypass flag is only equivalent to a profile
81
+ * in the mode it was written for. `--disable-update-underpost-config` means "skip one
82
+ * projection step" during a live deploy, but means "this run cannot reach a cluster at
83
+ * all" during a manifest build; aliasing it unconditionally would silently turn a real
84
+ * deploy into a no-op. Flags that are step toggles in every mode are deliberately absent:
85
+ * they keep their own guards, which the profile gate sits underneath rather than replaces.
86
+ * @memberof UnderpostExecution
87
+ */
88
+ const PROFILE_ALIAS_FLAGS = {
89
+ disableUpdateUnderpostConfig: {
90
+ profile: 'HERMETIC_BUILD',
91
+ when: (options) => options?.buildManifest === true,
92
+ },
93
+ };
94
+
95
+ // Verbs that only read cluster state. Everything else kubectl accepts is treated as a
96
+ // write: an unrecognised verb must not be assumed harmless.
97
+ const KUBECTL_READ_VERBS = new Set([
98
+ 'get',
99
+ 'describe',
100
+ 'logs',
101
+ 'top',
102
+ 'explain',
103
+ 'api-resources',
104
+ 'api-versions',
105
+ 'version',
106
+ 'cluster-info',
107
+ 'config',
108
+ 'auth',
109
+ 'diff',
110
+ ]);
111
+
112
+ // kubectl flags that consume the token after them, which would otherwise be mistaken
113
+ // for the verb (`kubectl -n default get pods`).
114
+ const KUBECTL_VALUE_FLAGS = new Set(['-n', '--namespace', '--context', '--kubeconfig', '--server', '--user']);
115
+
116
+ const HOST_WRITE_BINARIES = new Set([
117
+ 'systemctl',
118
+ 'journalctl',
119
+ 'dnf',
120
+ 'yum',
121
+ 'apt',
122
+ 'apt-get',
123
+ 'rpm',
124
+ 'firewall-cmd',
125
+ 'setenforce',
126
+ 'semanage',
127
+ 'restorecon',
128
+ 'chcon',
129
+ 'mount',
130
+ 'umount',
131
+ 'mkfs',
132
+ 'parted',
133
+ 'lvcreate',
134
+ 'modprobe',
135
+ 'sysctl',
136
+ 'useradd',
137
+ 'groupadd',
138
+ 'usermod',
139
+ 'lxc',
140
+ 'lxd',
141
+ 'virsh',
142
+ 'virt-install',
143
+ 'docker',
144
+ 'podman',
145
+ 'crictl',
146
+ 'kind',
147
+ 'kubeadm',
148
+ 'k3s',
149
+ 'wg',
150
+ 'wg-quick',
151
+ 'ip',
152
+ 'iptables',
153
+ 'nft',
154
+ 'haproxy',
155
+ ]);
156
+
157
+ // `gh` reaches a remote service and writes to it (`gh secret set`, `gh release create`), so it
158
+ // is a network effect rather than local filesystem work — an unclassified binary would default
159
+ // to `fs` and run under HERMETIC_BUILD.
160
+ const NET_BINARIES = new Set(['curl', 'wget', 'ssh', 'scp', 'sftp', 'rsync', 'nc', 'ping', 'dig', 'nslookup', 'gh']);
161
+
162
+ const CLUSTER_BINARIES = new Set(['kubectl', 'helm']);
163
+
164
+ // Binaries that re-enter the engine. They are classified as local process work because
165
+ // the child resolves its own profile from the environment — gating them here would stop
166
+ // a hermetic build from reaching the very stages it exists to run.
167
+ const NESTED_CLI_BINARIES = new Set(['node', 'npm', 'npx', 'underpost']);
168
+
169
+ const stripSudo = (tokens) => {
170
+ if (tokens[0] !== 'sudo') return { tokens, escalated: false };
171
+ let index = 1;
172
+ // `sudo -u dd -E cmd` — step over sudo's own flags and their values.
173
+ while (index < tokens.length && tokens[index].startsWith('-')) {
174
+ const consumesValue = tokens[index] === '-u' || tokens[index] === '--user' || tokens[index] === '-g';
175
+ index += consumesValue ? 2 : 1;
176
+ }
177
+ return { tokens: tokens.slice(index), escalated: true };
178
+ };
179
+
180
+ const kubectlVerb = (tokens) => {
181
+ for (let index = 1; index < tokens.length; index++) {
182
+ const token = tokens[index];
183
+ if (KUBECTL_VALUE_FLAGS.has(token)) {
184
+ index++;
185
+ continue;
186
+ }
187
+ if (token.startsWith('-')) continue;
188
+ return token;
189
+ }
190
+ return '';
191
+ };
192
+
193
+ const classifySegment = (segment) => {
194
+ const tokens = `${segment ?? ''}`.trim().split(/\s+/).filter(Boolean);
195
+ if (tokens.length === 0) return 'fs';
196
+
197
+ const { tokens: bare, escalated } = stripSudo(tokens);
198
+ if (bare.length === 0) return escalated ? 'host:write' : 'fs';
199
+
200
+ // Leading `VAR=value` assignments are not the command.
201
+ let start = 0;
202
+ while (start < bare.length && /^[A-Z_][A-Z0-9_]*=/i.test(bare[start])) start++;
203
+ const binary = nodePath.basename(bare[start] ?? '');
204
+ const rest = bare.slice(start);
205
+
206
+ if (CLUSTER_BINARIES.has(binary)) {
207
+ if (binary === 'helm') {
208
+ const verb = rest[1] ?? '';
209
+ return verb === 'list' || verb === 'get' || verb === 'status' ? 'cluster:read' : 'cluster:write';
210
+ }
211
+ return KUBECTL_READ_VERBS.has(kubectlVerb(rest)) ? 'cluster:read' : 'cluster:write';
212
+ }
213
+ if (NESTED_CLI_BINARIES.has(binary)) return 'fs';
214
+ if (binary === 'git') return 'git';
215
+ if (HOST_WRITE_BINARIES.has(binary)) return 'host:write';
216
+ if (NET_BINARIES.has(binary)) return 'net';
217
+
218
+ // An escalated command we do not otherwise recognise is a host mutation by definition.
219
+ return escalated ? 'host:write' : 'fs';
220
+ };
221
+
222
+ /**
223
+ * Classifies the side effect a command exercises.
224
+ *
225
+ * Compound commands resolve to their most privileged segment, so a pipeline cannot
226
+ * launder a write through a read.
227
+ * @param {string} cmd - The command string.
228
+ * @returns {string} One of {@link CAPABILITIES}.
229
+ * @memberof UnderpostExecution
230
+ */
231
+ const classifyCommand = (cmd = '') => {
232
+ const text = `${cmd ?? ''}`;
233
+ if (!text.trim()) return 'fs';
234
+ // Heredoc bodies are data, not commands: `kubectl apply -f - <<'EOF' … EOF` must be
235
+ // classified on its first line alone, or YAML content would be parsed as a pipeline.
236
+ const [head] = text.split(/<<-?\s*'?[A-Za-z_]+'?/);
237
+ const segments = `${head}`.split(/&&|\|\||[;|]/);
238
+ let capability = 'fs';
239
+ for (const segment of segments) {
240
+ const segmentCapability = classifySegment(segment);
241
+ if (capabilityRank(segmentCapability) > capabilityRank(capability)) capability = segmentCapability;
242
+ }
243
+ return capability;
244
+ };
245
+
246
+ /**
247
+ * Resolves a profile by name, falling back to the default rather than throwing on an
248
+ * unknown value — an unreadable environment variable must not brick every command.
249
+ * @param {string} [name] - Profile name.
250
+ * @returns {object} The profile record.
251
+ * @memberof UnderpostExecution
252
+ */
253
+ const executionProfileFactory = (name = '') => {
254
+ const key = `${name ?? ''}`.trim().toUpperCase().replace(/-/g, '_');
255
+ return EXECUTION_PROFILES[key] ?? EXECUTION_PROFILES[DEFAULT_PROFILE];
256
+ };
257
+
258
+ /**
259
+ * Maps a CLI options object onto the profile it selects.
260
+ *
261
+ * An explicit `--profile` wins. Otherwise a legacy bypass flag selects the profile that
262
+ * subsumes it, but only in the mode where the two actually mean the same thing.
263
+ * @param {object} [options] - Commander options.
264
+ * @returns {string} Profile name.
265
+ * @memberof UnderpostExecution
266
+ */
267
+ const profileFromOptionsFactory = (options = {}) => {
268
+ const explicit = `${options?.profile ?? ''}`.trim();
269
+ if (explicit) return executionProfileFactory(explicit).name;
270
+ for (const [flag, alias] of Object.entries(PROFILE_ALIAS_FLAGS))
271
+ if (options?.[flag] === true && alias.when(options)) return alias.profile;
272
+ return '';
273
+ };
274
+
275
+ /**
276
+ * The profile in effect for this process.
277
+ * @returns {object} The active profile record.
278
+ * @memberof UnderpostExecution
279
+ */
280
+ const activeExecutionProfile = () => executionProfileFactory(process.env[EXECUTION_PROFILE_ENV_KEY]);
281
+
282
+ /**
283
+ * Installs a profile as the process-wide default.
284
+ *
285
+ * Writing the environment variable is the propagation mechanism: children inherit the
286
+ * environment, so a nested `underpost` invocation resolves the same profile with no
287
+ * command-string threading.
288
+ * @param {string} name - Profile name.
289
+ * @returns {object} The profile now in effect.
290
+ * @memberof UnderpostExecution
291
+ */
292
+ const setExecutionProfile = (name) => {
293
+ const profile = executionProfileFactory(name);
294
+ process.env[EXECUTION_PROFILE_ENV_KEY] = profile.name;
295
+ return profile;
296
+ };
297
+
298
+ /**
299
+ * Runs `fn` under `name`, restoring the previous profile afterwards.
300
+ *
301
+ * Restores through a `finally` and awaits a thenable result, so a throwing or async
302
+ * stage cannot leak a hermetic profile into the rest of the process.
303
+ * @param {string} name - Profile to run under.
304
+ * @param {Function} fn - Work to run.
305
+ * @returns {*} Whatever `fn` returns.
306
+ * @memberof UnderpostExecution
307
+ */
308
+ const withExecutionProfile = (name, fn) => {
309
+ const previous = process.env[EXECUTION_PROFILE_ENV_KEY];
310
+ const restore = () => {
311
+ if (previous === undefined) delete process.env[EXECUTION_PROFILE_ENV_KEY];
312
+ else process.env[EXECUTION_PROFILE_ENV_KEY] = previous;
313
+ };
314
+ setExecutionProfile(name);
315
+ let result;
316
+ try {
317
+ result = fn();
318
+ } catch (error) {
319
+ restore();
320
+ throw error;
321
+ }
322
+ if (result && typeof result.then === 'function') return result.finally(restore);
323
+ restore();
324
+ return result;
325
+ };
326
+
327
+ /**
328
+ * Decides whether a command may run under the active profile.
329
+ *
330
+ * Returns a decision rather than acting on one; `shellExec` owns the consequence.
331
+ * @param {string} cmd - The command string.
332
+ * @param {object} [context] - Optional overrides.
333
+ * @param {string} [context.profile] - Profile name, defaulting to the active one.
334
+ * @returns {{permitted: boolean, capability: string, profile: string, neutral: boolean}} Decision.
335
+ * @memberof UnderpostExecution
336
+ */
337
+ const executionDecisionFactory = (cmd, { profile } = {}) => {
338
+ const record = profile ? executionProfileFactory(profile) : activeExecutionProfile();
339
+ const capability = classifyCommand(cmd);
340
+ return {
341
+ permitted: record.permits.includes(capability),
342
+ capability,
343
+ profile: record.name,
344
+ neutral: record.reads === 'neutral',
345
+ };
346
+ };
347
+
348
+ const localUnderpostCli = `node ${packageRoot}/bin/index.js`;
349
+ let underpostCliCache;
350
+
351
+ /**
352
+ * Resolves how to invoke a CLI from a subprocess.
353
+ *
354
+ * Two different questions hide behind "run underpost", and conflating them runs the
355
+ * wrong codebase:
356
+ *
357
+ * - `local: true` — re-enter the package this process is running from, resolved through
358
+ * `import.meta.url`. From a checkout that is the checkout; from a global install it is
359
+ * that same global install, so a deployed node is unaffected. Every multi-stage runner
360
+ * wants this: a stage that re-enters `underpost` must run the code its parent runs, or
361
+ * the two disagree about layout. A stale global install reading credential seeds from a
362
+ * directory this version moved is what that skew looks like in practice.
363
+ * - default — whichever CLI this machine is meant to use: the globally linked binary
364
+ * when one exists, otherwise this checkout by absolute path, so a build box without a
365
+ * global install still works and callers that `cd` elsewhere keep resolving the same
366
+ * CLI.
367
+ *
368
+ * This replaces the `options.dev ? 'node bin' : 'underpost'` branch copied across 14 call
369
+ * sites, none of which agreed on what happened when neither resolved.
370
+ * @param {string} [name] - CLI to resolve.
371
+ * @param {object} [options] - Resolution options.
372
+ * @param {boolean} [options.local] - Re-enter this checkout instead of a global install.
373
+ * @returns {string} Command prefix to invoke it with.
374
+ * @memberof UnderpostExecution
375
+ */
376
+ const cli = (name = 'underpost', { local = false } = {}) => {
377
+ if (name !== 'underpost') return name;
378
+ if (local) return localUnderpostCli;
379
+ if (underpostCliCache) return underpostCliCache;
380
+ try {
381
+ execFileSync('sh', ['-c', 'command -v underpost'], { stdio: 'ignore' });
382
+ underpostCliCache = 'underpost';
383
+ } catch {
384
+ underpostCliCache = localUnderpostCli;
385
+ }
386
+ return underpostCliCache;
387
+ };
388
+
389
+ export {
390
+ activeExecutionProfile,
391
+ CAPABILITIES,
392
+ classifyCommand,
393
+ cli,
394
+ EXECUTION_PROFILE_ENV_KEY,
395
+ EXECUTION_PROFILES,
396
+ executionDecisionFactory,
397
+ executionProfileFactory,
398
+ PROFILE_ALIAS_FLAGS,
399
+ profileFromOptionsFactory,
400
+ setExecutionProfile,
401
+ withExecutionProfile,
402
+ };