fifony 0.1.47 → 0.1.48
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.
- package/README.md +78 -0
- package/app/dist/assets/CommandPalette-CZDG20HW.js +1 -0
- package/app/dist/assets/{KeyboardShortcutsHelp-CqEFfGcE.js → KeyboardShortcutsHelp-TYhQc4aA.js} +1 -1
- package/app/dist/assets/OnboardingWizard-CQ9YmVIT.js +1 -0
- package/app/dist/assets/agents.lazy-CgakDm_P.js +1 -0
- package/app/dist/assets/analytics.lazy-C0rw3sov.js +1 -0
- package/app/dist/assets/{createLucideIcon-luywpIq4.js → createLucideIcon-B3bah5lk.js} +1 -1
- package/app/dist/assets/hooks-CNPue7d7.js +1 -0
- package/app/dist/assets/index-B8XCmr0-.css +1 -0
- package/app/dist/assets/index-Dfn02uW3.js +47 -0
- package/app/dist/assets/index.lazy-JdqhBwPd.js +44 -0
- package/app/dist/assets/services-CHpVij2M.css +1 -0
- package/app/dist/assets/services.lazy-BShKUCOt.js +12 -0
- package/app/dist/assets/vendor-X6HTElZW.js +9 -0
- package/app/dist/assets/viz-Dsh_q2DK.js +4 -0
- package/app/dist/index.html +5 -4
- package/app/dist/service-worker.js +32 -1
- package/dist/agent/run-local.js +89 -76
- package/dist/{agent-DFSFG6DG.js → agent-DJ4SCNBZ.js} +22 -17
- package/dist/{analytics-broadcaster-O4AE3RUK.js → analytics-broadcaster-INNYWHDJ.js} +25 -20
- package/dist/approve-plan.command-WE2CO3H2.js +21 -0
- package/dist/{chunk-HOIOVUHI.js → chunk-5M7PBFMZ.js} +8 -6
- package/dist/chunk-7R7XFXJM.js +1247 -0
- package/dist/{chunk-2PRRKBG6.js → chunk-A4P2MYJF.js} +22 -9
- package/dist/chunk-AFOV3ZAF.js +722 -0
- package/dist/chunk-AFP36N23.js +134 -0
- package/dist/{chunk-AAZKYWOY.js → chunk-AFYKGVSP.js} +103 -8
- package/dist/chunk-APJOZXRP.js +737 -0
- package/dist/chunk-DLSPRIQL.js +241 -0
- package/dist/{chunk-5AMWD66T.js → chunk-EDIPHR5B.js} +6 -4
- package/dist/{chunk-K36BWMUV.js → chunk-JU3MF3MW.js} +2526 -736
- package/dist/{chunk-7TXZYZR5.js → chunk-N5HCNY4O.js} +7 -5
- package/dist/{chunk-JRLWLZOD.js → chunk-NKMZYPIS.js} +31 -23
- package/dist/{chunk-PI7Y77R3.js → chunk-OFIVTM2E.js} +17 -7
- package/dist/{chunk-QH6VCTET.js → chunk-RCSJFMQG.js} +909 -98
- package/dist/{chunk-AAVROEQC.js → chunk-UR7T7IA6.js} +253 -349
- package/dist/{chunk-QHISYRXJ.js → chunk-VOYLU3MI.js} +57 -3
- package/dist/{chunk-EBCSQFPR.js → chunk-W5IULOWV.js} +2 -3
- package/dist/chunk-X37RNTWU.js +193 -0
- package/dist/{chunk-PACI3T4I.js → chunk-XY2APMDE.js} +13 -5
- package/dist/chunk-Z6ZWNWWR.js +34 -0
- package/dist/cli.js +45 -17
- package/dist/constants-AAP7ZGCX.js +124 -0
- package/dist/create-issue.command-SX3AXXIC.js +29 -0
- package/dist/fsm-agent-JGV22WK4.js +59 -0
- package/dist/{fsm-issue-EHTSKMFN.js → fsm-issue-LHIJM5VB.js} +12 -8
- package/dist/{fsm-service-7O4AJG2R.js → fsm-service-GGDKUTWS.js} +13 -4
- package/dist/{helpers-ON2S7UEF.js → helpers-AENVYEZJ.js} +6 -2
- package/dist/{issue-log-broadcaster-FZGVEEIX.js → issue-log-broadcaster-QQWM7LOV.js} +29 -18
- package/dist/{issues-3YNNTB4U.js → issues-RXFKKSXB.js} +10 -7
- package/dist/{log-analyzer-EIX6R6PP.js → log-analyzer-4LNXQISY.js} +30 -20
- package/dist/{logger-IFLXTQPS.js → logger-4F6ATWNA.js} +2 -1
- package/dist/mcp/server.js +6 -2
- package/dist/merge-workspace.command-ZNGIZC4O.js +29 -0
- package/dist/{parallel-executor-DWESCNX3.js → parallel-executor-OL5CB33L.js} +78 -19
- package/dist/{pid-manager-UBWXVSMD.js → pid-manager-EDT4DHAU.js} +2 -1
- package/dist/queue-workers-NSKIIMQ2.js +43 -0
- package/dist/replan-issue.command-73PETERX.js +21 -0
- package/dist/retry-issue.command-DIDP4OCS.js +21 -0
- package/dist/reverse-proxy-server-QSS3H4UH.js +97 -0
- package/dist/scheduler-5YORYECF.js +37 -0
- package/dist/service-log-broadcaster-JIUP2L3D.js +21 -0
- package/dist/{settings-SOTIS6ZD.js → settings-ZNDXYL46.js} +34 -23
- package/dist/settings.resource-OKUHXICJ.js +35 -0
- package/dist/{store-S3NAYZ3S.js → store-P3ACO6YA.js} +22 -17
- package/dist/telemetry-KVUFHDQS.js +828 -0
- package/dist/template-variants-HEPLYKMP.js +24 -0
- package/dist/trace-bundle-IJOV7IWH.js +41 -0
- package/dist/{web-push-QCTLS7EJ.js → web-push-X2LLMQ4M.js} +2 -1
- package/dist/websocket-Q2TUCIC2.js +103 -0
- package/dist/{workspace-OS7GPMCN.js → workspace-TDX3NJCX.js} +10 -6
- package/package.json +12 -9
- package/app/dist/assets/CommandPalette-CL8p78lG.js +0 -1
- package/app/dist/assets/OnboardingWizard-BmI50ZUv.js +0 -1
- package/app/dist/assets/analytics.lazy-CXGjZabc.js +0 -1
- package/app/dist/assets/index-CEaccpYh.js +0 -96
- package/app/dist/assets/index-CzzWGzux.css +0 -1
- package/app/dist/assets/vendor-uqBx3VSC.js +0 -9
- package/dist/approve-plan.command-QGQZZXTQ.js +0 -17
- package/dist/chunk-N4KFNX2G.js +0 -370
- package/dist/chunk-VM5QAYP5.js +0 -404
- package/dist/create-issue.command-VAKYRECC.js +0 -24
- package/dist/merge-workspace.command-T2NIGR4M.js +0 -24
- package/dist/queue-workers-V57BYXAY.js +0 -38
- package/dist/replan-issue.command-2GQ3QXCR.js +0 -17
- package/dist/retry-issue.command-GJBUUYDJ.js +0 -17
- package/dist/scheduler-KYILMWLD.js +0 -32
- package/dist/settings.resource-JMD3JQOS.js +0 -30
- package/dist/websocket-T2Y3BY4B.js +0 -61
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isProcessAlive
|
|
3
|
+
} from "./chunk-3NE23NYW.js";
|
|
4
|
+
import {
|
|
5
|
+
STATE_ROOT,
|
|
6
|
+
init_constants
|
|
7
|
+
} from "./chunk-X37RNTWU.js";
|
|
8
|
+
import {
|
|
9
|
+
logger
|
|
10
|
+
} from "./chunk-PXTIWKLQ.js";
|
|
11
|
+
|
|
12
|
+
// src/persistence/plugins/reverse-proxy-manager.ts
|
|
13
|
+
init_constants();
|
|
14
|
+
import {
|
|
15
|
+
closeSync,
|
|
16
|
+
existsSync as existsSync2,
|
|
17
|
+
mkdirSync as mkdirSync2,
|
|
18
|
+
openSync,
|
|
19
|
+
readSync,
|
|
20
|
+
readFileSync as readFileSync2,
|
|
21
|
+
rmSync as rmSync2,
|
|
22
|
+
statSync,
|
|
23
|
+
writeFileSync as writeFileSync2
|
|
24
|
+
} from "fs";
|
|
25
|
+
import { spawn } from "child_process";
|
|
26
|
+
import { fileURLToPath } from "url";
|
|
27
|
+
import { dirname, join as join2, resolve } from "path";
|
|
28
|
+
|
|
29
|
+
// src/persistence/plugins/reverse-proxy-runtime.ts
|
|
30
|
+
init_constants();
|
|
31
|
+
import {
|
|
32
|
+
existsSync,
|
|
33
|
+
mkdirSync,
|
|
34
|
+
readFileSync,
|
|
35
|
+
rmSync,
|
|
36
|
+
unlinkSync,
|
|
37
|
+
writeFileSync
|
|
38
|
+
} from "fs";
|
|
39
|
+
import { createServer } from "http";
|
|
40
|
+
import { join } from "path";
|
|
41
|
+
import { createSign, generateKeyPairSync } from "crypto";
|
|
42
|
+
|
|
43
|
+
// src/domains/traffic-proxy.ts
|
|
44
|
+
var TrafficRingBuffer = class {
|
|
45
|
+
constructor(capacity = 1e3) {
|
|
46
|
+
this.capacity = capacity;
|
|
47
|
+
this.buf = new Array(capacity);
|
|
48
|
+
}
|
|
49
|
+
buf;
|
|
50
|
+
head = 0;
|
|
51
|
+
count = 0;
|
|
52
|
+
push(entry) {
|
|
53
|
+
this.buf[this.head] = entry;
|
|
54
|
+
this.head = (this.head + 1) % this.capacity;
|
|
55
|
+
if (this.count < this.capacity) this.count++;
|
|
56
|
+
}
|
|
57
|
+
getAll() {
|
|
58
|
+
if (this.count === 0) return [];
|
|
59
|
+
if (this.count < this.capacity) return this.buf.slice(0, this.count);
|
|
60
|
+
return [...this.buf.slice(this.head), ...this.buf.slice(0, this.head)];
|
|
61
|
+
}
|
|
62
|
+
getRecent(n) {
|
|
63
|
+
const all = this.getAll();
|
|
64
|
+
return n >= all.length ? all : all.slice(-n);
|
|
65
|
+
}
|
|
66
|
+
clear() {
|
|
67
|
+
this.head = 0;
|
|
68
|
+
this.count = 0;
|
|
69
|
+
}
|
|
70
|
+
get size() {
|
|
71
|
+
return this.count;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function resolveTargetService(url, services) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = new URL(url);
|
|
77
|
+
const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
78
|
+
const match = services.find((s) => s.port === port);
|
|
79
|
+
return match?.id ?? null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function buildProxyEnvVars(proxyPort, serviceId, dashboardPort, extraNoPorts = []) {
|
|
85
|
+
const proxyUrl = `http://${encodeURIComponent(serviceId)}:fifony@localhost:${proxyPort}`;
|
|
86
|
+
const noProxyList = [`localhost:${dashboardPort}`, ...extraNoPorts.map((p) => `localhost:${p}`)].join(",");
|
|
87
|
+
return {
|
|
88
|
+
HTTP_PROXY: proxyUrl,
|
|
89
|
+
http_proxy: proxyUrl,
|
|
90
|
+
NO_PROXY: noProxyList,
|
|
91
|
+
no_proxy: noProxyList
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var entrySeq = 0;
|
|
95
|
+
function buildTrafficEntry(method, url, requestSize, statusCode, responseSize, sourceServiceId, targetServiceId, startTime, error) {
|
|
96
|
+
let path;
|
|
97
|
+
try {
|
|
98
|
+
path = new URL(url).pathname;
|
|
99
|
+
} catch {
|
|
100
|
+
path = url;
|
|
101
|
+
}
|
|
102
|
+
const protocol = (() => {
|
|
103
|
+
try {
|
|
104
|
+
return new URL(url).protocol.replace(":", "").toLowerCase();
|
|
105
|
+
} catch {
|
|
106
|
+
if (/^https:\/\//i.test(url)) return "https";
|
|
107
|
+
if (/^wss:\/\//i.test(url)) return "wss";
|
|
108
|
+
if (/^ws:\/\//i.test(url)) return "ws";
|
|
109
|
+
if (/^http:\/\//i.test(url)) return "http";
|
|
110
|
+
if (/^tcp:\/\//i.test(url)) return "tcp";
|
|
111
|
+
return "unknown";
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
return {
|
|
115
|
+
id: `tr_${Date.now()}_${++entrySeq}`,
|
|
116
|
+
sourceServiceId,
|
|
117
|
+
targetServiceId,
|
|
118
|
+
method,
|
|
119
|
+
protocol,
|
|
120
|
+
url,
|
|
121
|
+
path,
|
|
122
|
+
statusCode,
|
|
123
|
+
requestSize,
|
|
124
|
+
responseSize,
|
|
125
|
+
startedAt: new Date(startTime).toISOString(),
|
|
126
|
+
durationMs: Date.now() - startTime,
|
|
127
|
+
error
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/domains/raffel-telemetry.ts
|
|
132
|
+
async function loadCreateProxyTelemetry() {
|
|
133
|
+
const raffel = await import("raffel");
|
|
134
|
+
const publicFactory = raffel.createProxyTelemetry;
|
|
135
|
+
if (typeof publicFactory === "function") {
|
|
136
|
+
return publicFactory;
|
|
137
|
+
}
|
|
138
|
+
const internal = await import("./telemetry-KVUFHDQS.js");
|
|
139
|
+
const internalFactory = internal.createProxyTelemetry;
|
|
140
|
+
if (typeof internalFactory === "function") {
|
|
141
|
+
return internalFactory;
|
|
142
|
+
}
|
|
143
|
+
throw new Error("Raffel proxy telemetry factory is unavailable.");
|
|
144
|
+
}
|
|
145
|
+
async function createRaffelProxyTelemetry(config) {
|
|
146
|
+
const factory = await loadCreateProxyTelemetry();
|
|
147
|
+
return factory(config);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/persistence/plugins/reverse-proxy-runtime.ts
|
|
151
|
+
var reverseProxy = null;
|
|
152
|
+
var meshProxy = null;
|
|
153
|
+
var meshBuffer = null;
|
|
154
|
+
var meshTelemetryUnsubscribe = null;
|
|
155
|
+
var meshTelemetryEvents = [];
|
|
156
|
+
var meshTelemetryCollector = null;
|
|
157
|
+
var meshExpireTimer = null;
|
|
158
|
+
var MAX_MESH_TELEMETRY_EVENTS = 2e3;
|
|
159
|
+
var MESH_EXPIRE_INTERVAL_MS = 30 * 1e3;
|
|
160
|
+
var NETWORK_RUNTIME_ID = "reverse-proxy";
|
|
161
|
+
var RUNTIME_CONFIG_PATH = join(STATE_ROOT, `service-${NETWORK_RUNTIME_ID}.runtime.json`);
|
|
162
|
+
var RUNTIME_PID_PATH = join(STATE_ROOT, `service-${NETWORK_RUNTIME_ID}.pid`);
|
|
163
|
+
function writeRuntimePidInfo(info) {
|
|
164
|
+
writeFileSync(RUNTIME_PID_PATH, JSON.stringify(info));
|
|
165
|
+
}
|
|
166
|
+
function removeRuntimePidInfo() {
|
|
167
|
+
try {
|
|
168
|
+
rmSync(RUNTIME_PID_PATH, { force: true });
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function removeRuntimeConfig() {
|
|
173
|
+
try {
|
|
174
|
+
rmSync(RUNTIME_CONFIG_PATH, { force: true });
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function derLen(n) {
|
|
179
|
+
if (n < 128) return Buffer.from([n]);
|
|
180
|
+
const bytes = [];
|
|
181
|
+
let tmp = n;
|
|
182
|
+
while (tmp > 0) {
|
|
183
|
+
bytes.unshift(tmp & 255);
|
|
184
|
+
tmp >>= 8;
|
|
185
|
+
}
|
|
186
|
+
return Buffer.from([128 | bytes.length, ...bytes]);
|
|
187
|
+
}
|
|
188
|
+
function tlv(tag, value) {
|
|
189
|
+
return Buffer.concat([Buffer.from([tag]), derLen(value.length), value]);
|
|
190
|
+
}
|
|
191
|
+
function encodeOID(oidStr) {
|
|
192
|
+
const parts = oidStr.split(".").map(Number);
|
|
193
|
+
const body = [parts[0] * 40 + parts[1]];
|
|
194
|
+
for (let i = 2; i < parts.length; i++) {
|
|
195
|
+
let v = parts[i];
|
|
196
|
+
const chunk = [];
|
|
197
|
+
chunk.push(v & 127);
|
|
198
|
+
v >>= 7;
|
|
199
|
+
while (v > 0) {
|
|
200
|
+
chunk.unshift(v & 127 | 128);
|
|
201
|
+
v >>= 7;
|
|
202
|
+
}
|
|
203
|
+
body.push(...chunk);
|
|
204
|
+
}
|
|
205
|
+
return tlv(6, Buffer.from(body));
|
|
206
|
+
}
|
|
207
|
+
function algId(oidStr) {
|
|
208
|
+
return tlv(48, Buffer.concat([encodeOID(oidStr), tlv(5, Buffer.alloc(0))]));
|
|
209
|
+
}
|
|
210
|
+
function encodeRDN(attrOid, value) {
|
|
211
|
+
return tlv(49, tlv(48, Buffer.concat([encodeOID(attrOid), tlv(12, Buffer.from(value, "utf8"))])));
|
|
212
|
+
}
|
|
213
|
+
function encodeName(components) {
|
|
214
|
+
return tlv(48, Buffer.concat(components.map(([oid, v]) => encodeRDN(oid, v))));
|
|
215
|
+
}
|
|
216
|
+
function encodeUTCTime(d) {
|
|
217
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
218
|
+
const s = `${String(d.getUTCFullYear()).slice(-2)}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}Z`;
|
|
219
|
+
return tlv(23, Buffer.from(s, "ascii"));
|
|
220
|
+
}
|
|
221
|
+
function encodeExtension(extOid, critical, extValueDer) {
|
|
222
|
+
const parts = [encodeOID(extOid)];
|
|
223
|
+
if (critical) parts.push(tlv(1, Buffer.from([255])));
|
|
224
|
+
parts.push(tlv(4, extValueDer));
|
|
225
|
+
return tlv(48, Buffer.concat(parts));
|
|
226
|
+
}
|
|
227
|
+
function encodeSAN(hosts) {
|
|
228
|
+
const names = hosts.map(
|
|
229
|
+
(h) => /^\d+\.\d+\.\d+\.\d+$/.test(h) ? tlv(135, Buffer.from(h.split(".").map(Number))) : tlv(130, Buffer.from(h, "ascii"))
|
|
230
|
+
);
|
|
231
|
+
return encodeExtension("2.5.29.17", false, tlv(48, Buffer.concat(names)));
|
|
232
|
+
}
|
|
233
|
+
function encodeBasicConstraints() {
|
|
234
|
+
return encodeExtension("2.5.29.19", true, tlv(48, Buffer.alloc(0)));
|
|
235
|
+
}
|
|
236
|
+
function posInt(bytes) {
|
|
237
|
+
return bytes.length > 0 && bytes[0] & 128 ? Buffer.concat([Buffer.from([0]), bytes]) : bytes;
|
|
238
|
+
}
|
|
239
|
+
function toPEM(der, label) {
|
|
240
|
+
const lines = der.toString("base64").match(/.{1,64}/g) ?? [];
|
|
241
|
+
return `-----BEGIN ${label}-----
|
|
242
|
+
${lines.join("\n")}
|
|
243
|
+
-----END ${label}-----
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
var SHA256_WITH_RSA = "1.2.840.113549.1.1.11";
|
|
247
|
+
var OID_CN = "2.5.4.3";
|
|
248
|
+
async function generateMultiSanCert(hosts) {
|
|
249
|
+
const { getDefaultCA } = await import("raffel");
|
|
250
|
+
const ca = getDefaultCA();
|
|
251
|
+
const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
252
|
+
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
253
|
+
const spkiDer = publicKey.export({ type: "spki", format: "der" });
|
|
254
|
+
const { X509Certificate } = await import("crypto");
|
|
255
|
+
let caIssuerName;
|
|
256
|
+
try {
|
|
257
|
+
const x509 = new X509Certificate(ca.cert);
|
|
258
|
+
const cnMatch = x509.subject.match(/CN=([^\n,/]+)/);
|
|
259
|
+
const cn = cnMatch?.[1]?.trim() ?? "Spark Local CA";
|
|
260
|
+
caIssuerName = encodeName([[OID_CN, cn]]);
|
|
261
|
+
} catch {
|
|
262
|
+
caIssuerName = encodeName([[OID_CN, "Spark Local CA"]]);
|
|
263
|
+
}
|
|
264
|
+
const now = /* @__PURE__ */ new Date();
|
|
265
|
+
const notAfter = new Date(now.getTime() + 825 * 24 * 60 * 60 * 1e3);
|
|
266
|
+
const sigAlg = algId(SHA256_WITH_RSA);
|
|
267
|
+
const serialBytes = Buffer.from(Array.from({ length: 16 }, () => Math.floor(Math.random() * 256)));
|
|
268
|
+
serialBytes[0] = serialBytes[0] & 127;
|
|
269
|
+
const version = tlv(160, tlv(2, Buffer.from([2])));
|
|
270
|
+
const serial = tlv(2, posInt(serialBytes));
|
|
271
|
+
const validity = tlv(48, Buffer.concat([encodeUTCTime(now), encodeUTCTime(notAfter)]));
|
|
272
|
+
const subjectName = encodeName([[OID_CN, hosts[0]]]);
|
|
273
|
+
const exts = tlv(163, tlv(48, Buffer.concat([encodeBasicConstraints(), encodeSAN(hosts)])));
|
|
274
|
+
const tbs = tlv(48, Buffer.concat([
|
|
275
|
+
version,
|
|
276
|
+
serial,
|
|
277
|
+
sigAlg,
|
|
278
|
+
caIssuerName,
|
|
279
|
+
validity,
|
|
280
|
+
subjectName,
|
|
281
|
+
spkiDer,
|
|
282
|
+
exts
|
|
283
|
+
]));
|
|
284
|
+
const signer = createSign("SHA256");
|
|
285
|
+
signer.update(tbs);
|
|
286
|
+
const sig = signer.sign(ca.key);
|
|
287
|
+
const certDer = tlv(48, Buffer.concat([
|
|
288
|
+
tbs,
|
|
289
|
+
sigAlg,
|
|
290
|
+
tlv(3, Buffer.concat([Buffer.from([0]), sig]))
|
|
291
|
+
]));
|
|
292
|
+
return { key: privateKeyPem, cert: toPEM(certDer, "CERTIFICATE"), ca: ca.cert };
|
|
293
|
+
}
|
|
294
|
+
var TLS_DIR = join(STATE_ROOT, "tls");
|
|
295
|
+
var KEY_PATH = join(TLS_DIR, "key.pem");
|
|
296
|
+
var CERT_PATH = join(TLS_DIR, "cert.pem");
|
|
297
|
+
var CA_PATH = join(TLS_DIR, "ca.pem");
|
|
298
|
+
var DOMAIN_PATH = join(TLS_DIR, "domain.txt");
|
|
299
|
+
var LOCAL_DOMAIN_PORT_SUFFIX = /:\d+$/;
|
|
300
|
+
function getReverseProxyCaCertPath() {
|
|
301
|
+
return CA_PATH;
|
|
302
|
+
}
|
|
303
|
+
async function ensureReverseProxyTlsCert(localDomain) {
|
|
304
|
+
mkdirSync(TLS_DIR, { recursive: true });
|
|
305
|
+
const storedDomain = existsSync(DOMAIN_PATH) ? readFileSync(DOMAIN_PATH, "utf8").trim() : "";
|
|
306
|
+
const needsRegen = !existsSync(KEY_PATH) || !existsSync(CERT_PATH) || storedDomain !== (localDomain ?? "");
|
|
307
|
+
if (!needsRegen) {
|
|
308
|
+
return { key: readFileSync(KEY_PATH, "utf8"), cert: readFileSync(CERT_PATH, "utf8") };
|
|
309
|
+
}
|
|
310
|
+
const hosts = ["localhost", "127.0.0.1"];
|
|
311
|
+
if (localDomain) {
|
|
312
|
+
hosts.push(localDomain);
|
|
313
|
+
hosts.push(`*.${localDomain}`);
|
|
314
|
+
}
|
|
315
|
+
logger.info({ hosts }, "[NetworkRuntime] Generating TLS cert");
|
|
316
|
+
const { key, cert, ca } = await generateMultiSanCert(hosts);
|
|
317
|
+
writeFileSync(KEY_PATH, key);
|
|
318
|
+
writeFileSync(CERT_PATH, cert);
|
|
319
|
+
writeFileSync(CA_PATH, ca);
|
|
320
|
+
writeFileSync(DOMAIN_PATH, localDomain ?? "");
|
|
321
|
+
return { key, cert };
|
|
322
|
+
}
|
|
323
|
+
function invalidateReverseProxyCert() {
|
|
324
|
+
try {
|
|
325
|
+
if (existsSync(DOMAIN_PATH)) unlinkSync(DOMAIN_PATH);
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function buildRaffelRoutes(routes, services, dashPort) {
|
|
330
|
+
const normalizeOneHost = (value) => {
|
|
331
|
+
const trimmed = value.trim();
|
|
332
|
+
if (!trimmed) return void 0;
|
|
333
|
+
const withoutScheme = trimmed.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
334
|
+
const hostOnly = withoutScheme.split("/")[0]?.split("?")[0] ?? "";
|
|
335
|
+
const normalized = hostOnly.replace(LOCAL_DOMAIN_PORT_SUFFIX, "").toLowerCase();
|
|
336
|
+
return normalized || void 0;
|
|
337
|
+
};
|
|
338
|
+
const normalizeHost = (value) => {
|
|
339
|
+
if (!value) return void 0;
|
|
340
|
+
if (Array.isArray(value)) {
|
|
341
|
+
const normalized = value.map(normalizeOneHost).filter((h) => !!h);
|
|
342
|
+
return normalized.length === 1 ? normalized[0] : normalized.length > 1 ? normalized : void 0;
|
|
343
|
+
}
|
|
344
|
+
return normalizeOneHost(value);
|
|
345
|
+
};
|
|
346
|
+
const normalizePathPrefix = (value) => {
|
|
347
|
+
if (!value) return void 0;
|
|
348
|
+
const trimmed = value.trim();
|
|
349
|
+
if (!trimmed) return void 0;
|
|
350
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
351
|
+
};
|
|
352
|
+
const portById = new Map(services.map((s) => [s.id, s.port]));
|
|
353
|
+
const normalizedRoutes = routes.map((route) => ({
|
|
354
|
+
...route,
|
|
355
|
+
host: normalizeHost(route.host),
|
|
356
|
+
pathPrefix: normalizePathPrefix(route.pathPrefix),
|
|
357
|
+
target: route.target?.trim() || void 0
|
|
358
|
+
}));
|
|
359
|
+
const specificity = (r) => ((Array.isArray(r.host) ? r.host.length > 0 : !!r.host) ? 2 : 0) + (r.pathPrefix ? 1 : 0);
|
|
360
|
+
const sorted = [...normalizedRoutes].sort((a, b) => specificity(b) - specificity(a));
|
|
361
|
+
const raffelRoutes = [];
|
|
362
|
+
for (const route of sorted) {
|
|
363
|
+
const port = route.serviceId ? portById.get(route.serviceId) : void 0;
|
|
364
|
+
const target = port ? `http://127.0.0.1:${port}` : route.target?.trim() ?? null;
|
|
365
|
+
if (!target) {
|
|
366
|
+
logger.warn({ routeId: route.id }, "[NetworkRuntime] Skipping route \u2014 no resolvable target");
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const match = {};
|
|
370
|
+
if (route.host) match.host = route.host;
|
|
371
|
+
if (route.pathPrefix) match.pathPrefix = route.pathPrefix;
|
|
372
|
+
const raffelRoute = { match, target };
|
|
373
|
+
if (route.pathPrefix && route.stripPrefix !== false) {
|
|
374
|
+
raffelRoute.stripPrefix = route.pathPrefix;
|
|
375
|
+
}
|
|
376
|
+
raffelRoutes.push(raffelRoute);
|
|
377
|
+
}
|
|
378
|
+
raffelRoutes.push({ match: { pathPrefix: "/" }, target: `http://127.0.0.1:${dashPort}` });
|
|
379
|
+
return raffelRoutes;
|
|
380
|
+
}
|
|
381
|
+
function toRuntimeServiceStatuses(services) {
|
|
382
|
+
return services.map((service) => ({
|
|
383
|
+
id: service.id,
|
|
384
|
+
name: service.name,
|
|
385
|
+
command: service.command,
|
|
386
|
+
cwd: service.cwd,
|
|
387
|
+
env: service.env,
|
|
388
|
+
autoStart: service.autoStart,
|
|
389
|
+
autoRestart: service.autoRestart,
|
|
390
|
+
maxCrashes: service.maxCrashes,
|
|
391
|
+
port: service.port,
|
|
392
|
+
state: "running",
|
|
393
|
+
running: true,
|
|
394
|
+
pid: null,
|
|
395
|
+
startedAt: null,
|
|
396
|
+
uptime: 0,
|
|
397
|
+
logSize: 0,
|
|
398
|
+
crashCount: 0,
|
|
399
|
+
errorCount: 0
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
async function startReverseProxy(options) {
|
|
403
|
+
if (!options.reverseProxyEnabled) return null;
|
|
404
|
+
if (reverseProxy?.isRunning) {
|
|
405
|
+
logger.warn("[NetworkRuntime] Reverse proxy already running, skipping start");
|
|
406
|
+
return reverseProxy.boundPort;
|
|
407
|
+
}
|
|
408
|
+
const { key, cert } = await ensureReverseProxyTlsCert(options.localDomain);
|
|
409
|
+
const { createReverseProxy } = await import("raffel");
|
|
410
|
+
const routes = buildRaffelRoutes(options.routes ?? [], options.services ?? [], options.dashPort);
|
|
411
|
+
reverseProxy = await createReverseProxy({
|
|
412
|
+
server: { host: "0.0.0.0", port: options.port ?? 4433, tls: { key, cert } },
|
|
413
|
+
routes
|
|
414
|
+
});
|
|
415
|
+
const boundPort = await reverseProxy.start();
|
|
416
|
+
logger.info({ port: boundPort, routeCount: routes.length, localDomain: options.localDomain }, "[NetworkRuntime] HTTPS reverse proxy started");
|
|
417
|
+
return boundPort;
|
|
418
|
+
}
|
|
419
|
+
async function stopReverseProxy() {
|
|
420
|
+
if (!reverseProxy?.isRunning) return;
|
|
421
|
+
await reverseProxy.stop();
|
|
422
|
+
logger.info("[NetworkRuntime] HTTPS reverse proxy stopped");
|
|
423
|
+
reverseProxy = null;
|
|
424
|
+
}
|
|
425
|
+
var meshRequestStartTimes = /* @__PURE__ */ new Map();
|
|
426
|
+
function buildMeshMiddleware(services) {
|
|
427
|
+
return async (ctx, next) => {
|
|
428
|
+
if (ctx.kind === "http-request") {
|
|
429
|
+
meshRequestStartTimes.set(ctx.clientAddress, Date.now());
|
|
430
|
+
await next();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (ctx.kind === "http-response") {
|
|
434
|
+
await next();
|
|
435
|
+
const startTime = meshRequestStartTimes.get(ctx.clientAddress) ?? Date.now();
|
|
436
|
+
meshRequestStartTimes.delete(ctx.clientAddress);
|
|
437
|
+
const url = ctx.request?.url ?? "";
|
|
438
|
+
const method = ctx.request?.method ?? "GET";
|
|
439
|
+
const sourceId = ctx.authUsername ?? null;
|
|
440
|
+
const targetId = resolveTargetService(url, toRuntimeServiceStatuses(services));
|
|
441
|
+
const entry = buildTrafficEntry(
|
|
442
|
+
method,
|
|
443
|
+
url,
|
|
444
|
+
0,
|
|
445
|
+
ctx.response?.statusCode ?? 0,
|
|
446
|
+
0,
|
|
447
|
+
sourceId,
|
|
448
|
+
targetId,
|
|
449
|
+
startTime
|
|
450
|
+
);
|
|
451
|
+
meshBuffer?.push(entry);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function queueMeshTelemetryEvent(event) {
|
|
456
|
+
meshTelemetryEvents.push(event);
|
|
457
|
+
if (meshTelemetryEvents.length > MAX_MESH_TELEMETRY_EVENTS) {
|
|
458
|
+
meshTelemetryEvents = meshTelemetryEvents.slice(-MAX_MESH_TELEMETRY_EVENTS);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function getMeshLiveWindowMs(options) {
|
|
462
|
+
const seconds = Number(options?.meshLiveWindowSeconds ?? 900);
|
|
463
|
+
if (!Number.isFinite(seconds)) return 9e5;
|
|
464
|
+
return Math.min(Math.max(seconds, 30), 86400) * 1e3;
|
|
465
|
+
}
|
|
466
|
+
function resolveMeshDestinationNode(services, host, port) {
|
|
467
|
+
if (!host && !port) return null;
|
|
468
|
+
const match = services.find((service) => service.port != null && service.port === port);
|
|
469
|
+
if (match?.id) return match.id;
|
|
470
|
+
if (host && port != null) return `${host}:${port}`;
|
|
471
|
+
return host ?? null;
|
|
472
|
+
}
|
|
473
|
+
async function startMeshProxy(options) {
|
|
474
|
+
if (!options.meshEnabled) return null;
|
|
475
|
+
if (meshProxy?.isRunning) {
|
|
476
|
+
logger.warn("[NetworkRuntime] Mesh proxy already running, skipping start");
|
|
477
|
+
return meshProxy.boundPort;
|
|
478
|
+
}
|
|
479
|
+
meshBuffer = new TrafficRingBuffer(options.meshBufferSize ?? 1e3);
|
|
480
|
+
meshTelemetryEvents = [];
|
|
481
|
+
const { createExplicitProxy } = await import("raffel");
|
|
482
|
+
meshTelemetryCollector = await createRaffelProxyTelemetry({
|
|
483
|
+
rateWindowMs: 6e4
|
|
484
|
+
});
|
|
485
|
+
meshProxy = createExplicitProxy({
|
|
486
|
+
port: options.meshPort ?? 0,
|
|
487
|
+
host: "127.0.0.1",
|
|
488
|
+
forward: {
|
|
489
|
+
timeout: 3e4,
|
|
490
|
+
maxBodySize: 10 * 1024 * 1024
|
|
491
|
+
},
|
|
492
|
+
telemetry: {
|
|
493
|
+
collector: meshTelemetryCollector,
|
|
494
|
+
resolveNode: (ctx) => {
|
|
495
|
+
if (ctx.role === "destination") {
|
|
496
|
+
return resolveMeshDestinationNode(options.services ?? [], ctx.host, ctx.port);
|
|
497
|
+
}
|
|
498
|
+
if (ctx.authUsername) return ctx.authUsername;
|
|
499
|
+
return null;
|
|
500
|
+
},
|
|
501
|
+
metricsEndpoint: false,
|
|
502
|
+
graphEndpoint: false
|
|
503
|
+
},
|
|
504
|
+
middleware: [buildMeshMiddleware(options.services ?? [])]
|
|
505
|
+
});
|
|
506
|
+
await meshProxy.start();
|
|
507
|
+
meshTelemetryUnsubscribe?.();
|
|
508
|
+
meshTelemetryUnsubscribe = meshProxy.subscribe((event) => {
|
|
509
|
+
queueMeshTelemetryEvent(event);
|
|
510
|
+
});
|
|
511
|
+
if (meshExpireTimer) clearInterval(meshExpireTimer);
|
|
512
|
+
meshExpireTimer = setInterval(() => {
|
|
513
|
+
if (!meshTelemetryCollector) return;
|
|
514
|
+
const cutoff = new Date(Date.now() - getMeshLiveWindowMs(options)).toISOString();
|
|
515
|
+
meshTelemetryCollector.expireEdgesBefore(cutoff);
|
|
516
|
+
}, MESH_EXPIRE_INTERVAL_MS);
|
|
517
|
+
logger.info({ port: meshProxy.boundPort }, "[NetworkRuntime] Mesh proxy started");
|
|
518
|
+
return meshProxy.boundPort;
|
|
519
|
+
}
|
|
520
|
+
async function stopMeshProxy() {
|
|
521
|
+
if (!meshProxy?.isRunning) return;
|
|
522
|
+
await meshProxy.stop();
|
|
523
|
+
logger.info("[NetworkRuntime] Mesh proxy stopped");
|
|
524
|
+
if (meshExpireTimer) clearInterval(meshExpireTimer);
|
|
525
|
+
meshExpireTimer = null;
|
|
526
|
+
meshTelemetryUnsubscribe?.();
|
|
527
|
+
meshTelemetryUnsubscribe = null;
|
|
528
|
+
meshProxy = null;
|
|
529
|
+
meshBuffer = null;
|
|
530
|
+
meshTelemetryEvents = [];
|
|
531
|
+
meshTelemetryCollector = null;
|
|
532
|
+
}
|
|
533
|
+
function isReverseProxyRunning() {
|
|
534
|
+
return reverseProxy?.isRunning ?? false;
|
|
535
|
+
}
|
|
536
|
+
function isMeshProxyRunning() {
|
|
537
|
+
return meshProxy?.isRunning ?? false;
|
|
538
|
+
}
|
|
539
|
+
function getReverseProxyPort() {
|
|
540
|
+
return reverseProxy?.boundPort ?? null;
|
|
541
|
+
}
|
|
542
|
+
function getMeshProxyPort() {
|
|
543
|
+
return meshProxy?.boundPort ?? null;
|
|
544
|
+
}
|
|
545
|
+
function getReverseProxyCaCert() {
|
|
546
|
+
return reverseProxy?.caCert ?? null;
|
|
547
|
+
}
|
|
548
|
+
function getReverseProxyStats() {
|
|
549
|
+
return reverseProxy?.stats ?? null;
|
|
550
|
+
}
|
|
551
|
+
function getReverseProxyGraphSnapshot() {
|
|
552
|
+
if (!reverseProxy?.isRunning) return null;
|
|
553
|
+
return reverseProxy.graphSnapshot();
|
|
554
|
+
}
|
|
555
|
+
function getMeshStats() {
|
|
556
|
+
return meshProxy?.stats ?? null;
|
|
557
|
+
}
|
|
558
|
+
function getMeshNativeGraphSnapshot() {
|
|
559
|
+
if (!meshProxy?.isRunning) return null;
|
|
560
|
+
return meshProxy.graphSnapshot();
|
|
561
|
+
}
|
|
562
|
+
function getMeshMetrics(format = "prometheus") {
|
|
563
|
+
return meshProxy?.metricsRegistry?.export(format) ?? null;
|
|
564
|
+
}
|
|
565
|
+
function getMeshTrafficEntries(limit = 200) {
|
|
566
|
+
return meshBuffer?.getRecent(limit) ?? [];
|
|
567
|
+
}
|
|
568
|
+
async function clearMeshData() {
|
|
569
|
+
meshBuffer?.clear();
|
|
570
|
+
if (!meshTelemetryCollector || !meshProxy?.isRunning) {
|
|
571
|
+
meshTelemetryEvents = [];
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
meshTelemetryCollector.reset();
|
|
575
|
+
meshTelemetryEvents = [];
|
|
576
|
+
}
|
|
577
|
+
function getMeshServiceGraph(services) {
|
|
578
|
+
if (!meshProxy?.isRunning) return null;
|
|
579
|
+
const snapshot = meshProxy.graphSnapshot();
|
|
580
|
+
const serviceStatuses = toRuntimeServiceStatuses(services);
|
|
581
|
+
const serviceNames = new Map(serviceStatuses.map((service) => [service.id, service.name]));
|
|
582
|
+
const servicePorts = new Map(serviceStatuses.map((service) => [service.id, service.port]));
|
|
583
|
+
const serviceStates = new Map(serviceStatuses.map((service) => [service.id, service.state]));
|
|
584
|
+
const nodeIds = new Set(serviceStatuses.map((service) => service.id));
|
|
585
|
+
const snapshotNodes = new Map(snapshot.nodes.map((node) => [node.id, node]));
|
|
586
|
+
const edges = snapshot.edges.filter((edge) => nodeIds.has(edge.target)).map((edge) => ({
|
|
587
|
+
id: edge.id,
|
|
588
|
+
source: edge.source,
|
|
589
|
+
target: edge.target,
|
|
590
|
+
requestCount: edge.requestsTotal > 0 ? edge.requestsTotal : edge.flowsTotal,
|
|
591
|
+
errorCount: edge.errorsTotal,
|
|
592
|
+
dominantProtocol: edge.protocol,
|
|
593
|
+
protocolCounts: [{ protocol: edge.protocol, count: edge.flowsTotal }],
|
|
594
|
+
avgLatencyMs: edge.latency.averageSeconds != null ? Math.round(edge.latency.averageSeconds * 1e3) : 0,
|
|
595
|
+
p50LatencyMs: edge.latency.percentiles.p50 != null ? Math.round(edge.latency.percentiles.p50 * 1e3) : 0,
|
|
596
|
+
p90LatencyMs: edge.latency.percentiles.p90 != null ? Math.round(edge.latency.percentiles.p90 * 1e3) : 0,
|
|
597
|
+
p95LatencyMs: edge.latency.percentiles.p95 != null ? Math.round(edge.latency.percentiles.p95 * 1e3) : 0,
|
|
598
|
+
p99LatencyMs: edge.latency.percentiles.p99 != null ? Math.round(edge.latency.percentiles.p99 * 1e3) : 0,
|
|
599
|
+
lastSeenAt: edge.lastSeenAt,
|
|
600
|
+
topPaths: edge.topPaths,
|
|
601
|
+
bytesIn: edge.bytesFromSource,
|
|
602
|
+
bytesOut: edge.bytesToSource,
|
|
603
|
+
activeFlows: edge.activeFlows,
|
|
604
|
+
flowsTotal: edge.flowsTotal,
|
|
605
|
+
statusClassCounts: edge.statusClassCounts,
|
|
606
|
+
methodCounts: edge.methodCounts
|
|
607
|
+
}));
|
|
608
|
+
const externalIds = /* @__PURE__ */ new Set();
|
|
609
|
+
for (const edge of edges) {
|
|
610
|
+
if (edge.source && !nodeIds.has(edge.source)) externalIds.add(edge.source);
|
|
611
|
+
}
|
|
612
|
+
const externalNodes = [...externalIds].map((id) => ({
|
|
613
|
+
id,
|
|
614
|
+
name: id.length > 22 ? id.slice(0, 20) + "\u2026" : id,
|
|
615
|
+
state: "external",
|
|
616
|
+
port: null,
|
|
617
|
+
requestsIn: 0,
|
|
618
|
+
requestsOut: 0,
|
|
619
|
+
errorsIn: 0,
|
|
620
|
+
errorsOut: 0,
|
|
621
|
+
bytesIn: 0,
|
|
622
|
+
bytesOut: 0,
|
|
623
|
+
activeFlows: 0,
|
|
624
|
+
protocols: {},
|
|
625
|
+
lastSeenAt: null,
|
|
626
|
+
external: true
|
|
627
|
+
}));
|
|
628
|
+
return {
|
|
629
|
+
nodes: [
|
|
630
|
+
...serviceStatuses.map((service) => ({
|
|
631
|
+
id: service.id,
|
|
632
|
+
name: serviceNames.get(service.id) ?? service.id,
|
|
633
|
+
state: serviceStates.get(service.id) ?? "running",
|
|
634
|
+
port: servicePorts.get(service.id),
|
|
635
|
+
requestsIn: snapshotNodes.get(service.id)?.requestsIn ?? 0,
|
|
636
|
+
requestsOut: snapshotNodes.get(service.id)?.requestsOut ?? 0,
|
|
637
|
+
errorsIn: snapshotNodes.get(service.id)?.errorsIn ?? 0,
|
|
638
|
+
errorsOut: snapshotNodes.get(service.id)?.errorsOut ?? 0,
|
|
639
|
+
bytesIn: snapshotNodes.get(service.id)?.bytesIn ?? 0,
|
|
640
|
+
bytesOut: snapshotNodes.get(service.id)?.bytesOut ?? 0,
|
|
641
|
+
activeFlows: snapshotNodes.get(service.id)?.activeFlows ?? 0,
|
|
642
|
+
protocols: snapshotNodes.get(service.id)?.protocols ?? {},
|
|
643
|
+
lastSeenAt: snapshotNodes.get(service.id)?.lastSeenAt ?? null
|
|
644
|
+
})),
|
|
645
|
+
...externalNodes
|
|
646
|
+
],
|
|
647
|
+
edges,
|
|
648
|
+
capturedSince: snapshot.windowStart,
|
|
649
|
+
totalRequests: edges.reduce((sum, edge) => sum + (typeof edge.requestCount === "number" ? edge.requestCount : 0), 0),
|
|
650
|
+
windowStart: snapshot.windowStart,
|
|
651
|
+
windowEnd: snapshot.windowEnd,
|
|
652
|
+
seq: snapshot.seq
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function getMeshTelemetryEvents(afterSeq = 0, limit = 200) {
|
|
656
|
+
return meshTelemetryEvents.filter((event) => event.seq > afterSeq).slice(0, Math.max(1, limit)).map((event) => event);
|
|
657
|
+
}
|
|
658
|
+
async function runReverseProxyRuntimeProcess(configPath) {
|
|
659
|
+
const syncErr = (msg) => {
|
|
660
|
+
try {
|
|
661
|
+
process.stderr.write(`[NetworkRuntime] ${msg}
|
|
662
|
+
`);
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
let config;
|
|
667
|
+
try {
|
|
668
|
+
const raw = readFileSync(configPath, "utf8");
|
|
669
|
+
config = JSON.parse(raw);
|
|
670
|
+
} catch (err) {
|
|
671
|
+
syncErr(`Failed to read config: ${err instanceof Error ? err.message : String(err)}`);
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
process.exit(1);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
677
|
+
let proxyPort;
|
|
678
|
+
let meshPort;
|
|
679
|
+
try {
|
|
680
|
+
proxyPort = await startReverseProxy(config);
|
|
681
|
+
meshPort = await startMeshProxy(config);
|
|
682
|
+
} catch (err) {
|
|
683
|
+
syncErr(`Failed to start proxy: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
684
|
+
process.exitCode = 1;
|
|
685
|
+
process.exit(1);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const controlServer = createServer(async (req, res) => {
|
|
689
|
+
const sendJson = (statusCode, payload) => {
|
|
690
|
+
res.statusCode = statusCode;
|
|
691
|
+
res.setHeader("content-type", "application/json");
|
|
692
|
+
res.end(JSON.stringify(payload));
|
|
693
|
+
};
|
|
694
|
+
if (!req.url) return void sendJson(404, { ok: false });
|
|
695
|
+
const url = new URL(req.url, "http://127.0.0.1");
|
|
696
|
+
if (req.method === "GET" && url.pathname === "/status") {
|
|
697
|
+
return void sendJson(200, {
|
|
698
|
+
ok: true,
|
|
699
|
+
running: isReverseProxyRunning() || isMeshProxyRunning(),
|
|
700
|
+
startedAt,
|
|
701
|
+
pid: process.pid,
|
|
702
|
+
controlPort: controlServer.address()?.port ?? null,
|
|
703
|
+
localDomain: config.localDomain ?? null,
|
|
704
|
+
reverseProxy: {
|
|
705
|
+
enabled: config.reverseProxyEnabled === true,
|
|
706
|
+
running: isReverseProxyRunning(),
|
|
707
|
+
proxyPort: getReverseProxyPort()
|
|
708
|
+
},
|
|
709
|
+
mesh: {
|
|
710
|
+
enabled: config.meshEnabled === true,
|
|
711
|
+
running: isMeshProxyRunning(),
|
|
712
|
+
port: getMeshProxyPort()
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
if (req.method === "GET" && url.pathname === "/stats") return void sendJson(200, { ok: true, stats: getReverseProxyStats() });
|
|
717
|
+
if (req.method === "GET" && url.pathname === "/graph") return void sendJson(200, { ok: true, snapshot: getReverseProxyGraphSnapshot() });
|
|
718
|
+
if (req.method === "GET" && url.pathname === "/mesh/status") {
|
|
719
|
+
return void sendJson(200, { ok: true, running: isMeshProxyRunning(), port: getMeshProxyPort() });
|
|
720
|
+
}
|
|
721
|
+
if (req.method === "GET" && url.pathname === "/mesh/traffic") {
|
|
722
|
+
const limit = Number(url.searchParams.get("limit") ?? "200");
|
|
723
|
+
return void sendJson(200, { ok: true, entries: getMeshTrafficEntries(limit) });
|
|
724
|
+
}
|
|
725
|
+
if (req.method === "GET" && url.pathname === "/mesh/events") {
|
|
726
|
+
const afterSeq = Number(url.searchParams.get("afterSeq") ?? "0");
|
|
727
|
+
const limit = Number(url.searchParams.get("limit") ?? "200");
|
|
728
|
+
return void sendJson(200, {
|
|
729
|
+
ok: true,
|
|
730
|
+
events: getMeshTelemetryEvents(Number.isFinite(afterSeq) ? afterSeq : 0, Number.isFinite(limit) ? limit : 200),
|
|
731
|
+
currentSeq: meshProxy?.graphSnapshot().seq ?? 0
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (req.method === "GET" && url.pathname === "/mesh/stats") {
|
|
735
|
+
return void sendJson(200, { ok: true, stats: getMeshStats() });
|
|
736
|
+
}
|
|
737
|
+
if (req.method === "GET" && url.pathname === "/mesh/graph") {
|
|
738
|
+
return void sendJson(200, {
|
|
739
|
+
ok: true,
|
|
740
|
+
graph: getMeshServiceGraph(config.services ?? []),
|
|
741
|
+
nativeGraph: getMeshNativeGraphSnapshot()
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (req.method === "GET" && url.pathname === "/mesh/metrics") {
|
|
745
|
+
const format = url.searchParams.get("format") === "json" ? "json" : "prometheus";
|
|
746
|
+
const metrics = getMeshMetrics(format);
|
|
747
|
+
return void sendJson(200, { ok: true, metrics, format });
|
|
748
|
+
}
|
|
749
|
+
if (req.method === "POST" && url.pathname === "/mesh/clear") {
|
|
750
|
+
await clearMeshData();
|
|
751
|
+
return void sendJson(200, { ok: true });
|
|
752
|
+
}
|
|
753
|
+
if (req.method === "POST" && url.pathname === "/stop") {
|
|
754
|
+
sendJson(200, { ok: true });
|
|
755
|
+
setTimeout(() => {
|
|
756
|
+
Promise.allSettled([stopReverseProxy(), stopMeshProxy()]).finally(() => {
|
|
757
|
+
try {
|
|
758
|
+
controlServer.close();
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
removeRuntimePidInfo();
|
|
762
|
+
removeRuntimeConfig();
|
|
763
|
+
process.exit(0);
|
|
764
|
+
});
|
|
765
|
+
}, 20);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
sendJson(404, { ok: false });
|
|
769
|
+
});
|
|
770
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
771
|
+
controlServer.once("error", rejectPromise);
|
|
772
|
+
controlServer.listen(0, "127.0.0.1", () => resolvePromise());
|
|
773
|
+
});
|
|
774
|
+
writeRuntimePidInfo({
|
|
775
|
+
pid: process.pid,
|
|
776
|
+
command: process.argv.join(" "),
|
|
777
|
+
startedAt,
|
|
778
|
+
controlPort: controlServer.address()?.port,
|
|
779
|
+
proxyPort: proxyPort ?? void 0,
|
|
780
|
+
meshPort: meshPort ?? void 0,
|
|
781
|
+
dashPort: config.dashPort,
|
|
782
|
+
localDomain: config.localDomain
|
|
783
|
+
});
|
|
784
|
+
const shutdown = async () => {
|
|
785
|
+
await Promise.allSettled([stopReverseProxy(), stopMeshProxy()]);
|
|
786
|
+
try {
|
|
787
|
+
controlServer.close();
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
removeRuntimePidInfo();
|
|
791
|
+
removeRuntimeConfig();
|
|
792
|
+
process.exit(0);
|
|
793
|
+
};
|
|
794
|
+
process.once("SIGINT", () => {
|
|
795
|
+
void shutdown();
|
|
796
|
+
});
|
|
797
|
+
process.once("SIGTERM", () => {
|
|
798
|
+
void shutdown();
|
|
799
|
+
});
|
|
800
|
+
await new Promise(() => {
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/persistence/plugins/reverse-proxy-manager.ts
|
|
805
|
+
var NETWORK_RUNTIME_LOGICAL_ID = "reverse-proxy";
|
|
806
|
+
var RUNTIME_CONFIG_PATH2 = join2(STATE_ROOT, `service-${NETWORK_RUNTIME_LOGICAL_ID}.runtime.json`);
|
|
807
|
+
var RUNTIME_PID_PATH2 = join2(STATE_ROOT, `service-${NETWORK_RUNTIME_LOGICAL_ID}.pid`);
|
|
808
|
+
var RUNTIME_LOG_PATH = join2(STATE_ROOT, `service-${NETWORK_RUNTIME_LOGICAL_ID}.log`);
|
|
809
|
+
var RUNTIME_READY_TIMEOUT_MS = 2e4;
|
|
810
|
+
var RUNTIME_EXISTING_READY_TIMEOUT_MS = 1e4;
|
|
811
|
+
var RUNTIME_POLL_INTERVAL_MS = 100;
|
|
812
|
+
function readRuntimePidInfo() {
|
|
813
|
+
if (!existsSync2(RUNTIME_PID_PATH2)) return null;
|
|
814
|
+
try {
|
|
815
|
+
const data = JSON.parse(readFileSync2(RUNTIME_PID_PATH2, "utf8"));
|
|
816
|
+
if (!data?.pid || typeof data.pid !== "number") return null;
|
|
817
|
+
return data;
|
|
818
|
+
} catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function writeRuntimePidInfo2(info) {
|
|
823
|
+
writeFileSync2(RUNTIME_PID_PATH2, JSON.stringify(info));
|
|
824
|
+
}
|
|
825
|
+
function readRuntimeConfig() {
|
|
826
|
+
if (!existsSync2(RUNTIME_CONFIG_PATH2)) return null;
|
|
827
|
+
try {
|
|
828
|
+
return JSON.parse(readFileSync2(RUNTIME_CONFIG_PATH2, "utf8"));
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function removeRuntimePidInfo2() {
|
|
834
|
+
try {
|
|
835
|
+
rmSync2(RUNTIME_PID_PATH2, { force: true });
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function removeRuntimeConfig2() {
|
|
840
|
+
try {
|
|
841
|
+
rmSync2(RUNTIME_CONFIG_PATH2, { force: true });
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function getReverseProxyRuntimeLogPath() {
|
|
846
|
+
return RUNTIME_LOG_PATH;
|
|
847
|
+
}
|
|
848
|
+
function getMeshRuntimeLogPath() {
|
|
849
|
+
return RUNTIME_LOG_PATH;
|
|
850
|
+
}
|
|
851
|
+
function getReverseProxyRuntimePidPath() {
|
|
852
|
+
return RUNTIME_PID_PATH2;
|
|
853
|
+
}
|
|
854
|
+
function getPackageRoot() {
|
|
855
|
+
const filePath = fileURLToPath(import.meta.url);
|
|
856
|
+
return resolve(dirname(filePath), "../../..");
|
|
857
|
+
}
|
|
858
|
+
function getNetworkRuntimeCommand() {
|
|
859
|
+
const packageRoot = process.env.FIFONY_PKG_ROOT ?? getPackageRoot();
|
|
860
|
+
const file = resolve(packageRoot, "bin", "fifony.js");
|
|
861
|
+
return {
|
|
862
|
+
file,
|
|
863
|
+
command: `${process.execPath} ${file} reverse-proxy-runtime --runtimeConfig ${RUNTIME_CONFIG_PATH2}`
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function readRuntimeLogTail(bytes = 4096) {
|
|
867
|
+
if (!existsSync2(RUNTIME_LOG_PATH)) return "";
|
|
868
|
+
try {
|
|
869
|
+
const size = statSync(RUNTIME_LOG_PATH).size;
|
|
870
|
+
const readSize = Math.min(size, bytes);
|
|
871
|
+
if (readSize <= 0) return "";
|
|
872
|
+
const fd = openSync(RUNTIME_LOG_PATH, "r");
|
|
873
|
+
const buf = Buffer.alloc(readSize);
|
|
874
|
+
readSync(fd, buf, 0, readSize, Math.max(0, size - readSize));
|
|
875
|
+
closeSync(fd);
|
|
876
|
+
return buf.toString("utf8");
|
|
877
|
+
} catch {
|
|
878
|
+
return "";
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function queryRuntimeControl(pathname, init) {
|
|
882
|
+
const info = readRuntimePidInfo();
|
|
883
|
+
if (!info?.controlPort) return { ok: false, kind: "missing" };
|
|
884
|
+
if (!isProcessAlive(info.pid)) return { ok: false, kind: "dead" };
|
|
885
|
+
try {
|
|
886
|
+
const response = await fetch(`http://127.0.0.1:${info.controlPort}${pathname}`, {
|
|
887
|
+
...init,
|
|
888
|
+
signal: AbortSignal.timeout(2e3)
|
|
889
|
+
});
|
|
890
|
+
if (!response.ok) return { ok: false, kind: "http_error", status: response.status };
|
|
891
|
+
return { ok: true, data: await response.json(), status: response.status };
|
|
892
|
+
} catch {
|
|
893
|
+
return { ok: false, kind: "unreachable" };
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function configRequiresReadiness(config, info) {
|
|
897
|
+
if (!info.controlPort) return false;
|
|
898
|
+
if (config.reverseProxyEnabled && !info.proxyPort) return false;
|
|
899
|
+
if (config.meshEnabled && !info.meshPort) return false;
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
async function waitForRuntimeReady(expectedPid, config, timeoutMs) {
|
|
903
|
+
const started = Date.now();
|
|
904
|
+
while (Date.now() - started < timeoutMs) {
|
|
905
|
+
const info = readRuntimePidInfo();
|
|
906
|
+
if (info && configRequiresReadiness(config, info) && isProcessAlive(expectedPid)) {
|
|
907
|
+
return info;
|
|
908
|
+
}
|
|
909
|
+
if (!isProcessAlive(expectedPid)) break;
|
|
910
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, RUNTIME_POLL_INTERVAL_MS));
|
|
911
|
+
}
|
|
912
|
+
const logTail = readRuntimeLogTail();
|
|
913
|
+
const suffix = logTail.trim() ? ` Log tail: ${logTail.slice(-500)}` : "";
|
|
914
|
+
throw new Error(`Network runtime did not become ready within ${timeoutMs}ms.${suffix}`);
|
|
915
|
+
}
|
|
916
|
+
function runtimeMatchesConfig(status, config) {
|
|
917
|
+
const reverseRunning = status.reverseProxy?.running === true;
|
|
918
|
+
const meshRunning = status.mesh?.running === true;
|
|
919
|
+
const reversePort = status.reverseProxy?.proxyPort ?? null;
|
|
920
|
+
const meshPort = status.mesh?.port ?? null;
|
|
921
|
+
if (config.reverseProxyEnabled) {
|
|
922
|
+
if (!reverseRunning) return false;
|
|
923
|
+
if (config.port && reversePort !== config.port) return false;
|
|
924
|
+
} else if (reverseRunning) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (config.meshEnabled) {
|
|
928
|
+
if (!meshRunning) return false;
|
|
929
|
+
if (config.meshPort && meshPort !== config.meshPort) return false;
|
|
930
|
+
} else if (meshRunning) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
async function spawnRuntime(config) {
|
|
936
|
+
mkdirSync2(STATE_ROOT, { recursive: true });
|
|
937
|
+
writeFileSync2(RUNTIME_CONFIG_PATH2, JSON.stringify(config));
|
|
938
|
+
try {
|
|
939
|
+
writeFileSync2(RUNTIME_LOG_PATH, "");
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
const { file, command } = getNetworkRuntimeCommand();
|
|
943
|
+
const logFd = openSync(RUNTIME_LOG_PATH, "a");
|
|
944
|
+
const child = spawn(process.execPath, [file, "reverse-proxy-runtime", "--runtimeConfig", RUNTIME_CONFIG_PATH2], {
|
|
945
|
+
cwd: process.cwd(),
|
|
946
|
+
detached: true,
|
|
947
|
+
stdio: ["ignore", logFd, logFd],
|
|
948
|
+
env: process.env
|
|
949
|
+
});
|
|
950
|
+
try {
|
|
951
|
+
closeSync(logFd);
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
child.unref();
|
|
955
|
+
if (child.pid == null) throw new Error("Failed to spawn network runtime process.");
|
|
956
|
+
writeRuntimePidInfo2({
|
|
957
|
+
pid: child.pid,
|
|
958
|
+
command,
|
|
959
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
960
|
+
dashPort: config.dashPort,
|
|
961
|
+
proxyPort: config.port,
|
|
962
|
+
meshPort: config.meshPort,
|
|
963
|
+
localDomain: config.localDomain
|
|
964
|
+
});
|
|
965
|
+
return waitForRuntimeReady(child.pid, config, RUNTIME_READY_TIMEOUT_MS);
|
|
966
|
+
}
|
|
967
|
+
var _runtimeLock = Promise.resolve();
|
|
968
|
+
async function withRuntimeLock(fn) {
|
|
969
|
+
const prev = _runtimeLock;
|
|
970
|
+
let release;
|
|
971
|
+
_runtimeLock = new Promise((r) => {
|
|
972
|
+
release = r;
|
|
973
|
+
});
|
|
974
|
+
await prev;
|
|
975
|
+
try {
|
|
976
|
+
return await fn();
|
|
977
|
+
} finally {
|
|
978
|
+
release();
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
async function stopNetworkRuntimeImpl() {
|
|
982
|
+
const info = readRuntimePidInfo();
|
|
983
|
+
if (!info) return;
|
|
984
|
+
if (info.controlPort) await queryRuntimeControl("/stop", { method: "POST" });
|
|
985
|
+
if (isProcessAlive(info.pid)) {
|
|
986
|
+
try {
|
|
987
|
+
process.kill(-info.pid, "SIGTERM");
|
|
988
|
+
} catch (error) {
|
|
989
|
+
logger.debug({ err: error, pid: info.pid }, "[NetworkRuntime] Failed to SIGTERM process group");
|
|
990
|
+
}
|
|
991
|
+
try {
|
|
992
|
+
process.kill(info.pid, "SIGTERM");
|
|
993
|
+
} catch (error) {
|
|
994
|
+
logger.debug({ err: error, pid: info.pid }, "[NetworkRuntime] Failed to SIGTERM process");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const started = Date.now();
|
|
998
|
+
while (Date.now() - started < 3e3) {
|
|
999
|
+
if (!isProcessAlive(info.pid)) break;
|
|
1000
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
|
|
1001
|
+
}
|
|
1002
|
+
if (isProcessAlive(info.pid)) {
|
|
1003
|
+
try {
|
|
1004
|
+
process.kill(-info.pid, "SIGKILL");
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
logger.debug({ err: error, pid: info.pid }, "[NetworkRuntime] Failed to SIGKILL process group");
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
process.kill(info.pid, "SIGKILL");
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
logger.debug({ err: error, pid: info.pid }, "[NetworkRuntime] Failed to SIGKILL process");
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
removeRuntimePidInfo2();
|
|
1015
|
+
removeRuntimeConfig2();
|
|
1016
|
+
}
|
|
1017
|
+
async function applyNetworkRuntimeConfigImpl(config) {
|
|
1018
|
+
if (!config.reverseProxyEnabled && !config.meshEnabled) {
|
|
1019
|
+
await stopNetworkRuntimeImpl();
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const existing = readRuntimePidInfo();
|
|
1023
|
+
if (existing?.pid && isProcessAlive(existing.pid)) {
|
|
1024
|
+
if (existing.controlPort) {
|
|
1025
|
+
const currentConfig = readRuntimeConfig();
|
|
1026
|
+
const status = await queryRuntimeControl("/status");
|
|
1027
|
+
const proxyConfig = (c) => c ? { ...c, services: [] } : null;
|
|
1028
|
+
if (status.ok && runtimeMatchesConfig(status.data, config) && JSON.stringify(proxyConfig(currentConfig)) === JSON.stringify(proxyConfig(config))) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
await stopNetworkRuntimeImpl();
|
|
1032
|
+
await spawnRuntime(config);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
await waitForRuntimeReady(existing.pid, config, RUNTIME_EXISTING_READY_TIMEOUT_MS);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
await spawnRuntime(config);
|
|
1039
|
+
}
|
|
1040
|
+
async function stopNetworkRuntime() {
|
|
1041
|
+
return withRuntimeLock(() => stopNetworkRuntimeImpl());
|
|
1042
|
+
}
|
|
1043
|
+
async function applyNetworkRuntimeConfig(config) {
|
|
1044
|
+
return withRuntimeLock(() => applyNetworkRuntimeConfigImpl(config));
|
|
1045
|
+
}
|
|
1046
|
+
async function startReverseProxyRuntime(options) {
|
|
1047
|
+
return withRuntimeLock(async () => {
|
|
1048
|
+
const config = { ...options, reverseProxyEnabled: true };
|
|
1049
|
+
await applyNetworkRuntimeConfigImpl(config);
|
|
1050
|
+
const state = await getReverseProxyRuntimeState();
|
|
1051
|
+
return state.proxyPort ?? options.port ?? 4433;
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
async function stopReverseProxyRuntime() {
|
|
1055
|
+
return withRuntimeLock(() => stopNetworkRuntimeImpl());
|
|
1056
|
+
}
|
|
1057
|
+
async function restartReverseProxyRuntime(options) {
|
|
1058
|
+
return withRuntimeLock(async () => {
|
|
1059
|
+
await stopNetworkRuntimeImpl();
|
|
1060
|
+
const config = { ...options, reverseProxyEnabled: true };
|
|
1061
|
+
await applyNetworkRuntimeConfigImpl(config);
|
|
1062
|
+
const state = await getReverseProxyRuntimeState();
|
|
1063
|
+
return state.proxyPort ?? options.port ?? 4433;
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
function readLogSize() {
|
|
1067
|
+
if (!existsSync2(RUNTIME_LOG_PATH)) return 0;
|
|
1068
|
+
try {
|
|
1069
|
+
return statSync(RUNTIME_LOG_PATH).size;
|
|
1070
|
+
} catch {
|
|
1071
|
+
return 0;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
function getReverseProxyRuntimeSnapshotStatus(options) {
|
|
1075
|
+
const info = readRuntimePidInfo();
|
|
1076
|
+
const alive = info?.pid != null && isProcessAlive(info.pid);
|
|
1077
|
+
const ready = alive && Boolean(info?.controlPort) && Boolean(info?.proxyPort);
|
|
1078
|
+
return {
|
|
1079
|
+
id: "reverse-proxy",
|
|
1080
|
+
name: "HTTPS Reverse Proxy",
|
|
1081
|
+
command: info?.command ?? getNetworkRuntimeCommand().command,
|
|
1082
|
+
port: info?.proxyPort ?? options?.configuredPort,
|
|
1083
|
+
state: ready ? "running" : "stopped",
|
|
1084
|
+
running: ready,
|
|
1085
|
+
pid: ready ? info?.pid ?? null : null,
|
|
1086
|
+
startedAt: info?.startedAt ?? null,
|
|
1087
|
+
uptime: alive && info?.startedAt ? Math.max(0, Date.now() - Date.parse(info.startedAt)) : 0,
|
|
1088
|
+
logSize: readLogSize(),
|
|
1089
|
+
crashCount: 0,
|
|
1090
|
+
errorCount: 0,
|
|
1091
|
+
isRuntimeService: true,
|
|
1092
|
+
runtimeServiceKind: "reverse-proxy",
|
|
1093
|
+
managedByFifonyRuntime: true,
|
|
1094
|
+
enabled: options?.enabled ?? false,
|
|
1095
|
+
localDomain: options?.localDomain ?? null
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function getMeshRuntimeSnapshotStatus(options) {
|
|
1099
|
+
const info = readRuntimePidInfo();
|
|
1100
|
+
const alive = info?.pid != null && isProcessAlive(info.pid);
|
|
1101
|
+
const ready = alive && Boolean(info?.controlPort) && Boolean(info?.meshPort);
|
|
1102
|
+
return {
|
|
1103
|
+
id: "mesh",
|
|
1104
|
+
name: "Service Mesh",
|
|
1105
|
+
command: info?.command ?? getNetworkRuntimeCommand().command,
|
|
1106
|
+
port: info?.meshPort ?? options?.configuredPort,
|
|
1107
|
+
state: ready ? "running" : "stopped",
|
|
1108
|
+
running: ready,
|
|
1109
|
+
pid: ready ? info?.pid ?? null : null,
|
|
1110
|
+
startedAt: info?.startedAt ?? null,
|
|
1111
|
+
uptime: alive && info?.startedAt ? Math.max(0, Date.now() - Date.parse(info.startedAt)) : 0,
|
|
1112
|
+
logSize: readLogSize(),
|
|
1113
|
+
crashCount: 0,
|
|
1114
|
+
errorCount: 0,
|
|
1115
|
+
isRuntimeService: true,
|
|
1116
|
+
runtimeServiceKind: "mesh",
|
|
1117
|
+
managedByFifonyRuntime: true,
|
|
1118
|
+
enabled: options?.enabled ?? false
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function getMeshRuntimePortSnapshot() {
|
|
1122
|
+
const info = readRuntimePidInfo();
|
|
1123
|
+
return info?.meshPort ?? null;
|
|
1124
|
+
}
|
|
1125
|
+
async function getReverseProxyRuntimeStats() {
|
|
1126
|
+
const payload = await queryRuntimeControl("/stats");
|
|
1127
|
+
return payload.ok ? payload.data.stats ?? null : null;
|
|
1128
|
+
}
|
|
1129
|
+
async function getReverseProxyRuntimeGraphSnapshot() {
|
|
1130
|
+
const payload = await queryRuntimeControl("/graph");
|
|
1131
|
+
return payload.ok ? payload.data.snapshot ?? null : null;
|
|
1132
|
+
}
|
|
1133
|
+
async function getMeshRuntimeState() {
|
|
1134
|
+
const payload = await queryRuntimeControl("/mesh/status");
|
|
1135
|
+
if (!payload.ok) return { running: false, port: null };
|
|
1136
|
+
return {
|
|
1137
|
+
running: payload.data.running === true,
|
|
1138
|
+
port: payload.data.port ?? null
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
async function getMeshRuntimeTraffic(limit = 200) {
|
|
1142
|
+
const payload = await queryRuntimeControl(`/mesh/traffic?limit=${limit}`);
|
|
1143
|
+
return payload.ok ? payload.data.entries ?? null : null;
|
|
1144
|
+
}
|
|
1145
|
+
async function getMeshRuntimeEvents(afterSeq = 0, limit = 200) {
|
|
1146
|
+
const payload = await queryRuntimeControl(
|
|
1147
|
+
`/mesh/events?afterSeq=${afterSeq}&limit=${limit}`
|
|
1148
|
+
);
|
|
1149
|
+
return payload.ok ? {
|
|
1150
|
+
events: payload.data.events ?? [],
|
|
1151
|
+
currentSeq: typeof payload.data.currentSeq === "number" ? payload.data.currentSeq : null
|
|
1152
|
+
} : null;
|
|
1153
|
+
}
|
|
1154
|
+
async function clearMeshRuntimeTraffic() {
|
|
1155
|
+
const payload = await queryRuntimeControl("/mesh/clear", { method: "POST" });
|
|
1156
|
+
return payload.ok;
|
|
1157
|
+
}
|
|
1158
|
+
async function getMeshRuntimeStats() {
|
|
1159
|
+
const payload = await queryRuntimeControl("/mesh/stats");
|
|
1160
|
+
return payload.ok ? payload.data.stats ?? null : null;
|
|
1161
|
+
}
|
|
1162
|
+
async function getMeshRuntimeGraph() {
|
|
1163
|
+
const payload = await queryRuntimeControl("/mesh/graph");
|
|
1164
|
+
return payload.ok ? {
|
|
1165
|
+
graph: payload.data.graph ?? null,
|
|
1166
|
+
nativeGraph: payload.data.nativeGraph ?? null
|
|
1167
|
+
} : null;
|
|
1168
|
+
}
|
|
1169
|
+
async function getMeshRuntimeMetrics(format = "prometheus") {
|
|
1170
|
+
const payload = await queryRuntimeControl(`/mesh/metrics?format=${format}`);
|
|
1171
|
+
return payload.ok ? payload.data.metrics ?? null : null;
|
|
1172
|
+
}
|
|
1173
|
+
async function getReverseProxyRuntimeState() {
|
|
1174
|
+
const info = readRuntimePidInfo();
|
|
1175
|
+
const alive = info?.pid != null && isProcessAlive(info.pid);
|
|
1176
|
+
if (!info) {
|
|
1177
|
+
return { running: false, pid: null, proxyPort: null, controlPort: null, startedAt: null, localDomain: null };
|
|
1178
|
+
}
|
|
1179
|
+
const status = await queryRuntimeControl("/status");
|
|
1180
|
+
if (status.ok) {
|
|
1181
|
+
return {
|
|
1182
|
+
running: status.data.reverseProxy?.running ?? false,
|
|
1183
|
+
pid: alive ? info.pid : null,
|
|
1184
|
+
proxyPort: status.data.reverseProxy?.proxyPort ?? info.proxyPort ?? null,
|
|
1185
|
+
controlPort: status.data.controlPort ?? info.controlPort ?? null,
|
|
1186
|
+
startedAt: status.data.startedAt ?? info.startedAt ?? null,
|
|
1187
|
+
localDomain: status.data.localDomain ?? info.localDomain ?? null
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
running: false,
|
|
1192
|
+
pid: null,
|
|
1193
|
+
proxyPort: info.proxyPort ?? null,
|
|
1194
|
+
controlPort: info.controlPort ?? null,
|
|
1195
|
+
startedAt: info.startedAt ?? null,
|
|
1196
|
+
localDomain: info.localDomain ?? null
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
export {
|
|
1201
|
+
buildProxyEnvVars,
|
|
1202
|
+
getReverseProxyCaCertPath,
|
|
1203
|
+
ensureReverseProxyTlsCert,
|
|
1204
|
+
invalidateReverseProxyCert,
|
|
1205
|
+
buildRaffelRoutes,
|
|
1206
|
+
startReverseProxy,
|
|
1207
|
+
stopReverseProxy,
|
|
1208
|
+
startMeshProxy,
|
|
1209
|
+
stopMeshProxy,
|
|
1210
|
+
isReverseProxyRunning,
|
|
1211
|
+
isMeshProxyRunning,
|
|
1212
|
+
getReverseProxyPort,
|
|
1213
|
+
getMeshProxyPort,
|
|
1214
|
+
getReverseProxyCaCert,
|
|
1215
|
+
getReverseProxyStats,
|
|
1216
|
+
getReverseProxyGraphSnapshot,
|
|
1217
|
+
getMeshStats,
|
|
1218
|
+
getMeshNativeGraphSnapshot,
|
|
1219
|
+
getMeshMetrics,
|
|
1220
|
+
getMeshTrafficEntries,
|
|
1221
|
+
clearMeshData,
|
|
1222
|
+
getMeshServiceGraph,
|
|
1223
|
+
getMeshTelemetryEvents,
|
|
1224
|
+
runReverseProxyRuntimeProcess,
|
|
1225
|
+
getReverseProxyRuntimeLogPath,
|
|
1226
|
+
getMeshRuntimeLogPath,
|
|
1227
|
+
getReverseProxyRuntimePidPath,
|
|
1228
|
+
stopNetworkRuntime,
|
|
1229
|
+
applyNetworkRuntimeConfig,
|
|
1230
|
+
startReverseProxyRuntime,
|
|
1231
|
+
stopReverseProxyRuntime,
|
|
1232
|
+
restartReverseProxyRuntime,
|
|
1233
|
+
getReverseProxyRuntimeSnapshotStatus,
|
|
1234
|
+
getMeshRuntimeSnapshotStatus,
|
|
1235
|
+
getMeshRuntimePortSnapshot,
|
|
1236
|
+
getReverseProxyRuntimeStats,
|
|
1237
|
+
getReverseProxyRuntimeGraphSnapshot,
|
|
1238
|
+
getMeshRuntimeState,
|
|
1239
|
+
getMeshRuntimeTraffic,
|
|
1240
|
+
getMeshRuntimeEvents,
|
|
1241
|
+
clearMeshRuntimeTraffic,
|
|
1242
|
+
getMeshRuntimeStats,
|
|
1243
|
+
getMeshRuntimeGraph,
|
|
1244
|
+
getMeshRuntimeMetrics,
|
|
1245
|
+
getReverseProxyRuntimeState
|
|
1246
|
+
};
|
|
1247
|
+
//# sourceMappingURL=chunk-7R7XFXJM.js.map
|