@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
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Ownership of configuration keys, and the projections that carry a value out of its owner.
3
+ *
4
+ * The platform's domains are `app`, `host`, `secret` and `state`; this module does not add a
5
+ * fifth. What it adds is the missing half of the host domain's model: its durable source is one
6
+ * env file, but the values in it answer to different concerns with different consumers and very
7
+ * different blast radii. Provisioning credentials, registry credentials and the cron jobs' own
8
+ * connection settings were reaching every workload that read that file, because "the host
9
+ * environment" was the only granularity the system had.
10
+ *
11
+ * Ownership and visibility are separate questions, and only ownership is answered by pattern.
12
+ *
13
+ * {@link CONFIG_OWNERSHIP} says where a key lives. A family pattern is right for that: a new
14
+ * `MAAS_` key is a provisioning key wherever it came from.
15
+ *
16
+ * {@link SCOPE_ENTITLEMENTS} says what a workload may read, and is an exact key list. A pattern
17
+ * there would mean the next credential added to a family silently inherits that family's audience
18
+ * — `GITHUB_NEW_SENSITIVE_TOKEN` becoming cron-visible because `GITHUB_` once was. So the policy
19
+ * is readable from the table alone: what cron can see is what the table names, and nothing a
20
+ * naming convention implies.
21
+ *
22
+ * Classification is closed at both ends: {@link classifyConfigKeys} reports anything it cannot
23
+ * place instead of defaulting it into a scope, and an entitlement the table does not name is
24
+ * refused. An unknown key is a design question, not a value to guess at.
25
+ *
26
+ * @module src/server/runtime/config-scope.js
27
+ * @namespace ConfigScope
28
+ */
29
+
30
+ /**
31
+ * @constant CONFIG_SCOPES
32
+ * @description The scopes a configuration key can belong to, and what consumes each.
33
+ * @memberof ConfigScope
34
+ */
35
+ const CONFIG_SCOPES = Object.freeze({
36
+ runtime: 'Process settings a manifest states about a workload; never carried by a projection.',
37
+ host: 'Node-level operation: the edge, the observability stack, cluster mail and deployment targets.',
38
+ cron: 'The scheduled jobs: their APIs, their DNS providers and the connection they reach the fleet on.',
39
+ baremetal: 'Machine provisioning: MAAS, its database, and the network a commissioned node boots on.',
40
+ publishing: 'Release identities: the registries and package indexes a publish authenticates to.',
41
+ app: "A deployment's own runtime: its database, its sessions, its integrations.",
42
+ });
43
+
44
+ /**
45
+ * @constant CONFIG_OWNERSHIP
46
+ * @description Key patterns in resolution order, each naming its owner and the scopes it is
47
+ * projected into. First match wins, so a specific rule is placed above the family it narrows.
48
+ * @memberof ConfigScope
49
+ */
50
+ const CONFIG_OWNERSHIP = Object.freeze([
51
+ { match: /^(NODE_ENV|NODE_OPTIONS|DOTENV_CONFIG_QUIET|ENABLE_FILE_LOGS|TIME_ZONE)$/, owner: 'runtime' },
52
+
53
+ { match: /^(VULTR_|DDNS_|HTTP_PLAIN_IP_URL$)/, owner: 'cron' },
54
+
55
+ { match: /^(MAAS_|DB_PG_MAAS_)/, owner: 'baremetal' },
56
+ { match: /^(TFTP_ROOT|NETMASK|NFS_EXPORT_PATH)$/, owner: 'baremetal' },
57
+
58
+ { match: /^(NPM_|DOCKER_HUB_|POSTMAN_)/, owner: 'publishing' },
59
+
60
+ { match: /^(GITHUB_|DEFAULT_SSH_|FORWARD_PROXY_|DEFAULT_DEPLOY_)/, owner: 'host' },
61
+ { match: /^(GF_SECURITY_|CLUSTER_MAILER_)/, owner: 'host' },
62
+ { match: /^UNDERPOST_EVENT_TOKEN$/, owner: 'host' },
63
+
64
+ { match: /^(DB_|MARIADB_|VALKEY_)/, owner: 'app' },
65
+ { match: /^(JWT_SECRET|ACCESS_EXPIRE_MINUTES|REFRESH_EXPIRE_MINUTES|PORT|DEPLOY_ID)$/, owner: 'app' },
66
+ { match: /^(DEFAULT_ADMIN_|CLOUDINARY_|WP_)/, owner: 'app' },
67
+ { match: /^(GEMINI_API_KEY|NVIDIA_API_KEY)$/, owner: 'app' },
68
+ ]);
69
+
70
+ /**
71
+ * @constant SCOPE_ENTITLEMENTS
72
+ * @description Exactly what each workload scope may read, by key name.
73
+ *
74
+ * Every entry is a key some code in this repository reads. A value the workload does not consume
75
+ * is not listed, because projecting one is not merely waste: `*_SSH_KEY_PATH` named a path that
76
+ * only exists on a host, and handing it to a pod made the resolver prefer it over the Secret
77
+ * mount holding the actual key.
78
+ * @memberof ConfigScope
79
+ */
80
+ const SCOPE_ENTITLEMENTS = Object.freeze({
81
+ cron: Object.freeze([
82
+ // vultr: the API it meters against, and the edge it toggles egress on.
83
+ 'VULTR_API_KEY',
84
+ 'VULTR_INSTANCE_ID',
85
+ 'VULTR_BANDWIDTH_THRESHOLD',
86
+ 'VULTR_VPS_IP',
87
+ 'VULTR_SSH_USER',
88
+ 'VULTR_SSH_PORT',
89
+ 'FORWARD_PROXY_API_KEY',
90
+ 'FORWARD_PROXY_HOST',
91
+ 'FORWARD_PROXY_PORT',
92
+ // backup and vultr: the connection out of the pod. The key itself is a Secret volume.
93
+ 'DEFAULT_SSH_USER',
94
+ 'DEFAULT_SSH_HOST',
95
+ 'DEFAULT_SSH_PORT',
96
+ // backup: the identity its git operations authenticate as.
97
+ 'GITHUB_TOKEN',
98
+ 'GITHUB_USERNAME',
99
+ // dns: the provider it updates and the host it probes.
100
+ 'DDNS_HOST',
101
+ 'DDNS_PROVIDER',
102
+ 'DDNS_API_KEY',
103
+ 'DDNS_USER',
104
+ 'HTTP_PLAIN_IP_URL',
105
+ 'DEFAULT_DEPLOY_HOST',
106
+ ]),
107
+ });
108
+
109
+ /**
110
+ * The owning scope of a key, and the scopes it reaches.
111
+ * @param {string} key - Environment variable name.
112
+ * @returns {{owner: string, projectedTo: string[]}|null} Ownership, or null when unclassified.
113
+ * @memberof ConfigScope
114
+ */
115
+ const configOwnershipFactory = (key) => {
116
+ const name = `${key || ''}`;
117
+ const rule = CONFIG_OWNERSHIP.find((entry) => entry.match.test(name));
118
+ if (!rule) return null;
119
+ const projectedTo = Object.entries(SCOPE_ENTITLEMENTS)
120
+ .filter(([scope, keys]) => scope !== rule.owner && keys.includes(name))
121
+ .map(([scope]) => scope);
122
+ return { owner: rule.owner, projectedTo };
123
+ };
124
+
125
+ /**
126
+ * Whether a scope receives a key.
127
+ *
128
+ * A scope with an entitlement list reads that list and nothing else — owning a key is not by
129
+ * itself permission to ship it to a workload, which is what keeps a new member of an owned family
130
+ * from arriving in a pod unreviewed. Scopes without a list are host-side and read what they own.
131
+ *
132
+ * `runtime` is never a pass-through: process settings are what a manifest states about the
133
+ * workload it defines, not credentials a projection carries.
134
+ * @param {string} key - Environment variable name.
135
+ * @param {string} scope - Scope name.
136
+ * @returns {boolean} True when the scope may see the key.
137
+ * @memberof ConfigScope
138
+ */
139
+ const scopeReceivesKey = (key, scope) => {
140
+ const ownership = configOwnershipFactory(key);
141
+ if (!ownership) return false;
142
+ const entitlement = SCOPE_ENTITLEMENTS[scope];
143
+ if (entitlement) return entitlement.includes(`${key || ''}`);
144
+ return ownership.owner === scope;
145
+ };
146
+
147
+ /**
148
+ * Splits an environment into the scopes that receive each key.
149
+ *
150
+ * `unclassified` is the fail-closed half: a key no rule places is reported rather than dropped
151
+ * into a default scope, because the two wrong answers — hiding it from a consumer that needs it,
152
+ * or handing a credential to a workload that does not — are both silent.
153
+ * @param {Object<string, string>} values - Parsed environment.
154
+ * @returns {{scopes: Object<string, string[]>, unclassified: string[]}} Key names per scope.
155
+ * @memberof ConfigScope
156
+ */
157
+ const classifyConfigKeys = (values = {}) => {
158
+ const scopes = Object.fromEntries(Object.keys(CONFIG_SCOPES).map((scope) => [scope, []]));
159
+ const unclassified = [];
160
+
161
+ for (const key of Object.keys(values)) {
162
+ const ownership = configOwnershipFactory(key);
163
+ if (!ownership) {
164
+ unclassified.push(key);
165
+ continue;
166
+ }
167
+ scopes[ownership.owner].push(key);
168
+ }
169
+
170
+ return { scopes, unclassified };
171
+ };
172
+
173
+ /**
174
+ * The subset of an environment a scope is entitled to.
175
+ * @param {Object<string, string>} values - Parsed environment.
176
+ * @param {string} scope - Scope name.
177
+ * @returns {Object<string, string>} Entitled key/value pairs.
178
+ * @memberof ConfigScope
179
+ */
180
+ const scopeValuesFactory = (values = {}, scope = '') =>
181
+ Object.fromEntries(Object.entries(values).filter(([key]) => scopeReceivesKey(key, scope)));
182
+
183
+ /**
184
+ * Renders a scope rejection without disclosing the value that caused it.
185
+ * @param {object} params - Rejection context.
186
+ * @param {string} params.domain - Domain reporting the rejection.
187
+ * @param {string} [params.deployId] - Deploy the source belongs to.
188
+ * @param {string} [params.env] - Environment selector.
189
+ * @param {string} params.key - Key that was rejected.
190
+ * @param {string} params.reason - Why it was rejected.
191
+ * @returns {string} Diagnostic line, values omitted by construction.
192
+ * @memberof ConfigScope
193
+ */
194
+ const configRejectionFactory = ({ domain, deployId = '', env = '', key, reason }) =>
195
+ [
196
+ 'configuration source rejected:',
197
+ `domain=${domain}`,
198
+ deployId ? `deploy=${deployId}` : '',
199
+ env ? `env=${env}` : '',
200
+ `key=${key}`,
201
+ `reason=${reason}`,
202
+ ]
203
+ .filter(Boolean)
204
+ .join(' ');
205
+
206
+ export {
207
+ CONFIG_OWNERSHIP,
208
+ CONFIG_SCOPES,
209
+ SCOPE_ENTITLEMENTS,
210
+ classifyConfigKeys,
211
+ configOwnershipFactory,
212
+ configRejectionFactory,
213
+ scopeReceivesKey,
214
+ scopeValuesFactory,
215
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Environment and global installation path resolution.
3
+ * @module src/server/runtime/environment.js
4
+ * @namespace ServerEnvironment
5
+ */
6
+ 'use strict';
7
+
8
+ import dotenv from 'dotenv';
9
+ import fs from 'fs-extra';
10
+ import { execFileSync } from 'node:child_process';
11
+ import nodePath from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { loggerFactory } from '../ops/logger.js';
14
+
15
+ const logger = loggerFactory(import.meta);
16
+
17
+ /**
18
+ * Node directory backing every hostPath PersistentVolume the deploy flow
19
+ * materializes (`<root>/<pv id>`), including the shared gateway's static tree.
20
+ * Cluster bring-up gives it the shared container label, because the pods that
21
+ * read these documents are unprivileged.
22
+ * @constant {string}
23
+ * @memberof ServerEnvironment
24
+ */
25
+ const HOST_VOLUME_ROOT = '/home/dd/engine/volume';
26
+ const packageRoot = nodePath.resolve(nodePath.dirname(fileURLToPath(import.meta.url)), '../../..');
27
+ const envFileCache = new Map();
28
+ let rootEnvPath;
29
+
30
+ /**
31
+ * Resolves the global npm module directory.
32
+ * @returns {string}
33
+ * @memberof ServerEnvironment
34
+ */
35
+ const getNpmRootPath = () => {
36
+ try {
37
+ return execFileSync('npm', ['root', '-g'], {
38
+ encoding: 'utf8',
39
+ stdio: ['ignore', 'pipe', 'ignore'],
40
+ }).trim();
41
+ } catch {
42
+ return '';
43
+ }
44
+ };
45
+
46
+ /**
47
+ * Resolves the global Underpost installation directory.
48
+ * @returns {string}
49
+ * @memberof ServerEnvironment
50
+ */
51
+ const getUnderpostRootPath = () => {
52
+ const npmRoot = getNpmRootPath();
53
+ return npmRoot ? `${npmRoot}/underpost` : `${packageRoot}/.underpost`;
54
+ };
55
+
56
+ const readEnvFile = (path) => {
57
+ if (envFileCache.has(path)) return envFileCache.get(path);
58
+ let values = {};
59
+ try {
60
+ if (path && fs.existsSync(path)) values = dotenv.parse(fs.readFileSync(path, 'utf8'));
61
+ } catch (error) {
62
+ logger.warn('Ignoring unreadable env file', { target: path, message: error.message });
63
+ }
64
+ envFileCache.set(path, values);
65
+ return values;
66
+ };
67
+
68
+ const environmentValueFactory = (key) => {
69
+ const processValue = `${process.env[key] ?? ''}`.trim();
70
+ if (processValue) return processValue;
71
+
72
+ if (rootEnvPath === undefined) {
73
+ const underpostRoot = getUnderpostRootPath();
74
+ rootEnvPath = underpostRoot ? `${underpostRoot}/.env` : '';
75
+ }
76
+
77
+ for (const path of ['./.env', rootEnvPath]) {
78
+ const value = `${readEnvFile(path)[key] ?? ''}`.trim();
79
+ if (value) return value;
80
+ }
81
+ return '';
82
+ };
83
+
84
+ /**
85
+ * Single source of truth for resolving a deployment environment name from CLI options.
86
+ * Explicit `--env` always wins; `--dev` is the development shorthand; otherwise the
87
+ * caller's `fallback` decides. Deliberately does not read `process.env.NODE_ENV`, so a
88
+ * developer shell that exports it cannot silently retarget a deploy — callers whose
89
+ * contract is ambient (the client build) pass it in as `fallback` themselves.
90
+ * @method deployEnvFactory
91
+ * @param {object} [options] - CLI options carrying the environment selection.
92
+ * @param {string} [options.env] - Explicit environment name.
93
+ * @param {boolean} [options.dev] - Development context shorthand.
94
+ * @param {string} [fallback='production'] - Value when no option selects an environment.
95
+ * @returns {string} `development` | `production` | `test` | custom env name.
96
+ * @memberof ServerEnvironment
97
+ */
98
+ const deployEnvFactory = (options = {}, fallback = 'production') => {
99
+ const explicit = `${options?.env ?? ''}`.trim();
100
+ if (explicit) return explicit;
101
+ if (options?.dev === true) return 'development';
102
+ return fallback;
103
+ };
104
+
105
+ /**
106
+ * Renders environment values as dotenv file contents.
107
+ * @method renderEnv
108
+ * @param {Object<string, *>} envObj - Environment values keyed by variable name.
109
+ * @returns {string} Dotenv text, one `KEY=value` per line.
110
+ * @memberof ServerEnvironment
111
+ */
112
+ const renderEnv = (envObj) =>
113
+ Object.keys(envObj)
114
+ .map((key) => `${key}=${envObj[key]}`)
115
+ .join('\n');
116
+
117
+ /**
118
+ * Writes environment values as a dotenv file.
119
+ * @method writeEnv
120
+ * @param {string} envPath - Destination file path.
121
+ * @param {Object<string, *>} envObj - Environment values keyed by variable name.
122
+ * @returns {void}
123
+ * @memberof ServerEnvironment
124
+ */
125
+ const writeEnv = (envPath, envObj) => fs.writeFileSync(envPath, renderEnv(envObj), 'utf8');
126
+
127
+ /**
128
+ * Whether this process runs inside an OCI container image, by Kubernetes service injection or
129
+ * Docker's marker file.
130
+ *
131
+ * Lives in this module because it is an environment fact two unrelated concerns read: the
132
+ * container runtime state store ({@link UnderpostState.isInsideContainer}, which re-exports it)
133
+ * and the OCI env overlay {@link ServerConfBuilder.deployEnvContentFactory} resolves.
134
+ * @method isOciRuntime
135
+ * @returns {boolean} True when running inside a container.
136
+ * @memberof ServerEnvironment
137
+ */
138
+ const isOciRuntime = () => !!process.env.KUBERNETES_SERVICE_HOST || fs.existsSync('/.dockerenv');
139
+
140
+ export {
141
+ deployEnvFactory,
142
+ environmentValueFactory,
143
+ getNpmRootPath,
144
+ getUnderpostRootPath,
145
+ HOST_VOLUME_ROOT,
146
+ isOciRuntime,
147
+ renderEnv,
148
+ writeEnv,
149
+ };
@@ -17,14 +17,18 @@
17
17
  * `ProcessController.children` so SIGINT/SIGTERM forwarding can reach
18
18
  * them before the parent exits.
19
19
  *
20
- * @module src/server/process.js
20
+ * @module src/server/runtime/process.js
21
21
  * @namespace Process
22
22
  */
23
23
  // https://nodejs.org/api/process
24
24
  import shell from 'shelljs';
25
- import { loggerFactory } from './logger.js';
25
+ import fs from 'fs-extra';
26
+ import nodePath from 'node:path';
27
+ import { loggerFactory, redactSensitiveText } from '../ops/logger.js';
26
28
  import clipboard from 'clipboardy';
27
- import Underpost from '../index.js';
29
+ import Underpost from '../../index.js';
30
+ import { latchRuntimeError } from './runtime-status.js';
31
+ import { executionDecisionFactory } from '../build/execution.js';
28
32
  const logger = loggerFactory(import.meta);
29
33
  /**
30
34
  * Gets the current working directory, replacing backslashes with forward slashes for consistency.
@@ -140,16 +144,26 @@ class ProcessController {
140
144
  }
141
145
  }
142
146
  /**
143
- * `ShellExecError` thrown by `shellExec` when the underlying command
144
- * exits with a non-zero code (the default fail-fast behaviour). Carries
145
- * the exit code, stdout, and stderr for inspection by callers / CI
146
- * pipelines that need structured failure data.
147
+ * Quotes one value as a single shell argument.
148
+ *
149
+ * Single source of that quoting: a composed command reaches `sh -c`, so any
150
+ * value carrying spaces, commas or quotes has to survive one more parse than
151
+ * the caller wrote it for. Single quotes are literal in POSIX shells, and an
152
+ * embedded single quote is closed, escaped and reopened.
153
+ * @memberof Process
154
+ * @param {*} value - Value to pass as one argument.
155
+ * @returns {string} The value, quoted.
156
+ */
157
+ const shellArgumentFactory = (value) => `'${`${value ?? ''}`.replaceAll("'", `'\\''`)}'`;
158
+
159
+ /**
160
+ * Error thrown by `shellExec` on a non-zero exit, with structured process output.
147
161
  */
148
162
  class ShellExecError extends Error {
149
163
  constructor(cmd, code, stdout, stderr) {
150
- super(`shellExec failed (exit=${code}): ${cmd}`);
164
+ super(`shellExec failed (exit=${code}): ${redactSensitiveText(cmd)}`);
151
165
  this.name = 'ShellExecError';
152
- this.cmd = cmd;
166
+ this.cmd = redactSensitiveText(cmd);
153
167
  this.code = code;
154
168
  this.stdout = stdout;
155
169
  this.stderr = stderr;
@@ -180,8 +194,46 @@ class ShellExecError extends Error {
180
194
  * @returns {string|shelljs.ShellString} `ShellString` by default; the stdout string when `stdout: true`.
181
195
  * @throws {ShellExecError} On non-zero exit when `silentOnError` is not set.
182
196
  */
197
+ /**
198
+ * The result a denied read hands back.
199
+ *
200
+ * Shaped as a `ShellString` so an elided command is indistinguishable from one that ran
201
+ * and found nothing — which is the answer the `silentOnError` call sites already handle.
202
+ * @returns {shelljs.ShellString} Empty successful result.
203
+ * @memberof Process
204
+ */
205
+ const neutralShellResult = () => {
206
+ const result = shell.ShellString('');
207
+ result.code = 0;
208
+ result.stdout = '';
209
+ result.stderr = '';
210
+ return result;
211
+ };
212
+
213
+ /**
214
+ * Applies the active execution profile to one command.
215
+ *
216
+ * This is the single gate the profile system rests on: because every external
217
+ * invocation reaches it, an offline stage needs no per-call-site opt-out.
218
+ * @param {string} cmd - The command about to run.
219
+ * @param {Object} options - The `shellExec` options.
220
+ * @returns {null|{elided: true, result: *}} `null` when the command may run.
221
+ * @memberof Process
222
+ */
223
+ const executionGate = (cmd, options) => {
224
+ const decision = executionDecisionFactory(cmd);
225
+ if (decision.permitted) return null;
226
+ logger.info(`[${decision.profile}] elided ${decision.capability}`, redactSensitiveText(cmd));
227
+ return { elided: true, result: options.stdout ? '' : neutralShellResult() };
228
+ };
229
+
183
230
  const shellExec = (cmd, options = {}) => {
184
- if (!options.disableLog) logger.info(`cmd`, cmd);
231
+ const gated = executionGate(cmd, options);
232
+ if (gated) {
233
+ if (options.callback) return options.callback(0, '', '');
234
+ return gated.result;
235
+ }
236
+ if (!options.disableLog) logger.info(`cmd`, redactSensitiveText(cmd));
185
237
 
186
238
  // Whitelist exactly the keys `shelljs.exec` understands. Passing our own
187
239
  // bookkeeping keys through (or a literal `cwd: undefined`) makes shelljs
@@ -189,6 +241,7 @@ const shellExec = (cmd, options = {}) => {
189
241
  const shellOpts = {};
190
242
  if (options.silent !== undefined) shellOpts.silent = options.silent;
191
243
  if (options.async !== undefined) shellOpts.async = options.async;
244
+ if (options.env !== undefined) shellOpts.env = options.env;
192
245
 
193
246
  // Hermetic cwd. shelljs.cd mutates a process-wide global; instead we
194
247
  // snapshot the current cwd here, switch for the duration of this call,
@@ -200,7 +253,7 @@ const shellExec = (cmd, options = {}) => {
200
253
  try {
201
254
  process.chdir(options.cwd);
202
255
  } catch (err) {
203
- if (Underpost.env.isInsideContainer()) Underpost.env.set('container-status', 'error')
256
+ latchRuntimeError();
204
257
  throw new ShellExecError(cmd, -1, '', `chdir(${options.cwd}) failed: ${err.message}`);
205
258
  }
206
259
  }
@@ -214,7 +267,7 @@ const shellExec = (cmd, options = {}) => {
214
267
  const result = shell.exec(cmd, shellOpts);
215
268
 
216
269
  if (!options.silentOnError && result && typeof result.code === 'number' && result.code !== 0) {
217
- if (Underpost.env.isInsideContainer()) Underpost.env.set('container-status', 'error')
270
+ latchRuntimeError();
218
271
  throw new ShellExecError(cmd, result.code, result.stdout || '', result.stderr || '');
219
272
  }
220
273
 
@@ -229,6 +282,58 @@ const shellExec = (cmd, options = {}) => {
229
282
  }
230
283
  }
231
284
  };
285
+ /**
286
+ * Runs a command as a child process and resolves when it exits, without blocking the event loop.
287
+ *
288
+ * Same contract as {@link shellExec}: a non-zero exit throws {@link ShellExecError} unless
289
+ * `silentOnError`, and `stdout` returns the captured output. The difference is that timers and
290
+ * sockets keep running while the child does — which is what lets the in-pod status endpoint
291
+ * answer during a build instead of going silent for its whole duration.
292
+ *
293
+ * @memberof Process
294
+ * @param {string} cmd - The command to run.
295
+ * @param {Object} [options] - Options.
296
+ * @param {boolean} [options.silent] - Suppress child output on the parent stdio.
297
+ * @param {boolean} [options.silentOnError] - Resolve instead of throwing on a non-zero exit.
298
+ * @param {boolean} [options.stdout] - Resolve with the captured stdout rather than the result.
299
+ * @param {boolean} [options.disableLog] - Suppress the command log line.
300
+ * @param {string} [options.cwd] - Directory to run in.
301
+ * @param {NodeJS.ProcessEnv} [options.env] - Environment passed only to the child process.
302
+ * @returns {Promise<*>} Resolves with the result, or the stdout when `stdout` is set.
303
+ */
304
+ const shellExecAsync = (cmd, options = {}) =>
305
+ new Promise((resolve, reject) => {
306
+ const gated = executionGate(cmd, options);
307
+ if (gated) return resolve(options.stdout ? '' : { code: 0, stdout: '', stderr: '' });
308
+ if (!options.disableLog) logger.info(`cmd`, redactSensitiveText(cmd));
309
+ const shellOpts = { async: true };
310
+ if (options.silent !== undefined) shellOpts.silent = options.silent;
311
+ if (options.cwd) shellOpts.cwd = options.cwd;
312
+ if (options.env !== undefined) shellOpts.env = options.env;
313
+ shell.exec(cmd, shellOpts, (code, stdout, stderr) => {
314
+ if (code !== 0 && !options.silentOnError) {
315
+ latchRuntimeError();
316
+ return reject(new ShellExecError(cmd, code, stdout || '', stderr || ''));
317
+ }
318
+ resolve(options.stdout ? stdout : { code, stdout, stderr });
319
+ });
320
+ });
321
+
322
+ /**
323
+ * Blocks the calling thread for `ms` milliseconds.
324
+ *
325
+ * Retry backoff around the synchronous `shellExec` chains (kubectl exec, pod
326
+ * readiness polling) has no async boundary to await on, so the wait has to
327
+ * block. `Atomics.wait` keeps the CPU idle instead of spinning.
328
+ *
329
+ * @memberof Process
330
+ * @param {number} ms - Milliseconds to block for. Non-positive values are a no-op.
331
+ * @returns {void}
332
+ */
333
+ const sleepSync = (ms) => {
334
+ if (!(typeof ms === 'number') || !(ms > 0)) return;
335
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
336
+ };
232
337
  /**
233
338
  * Changes the current working directory using shelljs.
234
339
  *
@@ -258,6 +363,38 @@ const shellCd = (cd, options = { disableLog: false }) => {
258
363
  * @returns {string} The shell command string for the daemon process.
259
364
  */
260
365
  const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
366
+
367
+ /**
368
+ * Installs a root-owned host file at an explicit mode, reporting whether it changed.
369
+ *
370
+ * Staged then installed rather than written in place: the caller may run
371
+ * unprivileged while the destination is root-owned, and `install -m` sets the
372
+ * mode as it copies, so a 0600 file is never briefly readable at the default
373
+ * umask. Returning the changed flag is what lets callers restart a service only
374
+ * when its unit actually differs.
375
+ *
376
+ * @param {object} params
377
+ * @param {string} params.target - Destination path.
378
+ * @param {string} params.content - File contents.
379
+ * @param {string} [params.mode] - Octal mode.
380
+ * @param {boolean} [params.dryRun] - Report the destination instead of writing.
381
+ * @returns {boolean} True when the file changed.
382
+ * @memberof ProcessService
383
+ */
384
+ const installRootFile = ({ target, content, mode = '0644', dryRun = false }) => {
385
+ const current = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
386
+ if (current === content) return false;
387
+ if (dryRun) {
388
+ logger.info(`[dry-run] write ${target} (mode ${mode})`, { bytes: content.length });
389
+ return true;
390
+ }
391
+ const staged = nodePath.join('/tmp', `underpost-root-file-${nodePath.basename(target)}-${process.pid}`);
392
+ fs.writeFileSync(staged, content, { mode: 0o600 });
393
+ shellExec(`sudo mkdir -p ${nodePath.dirname(target)}`, { silent: true });
394
+ shellExec(`sudo install -m ${mode} -o root -g root ${staged} ${target}`, { silent: true });
395
+ fs.removeSync(staged);
396
+ return true;
397
+ };
261
398
  /**
262
399
  * Retrieves the process ID (PID) of the most recently created gnome-terminal instance.
263
400
  * Note: This function is environment-specific (GNOME/Linux) and uses `pgrep -n`.
@@ -287,11 +424,15 @@ function pbcopy(data) {
287
424
  logger.info(`copied to clipboard`, clipboard.readSync());
288
425
  }
289
426
  export {
427
+ installRootFile,
290
428
  ProcessController,
291
429
  ShellExecError,
292
430
  getRootDirectory,
431
+ shellArgumentFactory,
293
432
  shellExec,
433
+ shellExecAsync,
294
434
  shellCd,
435
+ sleepSync,
295
436
  pbcopy,
296
437
  getTerminalPid,
297
438
  daemonProcess,