@aopslab/aops-server 0.1.0

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 (185) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +21 -0
  4. package/aops-server-runtime.json +11 -0
  5. package/build/client/_app/immutable/chunks/2_TffmqH.js +1 -0
  6. package/build/client/_app/immutable/chunks/3DOfIKW0.js +1 -0
  7. package/build/client/_app/immutable/chunks/B2EkC-sy.js +1 -0
  8. package/build/client/_app/immutable/chunks/CMkIcmi1.js +2 -0
  9. package/build/client/_app/immutable/chunks/rqnVXeyB.js +1 -0
  10. package/build/client/_app/immutable/chunks/sZYZbj9W.js +1 -0
  11. package/build/client/_app/immutable/chunks/wbPk3Yxo.js +81 -0
  12. package/build/client/_app/immutable/entry/app.C7STLTX8.js +2 -0
  13. package/build/client/_app/immutable/entry/start.BC94hOaC.js +1 -0
  14. package/build/client/_app/immutable/nodes/0.BYBfjxZ5.js +1 -0
  15. package/build/client/_app/immutable/nodes/1.BNXFXajG.js +1 -0
  16. package/build/client/_app/version.json +1 -0
  17. package/build/env.js +70 -0
  18. package/build/env.js.map +1 -0
  19. package/build/handler.js +16 -0
  20. package/build/handler.js.map +1 -0
  21. package/build/index.js +139 -0
  22. package/build/index.js.map +1 -0
  23. package/build/server/chunks/0-RfB6m2nq.js +7 -0
  24. package/build/server/chunks/0-RfB6m2nq.js.map +1 -0
  25. package/build/server/chunks/1-_gg8Q0UT.js +9 -0
  26. package/build/server/chunks/1-_gg8Q0UT.js.map +1 -0
  27. package/build/server/chunks/_server.ts-6276EQIF.js +44 -0
  28. package/build/server/chunks/_server.ts-6276EQIF.js.map +1 -0
  29. package/build/server/chunks/_server.ts-B59WFQzX.js +174 -0
  30. package/build/server/chunks/_server.ts-B59WFQzX.js.map +1 -0
  31. package/build/server/chunks/_server.ts-BauJsLZI.js +40 -0
  32. package/build/server/chunks/_server.ts-BauJsLZI.js.map +1 -0
  33. package/build/server/chunks/_server.ts-BnGzJw2-.js +81 -0
  34. package/build/server/chunks/_server.ts-BnGzJw2-.js.map +1 -0
  35. package/build/server/chunks/_server.ts-CP7qM24V.js +34 -0
  36. package/build/server/chunks/_server.ts-CP7qM24V.js.map +1 -0
  37. package/build/server/chunks/_server.ts-Cbq_u99d.js +9 -0
  38. package/build/server/chunks/_server.ts-Cbq_u99d.js.map +1 -0
  39. package/build/server/chunks/_server.ts-Ch8fPT-o.js +92 -0
  40. package/build/server/chunks/_server.ts-Ch8fPT-o.js.map +1 -0
  41. package/build/server/chunks/_server.ts-Cv-yaXZI.js +7 -0
  42. package/build/server/chunks/_server.ts-Cv-yaXZI.js.map +1 -0
  43. package/build/server/chunks/_server.ts-D6lBwOON.js +34 -0
  44. package/build/server/chunks/_server.ts-D6lBwOON.js.map +1 -0
  45. package/build/server/chunks/_server.ts-D84K3pp9.js +11 -0
  46. package/build/server/chunks/_server.ts-D84K3pp9.js.map +1 -0
  47. package/build/server/chunks/_server.ts-DNzU1ToA.js +277 -0
  48. package/build/server/chunks/_server.ts-DNzU1ToA.js.map +1 -0
  49. package/build/server/chunks/_server.ts-DQQnw77t.js +17 -0
  50. package/build/server/chunks/_server.ts-DQQnw77t.js.map +1 -0
  51. package/build/server/chunks/_server.ts-DSxr5l15.js +18 -0
  52. package/build/server/chunks/_server.ts-DSxr5l15.js.map +1 -0
  53. package/build/server/chunks/_server.ts-DZYokvuR.js +80 -0
  54. package/build/server/chunks/_server.ts-DZYokvuR.js.map +1 -0
  55. package/build/server/chunks/_server.ts-Dcqwpz2t.js +144 -0
  56. package/build/server/chunks/_server.ts-Dcqwpz2t.js.map +1 -0
  57. package/build/server/chunks/_server.ts-Di9VJnF4.js +81 -0
  58. package/build/server/chunks/_server.ts-Di9VJnF4.js.map +1 -0
  59. package/build/server/chunks/_server.ts-FBEHFT8b.js +33 -0
  60. package/build/server/chunks/_server.ts-FBEHFT8b.js.map +1 -0
  61. package/build/server/chunks/_server.ts-gZytCuiJ.js +10 -0
  62. package/build/server/chunks/_server.ts-gZytCuiJ.js.map +1 -0
  63. package/build/server/chunks/_server.ts-tNULLyEO.js +261 -0
  64. package/build/server/chunks/_server.ts-tNULLyEO.js.map +1 -0
  65. package/build/server/chunks/agent-gateway-runtime-DTZ4SxaC.js +2598 -0
  66. package/build/server/chunks/agent-gateway-runtime-DTZ4SxaC.js.map +1 -0
  67. package/build/server/chunks/aops-runtime-config-wpVmg7T-.js +179 -0
  68. package/build/server/chunks/aops-runtime-config-wpVmg7T-.js.map +1 -0
  69. package/build/server/chunks/discovery-D9JnSgfu.js +894 -0
  70. package/build/server/chunks/discovery-D9JnSgfu.js.map +1 -0
  71. package/build/server/chunks/error.svelte-DgUOqi-L.js +45 -0
  72. package/build/server/chunks/error.svelte-DgUOqi-L.js.map +1 -0
  73. package/build/server/chunks/handler-BN3-p5rP.js +1733 -0
  74. package/build/server/chunks/handler-BN3-p5rP.js.map +1 -0
  75. package/build/server/chunks/hooks.server-C4wxiH5m.js +52 -0
  76. package/build/server/chunks/hooks.server-C4wxiH5m.js.map +1 -0
  77. package/build/server/chunks/index.js-nbqdVuWC.js +10698 -0
  78. package/build/server/chunks/index.js-nbqdVuWC.js.map +1 -0
  79. package/build/server/chunks/manifest.js-_Re-Wj-y.js +167 -0
  80. package/build/server/chunks/manifest.js-_Re-Wj-y.js.map +1 -0
  81. package/build/server/chunks/plugin-error-normalize-Bq14Qbz3.js +297 -0
  82. package/build/server/chunks/plugin-error-normalize-Bq14Qbz3.js.map +1 -0
  83. package/build/server/chunks/project-alias-DqE2v58Q.js +29 -0
  84. package/build/server/chunks/project-alias-DqE2v58Q.js.map +1 -0
  85. package/build/server/chunks/registry-5amIZito.js +289 -0
  86. package/build/server/chunks/registry-5amIZito.js.map +1 -0
  87. package/build/server/chunks/request-base-url-BDdrsaga.js +24 -0
  88. package/build/server/chunks/request-base-url-BDdrsaga.js.map +1 -0
  89. package/build/server/chunks/request-context-tNeFQjgb.js +422 -0
  90. package/build/server/chunks/request-context-tNeFQjgb.js.map +1 -0
  91. package/build/server/chunks/xf-result-BRDjvftr.js +17 -0
  92. package/build/server/chunks/xf-result-BRDjvftr.js.map +1 -0
  93. package/build/shims.js +33 -0
  94. package/build/shims.js.map +1 -0
  95. package/cockpit/.vite/manifest.json +198 -0
  96. package/cockpit/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
  97. package/cockpit/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
  98. package/cockpit/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
  99. package/cockpit/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
  100. package/cockpit/assets/dm-sans-latin-700-normal-CUSSCpQX.woff +0 -0
  101. package/cockpit/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 +0 -0
  102. package/cockpit/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff +0 -0
  103. package/cockpit/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 +0 -0
  104. package/cockpit/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 +0 -0
  105. package/cockpit/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff +0 -0
  106. package/cockpit/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff +0 -0
  107. package/cockpit/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2 +0 -0
  108. package/cockpit/assets/index-DDswO4B1.css +1 -0
  109. package/cockpit/assets/index-bovtnZ98.js +18 -0
  110. package/cockpit/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  111. package/cockpit/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  112. package/cockpit/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  113. package/cockpit/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  114. package/cockpit/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  115. package/cockpit/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  116. package/cockpit/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  117. package/cockpit/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  118. package/cockpit/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  119. package/cockpit/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  120. package/cockpit/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  121. package/cockpit/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  122. package/cockpit/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  123. package/cockpit/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
  124. package/cockpit/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
  125. package/cockpit/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
  126. package/cockpit/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
  127. package/cockpit/assets/unbounded-cyrillic-600-normal-B-s5c2Kn.woff +0 -0
  128. package/cockpit/assets/unbounded-cyrillic-600-normal-DW6uzIMB.woff2 +0 -0
  129. package/cockpit/assets/unbounded-latin-600-normal-BPdB_PEv.woff +0 -0
  130. package/cockpit/assets/unbounded-latin-600-normal-oRSANpZr.woff2 +0 -0
  131. package/cockpit/assets/unbounded-latin-ext-600-normal-0iZLtTDV.woff2 +0 -0
  132. package/cockpit/assets/unbounded-latin-ext-600-normal-ClMPf-bu.woff +0 -0
  133. package/cockpit/assets/unbounded-vietnamese-600-normal-BzfrgZ_C.woff +0 -0
  134. package/cockpit/assets/unbounded-vietnamese-600-normal-Cx2bTSHN.woff2 +0 -0
  135. package/cockpit/community.module-inventory.json +2389 -0
  136. package/cockpit/index.html +14 -0
  137. package/community-postgres.json +13 -0
  138. package/migrations/agentspace/0000_thin_carlie_cooper.sql +764 -0
  139. package/migrations/agentspace/meta/0000_snapshot.json +6885 -0
  140. package/migrations/agentspace/meta/_journal.json +13 -0
  141. package/migrations/chatv3/0000_dry_ego.sql +218 -0
  142. package/migrations/chatv3/0001_nosy_obadiah_stane.sql +35 -0
  143. package/migrations/chatv3/0002_narrow_darkhawk.sql +4 -0
  144. package/migrations/chatv3/0003_damp_zarek.sql +2 -0
  145. package/migrations/chatv3/0004_nervous_martin_li.sql +60 -0
  146. package/migrations/chatv3/0005_chatv3_server_encrypted_mode.sql +1 -0
  147. package/migrations/chatv3/meta/0000_snapshot.json +1887 -0
  148. package/migrations/chatv3/meta/0001_snapshot.json +2175 -0
  149. package/migrations/chatv3/meta/0002_snapshot.json +2229 -0
  150. package/migrations/chatv3/meta/0003_snapshot.json +2241 -0
  151. package/migrations/chatv3/meta/0004_snapshot.json +2746 -0
  152. package/migrations/chatv3/meta/_journal.json +48 -0
  153. package/migrations/docman/0000_curvy_valeria_richards.sql +257 -0
  154. package/migrations/docman/0001_massive_risque.sql +2 -0
  155. package/migrations/docman/0002_simple_logan.sql +50 -0
  156. package/migrations/docman/0003_quick_index.sql +45 -0
  157. package/migrations/docman/0004_known_summary.sql +9 -0
  158. package/migrations/docman/0005_vector_signal.sql +9 -0
  159. package/migrations/docman/0006_document_version_current_invariant.sql +15 -0
  160. package/migrations/docman/meta/_journal.json +55 -0
  161. package/migrations/projectman/0000_smooth_klaw.sql +257 -0
  162. package/migrations/projectman/0001_worthless_zodiak.sql +9 -0
  163. package/migrations/projectman/0002_board_rename.sql +108 -0
  164. package/migrations/projectman/0003_review_request.sql +62 -0
  165. package/migrations/projectman/0004_review_request_idempotency.sql +3 -0
  166. package/migrations/projectman/0005_allow_multi_sprint_per_task.sql +1 -0
  167. package/migrations/projectman/meta/_journal.json +48 -0
  168. package/migrations/sys/0000_loose_praxagora.sql +31 -0
  169. package/migrations/sys/0001_awesome_ronan.sql +2 -0
  170. package/migrations/sys/0002_steady_maximus.sql +2 -0
  171. package/migrations/sys/0003_escalating_rate_limiter.sql +2 -0
  172. package/migrations/sys/0004_rate_limiter_timestamptz.sql +22 -0
  173. package/migrations/sys/0005_sys_counters.sql +21 -0
  174. package/migrations/sys/meta/_journal.json +48 -0
  175. package/npm-shrinkwrap.json +4375 -0
  176. package/package.json +62 -0
  177. package/runtime/agentspace-host-adapter.mjs +64 -0
  178. package/runtime/agentspace-tooling.mjs +389 -0
  179. package/runtime/docman-host-adapter.mjs +177 -0
  180. package/runtime/docman-policy.mjs +450 -0
  181. package/runtime/docman-tooling.mjs +20 -0
  182. package/runtime/projectman-host-adapter.mjs +597 -0
  183. package/runtime/scope-context.mjs +99 -0
  184. package/scripts/community-host.mjs +626 -0
  185. package/scripts/community-migration-policy-v1.json +296 -0
