@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,541 @@
1
+ /**
2
+ * Authenticated HTTP/HTTPS forward-proxy primitives.
3
+ * @module src/server/network/forward-proxy.js
4
+ * @namespace ForwardProxy
5
+ */
6
+ 'use strict';
7
+
8
+ import http from 'node:http';
9
+ import https from 'node:https';
10
+ import net from 'node:net';
11
+ import os from 'node:os';
12
+ import { environmentValueFactory } from '../runtime/environment.js';
13
+ import { loggerFactory, loggerMiddleware } from '../ops/logger.js';
14
+ import {
15
+ nodeCandidatesFactory,
16
+ nodeProbeCommandFactory,
17
+ scriptProbeCommandFactory,
18
+ systemdServiceCommandsFactory,
19
+ systemdUnitFactory,
20
+ } from '../ops/systemd.js';
21
+
22
+ const logger = loggerFactory(import.meta);
23
+ const proxyLogger = loggerFactory(import.meta, 'debug');
24
+
25
+ /**
26
+ * Forward-proxy defaults, service metadata, and environment variable names.
27
+ * @constant {object}
28
+ * @memberof ForwardProxy
29
+ */
30
+ const FORWARD_PROXY = Object.freeze({
31
+ port: 1080,
32
+ timeoutMs: 30000,
33
+ env: Object.freeze({
34
+ apiKey: 'FORWARD_PROXY_API_KEY',
35
+ host: 'FORWARD_PROXY_HOST',
36
+ port: 'FORWARD_PROXY_PORT',
37
+ }),
38
+ serviceName: 'underpost-forward-proxy',
39
+ unitPath: '/etc/systemd/system/underpost-forward-proxy.service',
40
+ supervisedEnv: 'UNDERPOST_FORWARD_PROXY_SUPERVISED',
41
+ restartSeconds: 5,
42
+ nodePaths: Object.freeze(['/usr/bin/node', '/usr/local/bin/node', '/bin/node']),
43
+ defaultHost: '10.0.0.1',
44
+ });
45
+
46
+ /**
47
+ * Hop-by-hop headers that must not be sent to an upstream server.
48
+ * @constant {Set<string>}
49
+ * @private
50
+ * @memberof ForwardProxy
51
+ */
52
+ const FORWARD_PROXY_HOP_HEADERS = new Set([
53
+ 'connection',
54
+ 'keep-alive',
55
+ 'proxy-authenticate',
56
+ 'proxy-authorization',
57
+ 'proxy-connection',
58
+ 'te',
59
+ 'trailer',
60
+ 'transfer-encoding',
61
+ 'upgrade',
62
+ ]);
63
+
64
+ /**
65
+ * Compares two non-empty values without returning early on a mismatched byte.
66
+ * @method secretEqual
67
+ * @param {*} a - First value.
68
+ * @param {*} b - Second value.
69
+ * @returns {boolean} Whether both values are equal and non-empty.
70
+ * @private
71
+ * @memberof ForwardProxy
72
+ */
73
+ const secretEqual = (a, b) => {
74
+ const left = `${a ?? ''}`;
75
+ const right = `${b ?? ''}`;
76
+ if (left.length !== right.length || left.length === 0) return false;
77
+ let diff = 0;
78
+ for (let index = 0; index < left.length; index++) diff |= left.charCodeAt(index) ^ right.charCodeAt(index);
79
+ return diff === 0;
80
+ };
81
+
82
+ /**
83
+ * Validates a bearer credential against a configured proxy API key.
84
+ * @method forwardProxyAuthorizedFactory
85
+ * @param {{header?: string, apiKey?: string}} [options={}] - Credential inputs.
86
+ * @returns {boolean} Whether the request is authorized.
87
+ * @memberof ForwardProxy
88
+ */
89
+ const forwardProxyAuthorizedFactory = ({ header = '', apiKey = '' } = {}) => {
90
+ const expected = `${apiKey || ''}`.trim();
91
+ if (!expected) return false;
92
+ const match = /^Bearer\s+(.+)$/i.exec(`${header || ''}`.trim());
93
+ return match ? secretEqual(match[1].trim(), expected) : false;
94
+ };
95
+
96
+ /**
97
+ * Parses an absolute HTTP request URI into upstream request options.
98
+ * @method forwardProxyTargetFactory
99
+ * @param {string} requestUrl - Absolute HTTP request URI.
100
+ * @returns {{hostname: string, port: number, host: string, path: string}|null} Parsed target or `null` when invalid.
101
+ * @memberof ForwardProxy
102
+ */
103
+ const forwardProxyTargetFactory = (requestUrl) => {
104
+ try {
105
+ const target = new URL(requestUrl);
106
+ if (target.protocol !== 'http:' || !target.hostname) return null;
107
+ return {
108
+ hostname: target.hostname,
109
+ port: Number(target.port) || 80,
110
+ host: target.host,
111
+ path: `${target.pathname}${target.search}`,
112
+ };
113
+ } catch {
114
+ return null;
115
+ }
116
+ };
117
+
118
+ /**
119
+ * Parses a CONNECT authority into a hostname and TCP port.
120
+ * @method forwardProxyTunnelTargetFactory
121
+ * @param {string} authority - CONNECT authority, optionally including a port.
122
+ * @returns {{hostname: string, port: number}|null} Parsed target or `null` when invalid.
123
+ * @memberof ForwardProxy
124
+ */
125
+ const forwardProxyTunnelTargetFactory = (authority) => {
126
+ const match = /^(\[[^\]]+\]|[^:@/\s]+)(?::(\d+))?$/.exec(`${authority || ''}`.trim());
127
+ if (!match) return null;
128
+ const port = Number(match[2] || 443);
129
+ if (port < 1 || port > 65535) return null;
130
+ return { hostname: match[1].replace(/^\[|\]$/g, ''), port };
131
+ };
132
+
133
+ /**
134
+ * Removes hop-by-hop headers before forwarding a request or response.
135
+ * @method forwardProxyHeadersFactory
136
+ * @param {object} [headers={}] - Header map to filter.
137
+ * @returns {object} Header map safe to forward.
138
+ * @memberof ForwardProxy
139
+ */
140
+ const forwardProxyHeadersFactory = (headers = {}) =>
141
+ Object.fromEntries(
142
+ Object.entries(headers || {}).filter(([name]) => !FORWARD_PROXY_HOP_HEADERS.has(`${name}`.toLowerCase())),
143
+ );
144
+
145
+ /**
146
+ * Resolves proxy connection settings from explicit values and environment variables.
147
+ * @method forwardProxyConfigFactory
148
+ * @param {{host?: string, port?: string|number, apiKey?: string}} [options={}] - Proxy overrides.
149
+ * @returns {{host: string, port: number, apiKey: string}} Resolved proxy configuration.
150
+ * @memberof ForwardProxy
151
+ */
152
+ const forwardProxyConfigFactory = ({ host, port, apiKey } = {}) => ({
153
+ host: `${host || environmentValueFactory(FORWARD_PROXY.env.host)}`.trim() || FORWARD_PROXY.defaultHost,
154
+ port: Number(port || environmentValueFactory(FORWARD_PROXY.env.port)) || FORWARD_PROXY.port,
155
+ apiKey: `${apiKey || environmentValueFactory(FORWARD_PROXY.env.apiKey)}`.trim(),
156
+ });
157
+
158
+ /**
159
+ * Builds the CLI command that starts the forward-proxy server.
160
+ * @method forwardProxyCommandFactory
161
+ * @param {{host?: string, port?: string|number, execPath?: string, scriptPath?: string}} options - Command inputs.
162
+ * @returns {string} Shell command.
163
+ * @memberof ForwardProxy
164
+ */
165
+ const forwardProxyCommandFactory = ({ host, port, execPath = process.execPath, scriptPath = process.argv[1] }) =>
166
+ [
167
+ execPath,
168
+ scriptPath,
169
+ 'wireguard',
170
+ '--forward-proxy-server',
171
+ `--forward-proxy-server-host ${host}`,
172
+ `--forward-proxy-server-port ${port}`,
173
+ ].join(' ');
174
+
175
+ /**
176
+ * Orders Node executable candidates for a systemd service probe.
177
+ * @method forwardProxyNodeCandidatesFactory
178
+ * @param {{execPath?: string, systemPaths?: string[]}} [options={}] - Candidate sources.
179
+ * @returns {string[]} Ordered, unique executable paths.
180
+ * @memberof ForwardProxy
181
+ */
182
+ const forwardProxyNodeCandidatesFactory = ({
183
+ execPath = process.execPath,
184
+ systemPaths = FORWARD_PROXY.nodePaths,
185
+ } = {}) => nodeCandidatesFactory({ execPath, systemPaths });
186
+
187
+ /**
188
+ * Builds a transient systemd command that probes a Node executable.
189
+ * @method forwardProxyNodeProbeCommandFactory
190
+ * @param {string} nodePath - Candidate Node executable path.
191
+ * @param {string} [user] - User that will own the service.
192
+ * @returns {string} systemd-run command.
193
+ * @memberof ForwardProxy
194
+ */
195
+ const forwardProxyNodeProbeCommandFactory = (nodePath, user = os.userInfo().username) =>
196
+ nodeProbeCommandFactory(nodePath, user);
197
+
198
+ /**
199
+ * Builds a transient systemd command that probes the proxy entry script.
200
+ * @method forwardProxyStartProbeCommandFactory
201
+ * @param {{nodePath: string, scriptPath?: string, user?: string, workingDirectory?: string}} options - Probe inputs.
202
+ * @returns {string} systemd-run command.
203
+ * @memberof ForwardProxy
204
+ */
205
+ const forwardProxyStartProbeCommandFactory = ({
206
+ nodePath,
207
+ scriptPath = process.argv[1],
208
+ user = os.userInfo().username,
209
+ workingDirectory = process.cwd(),
210
+ }) => scriptProbeCommandFactory({ nodePath, scriptPath, user, workingDirectory });
211
+
212
+ /**
213
+ * Renders the systemd unit used to supervise the forward proxy.
214
+ * @method forwardProxyUnitFactory
215
+ * @param {{host?: string, port?: string|number, apiKey?: string, interfaceName?: string, workingDirectory?: string, user?: string, command?: string}} [options={}] - Unit inputs.
216
+ * @returns {string} Rendered systemd unit file.
217
+ * @memberof ForwardProxy
218
+ */
219
+ const forwardProxyUnitFactory = ({
220
+ host,
221
+ port,
222
+ apiKey,
223
+ interfaceName = 'wg0',
224
+ workingDirectory = process.cwd(),
225
+ user = os.userInfo().username,
226
+ command,
227
+ } = {}) => {
228
+ const tunnelUnit = `wg-quick@${interfaceName}.service`;
229
+ return systemdUnitFactory({
230
+ header:
231
+ '# Generated by `underpost wireguard --forward-proxy-server`. Do not edit by\n' +
232
+ '# hand: the next run rewrites the file and restarts the service.',
233
+ sections: {
234
+ Unit: {
235
+ Description: `Underpost edge forward proxy on ${host}:${port}`,
236
+ Documentation: 'https://www.nexodev.org/docs',
237
+ After: `network-online.target ${tunnelUnit}`,
238
+ Wants: 'network-online.target',
239
+ Requires: tunnelUnit,
240
+ PartOf: tunnelUnit,
241
+ StartLimitIntervalSec: 0,
242
+ },
243
+ Service: {
244
+ Type: 'simple',
245
+ User: user,
246
+ WorkingDirectory: workingDirectory,
247
+ Environment: [`${FORWARD_PROXY.supervisedEnv}=1`, `${FORWARD_PROXY.env.apiKey}=${apiKey}`],
248
+ ExecStart: command || forwardProxyCommandFactory({ host, port }),
249
+ Restart: 'always',
250
+ RestartSec: FORWARD_PROXY.restartSeconds,
251
+ },
252
+ Install: { WantedBy: `multi-user.target ${tunnelUnit}` },
253
+ },
254
+ });
255
+ };
256
+
257
+ /**
258
+ * Builds lifecycle commands for the forward-proxy systemd service.
259
+ * @method forwardProxyServiceCommandsFactory
260
+ * @param {{changed?: boolean, name?: string, unitPath?: string}} [options={}] - Service state inputs.
261
+ * @returns {{ensure: string[], remove: string[]}} Commands grouped by lifecycle operation.
262
+ * @memberof ForwardProxy
263
+ */
264
+ const forwardProxyServiceCommandsFactory = ({
265
+ changed = false,
266
+ name = FORWARD_PROXY.serviceName,
267
+ unitPath = FORWARD_PROXY.unitPath,
268
+ } = {}) => systemdServiceCommandsFactory({ changed, name, unitPath });
269
+
270
+ /**
271
+ * Sends a plain-text proxy refusal response.
272
+ * @method forwardProxyRefuse
273
+ * @param {import('node:http').ServerResponse} res - Response to close.
274
+ * @param {number} status - HTTP status code.
275
+ * @param {string} message - Response body message.
276
+ * @returns {void}
277
+ * @private
278
+ * @memberof ForwardProxy
279
+ */
280
+ const forwardProxyRefuse = (res, status, message) => {
281
+ res.writeHead(status, {
282
+ 'content-type': 'text/plain',
283
+ ...(status === 407 ? { 'proxy-authenticate': 'Bearer realm="underpost-forward-proxy"' } : {}),
284
+ });
285
+ res.end(`${message}\n`);
286
+ };
287
+
288
+ /**
289
+ * Creates an HTTP request handler that relays authenticated proxy traffic.
290
+ * @method forwardProxyRequestHandlerFactory
291
+ * @param {{apiKey: string, timeoutMs?: number}} options - Authentication and timeout settings.
292
+ * @returns {Function} Node HTTP request handler.
293
+ * @memberof ForwardProxy
294
+ */
295
+ const forwardProxyRequestHandlerFactory = ({ apiKey, timeoutMs = FORWARD_PROXY.timeoutMs }) =>
296
+ function forwardProxyRequestHandler(req, res) {
297
+ if (!forwardProxyAuthorizedFactory({ header: req.headers['proxy-authorization'], apiKey }))
298
+ return void forwardProxyRefuse(res, 407, 'proxy authentication required');
299
+ const target = forwardProxyTargetFactory(req.url);
300
+ if (!target)
301
+ return void forwardProxyRefuse(res, 400, 'an absolute http:// request-URI is required; use CONNECT for https');
302
+
303
+ const upstream = http.request(
304
+ {
305
+ host: target.hostname,
306
+ port: target.port,
307
+ method: req.method,
308
+ path: target.path,
309
+ headers: { ...forwardProxyHeadersFactory(req.headers), host: target.host },
310
+ timeout: timeoutMs,
311
+ },
312
+ (upstreamRes) => {
313
+ res.writeHead(upstreamRes.statusCode, forwardProxyHeadersFactory(upstreamRes.headers));
314
+ upstreamRes.pipe(res);
315
+ },
316
+ );
317
+ upstream.on('timeout', () => upstream.destroy(new Error('upstream timed out')));
318
+ upstream.on('error', (error) => {
319
+ logger.warn('Forward proxy upstream failed', { target: `${target.host}${target.path}`, message: error.message });
320
+ if (res.headersSent) res.destroy();
321
+ else forwardProxyRefuse(res, 502, 'upstream request failed');
322
+ });
323
+ res.on('close', () => upstream.destroy());
324
+ req.pipe(upstream);
325
+ };
326
+
327
+ /**
328
+ * Creates a CONNECT handler that relays authenticated TLS tunnels.
329
+ * @method forwardProxyConnectHandlerFactory
330
+ * @param {{apiKey: string, timeoutMs?: number}} options - Authentication and timeout settings.
331
+ * @returns {Function} Node HTTP CONNECT handler.
332
+ * @memberof ForwardProxy
333
+ */
334
+ const forwardProxyConnectHandlerFactory = ({ apiKey, timeoutMs = FORWARD_PROXY.timeoutMs }) =>
335
+ function forwardProxyConnectHandler(req, clientSocket, head) {
336
+ const startedAt = Date.now();
337
+ const log = (status, bytes = '-') =>
338
+ proxyLogger.http(
339
+ `${clientSocket.remoteAddress || '-'} CONNECT ${req.url} ${status} ${bytes} - ${Date.now() - startedAt} ms`,
340
+ );
341
+ const reject = (status, reason) => {
342
+ log(status);
343
+ clientSocket.end(`HTTP/1.1 ${status} ${reason}\r\n\r\n`);
344
+ };
345
+ if (!forwardProxyAuthorizedFactory({ header: req.headers['proxy-authorization'], apiKey }))
346
+ return void reject(407, 'Proxy Authentication Required');
347
+ const target = forwardProxyTunnelTargetFactory(req.url);
348
+ if (!target) return void reject(400, 'Bad Request');
349
+
350
+ let established = false;
351
+ const upstream = net.connect(target.port, target.hostname, () => {
352
+ established = true;
353
+ clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
354
+ if (head && head.length > 0) upstream.write(head);
355
+ upstream.pipe(clientSocket);
356
+ clientSocket.pipe(upstream);
357
+ });
358
+ upstream.setTimeout(timeoutMs, () => upstream.destroy());
359
+ upstream.on('error', (error) => {
360
+ logger.warn('Forward proxy tunnel failed', {
361
+ target: `${target.hostname}:${target.port}`,
362
+ message: error.message,
363
+ });
364
+ if (established || clientSocket.writableEnded || clientSocket.destroyed) clientSocket.destroy();
365
+ else reject(502, 'Bad Gateway');
366
+ });
367
+ upstream.on('close', () => {
368
+ if (established) log(200, upstream.bytesRead + upstream.bytesWritten);
369
+ });
370
+ clientSocket.on('error', () => upstream.destroy());
371
+ clientSocket.on('close', () => upstream.destroy());
372
+ };
373
+
374
+ /**
375
+ * Resolves a completed client request to its buffered proxy response.
376
+ * @method forwardProxyResponseFactory
377
+ * @param {{request: import('node:http').ClientRequest, body?: string|null, timeoutMs: number}} options - Request inputs.
378
+ * @returns {Promise<{status: number|undefined, headers: object, body: string}>} Buffered response.
379
+ * @private
380
+ * @memberof ForwardProxy
381
+ */
382
+ const forwardProxyResponseFactory = ({ request, body = null, timeoutMs }) =>
383
+ new Promise((resolve, reject) => {
384
+ request.once('response', (res) => {
385
+ const chunks = [];
386
+ res.on('data', (chunk) => chunks.push(chunk));
387
+ res.once('error', reject);
388
+ res.once('end', () =>
389
+ resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks).toString('utf8') }),
390
+ );
391
+ });
392
+ request.once('error', reject);
393
+ request.setTimeout(timeoutMs, () => request.destroy(new Error(`request timed out after ${timeoutMs}ms`)));
394
+ if (body !== null) request.write(body);
395
+ request.end();
396
+ });
397
+
398
+ /**
399
+ * Opens an authenticated CONNECT tunnel through the configured proxy.
400
+ * @method forwardProxyTunnelFactory
401
+ * @param {{proxy: {host: string, port: number, apiKey: string}, authority: string, timeoutMs: number}} options - Tunnel inputs.
402
+ * @returns {Promise<import('node:net').Socket>} Connected tunnel socket.
403
+ * @private
404
+ * @memberof ForwardProxy
405
+ */
406
+ const forwardProxyTunnelFactory = ({ proxy, authority, timeoutMs }) =>
407
+ new Promise((resolve, reject) => {
408
+ const request = http.request({
409
+ host: proxy.host,
410
+ port: proxy.port,
411
+ method: 'CONNECT',
412
+ path: authority,
413
+ headers: { host: authority, 'proxy-authorization': `Bearer ${proxy.apiKey}` },
414
+ agent: false,
415
+ });
416
+ const refused = (status) => reject(new Error(`[forward-proxy] proxy refused CONNECT ${authority} (${status})`));
417
+ request.once('connect', (res, socket) => {
418
+ if (res.statusCode !== 200) {
419
+ socket.destroy();
420
+ refused(res.statusCode);
421
+ return;
422
+ }
423
+ socket.setTimeout(0);
424
+ resolve(socket);
425
+ });
426
+ request.once('response', (res) => {
427
+ res.resume();
428
+ refused(res.statusCode);
429
+ });
430
+ request.once('error', reject);
431
+ request.setTimeout(timeoutMs, () => request.destroy(new Error('[forward-proxy] CONNECT timed out')));
432
+ request.end();
433
+ });
434
+
435
+ /**
436
+ * Starts an authenticated HTTP forward-proxy server.
437
+ * @method forwardProxyServerFactory
438
+ * @param {{config: {host: string, port: number, apiKey: string, timeoutMs?: number}, requestMiddleware?: Function, onError?: Function, onListen?: Function}} [options={}] - Server configuration and hooks.
439
+ * @returns {import('node:http').Server} Listening proxy server.
440
+ * @memberof ForwardProxy
441
+ */
442
+ const forwardProxyServerFactory = ({ config, requestMiddleware, onError, onListen } = {}) => {
443
+ const server = http.createServer();
444
+ const relay = forwardProxyRequestHandlerFactory(config);
445
+ const middleware = requestMiddleware || loggerMiddleware(import.meta, 'debug', () => false);
446
+ server.on('request', (req, res) => middleware(req, res, () => relay(req, res)));
447
+ server.on('connect', forwardProxyConnectHandlerFactory(config));
448
+ server.on('clientError', (_error, socket) => {
449
+ if (!socket.destroyed) socket.destroy();
450
+ });
451
+ if (onError) server.on('error', onError);
452
+ server.listen(config.port, config.host, onListen);
453
+ return server;
454
+ };
455
+
456
+ /**
457
+ * Fetches an HTTP or HTTPS resource through the authenticated forward proxy.
458
+ * @async
459
+ * @method fetchViaForwardProxy
460
+ * @param {string|URL} url - Target resource URL.
461
+ * @param {{proxy?: {host?: string, port?: string|number, apiKey?: string}, method?: string, timeout?: number, body?: *, headers?: object}} [options={}] - Request options.
462
+ * @returns {Promise<{status: number|undefined, headers: object, body: string}>} Buffered upstream response.
463
+ * @memberof ForwardProxy
464
+ */
465
+ const fetchViaForwardProxy = async (url, options = {}) => {
466
+ const target = new URL(url);
467
+ const proxy = forwardProxyConfigFactory(options.proxy);
468
+ if (!proxy.apiKey)
469
+ throw new Error(`[forward-proxy] ${FORWARD_PROXY.env.apiKey} is not set; the proxy cannot be used without it`);
470
+ const method = `${options.method || 'GET'}`.toUpperCase();
471
+ const timeoutMs = Number(options.timeout) > 0 ? Number(options.timeout) : FORWARD_PROXY.timeoutMs;
472
+ const body =
473
+ options.body === undefined || options.body === null
474
+ ? null
475
+ : typeof options.body === 'string'
476
+ ? options.body
477
+ : JSON.stringify(options.body);
478
+ const headers = {
479
+ host: target.host,
480
+ ...(body === null ? {} : { 'content-length': `${Buffer.byteLength(body)}` }),
481
+ ...(options.headers || {}),
482
+ };
483
+
484
+ if (target.protocol === 'http:')
485
+ return await forwardProxyResponseFactory({
486
+ request: http.request({
487
+ host: proxy.host,
488
+ port: proxy.port,
489
+ method,
490
+ path: target.href,
491
+ headers: { ...headers, 'proxy-authorization': `Bearer ${proxy.apiKey}` },
492
+ agent: false,
493
+ }),
494
+ body,
495
+ timeoutMs,
496
+ });
497
+ if (target.protocol !== 'https:')
498
+ throw new Error(`[forward-proxy] fetch supports http: and https: targets only, not ${target.protocol}`);
499
+
500
+ const port = Number(target.port) || 443;
501
+ const socket = await forwardProxyTunnelFactory({ proxy, authority: `${target.hostname}:${port}`, timeoutMs });
502
+ const agent = new https.Agent({ keepAlive: false, maxSockets: 1 });
503
+ const createConnection = agent.createConnection.bind(agent);
504
+ agent.createConnection = (connectOptions, callback) =>
505
+ createConnection({ ...connectOptions, socket, servername: target.hostname }, callback);
506
+ try {
507
+ return await forwardProxyResponseFactory({
508
+ request: https.request({
509
+ host: target.hostname,
510
+ port,
511
+ method,
512
+ path: `${target.pathname}${target.search}`,
513
+ headers,
514
+ agent,
515
+ }),
516
+ body,
517
+ timeoutMs,
518
+ });
519
+ } finally {
520
+ socket.destroy();
521
+ }
522
+ };
523
+
524
+ export {
525
+ FORWARD_PROXY,
526
+ fetchViaForwardProxy,
527
+ forwardProxyAuthorizedFactory,
528
+ forwardProxyCommandFactory,
529
+ forwardProxyConfigFactory,
530
+ forwardProxyConnectHandlerFactory,
531
+ forwardProxyHeadersFactory,
532
+ forwardProxyNodeCandidatesFactory,
533
+ forwardProxyNodeProbeCommandFactory,
534
+ forwardProxyRequestHandlerFactory,
535
+ forwardProxyServerFactory,
536
+ forwardProxyServiceCommandsFactory,
537
+ forwardProxyStartProbeCommandFactory,
538
+ forwardProxyTargetFactory,
539
+ forwardProxyTunnelTargetFactory,
540
+ forwardProxyUnitFactory,
541
+ };
@@ -1,19 +1,23 @@
1
1
  /**
2
2
  * Express middleware and controller/router helpers for engine APIs.
3
3
  *
4
- * @module src/server/middlewares.js
4
+ * @module src/server/network/middlewares.js
5
+ * @namespace Middlewares
5
6
  */
