@underpostnet/cyberia 3.2.90 → 3.3.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +16 -0
  3. package/.github/workflows/coverall.cyberia.ci.yml +107 -0
  4. package/.github/workflows/cyberia-client.cd.yml +6 -18
  5. package/.github/workflows/cyberia-server.cd.yml +6 -18
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +6 -78
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +42 -21
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +2 -9
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +47 -0
  22. package/CHANGELOG.md +581 -2417
  23. package/CLI-HELP.md +416 -127
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +4 -4
  27. package/README.md +21 -1
  28. package/bin/build.js +70 -43
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +372 -135
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +372 -135
  33. package/compose.env +16 -0
  34. package/conf.js +93 -4
  35. package/deploy/cyberia-client/deploy.sh +47 -0
  36. package/deploy/cyberia-client/package.sh +18 -0
  37. package/deploy/cyberia-client/state.sh +14 -0
  38. package/deploy/cyberia-server/deploy.sh +47 -0
  39. package/deploy/cyberia-server/package.sh +18 -0
  40. package/deploy/cyberia-server/state.sh +14 -0
  41. package/deploy/dd-cyberia/deploy.sh +22 -0
  42. package/deploy/dd-cyberia/init.sh +55 -0
  43. package/deploy/dd-cyberia/package.sh +17 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +116 -0
  46. package/deploy/lib/github-actions-logging.sh +594 -0
  47. package/deploy/lib/host.sh +185 -0
  48. package/deploy/lib/state.sh +62 -0
  49. package/deploy/pwa-microservices-template/deploy.sh +45 -0
  50. package/deploy/release/deploy.sh +32 -0
  51. package/deployment.yaml +7 -2
  52. package/docker-compose.yml +31 -37
  53. package/examples/static-page/README.md +19 -19
  54. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  55. package/hardhat/hardhat.config.js +1 -6
  56. package/hardhat/package-lock.json +104 -43
  57. package/hardhat/package.json +3 -3
  58. package/manifests/alertmanager/deployment.yaml +81 -0
  59. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  60. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
  61. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
  62. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  63. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  64. package/manifests/grafana/deployment.yaml +26 -7
  65. package/manifests/grafana/kustomization.yaml +1 -0
  66. package/manifests/grafana/service.yaml +6 -1
  67. package/manifests/mariadb/pv.yaml +1 -0
  68. package/manifests/mongodb/statefulset.yaml +9 -2
  69. package/manifests/mysql/pv-pvc.yaml +14 -1
  70. package/manifests/postgresql/pv.yaml +12 -1
  71. package/manifests/postgresql/pvc.yaml +1 -0
  72. package/manifests/prometheus/deployment.yaml +18 -22
  73. package/package.json +83 -10
  74. package/scripts/audit-selinux.sh +64 -0
  75. package/scripts/event-monitor.sh +56 -0
  76. package/scripts/gpu-diag.sh +0 -0
  77. package/scripts/ip-info.sh +0 -0
  78. package/scripts/k3s-node-setup.sh +30 -14
  79. package/scripts/kubeadm-node-setup.sh +40 -31
  80. package/scripts/lxd-vm-setup.sh +0 -0
  81. package/scripts/maas-nat-firewalld.sh +0 -0
  82. package/scripts/maas-setup.sh +8 -8
  83. package/scripts/nat-iptables.sh +2 -0
  84. package/scripts/rhel-grpc-setup.sh +0 -0
  85. package/scripts/rocky-kickstart.sh +25 -9
  86. package/scripts/selinux-normalize.sh +907 -0
  87. package/scripts/shutdown-machine.sh +843 -0
  88. package/scripts/test-monitor.sh +3 -3
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +1 -1
  99. package/src/api/cyberia-action/cyberia-action.service.js +2 -2
  100. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
  101. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
  102. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
  103. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  104. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  105. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  106. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  107. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  108. package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
  109. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
  110. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
  111. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
  112. package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
  113. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  114. package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
  115. package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
  116. package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
  117. package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
  118. package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
  119. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  120. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  121. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  122. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
  123. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  124. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  125. package/src/api/cyberia-map/cyberia-map.service.js +2 -2
  126. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  127. package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
  128. package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
  129. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  130. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  131. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  132. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  133. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  134. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  135. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -29
  136. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  137. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  138. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  139. package/src/api/default/default.controller.js +1 -1
  140. package/src/api/default/default.router.js +1 -1
  141. package/src/api/default/default.service.js +2 -2
  142. package/src/api/document/document.controller.js +1 -1
  143. package/src/api/document/document.router.js +1 -1
  144. package/src/api/document/document.service.js +2 -2
  145. package/src/api/file/file.controller.js +1 -1
  146. package/src/api/file/file.router.js +1 -1
  147. package/src/api/file/file.service.js +2 -2
  148. package/src/api/instance/instance.controller.js +1 -1
  149. package/src/api/instance/instance.router.js +1 -1
  150. package/src/api/instance/instance.service.js +2 -2
  151. package/src/api/ipfs/ipfs.controller.js +1 -1
  152. package/src/api/ipfs/ipfs.router.js +2 -2
  153. package/src/api/ipfs/ipfs.service.js +2 -2
  154. package/src/api/object-layer/object-layer.controller.js +1 -1
  155. package/src/api/object-layer/object-layer.model.js +204 -3
  156. package/src/api/object-layer/object-layer.router.js +2 -2
  157. package/src/api/object-layer/object-layer.service.js +4 -17
  158. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  159. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  160. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  161. package/src/api/test/test.controller.js +1 -1
  162. package/src/api/test/test.service.js +1 -1
  163. package/src/api/user/guest.service.js +3 -3
  164. package/src/api/user/user.controller.js +1 -1
  165. package/src/api/user/user.router.js +2 -2
  166. package/src/api/user/user.service.js +4 -4
  167. package/src/api.js +5 -9
  168. package/src/cli/app.js +391 -0
  169. package/src/cli/baremetal.js +30 -36
  170. package/src/cli/client.js +306 -0
  171. package/src/cli/cloud-init.js +3 -3
  172. package/src/cli/cluster.js +242 -135
  173. package/src/cli/db.js +126 -67
  174. package/src/cli/deploy.js +158 -60
  175. package/src/cli/docker-compose.js +5 -5
  176. package/src/cli/domains.js +184 -0
  177. package/src/cli/dotenv-store.js +143 -0
  178. package/src/cli/event.js +2281 -0
  179. package/src/cli/fs.js +3 -3
  180. package/src/cli/host.js +672 -0
  181. package/src/cli/image.js +4 -7
  182. package/src/cli/index.js +426 -134
  183. package/src/cli/ipfs.js +34 -30
  184. package/src/cli/kickstart.js +1 -1
  185. package/src/cli/kubectl.js +218 -41
  186. package/src/cli/lxd.js +3 -3
  187. package/src/cli/monitor.js +1359 -26
  188. package/src/cli/package.js +90 -0
  189. package/src/cli/release.js +73 -34
  190. package/src/cli/repository.js +564 -367
  191. package/src/cli/run.js +362 -654
  192. package/src/cli/secrets.js +2158 -964
  193. package/src/cli/ssh.js +651 -331
  194. package/src/cli/state.js +394 -0
  195. package/src/cli/static.js +2 -2
  196. package/src/cli/system.js +26 -13
  197. package/src/cli/test.js +199 -125
  198. package/src/cli/vultr.js +623 -0
  199. package/src/cli/wireguard.js +2930 -0
  200. package/src/client/components/core/CalendarCore.js +53 -96
  201. package/src/client/components/core/Docs.js +258 -211
  202. package/src/client/components/core/FileExplorer.js +0 -21
  203. package/src/client/components/core/PanelForm.js +4 -4
  204. package/src/client/components/core/Repository.js +56 -0
  205. package/src/client/components/core/Translate.js +7 -0
  206. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  207. package/src/client/components/core/Wallet.js +0 -11
  208. package/src/client/components/core/Worker.js +7 -1
  209. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  210. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  211. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
  212. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  213. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  214. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  215. package/src/client/components/default/AppShellDefault.js +1 -1
  216. package/src/client/components/default/SettingsDefault.js +2 -2
  217. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  218. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  219. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  220. package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
  221. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
  222. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  223. package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
  224. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  225. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  226. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  227. package/src/client/public/default/android-chrome-144x144.png +0 -0
  228. package/src/client/public/default/android-chrome-192x192.png +0 -0
  229. package/src/client/public/default/android-chrome-256x256.png +0 -0
  230. package/src/client/public/default/android-chrome-36x36.png +0 -0
  231. package/src/client/public/default/android-chrome-384x384.png +0 -0
  232. package/src/client/public/default/android-chrome-48x48.png +0 -0
  233. package/src/client/public/default/android-chrome-512x512.png +0 -0
  234. package/src/client/public/default/android-chrome-72x72.png +0 -0
  235. package/src/client/public/default/android-chrome-96x96.png +0 -0
  236. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  237. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  238. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  239. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  240. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  241. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  242. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  243. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  244. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  245. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  246. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  247. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  248. package/src/client/public/default/apple-touch-icon.png +0 -0
  249. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  250. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  251. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  252. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  253. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  254. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  255. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  256. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  257. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  258. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  259. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  260. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  261. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  262. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  263. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  264. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  265. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  266. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  267. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  268. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  269. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  270. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  271. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  272. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  273. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  274. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  275. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  276. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  277. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  278. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  279. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  280. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  281. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  282. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  283. package/src/client/public/default/assets/background/white.jpg +0 -0
  284. package/src/client/public/default/browserconfig.xml +1 -1
  285. package/src/client/public/default/favicon-16x16.png +0 -0
  286. package/src/client/public/default/favicon-32x32.png +0 -0
  287. package/src/client/public/default/favicon-48x48.png +0 -0
  288. package/src/client/public/default/favicon.ico +0 -0
  289. package/src/client/public/default/manifest.webmanifest +5 -5
  290. package/src/client/public/default/mstile-144x144.png +0 -0
  291. package/src/client/public/default/mstile-150x150.png +0 -0
  292. package/src/client/public/default/mstile-310x150.png +0 -0
  293. package/src/client/public/default/mstile-310x310.png +0 -0
  294. package/src/client/public/default/mstile-70x70.png +0 -0
  295. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  296. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  297. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  298. package/src/client/services/object-layer/object-layer.management.js +4 -4
  299. package/src/client/ssr/RootDocument.js +1 -1
  300. package/src/client/ssr/body/404.js +1 -1
  301. package/src/client/ssr/body/500.js +1 -1
  302. package/src/client/ssr/body/CacheControl.js +1 -1
  303. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  304. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  305. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  306. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  307. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  308. package/src/client/ssr/head/Css.js +1 -1
  309. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  310. package/src/client/ssr/head/DefaultScripts.js +1 -1
  311. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  312. package/src/client/ssr/head/Microdata.js +1 -1
  313. package/src/client/ssr/head/Production.js +1 -1
  314. package/src/client/ssr/head/Pwa.js +1 -1
  315. package/src/client/ssr/head/PwaItemledger.js +1 -1
  316. package/src/client/ssr/head/Seo.js +1 -1
  317. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  318. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  319. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  320. package/src/client/ssr/views/Cyberia404.js +7 -5
  321. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  322. package/src/client/ssr/views/Maintenance.js +1 -1
  323. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  324. package/src/client/ssr/views/Test.js +1 -1
  325. package/src/client/sw/core.sw.js +20 -14
  326. package/src/client-builder/client-build-docs.js +61 -46
  327. package/src/client-builder/client-build-live.js +2 -2
  328. package/src/client-builder/client-build.js +58 -44
  329. package/src/client-builder/client-dev-server.js +3 -3
  330. package/src/client-builder/client-icons.js +2 -2
  331. package/src/client-builder/ssr.js +3 -3
  332. package/src/client.build.js +3 -6
  333. package/src/client.dev.js +3 -6
  334. package/src/db/DataBaseProvider.js +186 -3
  335. package/src/db/mariadb/MariaDB.js +3 -2
  336. package/src/db/mongo/MongoBootstrap.js +273 -101
  337. package/src/db/mongo/MongooseDB.js +11 -8
  338. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  339. package/src/grpc/cyberia/grpc-server.js +1 -1
  340. package/src/index.js +103 -23
  341. package/src/mailer/EmailRender.js +2 -2
  342. package/src/mailer/MailerInterceptor.js +87 -0
  343. package/src/mailer/MailerProvider.js +50 -6
  344. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
  345. package/src/projects/cyberia/besu-genesis-generator.js +3 -3
  346. package/src/projects/cyberia/catalog-cyberia.js +54 -6
  347. package/src/projects/cyberia/gemini-client.js +1 -1
  348. package/src/projects/cyberia/generate-saga.js +15 -24
  349. package/src/projects/cyberia/hot-reload-trigger.js +1 -1
  350. package/src/projects/cyberia/instance-data.js +22 -3
  351. package/src/projects/cyberia/ipfs-client.js +4 -4
  352. package/src/projects/cyberia/map-preview-generator.js +1 -1
  353. package/src/projects/cyberia/object-layer.js +12 -22
  354. package/src/projects/cyberia/semantic-layer-generator.js +1 -1
  355. package/src/projects/underpost/catalog-underpost.js +13 -2
  356. package/src/proxy.js +4 -7
  357. package/src/runtime/cyberia-client/Dockerfile +9 -4
  358. package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
  359. package/src/runtime/cyberia-server/Dockerfile +9 -4
  360. package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
  361. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  362. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  363. package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
  364. package/src/runtime/engine-cyberia/compose.env +16 -0
  365. package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
  366. package/src/runtime/express/Express.js +32 -27
  367. package/src/runtime/lampp/Lampp.js +22 -70
  368. package/src/runtime/nginx/Nginx.js +1 -1
  369. package/src/runtime/wp/Wp.js +16 -48
  370. package/src/server/{catalog.js → build/catalog.js} +15 -5
  371. package/src/server/build/coverage.js +117 -0
  372. package/src/server/build/execution.js +402 -0
  373. package/src/server/build/package.js +465 -0
  374. package/src/server/build/testing.js +528 -0
  375. package/src/server/{dns.js → network/dns.js} +241 -14
  376. package/src/server/network/forward-proxy.js +541 -0
  377. package/src/server/{middlewares.js → network/middlewares.js} +59 -4
  378. package/src/server/network/node-capability.js +98 -0
  379. package/src/server/{peer.js → network/peer.js} +3 -3
  380. package/src/server/{proxy.js → network/proxy.js} +7 -13
  381. package/src/server/network/router.js +356 -0
  382. package/src/server/{tls.js → network/tls.js} +2 -2
  383. package/src/server/network/underpost-compression.js +186 -0
  384. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
  385. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
  386. package/src/server/{cri.js → ops/cri.js} +2 -2
  387. package/src/server/ops/cron.js +949 -0
  388. package/src/server/ops/event-notification.js +284 -0
  389. package/src/server/{logger.js → ops/logger.js} +108 -23
  390. package/src/server/ops/monitoring.js +1724 -0
  391. package/src/server/ops/systemd.js +259 -0
  392. package/src/server/{conf.js → runtime/conf.js} +521 -520
  393. package/src/server/runtime/config-scope.js +215 -0
  394. package/src/server/runtime/environment.js +149 -0
  395. package/src/server/{process.js → runtime/process.js} +153 -13
  396. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  397. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  398. package/src/server/{start.js → runtime/start.js} +128 -39
  399. package/src/server/{auth.js → security/auth.js} +7 -8
  400. package/src/server/security/container-storage.js +382 -0
  401. package/src/server/{crypto.js → security/crypto.js} +1 -1
  402. package/src/server/security/selinux.js +185 -0
  403. package/src/server/{backup.js → storage/backup.js} +17 -8
  404. package/src/server/{data-query.js → storage/data-query.js} +1 -1
  405. package/src/server/{downloader.js → storage/downloader.js} +2 -2
  406. package/src/server/storage/repository.js +68 -0
  407. package/src/server.js +4 -7
  408. package/src/ws/IoInterface.js +1 -1
  409. package/src/ws/IoServer.js +1 -1
  410. package/src/ws/core/core.ws.connection.js +1 -1
  411. package/src/ws/core/core.ws.emit.js +1 -1
  412. package/src/ws/core/core.ws.server.js +1 -1
  413. package/src/ws/default/default.ws.connection.js +1 -1
  414. package/src/ws/default/default.ws.emit.js +1 -1
  415. package/src/ws/default/default.ws.server.js +1 -1
  416. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  417. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  418. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  419. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  420. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
  421. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
  422. package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
  423. package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
  424. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  425. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  426. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  427. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  428. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  429. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  430. package/test/integration/infra/1-security/selinux.test.js +69 -0
  431. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  432. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  433. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  434. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  435. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  436. package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
  437. package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
  438. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  439. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  440. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  441. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  442. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  443. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  444. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +47 -6
  445. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -4
  446. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  447. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  448. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  449. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  450. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  451. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  452. package/test/support/shell-harness.js +73 -0
  453. package/test/unit/build-template.test.js +97 -0
  454. package/test/unit/build-workflow-offline.test.js +132 -0
  455. package/test/unit/catalog.test.js +71 -0
  456. package/test/unit/client-build-docs.test.js +334 -0
  457. package/test/unit/conf-loading.test.js +303 -0
  458. package/test/unit/conf-resolution.test.js +979 -0
  459. package/test/unit/coverage-artifact.test.js +130 -0
  460. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  461. package/test/unit/deploy-env-resolution.test.js +38 -0
  462. package/test/unit/deploy-log-table.test.js +447 -0
  463. package/test/unit/execution-profiles.test.js +219 -0
  464. package/test/unit/fleet-sync-source.test.js +149 -0
  465. package/test/unit/logger-redaction.test.js +112 -0
  466. package/test/unit/package.test.js +403 -0
  467. package/test/unit/prepare-host.test.js +299 -0
  468. package/test/unit/process-environment.test.js +18 -0
  469. package/test/unit/propagation-message.test.js +78 -0
  470. package/test/unit/redeploy-plan.test.js +24 -0
  471. package/test/unit/release-bump.test.js +474 -0
  472. package/test/unit/ssh-output-redaction.test.js +28 -0
  473. package/test/unit/start-options.test.js +46 -0
  474. package/test/unit/test-tiers.test.js +193 -0
  475. package/vitest.config.js +58 -0
  476. package/.nycrc +0 -9
  477. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  478. package/manifests/mongodb/pv-pvc.yaml +0 -59
  479. package/scripts/link-local-underpost-cli.sh +0 -6
  480. package/src/api/user/user.build.js +0 -16
  481. package/src/cli/env.js +0 -177
  482. package/src/client/components/core/Blockchain.js +0 -41
  483. package/src/client/components/core/Blog.js +0 -9
  484. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  485. package/src/client/public/default/assets/background/dark.jpg +0 -0
  486. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  487. package/src/client/public/default/assets/background/white0.jpg +0 -0
  488. package/src/client/public/doc/favicon.ico +0 -0
  489. package/src/client/public/doc/sitemap +0 -148
  490. package/src/server/cron.js +0 -476