@@ -0,0 +1,626 @@
1
+ import {
2
+ closeSync,
3
+ constants as fsConstants,
4
+ createReadStream,
5
+ existsSync,
6
+ fstatSync,
7
+ lstatSync,
8
+ openSync,
9
+ realpathSync,
10
+ } from "node:fs";
11
+ import { createServer, request as httpRequest } from "node:http";
12
+ import path from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+
15
+ export const COMMUNITY_HOST_MODES = Object.freeze({
16
+ native: "direct-loopback",
17
+ oci: "container-loopback-proxy",
18
+ });
19
+
20
+ const NATIVE_EDGE_HOST = "127.0.0.1";
21
+ const OCI_EDGE_HOST = "0.0.0.0";
22
+ const INTERNAL_HOST = "127.0.0.1";
23
+ const DEFAULT_EDGE_PORT = "5900";
24
+ const DEFAULT_OCI_INTERNAL_PORT = "5901";
25
+ const PACKAGE_STATIC_ROOT = path.resolve(import.meta.dirname, "../cockpit");
26
+ const CHECKOUT_STATIC_ROOT = path.resolve(import.meta.dirname, "../../aops-cockpit-v2/dist");
27
+ const DEFAULT_PATHS = Object.freeze({
28
+ staticRoot: existsSync(path.join(PACKAGE_STATIC_ROOT, "index.html"))
29
+ ? PACKAGE_STATIC_ROOT
30
+ : CHECKOUT_STATIC_ROOT,
31
+ handlerEntry: path.resolve(import.meta.dirname, "../build/handler.js"),
32
+ });
33
+ const FORBIDDEN_PROXY_ENV = Object.freeze([
34
+ "ADDRESS_HEADER",
35
+ "XFF_DEPTH",
36
+ "PROTOCOL_HEADER",
37
+ "HOST_HEADER",
38
+ ]);
39
+ const BASE_HOP_BY_HOP_HEADERS = new Set([
40
+ "connection",
41
+ "keep-alive",
42
+ "proxy-authenticate",
43
+ "proxy-authorization",
44
+ "proxy-connection",
45
+ "te",
46
+ "trailer",
47
+ "transfer-encoding",
48
+ "upgrade",
49
+ ]);
50
+ const FORBIDDEN_INBOUND_HEADERS = new Set([
51
+ "cookie",
52
+ "forwarded",
53
+ "host",
54
+ "origin",
55
+ "proxy-connection",
56
+ "referer",
57
+ "via",
58
+ ]);
59
+ const FORBIDDEN_RESPONSE_HEADERS = new Set([
60
+ "set-cookie",
61
+ "access-control-allow-credentials",
62
+ "access-control-allow-headers",
63
+ "access-control-allow-methods",
64
+ "access-control-allow-origin",
65
+ "access-control-expose-headers",
66
+ "access-control-max-age",
67
+ ]);
68
+ const CONTENT_TYPES = new Map([
69
+ [".css", "text/css; charset=utf-8"],
70
+ [".html", "text/html; charset=utf-8"],
71
+ [".ico", "image/x-icon"],
72
+ [".js", "text/javascript; charset=utf-8"],
73
+ [".png", "image/png"],
74
+ [".svg", "image/svg+xml"],
75
+ [".woff", "font/woff"],
76
+ [".woff2", "font/woff2"],
77
+ ]);
78
+ const STATIC_SECURITY_HEADERS = Object.freeze({
79
+ "content-security-policy": "default-src 'self'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'",
80
+ "cross-origin-resource-policy": "same-origin",
81
+ "permissions-policy": "camera=(), geolocation=(), microphone=()",
82
+ "referrer-policy": "no-referrer",
83
+ "x-content-type-options": "nosniff",
84
+ "x-frame-options": "DENY",
85
+ });
86
+
87
+ function nonEmpty(value) {
88
+ return typeof value === "string" ? value.trim() : "";
89
+ }
90
+
91
+ function parsePort(value, fallback, label) {
92
+ const candidate = String(value ?? "").trim() || fallback;
93
+ if (!/^[1-9]\d{0,4}$/.test(candidate) || Number(candidate) > 65535) {
94
+ throw new Error(`community_host_invalid_${label}_port`);
95
+ }
96
+ return { text: candidate, number: Number(candidate) };
97
+ }
98
+
99
+ function isLoopbackPostgresqlHost(value) {
100
+ const host = nonEmpty(value).replace(/^\[|\]$/g, "").toLowerCase();
101
+ if (host === "localhost" || host === "::1") return true;
102
+ const match = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
103
+ return Boolean(match && match.slice(1).every((octet) => Number(octet) <= 255));
104
+ }
105
+
106
+ function requirePostgresqlUrl(value) {
107
+ const candidate = nonEmpty(value);
108
+ let parsed;
109
+ try {
110
+ parsed = new URL(candidate);
111
+ } catch {
112
+ throw new Error("community_server_postgresql_url_required");
113
+ }
114
+ if (
115
+ (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:") ||
116
+ parsed.hash ||
117
+ !parsed.hostname ||
118
+ !parsed.username ||
119
+ !parsed.password ||
120
+ !parsed.pathname.slice(1)
121
+ ) {
122
+ throw new Error("community_server_postgresql_url_required");
123
+ }
124
+ const entries = [...parsed.searchParams.entries()];
125
+ const uniqueQueryKeys = new Set(entries.map(([key]) => key));
126
+ const exactQuery = (expected) =>
127
+ entries.length === Object.keys(expected).length &&
128
+ uniqueQueryKeys.size === entries.length &&
129
+ entries.every(([key, entryValue]) => expected[key] === entryValue);
130
+ const loopback = isLoopbackPostgresqlHost(parsed.hostname);
131
+ const noQuery = entries.length === 0;
132
+ const localDisable = loopback && exactQuery({ sslmode: "disable" });
133
+ const tlsRequire = exactQuery({ sslmode: "require", uselibpqcompat: "true" });
134
+ const verifyFull = exactQuery({ sslmode: "verify-full" });
135
+ const verifyFullWithRoot = entries.length === 2 &&
136
+ uniqueQueryKeys.size === entries.length &&
137
+ parsed.searchParams.get("sslmode") === "verify-full" &&
138
+ path.isAbsolute(nonEmpty(parsed.searchParams.get("sslrootcert"))) &&
139
+ entries.every(([key]) => key === "sslmode" || key === "sslrootcert");
140
+ if (!(loopback && noQuery) && !localDisable && !tlsRequire && !verifyFull && !verifyFullWithRoot) {
141
+ throw new Error("community_server_postgresql_url_required");
142
+ }
143
+ return candidate;
144
+ }
145
+
146
+ function validateRuntimeEnvironment(env) {
147
+ if (nonEmpty(env.NODE_ENV) !== "production") {
148
+ throw new Error("community_host_production_mode_required");
149
+ }
150
+ const ambientPgKeys = Object.keys(env)
151
+ .filter((key) => /^PG[A-Z0-9_]+$/i.test(key) && nonEmpty(env[key]))
152
+ .sort();
153
+ if (ambientPgKeys.length > 0) {
154
+ throw new Error(`community_server_ambient_pg_env_forbidden:${ambientPgKeys.join(",")}`);
155
+ }
156
+ for (const key of FORBIDDEN_PROXY_ENV) {
157
+ if (nonEmpty(env[key])) {
158
+ throw new Error(`community_server_proxy_trust_env_forbidden:${key}`);
159
+ }
160
+ }
161
+ if (nonEmpty(env.AOPS_DB_BOOTSTRAP_MODE).toLowerCase() !== "explicit") {
162
+ throw new Error("community_strict_bootstrap_mode_required");
163
+ }
164
+ requirePostgresqlUrl(env.AOPS_PG_URL);
165
+ }
166
+
167
+ export function resolveCommunityHostConfig(options, env = process.env) {
168
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
169
+ throw new Error("community_host_options_required");
170
+ }
171
+ validateRuntimeEnvironment(env);
172
+ const mode = nonEmpty(options.mode);
173
+ const edgePort = parsePort(options.edgePort, DEFAULT_EDGE_PORT, "edge");
174
+ if (mode === COMMUNITY_HOST_MODES.native) {
175
+ if (
176
+ options.edgeHost !== NATIVE_EDGE_HOST ||
177
+ options.publicPort !== undefined ||
178
+ options.internalHost !== undefined ||
179
+ options.internalPort !== undefined
180
+ ) {
181
+ throw new Error("community_host_native_literal_contract_required");
182
+ }
183
+ return Object.freeze({
184
+ mode,
185
+ edgeHost: NATIVE_EDGE_HOST,
186
+ edgePort: edgePort.number,
187
+ publicPort: edgePort.number,
188
+ publicAuthority: `${NATIVE_EDGE_HOST}:${edgePort.text}`,
189
+ publicOrigin: `http://${NATIVE_EDGE_HOST}:${edgePort.text}`,
190
+ internalHost: null,
191
+ internalPort: null,
192
+ });
193
+ }
194
+ if (mode === COMMUNITY_HOST_MODES.oci) {
195
+ const publicPort = parsePort(options.publicPort, "", "public");
196
+ const internalPort = parsePort(options.internalPort, DEFAULT_OCI_INTERNAL_PORT, "internal");
197
+ if (
198
+ options.edgeHost !== OCI_EDGE_HOST ||
199
+ edgePort.text !== DEFAULT_EDGE_PORT ||
200
+ options.internalHost !== INTERNAL_HOST ||
201
+ internalPort.text !== DEFAULT_OCI_INTERNAL_PORT
202
+ ) {
203
+ throw new Error("community_host_oci_literal_contract_required");
204
+ }
205
+ return Object.freeze({
206
+ mode,
207
+ edgeHost: OCI_EDGE_HOST,
208
+ edgePort: edgePort.number,
209
+ publicPort: publicPort.number,
210
+ publicAuthority: `${NATIVE_EDGE_HOST}:${publicPort.text}`,
211
+ publicOrigin: `http://${NATIVE_EDGE_HOST}:${publicPort.text}`,
212
+ internalHost: INTERNAL_HOST,
213
+ internalPort: internalPort.number,
214
+ });
215
+ }
216
+ throw new Error("community_host_mode_required");
217
+ }
218
+
219
+ function isWithin(root, candidate) {
220
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
221
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
222
+ }
223
+
224
+ function safeRegularFile(root, candidate) {
225
+ try {
226
+ const rootStats = lstatSync(root);
227
+ if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) return null;
228
+ const candidateStats = lstatSync(candidate);
229
+ if (!candidateStats.isFile() || candidateStats.isSymbolicLink()) return null;
230
+ const realRoot = realpathSync(root);
231
+ const realCandidate = realpathSync(candidate);
232
+ return isWithin(realRoot, realCandidate) ? realCandidate : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ export function validateCommunityHostPaths(paths = DEFAULT_PATHS) {
239
+ const resolved = {
240
+ staticRoot: path.resolve(paths.staticRoot),
241
+ handlerEntry: path.resolve(paths.handlerEntry),
242
+ };
243
+ if (!safeRegularFile(resolved.staticRoot, path.join(resolved.staticRoot, "index.html"))) {
244
+ throw new Error("community_host_cockpit_index_missing");
245
+ }
246
+ if (!safeRegularFile(path.dirname(resolved.handlerEntry), resolved.handlerEntry)) {
247
+ throw new Error("community_host_handler_missing");
248
+ }
249
+ return Object.freeze(resolved);
250
+ }
251
+
252
+ export function shouldHandleApiPath(pathname) {
253
+ return pathname === "/api" || pathname.startsWith("/api/") ||
254
+ pathname === "/api-info" || pathname === "/api-info.json" || pathname === "/openapi.json";
255
+ }
256
+
257
+ export function resolveStaticPath(pathname, staticRoot = DEFAULT_PATHS.staticRoot) {
258
+ if (/%(?:2f|5c)/i.test(pathname)) return null;
259
+ let decoded;
260
+ try {
261
+ decoded = decodeURIComponent(pathname);
262
+ } catch {
263
+ return null;
264
+ }
265
+ if (decoded.includes("\0") || decoded.includes("\\")) return null;
266
+ const root = path.resolve(staticRoot);
267
+ const relative = decoded.replace(/^\/+/, "");
268
+ if (relative.split("/").some((segment) => segment === "." || segment === "..")) return null;
269
+ if (!relative || relative === "index.html") {
270
+ return safeRegularFile(root, path.join(root, "index.html"));
271
+ }
272
+ if (relative.startsWith("assets/")) {
273
+ const extension = path.posix.extname(relative).toLowerCase();
274
+ if (!CONTENT_TYPES.has(extension) || extension === ".html") return null;
275
+ const candidate = path.resolve(root, ...relative.split("/"));
276
+ if (!isWithin(root, candidate)) return null;
277
+ return safeRegularFile(root, candidate);
278
+ }
279
+ if (path.posix.extname(relative)) return null;
280
+ return safeRegularFile(root, path.join(root, "index.html"));
281
+ }
282
+
283
+ function parseLoopbackOrigin(value, label) {
284
+ if (typeof value !== "string" || !value.trim()) {
285
+ throw new Error(`community_host_${label}_required`);
286
+ }
287
+ let parsed;
288
+ try {
289
+ parsed = new URL(label === "host" ? `http://${value.trim()}` : value.trim());
290
+ } catch {
291
+ throw new Error(`community_host_${label}_invalid`);
292
+ }
293
+ if (
294
+ parsed.protocol !== "http:" ||
295
+ !["127.0.0.1", "localhost"].includes(parsed.hostname.toLowerCase()) ||
296
+ parsed.username ||
297
+ parsed.password ||
298
+ parsed.pathname !== "/" ||
299
+ parsed.search ||
300
+ parsed.hash
301
+ ) {
302
+ throw new Error(`community_host_${label}_loopback_required`);
303
+ }
304
+ return parsed.origin;
305
+ }
306
+
307
+ function requestHeaderValue(value, label, { required = false } = {}) {
308
+ if (value === undefined) {
309
+ if (required) throw new Error(`community_host_${label}_required`);
310
+ return "";
311
+ }
312
+ if (typeof value !== "string" || !value.trim()) {
313
+ throw new Error(`community_host_${label}_header_invalid`);
314
+ }
315
+ return value.trim();
316
+ }
317
+
318
+ export function validateCommunityBrowserRequest(headers = {}) {
319
+ const hostOrigin = parseLoopbackOrigin(
320
+ requestHeaderValue(headers.host, "host", { required: true }),
321
+ "host",
322
+ );
323
+ const origin = requestHeaderValue(headers.origin, "origin");
324
+ if (origin && parseLoopbackOrigin(origin, "origin") !== hostOrigin) {
325
+ throw new Error("community_host_origin_host_mismatch");
326
+ }
327
+ const fetchSite = requestHeaderValue(headers["sec-fetch-site"], "sec_fetch_site").toLowerCase();
328
+ if (fetchSite && fetchSite !== "same-origin" && fetchSite !== "none") {
329
+ throw new Error("community_host_cross_site_forbidden");
330
+ }
331
+ return { hostOrigin, browserOriginPresent: Boolean(origin) };
332
+ }
333
+
334
+ function headerTokens(value) {
335
+ const values = Array.isArray(value) ? value : [value];
336
+ return new Set(values
337
+ .flatMap((entry) => typeof entry === "string" ? entry.split(",") : [])
338
+ .map((entry) => entry.trim().toLowerCase())
339
+ .filter(Boolean));
340
+ }
341
+
342
+ function sanitizedRequestHeaders(headers, config, browserOriginPresent) {
343
+ const connectionTokens = headerTokens(headers.connection);
344
+ const result = {};
345
+ for (const [key, value] of Object.entries(headers)) {
346
+ const lower = key.toLowerCase();
347
+ if (
348
+ BASE_HOP_BY_HOP_HEADERS.has(lower) ||
349
+ connectionTokens.has(lower) ||
350
+ FORBIDDEN_INBOUND_HEADERS.has(lower) ||
351
+ lower.startsWith("x-forwarded-")
352
+ ) continue;
353
+ if (value !== undefined) result[lower] = value;
354
+ }
355
+ result.host = config.publicAuthority;
356
+ if (browserOriginPresent) result.origin = config.publicOrigin;
357
+ return result;
358
+ }
359
+
360
+ function applyDirectRequestHeaders(req, headers) {
361
+ for (const key of Object.keys(req.headers)) delete req.headers[key];
362
+ Object.assign(req.headers, headers);
363
+ }
364
+
365
+ function responseHeaders(headers) {
366
+ const connectionTokens = headerTokens(headers.connection);
367
+ const result = {};
368
+ for (const [key, value] of Object.entries(headers)) {
369
+ const lower = key.toLowerCase();
370
+ if (
371
+ BASE_HOP_BY_HOP_HEADERS.has(lower) ||
372
+ connectionTokens.has(lower) ||
373
+ FORBIDDEN_RESPONSE_HEADERS.has(lower) ||
374
+ value === undefined
375
+ ) continue;
376
+ result[lower] = value;
377
+ }
378
+ return result;
379
+ }
380
+
381
+ function applyDirectResponsePolicy(res) {
382
+ const setHeader = res.setHeader.bind(res);
383
+ const appendHeader = typeof res.appendHeader === "function" ? res.appendHeader.bind(res) : null;
384
+ const writeHead = res.writeHead.bind(res);
385
+ res.setHeader = (name, value) => {
386
+ const lower = String(name).toLowerCase();
387
+ if (BASE_HOP_BY_HOP_HEADERS.has(lower) || FORBIDDEN_RESPONSE_HEADERS.has(lower)) return res;
388
+ return setHeader(name, value);
389
+ };
390
+ if (appendHeader) {
391
+ res.appendHeader = (name, value) => {
392
+ const lower = String(name).toLowerCase();
393
+ if (BASE_HOP_BY_HOP_HEADERS.has(lower) || FORBIDDEN_RESPONSE_HEADERS.has(lower)) return res;
394
+ return appendHeader(name, value);
395
+ };
396
+ }
397
+ res.writeHead = (statusCode, statusMessageOrHeaders, maybeHeaders) => {
398
+ if (typeof statusMessageOrHeaders === "string") {
399
+ return writeHead(statusCode, statusMessageOrHeaders, responseHeaders(maybeHeaders ?? {}));
400
+ }
401
+ return writeHead(statusCode, responseHeaders(statusMessageOrHeaders ?? {}));
402
+ };
403
+ }
404
+
405
+ export function proxyCommunityRequest(req, res, config, browserOriginPresent) {
406
+ const upstream = httpRequest({
407
+ hostname: config.internalHost,
408
+ port: config.internalPort,
409
+ method: req.method,
410
+ path: req.url,
411
+ headers: sanitizedRequestHeaders(req.headers, config, browserOriginPresent),
412
+ }, (upstreamResponse) => {
413
+ res.writeHead(upstreamResponse.statusCode ?? 502, responseHeaders(upstreamResponse.headers));
414
+ upstreamResponse.once("error", () => res.destroy());
415
+ upstreamResponse.pipe(res);
416
+ });
417
+ const destroyUpstream = () => upstream.destroy();
418
+ req.once("aborted", destroyUpstream);
419
+ req.once("close", () => {
420
+ if (!req.complete) destroyUpstream();
421
+ });
422
+ res.once("close", destroyUpstream);
423
+ upstream.once("error", () => {
424
+ if (res.headersSent) {
425
+ res.destroy();
426
+ return;
427
+ }
428
+ res.writeHead(502, { "content-type": "application/json; charset=utf-8" });
429
+ res.end('{"error":"community_server_unavailable"}');
430
+ });
431
+ req.pipe(upstream);
432
+ }
433
+
434
+ function openStaticFile(filePath) {
435
+ const flags = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
436
+ const descriptor = openSync(filePath, flags);
437
+ const stats = fstatSync(descriptor);
438
+ if (!stats.isFile()) {
439
+ closeSync(descriptor);
440
+ throw new Error("community_host_static_file_unsafe");
441
+ }
442
+ return { descriptor, size: stats.size };
443
+ }
444
+
445
+ function serveStatic(req, res, pathname, paths) {
446
+ if (req.method !== "GET" && req.method !== "HEAD") {
447
+ res.writeHead(405, { allow: "GET, HEAD" });
448
+ res.end();
449
+ return;
450
+ }
451
+ const filePath = resolveStaticPath(pathname, paths.staticRoot);
452
+ if (!filePath) {
453
+ res.writeHead(404, { ...STATIC_SECURITY_HEADERS, "content-type": "text/plain; charset=utf-8" });
454
+ res.end("Not found");
455
+ return;
456
+ }
457
+ let opened;
458
+ try {
459
+ opened = openStaticFile(filePath);
460
+ } catch {
461
+ res.writeHead(404, { ...STATIC_SECURITY_HEADERS, "content-type": "text/plain; charset=utf-8" });
462
+ res.end("Not found");
463
+ return;
464
+ }
465
+ const extension = path.extname(filePath).toLowerCase();
466
+ res.writeHead(200, {
467
+ ...STATIC_SECURITY_HEADERS,
468
+ "content-type": CONTENT_TYPES.get(extension) ?? "application/octet-stream",
469
+ "content-length": String(opened.size),
470
+ "cache-control": path.basename(filePath) === "index.html"
471
+ ? "no-cache"
472
+ : "public, max-age=31536000, immutable",
473
+ });
474
+ if (req.method === "HEAD") {
475
+ closeSync(opened.descriptor);
476
+ res.end();
477
+ return;
478
+ }
479
+ const stream = createReadStream(filePath, { fd: opened.descriptor, autoClose: true });
480
+ stream.once("error", () => res.destroy());
481
+ stream.pipe(res);
482
+ }
483
+
484
+ function rejectUpgrade(_request, socket) {
485
+ socket.end([
486
+ "HTTP/1.1 426 Upgrade Required",
487
+ "Connection: close",
488
+ "Content-Length: 0",
489
+ "",
490
+ "",
491
+ ].join("\r\n"));
492
+ }
493
+
494
+ function trackServerSockets(server) {
495
+ const sockets = new Set();
496
+ server.on("connection", (socket) => {
497
+ sockets.add(socket);
498
+ socket.once("close", () => sockets.delete(socket));
499
+ });
500
+ server.on("upgrade", rejectUpgrade);
501
+ server.on("clientError", (_error, socket) => socket.destroy());
502
+ return sockets;
503
+ }
504
+
505
+ function listen(server, port, host) {
506
+ return new Promise((resolve, reject) => {
507
+ server.once("error", reject);
508
+ server.listen(port, host, () => {
509
+ server.off("error", reject);
510
+ resolve();
511
+ });
512
+ });
513
+ }
514
+
515
+ function closeServer(server, sockets) {
516
+ if (!server) return;
517
+ for (const socket of sockets) socket.destroy();
518
+ server.close();
519
+ }
520
+
521
+ async function loadHandler(handlerEntry) {
522
+ const loaded = await import(pathToFileURL(handlerEntry).href);
523
+ if (typeof loaded.handler !== "function") {
524
+ throw new Error("community_host_handler_export_missing");
525
+ }
526
+ return loaded.handler;
527
+ }
528
+
529
+ export async function runCommunityHost(options, env = process.env) {
530
+ if (env !== process.env) throw new Error("community_host_process_env_required");
531
+ const config = resolveCommunityHostConfig(options, env);
532
+ const paths = validateCommunityHostPaths();
533
+ process.env.HOST = NATIVE_EDGE_HOST;
534
+ process.env.PORT = String(config.internalPort ?? config.edgePort);
535
+ process.env.ORIGIN = config.publicOrigin;
536
+ const handler = await loadHandler(paths.handlerEntry);
537
+ let innerServer;
538
+ let innerSockets = new Set();
539
+ let edgeServer;
540
+ let edgeSockets = new Set();
541
+ let shuttingDown = false;
542
+ const shutdown = () => {
543
+ if (shuttingDown) return;
544
+ shuttingDown = true;
545
+ closeServer(edgeServer, edgeSockets);
546
+ closeServer(innerServer, innerSockets);
547
+ process.off("SIGINT", shutdown);
548
+ process.off("SIGTERM", shutdown);
549
+ };
550
+ const runtimeFailure = (error) => {
551
+ process.stderr.write(`[aops-community] runtime failure code=${error?.code ?? "unknown"}\n`);
552
+ process.exitCode = 1;
553
+ shutdown();
554
+ };
555
+ process.once("SIGINT", shutdown);
556
+ process.once("SIGTERM", shutdown);
557
+ try {
558
+ if (config.mode === COMMUNITY_HOST_MODES.oci) {
559
+ innerServer = createServer(handler);
560
+ innerSockets = trackServerSockets(innerServer);
561
+ await listen(innerServer, config.internalPort, config.internalHost);
562
+ innerServer.on("error", runtimeFailure);
563
+ }
564
+ edgeServer = createServer((req, res) => {
565
+ let url;
566
+ try {
567
+ url = new URL(req.url ?? "/", "http://community.local");
568
+ } catch {
569
+ res.writeHead(400);
570
+ res.end();
571
+ return;
572
+ }
573
+ if (!shouldHandleApiPath(url.pathname)) {
574
+ serveStatic(req, res, url.pathname, paths);
575
+ return;
576
+ }
577
+ let browser;
578
+ try {
579
+ browser = validateCommunityBrowserRequest(req.headers);
580
+ } catch {
581
+ res.writeHead(403, { "content-type": "application/json; charset=utf-8" });
582
+ res.end('{"error":"community_loopback_request_forbidden"}');
583
+ return;
584
+ }
585
+ if (config.mode === COMMUNITY_HOST_MODES.oci) {
586
+ proxyCommunityRequest(req, res, config, browser.browserOriginPresent);
587
+ } else {
588
+ applyDirectRequestHeaders(
589
+ req,
590
+ sanitizedRequestHeaders(req.headers, config, browser.browserOriginPresent),
591
+ );
592
+ applyDirectResponsePolicy(res);
593
+ try {
594
+ handler(req, res);
595
+ } catch {
596
+ if (!res.headersSent) {
597
+ res.writeHead(500, { "content-type": "application/json; charset=utf-8" });
598
+ res.end('{"error":"community_server_failure"}');
599
+ } else {
600
+ res.destroy();
601
+ }
602
+ }
603
+ }
604
+ });
605
+ edgeSockets = trackServerSockets(edgeServer);
606
+ await listen(edgeServer, config.edgePort, config.edgeHost);
607
+ edgeServer.on("error", runtimeFailure);
608
+ process.stderr.write(`[aops-community] Cockpit + API listening on ${config.publicOrigin}\n`);
609
+ return Object.freeze({ config, shutdown });
610
+ } catch (error) {
611
+ shutdown();
612
+ throw error;
613
+ }
614
+ }
615
+
616
+ const isMain = typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href;
617
+ if (isMain) {
618
+ runCommunityHost({
619
+ mode: COMMUNITY_HOST_MODES.native,
620
+ edgeHost: NATIVE_EDGE_HOST,
621
+ edgePort: process.env.COMMUNITY_PORT || DEFAULT_EDGE_PORT,
622
+ }).catch((error) => {
623
+ process.stderr.write(`[aops-community] ${error instanceof Error ? error.message : String(error)}\n`);
624
+ process.exitCode = 1;
625
+ });
626
+ }