6
7
 
7
- import { loggerFactory } from './logger.js';
8
- import { moderatorGuard, adminGuard } from './auth.js';
8
+ import { loggerFactory } from '../ops/logger.js';
9
+ import { moderatorGuard, adminGuard } from '../security/auth.js';
9
10
 
10
11
  const logger = loggerFactory(import.meta);
11
12
 
12
13
  /**
13
14
  * The public-read CORS policy: reflect the request origin (or allow any)
14
15
  * and mark the resource embeddable cross-origin.
16
+ * @method setCrossOriginHeaders
15
17
  * @param {import('express').Request} req
16
18
  * @param {import('express').Response} res
19
+ * @returns {void}
20
+ * @memberof Middlewares
17
21
  */
18
22
  const setCrossOriginHeaders = (req, res) => {
19
23
  if (req && req.headers && req.headers.origin) res.set('Access-Control-Allow-Origin', req.headers.origin);
@@ -21,7 +25,15 @@ const setCrossOriginHeaders = (req, res) => {
21
25
  res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
22
26
  };
23
27
 
24
- /** Express middleware form of {@link setCrossOriginHeaders}. */
28
+ /**
29
+ * Express middleware form of {@link setCrossOriginHeaders}.
30
+ * @method crossOriginMiddleware
31
+ * @param {import('express').Request} req
32
+ * @param {import('express').Response} res
33
+ * @param {import('express').NextFunction} next
34
+ * @returns {void}
35
+ * @memberof Middlewares
36
+ */
25
37
  const crossOriginMiddleware = (req, res, next) => {
26
38
  setCrossOriginHeaders(req, res);
27
39
  next();
@@ -31,7 +43,10 @@ const crossOriginMiddleware = (req, res, next) => {
31
43
  * Shallow request copy with `page`/`limit` parsed to integers.
32
44
  * `path` and `params` are copied explicitly because spreading an Express
33
45
  * request drops prototype getters.
46
+ * @method withParsedPagination
34
47
  * @param {import('express').Request} req
48
+ * @returns {import('express').Request} Request-like object with parsed pagination.
49
+ * @memberof Middlewares
35
50
  */
36
51
  const withParsedPagination = (req) => {
37
52
  const { page, limit } = req.query;
@@ -43,15 +58,35 @@ const withParsedPagination = (req) => {
43
58
  };
44
59
  };
45
60
 
61
+ /**
62
+ * Sends the standard success response envelope.
63
+ * @method sendSuccess
64
+ * @param {import('express').Response} res
65
+ * @param {*} data - Response payload.
66
+ * @returns {import('express').Response} JSON response.
67
+ * @memberof Middlewares
68
+ */
46
69
  const sendSuccess = (res, data) => res.status(200).json({ status: 'success', data });
47
70
 
71
+ /**
72
+ * Sends the standard error response envelope.
73
+ * @method sendError
74
+ * @param {import('express').Response} res
75
+ * @param {Error} error - Error to expose.
76
+ * @param {number} [status=400] - HTTP status code.
77
+ * @returns {import('express').Response} JSON response.
78
+ * @memberof Middlewares
79
+ */
48
80
  const sendError = (res, error, status = 400) => res.status(status).json({ status: 'error', message: error.message });
49
81
 
50
82
  /**
51
83
  * Binary response with cross-origin and content headers.
84
+ * @method sendBlob
52
85
  * @param {import('express').Request} req
53
86
  * @param {import('express').Response} res
54
87
  * @param {{ buffer: Buffer, mimetype: string, filename: string, disposition?: 'inline'|'attachment' }} blob
88
+ * @returns {import('express').Response} Completed binary response.
89
+ * @memberof Middlewares
55
90
  */
56
91
  const sendBlob = (req, res, { buffer, mimetype, filename, disposition = 'inline' }) => {
57
92
  setCrossOriginHeaders(req, res);
@@ -63,8 +98,11 @@ const sendBlob = (req, res, { buffer, mimetype, filename, disposition = 'inline'
63
98
 
64
99
  /**
65
100
  * Wraps a controller body with error logging and the error response envelope.
101
+ * @method controllerHandler
66
102
  * @param {(req, res, options) => Promise<any>} fn
67
103
  * @param {{ errorStatus?: number }} [config]
104
+ * @returns {Function} Async Express-compatible controller handler.
105
+ * @memberof Middlewares
68
106
  */
69
107
  const controllerHandler =
70
108
  (fn, { errorStatus = 400 } = {}) =>
@@ -80,8 +118,11 @@ const controllerHandler =
80
118
  /**
81
119
  * Builds a controller method that delegates to a service method and wraps the
82
120
  * result in the success envelope.
121
+ * @method serviceHandler
83
122
  * @param {(req, res, options) => Promise<any>} serviceFn
84
123
  * @param {{ errorStatus?: number, crossOrigin?: boolean, pagination?: boolean }} [config]
124
+ * @returns {Function} Async Express-compatible controller handler.
125
+ * @memberof Middlewares
85
126
  */
86
127
  const serviceHandler = (serviceFn, { errorStatus = 400, crossOrigin = false, pagination = false } = {}) =>
87
128
  controllerHandler(
@@ -96,14 +137,26 @@ const serviceHandler = (serviceFn, { errorStatus = 400, crossOrigin = false, pag
96
137
  /**
97
138
  * Builds a standard CRUD controller class (static post/get/put/delete) from a
98
139
  * service exposing the same methods. `get` parses pagination.
140
+ * @method buildCrudController
99
141
  * @param {{ post, get, put, delete }} service
100
142
  * @param {Object<string, Function>} [extend] - Extra or overriding static handlers.
143
+ * @returns {Function} CRUD controller class.
144
+ * @memberof Middlewares
101
145
  */
102
146
  const buildCrudController = (service, extend = {}) => {
147
+ /**
148
+ * Generated controller namespace containing static CRUD handlers.
149
+ * @class CrudController
150
+ * @memberof Middlewares
151
+ */
103
152
  class CrudController {
153
+ /** @static @memberof Middlewares */
104
154
  static post = serviceHandler(service.post);
155
+ /** @static @memberof Middlewares */
105
156
  static get = serviceHandler(service.get, { pagination: true });
157
+ /** @static @memberof Middlewares */
106
158
  static put = serviceHandler(service.put);
159
+ /** @static @memberof Middlewares */
107
160
  static delete = serviceHandler(service.delete);
108
161
  }
109
162
  Object.assign(CrudController, extend);
@@ -115,6 +168,7 @@ const buildCrudController = (service, extend = {}) => {
115
168
  * public reads, moderator-guarded writes, admin-guarded collection delete.
116
169
  * Custom routes must be registered before calling this (generic `/:id` routes
117
170
  * capture everything).
171
+ * @method registerCrudRoutes
118
172
  * @param {import('express').Router} router
119
173
  * @param {{ post, get, put, delete }} Controller
120
174
  * @param {import('../../api/types.js').RouterOptions} options
@@ -122,6 +176,7 @@ const buildCrudController = (service, extend = {}) => {
122
176
  * Pass empty arrays for unguarded endpoints (e.g. player-written progress)
123
177
  * or explicit guard chains (e.g. admin-only reads).
124
178
  * @returns {import('express').Router}
179
+ * @memberof Middlewares
125
180
  */
126
181
  const registerCrudRoutes = (router, Controller, options, { readGuards = [], writeGuards, deleteAllGuards } = {}) => {
127
182
  const write = writeGuards ?? [options.authMiddleware, moderatorGuard];