@@ -0,0 +1,453 @@
1
+ 'use strict';
2
+
3
+ import { expect } from 'chai';
4
+ import fs from 'fs-extra';
5
+ import Underpost from '../../../../src/index.js';
6
+ import {
7
+ UNDERPOST_GATEWAY,
8
+ assertStaticAssets,
9
+ gatewayFallbackProbeRunner,
10
+ gatewayStaticAssetExists,
11
+ installGatewayConf,
12
+ instanceFallbackChecksFactory,
13
+ placeInstanceStaticAssets,
14
+ pwaFallbackChecksFactory,
15
+ readHostInstanceRegistry,
16
+ seedDefaultStatusPage,
17
+ syncStaticAssetFromPod,
18
+ writeHostInstanceRegistry,
19
+ writeHostServerConf,
20
+ writeStaticAsset,
21
+ } from '../../../../src/server/network/underpost-gateway.js';
22
+ import { shellHarness } from '../../../support/shell-harness.js';
23
+
24
+ const HOST_ROOT = '/underpost/gateway';
25
+ const CONF_DIR = '/tmp/gateway-conf-fixture';
26
+
27
+ // The gateway root is a root-owned node directory and every install talks to a
28
+ // running Nginx in the cluster. Both are replaced so the assertions are over the
29
+ // command vector and the files a run would place.
30
+ const gatewayFixture = (files = {}) => {
31
+ const table = new Map(Object.entries(files));
32
+ const written = new Map();
33
+ const removed = [];
34
+ const keys = () => [...table.keys(), ...written.keys()];
35
+
36
+ vi.spyOn(fs, 'existsSync').mockImplementation((filePath) => {
37
+ const key = `${filePath}`;
38
+ if (removed.includes(key)) return false;
39
+ return table.has(key) || written.has(key) || keys().some((entry) => entry.startsWith(`${key}/`));
40
+ });
41
+ vi.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
42
+ const key = `${filePath}`;
43
+ if (written.has(key)) return written.get(key);
44
+ if (table.has(key)) return table.get(key);
45
+ throw Object.assign(new Error(`ENOENT: ${key}`), { code: 'ENOENT' });
46
+ });
47
+ const write = (filePath, value) => {
48
+ const key = `${filePath}`;
49
+ const index = removed.indexOf(key);
50
+ if (index !== -1) removed.splice(index, 1);
51
+ written.set(key, `${value}`);
52
+ };
53
+ vi.spyOn(fs, 'writeFileSync').mockImplementation(write);
54
+ vi.spyOn(fs, 'mkdirpSync').mockImplementation(() => undefined);
55
+ vi.spyOn(fs, 'removeSync').mockImplementation((filePath) => {
56
+ written.delete(`${filePath}`);
57
+ removed.push(`${filePath}`);
58
+ });
59
+ vi.spyOn(fs, 'statSync').mockImplementation((filePath) => ({
60
+ size: `${written.get(`${filePath}`) ?? table.get(`${filePath}`) ?? ''}`.length,
61
+ }));
62
+ vi.spyOn(fs, 'readdirSync').mockImplementation((dir) =>
63
+ keys()
64
+ .filter((filePath) => filePath.startsWith(`${dir}/`))
65
+ .map((filePath) => filePath.slice(`${dir}/`.length))
66
+ .filter((name) => !name.includes('/')),
67
+ );
68
+ return { table, written, removed, write };
69
+ };
70
+
71
+ describe('gateway static assets', () => {
72
+ let harness;
73
+
74
+ beforeEach(() => {
75
+ harness = shellHarness();
76
+ });
77
+
78
+ afterEach(() => {
79
+ harness.restore();
80
+ vi.restoreAllMocks();
81
+ });
82
+
83
+ describe('placement', () => {
84
+ it('copies a document into the root-owned gateway tree', () => {
85
+ gatewayFixture({ './project/public/404/index.html': '<!doctype html>404\n' });
86
+ expect(
87
+ writeStaticAsset({
88
+ hostRoot: HOST_ROOT,
89
+ assetPath: 'app.test/root/status-pages/404/index.html',
90
+ sourcePath: './project/public/404/index.html',
91
+ }),
92
+ ).to.equal(true);
93
+ expect(harness.ran(`sudo cp -f ./project/public/404/index.html ${HOST_ROOT}/app.test`)).to.equal(true);
94
+ });
95
+
96
+ // A half-finished copy leaves an empty file behind, and serving it would
97
+ // answer a status page with a blank body.
98
+ it('places nothing for a missing or empty source', () => {
99
+ gatewayFixture({ './project/public/empty.html': '' });
100
+ expect(
101
+ writeStaticAsset({ hostRoot: HOST_ROOT, assetPath: 'a', sourcePath: './project/public/empty.html' }),
102
+ ).to.equal(false);
103
+ expect(writeStaticAsset({ hostRoot: HOST_ROOT, assetPath: 'a', sourcePath: './absent.html' })).to.equal(false);
104
+ expect(writeStaticAsset({ hostRoot: HOST_ROOT, assetPath: 'a' })).to.equal(false);
105
+ expect(harness.ran('sudo cp')).to.equal(false);
106
+ });
107
+
108
+ it('treats an empty document under the gateway root as absent', () => {
109
+ gatewayFixture({ [`${HOST_ROOT}/a/index.html`]: 'body', [`${HOST_ROOT}/b/index.html`]: '' });
110
+ expect(gatewayStaticAssetExists({ hostRoot: HOST_ROOT, assetPath: 'a/index.html' })).to.equal(true);
111
+ expect(gatewayStaticAssetExists({ hostRoot: HOST_ROOT, assetPath: 'b/index.html' })).to.equal(false);
112
+ expect(gatewayStaticAssetExists({ hostRoot: HOST_ROOT, assetPath: 'c/index.html' })).to.equal(false);
113
+ });
114
+
115
+ // The pod is the authority: several clients are built from sources that only
116
+ // exist inside the container.
117
+ it('pulls a document out of the running workload through a keyed staging path', () => {
118
+ const { write } = gatewayFixture();
119
+ harness.route({
120
+ match: (command) => {
121
+ const staged = /kubectl cp \S+ (\/tmp\/underpost-gateway-\S+)/.exec(command)?.[1];
122
+ if (staged) write(staged, '<!doctype html>from pod\n');
123
+ return false;
124
+ },
125
+ });
126
+ expect(
127
+ syncStaticAssetFromPod({
128
+ podName: 'app-pod',
129
+ namespace: 'ops',
130
+ container: 'app',
131
+ sourcePath: '/app/public/404/index.html',
132
+ hostRoot: HOST_ROOT,
133
+ assetPath: 'app.test/root/status-pages/404/index.html',
134
+ }),
135
+ ).to.equal(true);
136
+ expect(harness.ran('kubectl cp ops/app-pod:/app/public/404/index.html')).to.equal(true);
137
+ expect(harness.ran('-c app')).to.equal(true);
138
+ expect(harness.ran(`sudo cp -f /tmp/underpost-gateway-`)).to.equal(true);
139
+ });
140
+
141
+ it('reports nothing placed when the workload has no such document', () => {
142
+ gatewayFixture();
143
+ expect(
144
+ syncStaticAssetFromPod({
145
+ podName: 'app-pod',
146
+ sourcePath: '/app/absent.html',
147
+ hostRoot: HOST_ROOT,
148
+ assetPath: 'a',
149
+ }),
150
+ ).to.equal(false);
151
+ expect(harness.ran('sudo cp -f')).to.equal(false);
152
+ });
153
+
154
+ // A missing document is invisible at deploy time and only surfaces later as
155
+ // a shared default page in place of the host's own.
156
+ it('fails the deploy when a configured document reached neither pass', () => {
157
+ gatewayFixture();
158
+ expect(() =>
159
+ assertStaticAssets({
160
+ records: [{ source: null, assetPath: 'app.test/root/status-pages/404/index.html' }],
161
+ hostRoot: HOST_ROOT,
162
+ label: 'run instance',
163
+ }),
164
+ ).to.throw('missing configured assets');
165
+ });
166
+
167
+ it('accepts a document that either pass supplied', () => {
168
+ gatewayFixture({ [`${HOST_ROOT}/b`]: 'body' });
169
+ const records = [
170
+ { source: 'project', assetPath: 'a' },
171
+ { source: null, assetPath: 'b' },
172
+ ];
173
+ expect(assertStaticAssets({ records, hostRoot: HOST_ROOT, label: 'run instance' })).to.equal(records);
174
+ });
175
+
176
+ it('places every declared status page a set of instances carries', () => {
177
+ gatewayFixture({ 'fixture-server/public/404/index.html': '<!doctype html>404\n' });
178
+ vi.spyOn(Underpost.deploy, 'underpostGatewayRootFactory').mockReturnValue(HOST_ROOT);
179
+ const records = placeInstanceStaticAssets({
180
+ instances: [
181
+ {
182
+ id: 'mmo-server',
183
+ host: 'server.fixture.test',
184
+ path: '/',
185
+ metadata: { repository: 'underpostnet/fixture-server' },
186
+ customStatusPages: [{ status: '404', hostPath: './public/404/index.html' }],
187
+ },
188
+ ],
189
+ options: {},
190
+ label: 'run instance',
191
+ });
192
+ expect(records[0].source).to.equal('project');
193
+ });
194
+ });
195
+
196
+ describe('host server blocks', () => {
197
+ it('writes a block and reports the change', () => {
198
+ const { written } = gatewayFixture();
199
+ expect(writeHostServerConf({ confDir: CONF_DIR, host: 'app.test', conf: 'server {}\n' })).to.equal(true);
200
+ expect(written.get(`${CONF_DIR}/app.test.conf`)).to.equal('server {}\n');
201
+ expect(writeHostServerConf({ confDir: CONF_DIR, host: 'app.test', conf: 'server {}\n' })).to.equal(false);
202
+ });
203
+
204
+ it('removes the block when there is nothing to serve', () => {
205
+ const { removed } = gatewayFixture({ [`${CONF_DIR}/app.test.conf`]: 'server {}\n' });
206
+ expect(writeHostServerConf({ confDir: CONF_DIR, host: 'app.test', conf: '' })).to.equal(true);
207
+ expect(removed).to.include(`${CONF_DIR}/app.test.conf`);
208
+ });
209
+
210
+ // The registry records what the host was last rendered with, which is the
211
+ // only place a variant's descriptor survives leaving the conf.
212
+ it('records and reads back the descriptors a host was rendered with', () => {
213
+ const { written } = gatewayFixture();
214
+ const instances = [{ id: 'mmo-server', host: 'app.test', path: '/' }];
215
+ expect(writeHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test', instances })).to.equal(true);
216
+ expect(writeHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test', instances })).to.equal(false);
217
+ expect(readHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test' })).to.deep.equal(instances);
218
+ // Given a non-.conf suffix so the install pass never feeds it to Nginx.
219
+ expect([...written.keys()][0].endsWith('.instances.json')).to.equal(true);
220
+ });
221
+
222
+ it('reads no descriptors from an absent or malformed registry', () => {
223
+ gatewayFixture();
224
+ expect(readHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test' })).to.deep.equal([]);
225
+ vi.restoreAllMocks();
226
+ gatewayFixture({ [`${CONF_DIR}/app.test.instances.json`]: '{ not json' });
227
+ expect(readHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test' })).to.deep.equal([]);
228
+ vi.restoreAllMocks();
229
+ gatewayFixture({ [`${CONF_DIR}/app.test.instances.json`]: JSON.stringify([{ id: 'a' }, {}]) });
230
+ expect(readHostInstanceRegistry({ confDir: CONF_DIR, host: 'app.test' })).to.deep.equal([{ id: 'a' }]);
231
+ });
232
+ });
233
+
234
+ describe('gateway config install', () => {
235
+ const installFixture = (files = {}) =>
236
+ gatewayFixture({ [`${CONF_DIR}/app.test.conf`]: 'server { listen 80; }\n', ...files });
237
+
238
+ it('validates the candidate config before signalling the running master', () => {
239
+ installFixture();
240
+ harness.route({ match: 'nginx -t', code: 0, stdout: 'configuration file test is successful\n' });
241
+ expect(installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.equal(true);
242
+ const testIndex = harness.calls.findIndex((command) => command.includes('nginx -t'));
243
+ const reloadIndex = harness.calls.findIndex((command) => command.includes('nginx -s reload'));
244
+ expect(reloadIndex).to.be.above(testIndex);
245
+ });
246
+
247
+ // A block that does not parse would take the whole edge down on reload.
248
+ it('restores the previous blocks and fails when Nginx rejects the candidate', () => {
249
+ const { written } = installFixture({
250
+ [`${HOST_ROOT}/${UNDERPOST_GATEWAY.confDir}/app.test.conf`]: 'server { listen 8080; }\n',
251
+ });
252
+ harness.route({ match: 'nginx -t', code: 1, stdout: 'nginx: [emerg] unknown directive\n' });
253
+ expect(() => installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.throw(
254
+ 'Gateway config rejected',
255
+ );
256
+ expect(harness.ran('underpost-gateway-restore-app.test.conf')).to.equal(true);
257
+ expect(harness.ran('nginx -s reload')).to.equal(false);
258
+ expect([...written.keys()].every((path) => !path.includes('restore'))).to.equal(true);
259
+ });
260
+
261
+ it('removes a block that was not there before the failed candidate', () => {
262
+ installFixture();
263
+ harness.route({ match: 'nginx -t', code: 1, stdout: 'nginx: [emerg]\n' });
264
+ expect(() => installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.throw();
265
+ expect(harness.ran(`sudo rm -f ${HOST_ROOT}/${UNDERPOST_GATEWAY.confDir}/app.test.conf`)).to.equal(true);
266
+ });
267
+
268
+ // The running master normally retains its old config when a reload fails, so
269
+ // the restore is re-signalled to converge it back.
270
+ it('restores and re-signals when the reload itself fails', () => {
271
+ installFixture();
272
+ harness.route({ match: 'nginx -t', code: 0, stdout: 'successful\n' });
273
+ let firstReload = true;
274
+ harness.route({
275
+ match: (command) => {
276
+ if (!command.includes('nginx -s reload')) return false;
277
+ if (!firstReload) return false;
278
+ firstReload = false;
279
+ return true;
280
+ },
281
+ throws: new Error('reload signal refused'),
282
+ });
283
+ expect(() => installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.throw(
284
+ 'reload signal refused',
285
+ );
286
+ expect(harness.count('nginx -s reload')).to.equal(2);
287
+ });
288
+
289
+ it('installs nothing when the build produced no blocks', () => {
290
+ gatewayFixture();
291
+ expect(installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.equal(false);
292
+ vi.restoreAllMocks();
293
+ gatewayFixture({ [`${CONF_DIR}/app.test.instances.json`]: '[]' });
294
+ expect(installGatewayConf({ hostRoot: HOST_ROOT, confSourceDir: CONF_DIR })).to.equal(false);
295
+ });
296
+ });
297
+
298
+ describe('default status page', () => {
299
+ it('creates the include directory and writes the shared fallback', () => {
300
+ gatewayFixture();
301
+ expect(seedDefaultStatusPage(HOST_ROOT)).to.equal(true);
302
+ expect(harness.ran(`sudo mkdir -p ${HOST_ROOT}/${UNDERPOST_GATEWAY.confDir}`)).to.equal(true);
303
+ expect(harness.ran('404 Not Found')).to.equal(true);
304
+ });
305
+
306
+ // An operator may have replaced it.
307
+ it('never overwrites an existing document', () => {
308
+ gatewayFixture({ [`${HOST_ROOT}/${UNDERPOST_GATEWAY.defaultHostDir}/status-pages/404/index.html`]: 'custom' });
309
+ expect(seedDefaultStatusPage(HOST_ROOT)).to.equal(false);
310
+ expect(harness.ran('sudo tee')).to.equal(false);
311
+ });
312
+ });
313
+
314
+ describe('fallback probe selection', () => {
315
+ it('probes one route per sub-path that declares a maintenance view', () => {
316
+ gatewayFixture({
317
+ './engine-private/conf/dd-core/conf.server.json': JSON.stringify({
318
+ 'app.fixture.test': {
319
+ '/': { client: 'App', ssr: 'App' },
320
+ '/single': { client: 'S', singleReplica: true, replicas: ['/blue'] },
321
+ },
322
+ }),
323
+ './engine-private/conf/dd-core/conf.ssr.json': JSON.stringify({
324
+ App: { head: [], body: [], mailer: {}, views: [{ path: '/', client: 'App' }] },
325
+ }),
326
+ });
327
+ expect(() => pwaFallbackChecksFactory('dd-core')).not.to.throw();
328
+ });
329
+
330
+ // A variant declaring several status pages is still one route to probe.
331
+ it('probes each instance sub-path once, however many pages it declares', () => {
332
+ const checks = instanceFallbackChecksFactory([
333
+ {
334
+ id: 'mmo-server',
335
+ host: 'server.fixture.test',
336
+ path: '/',
337
+ customStatusPages: [
338
+ { status: '404', hostPath: './public/404/index.html' },
339
+ { status: '500', hostPath: './public/500/index.html' },
340
+ ],
341
+ },
342
+ {
343
+ id: 'mmo-server-forest',
344
+ host: 'server.fixture.test',
345
+ path: '/FOREST',
346
+ customStatusPages: [{ status: '404', hostPath: './public/404/index.html' }],
347
+ },
348
+ ]);
349
+ expect(checks.map(({ path, kind }) => `${path}:${kind}`)).to.deep.equal(['/:status:404', '/FOREST:status:404']);
350
+ });
351
+
352
+ it('probes nothing for instances declaring no status page', () => {
353
+ expect(instanceFallbackChecksFactory([{ id: 'a', host: 'a.test', path: '/' }])).to.deep.equal([]);
354
+ expect(instanceFallbackChecksFactory()).to.deep.equal([]);
355
+ });
356
+ });
357
+
358
+ describe('pre-runtime fallback probes', () => {
359
+ const CHECK = {
360
+ host: 'app.fixture.test',
361
+ path: '/',
362
+ assetPath: 'app.fixture.test/root/status-pages/404/index.html',
363
+ kind: 'status:404',
364
+ };
365
+ const DOCUMENT = '<!doctype html>404\n';
366
+
367
+ const probeFixture = () => {
368
+ vi.spyOn(Underpost.deploy, 'underpostGatewayRootFactory').mockReturnValue(HOST_ROOT);
369
+ return gatewayFixture({ [`${HOST_ROOT}/${CHECK.assetPath}`]: DOCUMENT });
370
+ };
371
+
372
+ it('runs nothing when the Gateway API stack is not in use', async () => {
373
+ probeFixture();
374
+ expect(await gatewayFallbackProbeRunner({ checks: [CHECK], options: {}, label: 'deploy' })).to.deep.equal([]);
375
+ expect(harness.calls.length).to.equal(0);
376
+ });
377
+
378
+ it('runs nothing when no route declares a fallback', async () => {
379
+ probeFixture();
380
+ expect(
381
+ await gatewayFallbackProbeRunner({ checks: [], options: { gatewayApi: true }, label: 'deploy' }),
382
+ ).to.deep.equal([]);
383
+ });
384
+
385
+ it('refuses to continue when the gateway is not operational', async () => {
386
+ probeFixture();
387
+ await expect(
388
+ gatewayFallbackProbeRunner({
389
+ checks: [CHECK],
390
+ options: { gatewayApi: true },
391
+ label: 'deploy',
392
+ gatewayStatusRunner: async () => ({ programmed: false, servesHttps: false }),
393
+ }),
394
+ ).rejects.toThrow('not operational before application deployment');
395
+ });
396
+
397
+ it('refuses a dev gateway that does not terminate TLS', async () => {
398
+ probeFixture();
399
+ await expect(
400
+ gatewayFallbackProbeRunner({
401
+ checks: [CHECK],
402
+ options: { gatewayApi: true, dev: true },
403
+ label: 'deploy',
404
+ gatewayStatusRunner: async () => ({ programmed: true, servesHttps: false }),
405
+ }),
406
+ ).rejects.toThrow('not operational');
407
+ });
408
+
409
+ // The assertion is on the response body, not on object status: an accepted
410
+ // route says nothing about which document a client receives.
411
+ it('passes only when the edge answers an upstream failure with the configured document', async () => {
412
+ probeFixture();
413
+ harness.route({ match: 'wget -q -O -', code: 0, stdout: DOCUMENT });
414
+ harness.route({ match: 'wget -S -O /dev/null', code: 0, stdout: 'HTTP/1.1 502 Bad Gateway\n' });
415
+ const [result] = await gatewayFallbackProbeRunner({
416
+ checks: [CHECK],
417
+ options: { gatewayApi: true },
418
+ label: 'deploy',
419
+ gatewayStatusRunner: async () => ({ programmed: true, servesHttps: true }),
420
+ });
421
+ expect(result).to.include({ passed: true, status: '502', bodyMatchesConfiguredAsset: true, attempts: 1 });
422
+ });
423
+
424
+ it('probes over TLS through the local resolve override in dev', async () => {
425
+ probeFixture();
426
+ harness.route({ match: 'curl -sSk --noproxy', code: 0, stdout: DOCUMENT });
427
+ harness.route({ match: "-w '%{http_code}'", code: 0, stdout: '503\n' });
428
+ const [result] = await gatewayFallbackProbeRunner({
429
+ checks: [CHECK],
430
+ options: { gatewayApi: true, dev: true },
431
+ label: 'deploy',
432
+ gatewayStatusRunner: async () => ({ programmed: true, servesHttps: true }),
433
+ });
434
+ expect(result.passed).to.equal(true);
435
+ expect(harness.ran(`--resolve ${CHECK.host}:443:127.0.0.1`)).to.equal(true);
436
+ });
437
+
438
+ // A probe must not pass against a shared default page.
439
+ it('fails a probe answered with a body that is not the configured document', async () => {
440
+ probeFixture();
441
+ harness.route({ match: 'wget -q -O -', code: 0, stdout: '<!doctype html>shared default\n' });
442
+ harness.route({ match: 'wget -S -O /dev/null', code: 0, stdout: 'HTTP/1.1 502 Bad Gateway\n' });
443
+ await expect(
444
+ gatewayFallbackProbeRunner({
445
+ checks: [{ ...CHECK, host: 'app.fixture.test' }],
446
+ options: { gatewayApi: true },
447
+ label: 'deploy',
448
+ gatewayStatusRunner: async () => ({ programmed: true, servesHttps: true }),
449
+ }),
450
+ ).rejects.toThrow('fallback probes failed');
451
+ }, 90000);
452
+ });
453
+ });
@@ -14,7 +14,7 @@ import {
14
14
  trafficFromRoutingInfoFactory,
15
15
  trafficProbePathsFactory,
16
16
  trafficTableRowsFactory,
17
- } from '../src/server/conf.js';
17
+ } from '../../../../src/server/runtime/conf.js';
18
18
 
19
19
  // Pure resolution over the conf: the cluster lookups are injected, so every case
20
20
  // here fixes the routed colour and the endpoint readiness explicitly.
@@ -648,7 +648,7 @@ describe('blue/green traffic plan', () => {
648
648
  });
649
649
 
650
650
  it('keeps configured report rows when no Kubernetes deployments exist', () => {
651
- const runSource = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
651
+ const runSource = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
652
652
  const start = runSource.indexOf(" 'get-traffic': async");
653
653
  const end = runSource.indexOf(" 'instance-promote': async", start);
654
654
  const getTraffic = runSource.slice(start, end);
@@ -670,7 +670,7 @@ describe('blue/green traffic plan', () => {
670
670
  });
671
671
 
672
672
  it('feeds the report from stable Service selectors before legacy route text', () => {
673
- const runSource = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
673
+ const runSource = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
674
674
  const start = runSource.indexOf(" 'get-traffic': async");
675
675
  const end = runSource.indexOf(" 'instance-promote': async", start);
676
676
  const getTraffic = runSource.slice(start, end);
@@ -683,11 +683,15 @@ describe('blue/green traffic plan', () => {
683
683
  });
684
684
 
685
685
  it('uses real followed curl probes and reports current and opposite deployment status without ENV', () => {
686
- const runSource = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
686
+ const runSource = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
687
687
  const start = runSource.indexOf(" 'get-traffic': async");
688
688
  const end = runSource.indexOf(" 'instance-promote': async", start);
689
689
  const getTraffic = runSource.slice(start, end);
690
- expect(getTraffic).to.include('curl -L -v -i -s');
690
+ // The probe itself moved to one implementation the ingress event shares;
691
+ // what matters here is that the report still goes through it.
692
+ expect(getTraffic).to.include('publicIngressProbeFactory(url)');
693
+ const confSource = fs.readFileSync(new URL('../../../../src/server/runtime/conf.js', import.meta.url), 'utf8');
694
+ expect(confSource).to.include('curl -L -v -i -s');
691
695
  expect(getTraffic).to.include("'OPPOSITE'");
692
696
  expect(getTraffic).not.to.include('showOpposite');
693
697
  expect(getTraffic).to.include("'CURRENT'");
@@ -699,7 +703,7 @@ describe('blue/green traffic plan', () => {
699
703
  });
700
704
 
701
705
  it('caches stable Service readiness across hosts sharing the same deployment', () => {
702
- const runSource = fs.readFileSync(new URL('../src/cli/run.js', import.meta.url), 'utf8');
706
+ const runSource = fs.readFileSync(new URL('../../../../src/cli/run.js', import.meta.url), 'utf8');
703
707
  const start = runSource.indexOf(" 'get-traffic': async");
704
708
  const end = runSource.indexOf(" 'instance-promote': async", start);
705
709
  const getTraffic = runSource.slice(start, end);
@@ -13,8 +13,8 @@ import {
13
13
  staticPathSegmentFactory,
14
14
  statusPageAssetPathFactory,
15
15
  statusPageBuildSegment,
16
- } from '../src/server/underpost-gateway.js';
17
- import { staticContextRoutesFactory, statusPageRoutesFactory } from '../src/client-builder/client-build.js';
16
+ } from '../../../../src/server/network/underpost-gateway.js';
17
+ import { staticContextRoutesFactory, statusPageRoutesFactory } from '../../../../src/client-builder/client-build.js';
18
18
 
19
19
  // A `conf.ssr.json` client entry: two intercepted contexts, one status page,
20
20
  // and one ordinary view that must stay with the workload.
@@ -119,6 +119,47 @@ describe('underpost gateway edge tier', () => {
119
119
  expect(conf).to.include('map $http_upgrade $connection_upgrade');
120
120
  });
121
121
 
122
+ // For every host whose errors are intercepted, this workload is the hop the
123
+ // site path already passes through — and the last one holding the body in
124
+ // the clear. Compressing anywhere further out is compressing ciphertext.
125
+ describe('response compression', () => {
126
+ it('compresses what it serves and what it proxies', () => {
127
+ expect(conf).to.include('gzip on;');
128
+ expect(conf).to.include('gzip_vary on;');
129
+ // Nginx's default is `off`, which skips the proxied responses that carry
130
+ // most of the bytes this workload forwards.
131
+ expect(conf).to.include('gzip_proxied any;');
132
+ expect(conf).to.match(/gzip_types [^;]*application\/javascript[^;]*;/);
133
+ expect(conf).to.match(/gzip_types [^;]*text\/css[^;]*;/);
134
+ });
135
+
136
+ // Documents are served from a volume, so an operator who places a `.gz`
137
+ // beside one gets it sent with no compression pass at all.
138
+ it('prefers a pre-compressed sibling from the static root', () => {
139
+ expect(conf).to.include('gzip_static on;');
140
+ });
141
+
142
+ it('renders brotli and its modules only when the image is declared to carry them', () => {
143
+ expect(conf).to.not.include('brotli');
144
+ expect(conf).to.not.include('load_module');
145
+ const declared = nginxConfFactory({ compression: { brotliModuleDir: '/usr/lib/nginx/modules' } });
146
+ expect(declared).to.include('load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;');
147
+ expect(declared).to.include('brotli on;');
148
+ expect(declared).to.include('brotli_static on;');
149
+ // Main context: nginx rejects `load_module` anywhere else.
150
+ expect(declared.indexOf('load_module')).to.be.lessThan(declared.indexOf('events {'));
151
+ });
152
+
153
+ // The config is mounted with `subPath`, which Kubernetes never refreshes,
154
+ // so only a changed pod template rolls the workload onto a new policy.
155
+ it('rolls the deployment when the policy changes', () => {
156
+ const hash = (docs) => kind(docs, 'Deployment').match(/nginx-conf-hash: '([0-9a-f]+)'/)[1];
157
+ expect(hash(manifests({ compression: { brotliModuleDir: '/usr/lib/nginx/modules' } }))).to.not.equal(
158
+ hash(manifests()),
159
+ );
160
+ });
161
+ });
162
+
122
163
  // A 200 would let the PWA service worker store the shared page as the
123
164
  // host's own and keep serving it after the real document lands.
124
165
  it('serves the shared fallback as a 404 that is never stored', () => {
@@ -339,7 +380,7 @@ describe('underpost gateway edge tier', () => {
339
380
  // on a running gateway to validate against, and cannot mutate the host either.
340
381
  // Installing and reloading is the apply path's job.
341
382
  describe('build/apply separation', () => {
342
- const source = fs.readFileSync(new URL('../src/server/underpost-gateway.js', import.meta.url), 'utf8');
383
+ const source = fs.readFileSync(new URL('../../../../src/server/network/underpost-gateway.js', import.meta.url), 'utf8');
343
384
  const bodyOf = (name) => {
344
385
  const start = source.indexOf(`const ${name} = `);
345
386
  const next = source.slice(start + 1).search(/\nconst \w+ = |\nexport \{/);
@@ -389,7 +430,7 @@ describe('underpost gateway edge tier', () => {
389
430
  const applySites = () =>
390
431
  ['src/cli/cluster.js', 'src/cli/deploy.js', 'src/cli/run.js'].flatMap((file) =>
391
432
  fs
392
- .readFileSync(new URL(`../${file}`, import.meta.url), 'utf8')
433
+ .readFileSync(new URL(`../../../../${file}`, import.meta.url), 'utf8')
393
434
  .split('\n')
394
435
  .map((line, index) => ({ file, line: index + 1, text: line }))
395
436
  .filter((entry) => entry.text.includes('kubectl apply') && entry.text.includes('<<')),
@@ -407,7 +448,7 @@ describe('underpost gateway edge tier', () => {
407
448
  });
408
449
 
409
450
  describe('traffic switch publication order', () => {
410
- const deploySource = fs.readFileSync(new URL('../src/cli/deploy.js', import.meta.url), 'utf8');
451
+ const deploySource = fs.readFileSync(new URL('../../../../src/cli/deploy.js', import.meta.url), 'utf8');
411
452
  const switchTraffic = deploySource.slice(
412
453
  deploySource.indexOf(' switchTraffic('),
413
454
  deploySource.indexOf(' resolveDeployNode(', deploySource.indexOf(' switchTraffic(')),
@@ -442,7 +483,7 @@ describe('underpost gateway edge tier', () => {
442
483
  });
443
484
 
444
485
  describe('merged Gateway listener isolation', () => {
445
- const deploySource = fs.readFileSync(new URL('../src/cli/deploy.js', import.meta.url), 'utf8');
486
+ const deploySource = fs.readFileSync(new URL('../../../../src/cli/deploy.js', import.meta.url), 'utf8');
446
487
  const gatewayFactory = deploySource.slice(
447
488
  deploySource.indexOf(' gatewayYamlFactory('),
448
489
  deploySource.indexOf(' gatewayNameFactory(', deploySource.indexOf(' gatewayYamlFactory(')),