unplugin-https-reverse-proxy 4.0.3 → 4.1.0-beta.2

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 (53) hide show
  1. package/README.md +30 -6
  2. package/dist/{chunk-MD5CVZP7.js → chunk-7CA3EIIX.js} +682 -224
  3. package/dist/{chunk-RSBXFJ3Z.js → chunk-C5A7XLKL.js} +9 -2
  4. package/dist/{chunk-2P7SUCQ3.js → chunk-EY3HXBJ4.js} +0 -26
  5. package/dist/chunk-RKHJN6KY.js +53 -0
  6. package/dist/esbuild.cjs +790 -303
  7. package/dist/esbuild.js +4 -3
  8. package/dist/hmr-sse-bridge.cjs +12 -4
  9. package/dist/hmr-sse-bridge.d.cts +4 -3
  10. package/dist/hmr-sse-bridge.d.ts +4 -3
  11. package/dist/hmr-sse-bridge.js +5 -3
  12. package/dist/index.cjs +790 -303
  13. package/dist/index.js +4 -3
  14. package/dist/nuxt.cjs +787 -292
  15. package/dist/nuxt.js +5 -9
  16. package/dist/privileged-helper.cjs +668 -0
  17. package/dist/privileged-helper.d.cts +2 -0
  18. package/dist/privileged-helper.d.ts +2 -0
  19. package/dist/privileged-helper.js +641 -0
  20. package/dist/rollup.cjs +790 -303
  21. package/dist/rollup.js +4 -3
  22. package/dist/rspack.cjs +790 -303
  23. package/dist/rspack.js +4 -3
  24. package/dist/utils.cjs +0 -28
  25. package/dist/utils.d.cts +1 -3
  26. package/dist/utils.d.ts +1 -3
  27. package/dist/utils.js +1 -5
  28. package/dist/vite.cjs +790 -303
  29. package/dist/vite.js +4 -3
  30. package/dist/webpack.cjs +790 -303
  31. package/dist/webpack.js +4 -3
  32. package/package.json +1 -4
  33. package/src/caddy/caddyfile.ts +1 -0
  34. package/src/caddy/index.ts +183 -110
  35. package/src/caddy/privileged.ts +118 -0
  36. package/src/caddy/utils.ts +0 -43
  37. package/src/hmr-sse-bridge.ts +14 -3
  38. package/src/index.ts +4 -18
  39. package/src/nuxt.ts +1 -6
  40. package/src/privileged/bootstrap.ts +174 -0
  41. package/src/privileged/client.ts +287 -0
  42. package/src/privileged/entry.ts +45 -0
  43. package/src/privileged/hosts-file.ts +142 -0
  44. package/src/privileged/launch.ts +193 -0
  45. package/src/privileged/ports.ts +45 -0
  46. package/src/privileged/protocol.ts +111 -0
  47. package/src/privileged/server.ts +465 -0
  48. package/src/privileged/session.ts +114 -0
  49. package/src/privileged/validation.ts +38 -0
  50. package/src/privileged/windows-elevation.ts +80 -0
  51. package/src/privileged-helper.ts +109 -0
  52. package/src/utils.ts +0 -28
  53. package/src/host/index.ts +0 -52
@@ -0,0 +1,641 @@
1
+ import {
2
+ serializeMessage
3
+ } from "./chunk-RKHJN6KY.js";
4
+
5
+ // src/privileged-helper.ts
6
+ import process3 from "node:process";
7
+
8
+ // src/privileged/bootstrap.ts
9
+ import { Buffer } from "node:buffer";
10
+ import { chmodSync, chownSync, rmSync } from "node:fs";
11
+ import { createServer } from "node:net";
12
+ import process2 from "node:process";
13
+
14
+ // src/privileged/server.ts
15
+ import { execFile, spawn } from "node:child_process";
16
+ import { lchownSync, lstatSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { platform } from "node:os";
18
+ import { join } from "node:path";
19
+ import process from "node:process";
20
+ import { promisify } from "node:util";
21
+
22
+ // src/privileged/hosts-file.ts
23
+ import { isIP } from "node:net";
24
+ var HOSTS_MARKER = "# uhrp";
25
+ var HOSTS_MARKER_PATTERN = /#\s*uhrp\b/;
26
+ function detectEol(content) {
27
+ return content.includes("\r\n") ? "\r\n" : "\n";
28
+ }
29
+ function toBodyLines(content) {
30
+ const lines = content.split(/\r?\n/);
31
+ if (lines.length > 0 && lines[lines.length - 1] === "")
32
+ lines.pop();
33
+ return lines;
34
+ }
35
+ function parseLine(line) {
36
+ const withoutComment = line.replace(/#.*/, "").trim();
37
+ if (!withoutComment)
38
+ return void 0;
39
+ const [ip, ...hosts] = withoutComment.split(/\s+/);
40
+ if (!ip || hosts.length === 0)
41
+ return void 0;
42
+ return { ip, hosts };
43
+ }
44
+ function isOwned(line) {
45
+ return HOSTS_MARKER_PATTERN.test(line);
46
+ }
47
+ function mentionsHost(entry, host) {
48
+ return entry.hosts.some((candidate) => candidate.toLowerCase() === host.toLowerCase());
49
+ }
50
+ function sameIpFamily(left, right) {
51
+ const leftVersion = isIP(left);
52
+ return leftVersion !== 0 && leftVersion === isIP(right);
53
+ }
54
+ function rebuild(lines, original, eol) {
55
+ const content = lines.length > 0 ? lines.join(eol) + eol : "";
56
+ return { content, changed: content !== original };
57
+ }
58
+ function addHost(content, ip, host) {
59
+ var _a;
60
+ const eol = detectEol(content);
61
+ const lines = [];
62
+ for (const line of toBodyLines(content)) {
63
+ const entry = parseLine(line);
64
+ if (!entry || !mentionsHost(entry, host) || !sameIpFamily(entry.ip, ip)) {
65
+ lines.push(line);
66
+ continue;
67
+ }
68
+ const remaining = entry.hosts.filter((candidate) => candidate.toLowerCase() !== host.toLowerCase());
69
+ if (remaining.length === 0)
70
+ continue;
71
+ const comment = ((_a = line.match(/#.*/)) == null ? void 0 : _a[0]) ?? "";
72
+ const rebuilt = `${entry.ip} ${remaining.join(" ")}${comment ? ` ${comment}` : ""}`;
73
+ lines.push(rebuilt);
74
+ }
75
+ lines.push(`${ip} ${host} ${HOSTS_MARKER}`);
76
+ return rebuild(lines, content, eol);
77
+ }
78
+ function removeHost(content, ip, host) {
79
+ var _a;
80
+ const eol = detectEol(content);
81
+ const lines = [];
82
+ for (const line of toBodyLines(content)) {
83
+ const entry = parseLine(line);
84
+ if (!entry || entry.ip !== ip || !mentionsHost(entry, host)) {
85
+ lines.push(line);
86
+ continue;
87
+ }
88
+ const remaining = entry.hosts.filter((candidate) => candidate.toLowerCase() !== host.toLowerCase());
89
+ if (remaining.length === 0)
90
+ continue;
91
+ const comment = ((_a = line.match(/#.*/)) == null ? void 0 : _a[0]) ?? "";
92
+ lines.push(`${entry.ip} ${remaining.join(" ")}${comment ? ` ${comment}` : ""}`);
93
+ }
94
+ return rebuild(lines, content, eol);
95
+ }
96
+ function removeMarkedHosts(content) {
97
+ const eol = detectEol(content);
98
+ const lines = toBodyLines(content).filter((line) => !isOwned(line));
99
+ return rebuild(lines, content, eol);
100
+ }
101
+
102
+ // src/privileged/ports.ts
103
+ function parseListeningPids(output) {
104
+ const pids = /* @__PURE__ */ new Set();
105
+ for (const line of output.split(/\r?\n/)) {
106
+ const trimmed = line.trim();
107
+ if (!trimmed)
108
+ continue;
109
+ if (!/^\d+$/.test(trimmed))
110
+ continue;
111
+ const pid = Number(trimmed);
112
+ if (Number.isSafeInteger(pid) && pid > 0)
113
+ pids.add(pid);
114
+ }
115
+ return [...pids];
116
+ }
117
+ function parseWindowsListeningPids(output, port) {
118
+ const pids = /* @__PURE__ */ new Set();
119
+ const suffix = `:${port}`;
120
+ for (const line of output.split(/\r?\n/)) {
121
+ const columns = line.trim().split(/\s+/);
122
+ if (columns.length < 5)
123
+ continue;
124
+ const protocol = columns[0].toUpperCase();
125
+ if (protocol !== "TCP")
126
+ continue;
127
+ if (!columns[1].endsWith(suffix))
128
+ continue;
129
+ if (columns[3].toUpperCase() !== "LISTENING")
130
+ continue;
131
+ const pid = Number(columns[4]);
132
+ if (Number.isSafeInteger(pid) && pid > 0)
133
+ pids.add(pid);
134
+ }
135
+ return [...pids];
136
+ }
137
+
138
+ // src/privileged/validation.ts
139
+ import { isIP as isIP2 } from "node:net";
140
+ var ALLOWED_FREE_PORTS = /* @__PURE__ */ new Set([80, 443]);
141
+ var HOSTNAME_PATTERN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
142
+ var IPV6_IN_HOSTS = /^[0-9a-f:.]+$/i;
143
+ function isSafeHost(host) {
144
+ if (typeof host !== "string" || host.length === 0 || host.length > 253)
145
+ return false;
146
+ if (/[\s#\0]/.test(host))
147
+ return false;
148
+ return HOSTNAME_PATTERN.test(host);
149
+ }
150
+ function isSafeIp(ip) {
151
+ if (typeof ip !== "string")
152
+ return false;
153
+ if (isIP2(ip) !== 0)
154
+ return true;
155
+ return IPV6_IN_HOSTS.test(ip);
156
+ }
157
+ function isAllowedFreePort(port) {
158
+ return typeof port === "number" && Number.isInteger(port) && ALLOWED_FREE_PORTS.has(port);
159
+ }
160
+
161
+ // src/privileged/server.ts
162
+ var execFileAsync = promisify(execFile);
163
+ function execFileQuiet(file, args) {
164
+ return new Promise((resolve) => {
165
+ execFile(file, args, (error, stdout) => resolve({ stdout: error && !stdout ? "" : stdout }));
166
+ });
167
+ }
168
+ var HOSTS_PATH = platform() === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
169
+ var FLUSH_COMMANDS = {
170
+ win32: [{ file: "ipconfig", args: ["/flushdns"] }],
171
+ darwin: [{ file: "killall", args: ["-HUP", "mDNSResponder"] }],
172
+ // systemd-resolved is the common case; the legacy name covers older distros
173
+ // and hosts without resolved simply have no cache to drop.
174
+ linux: [
175
+ { file: "resolvectl", args: ["flush-caches"] },
176
+ { file: "systemd-resolve", args: ["--flush-caches"] }
177
+ ]
178
+ };
179
+ var FLUSH_FALLBACK = [{ file: "killall", args: ["-HUP", "mDNSResponder"] }];
180
+ async function flushDnsBestEffort(deps) {
181
+ try {
182
+ await deps.flushDns();
183
+ } catch (error) {
184
+ deps.log(`dns flush failed: ${error.message}`);
185
+ }
186
+ }
187
+ function createHelperRuntime(deps) {
188
+ const clients = /* @__PURE__ */ new Set();
189
+ let teardown;
190
+ let generation = 0;
191
+ let caddy;
192
+ const broadcast = (event) => {
193
+ const line = serializeMessage(event);
194
+ for (const client of clients) {
195
+ const socket = client;
196
+ if (typeof socket.write === "function" && !socket.destroyed)
197
+ socket.write(line);
198
+ }
199
+ };
200
+ async function stopCaddy() {
201
+ const current = caddy;
202
+ caddy = void 0;
203
+ if (current) {
204
+ try {
205
+ current.kill("SIGTERM");
206
+ } catch {
207
+ }
208
+ }
209
+ await deps.stopCaddy().catch(() => {
210
+ });
211
+ if (deps.caddyStoragePath && deps.storageOwner)
212
+ await reclaimTree(deps.caddyStoragePath, deps.storageOwner.uid, deps.storageOwner.gid).catch(() => {
213
+ });
214
+ }
215
+ function dispose() {
216
+ if (!teardown) {
217
+ const startedAt = generation;
218
+ teardown = (async () => {
219
+ try {
220
+ if (generation === startedAt) {
221
+ const edited = removeMarkedHosts(deps.readHosts());
222
+ if (edited.changed) {
223
+ deps.writeHosts(edited.content);
224
+ await deps.flushDns().catch(() => {
225
+ });
226
+ }
227
+ }
228
+ } catch (error) {
229
+ deps.log(`hosts cleanup failed: ${error.message}`);
230
+ }
231
+ if (generation === startedAt)
232
+ await stopCaddy();
233
+ })();
234
+ }
235
+ return teardown;
236
+ }
237
+ async function sweepStaleEntries() {
238
+ try {
239
+ const edited = removeMarkedHosts(deps.readHosts());
240
+ if (!edited.changed)
241
+ return false;
242
+ deps.writeHosts(edited.content);
243
+ await flushDnsBestEffort(deps);
244
+ return true;
245
+ } catch (error) {
246
+ deps.log(`stale hosts cleanup failed: ${error.message}`);
247
+ return false;
248
+ }
249
+ }
250
+ async function handle(request) {
251
+ try {
252
+ switch (request.method) {
253
+ case "ping":
254
+ return { id: request.id, ok: true, result: { pid: process.pid } };
255
+ case "hosts.add": {
256
+ const { ip, host } = request.params;
257
+ if (!isSafeIp(ip) || !isSafeHost(host))
258
+ return { id: request.id, ok: false, error: { message: "invalid hosts entry" } };
259
+ const edited = addHost(deps.readHosts(), ip, host);
260
+ if (edited.changed)
261
+ deps.writeHosts(edited.content);
262
+ await flushDnsBestEffort(deps);
263
+ return { id: request.id, ok: true };
264
+ }
265
+ case "hosts.remove": {
266
+ const { ip, host } = request.params;
267
+ if (!isSafeIp(ip) || !isSafeHost(host))
268
+ return { id: request.id, ok: false, error: { message: "invalid hosts entry" } };
269
+ const edited = removeHost(deps.readHosts(), ip, host);
270
+ if (edited.changed)
271
+ deps.writeHosts(edited.content);
272
+ await flushDnsBestEffort(deps);
273
+ return { id: request.id, ok: true };
274
+ }
275
+ case "port.free": {
276
+ const { port } = request.params;
277
+ if (!isAllowedFreePort(port))
278
+ return { id: request.id, ok: false, error: { message: `port ${port} is not releasable` } };
279
+ const killed = await deps.freePort(port);
280
+ return { id: request.id, ok: true, result: { killed } };
281
+ }
282
+ case "caddy.start": {
283
+ if (caddy)
284
+ await stopCaddy();
285
+ caddy = deps.startCaddy(broadcast);
286
+ return { id: request.id, ok: true, result: { pid: caddy.pid } };
287
+ }
288
+ case "caddy.stop": {
289
+ await stopCaddy();
290
+ return { id: request.id, ok: true };
291
+ }
292
+ case "ca.trust": {
293
+ const result = await deps.trustLocalCa();
294
+ return { id: request.id, ok: true, result };
295
+ }
296
+ default:
297
+ return { id: request.id, ok: false, error: { message: "unknown method" } };
298
+ }
299
+ } catch (error) {
300
+ return { id: request.id, ok: false, error: { message: error.message } };
301
+ }
302
+ }
303
+ return {
304
+ handle,
305
+ attach(client) {
306
+ clients.add(client);
307
+ generation++;
308
+ teardown = void 0;
309
+ },
310
+ async detach(client) {
311
+ clients.delete(client);
312
+ if (clients.size === 0)
313
+ await dispose();
314
+ },
315
+ dispose,
316
+ sweepStaleEntries
317
+ };
318
+ }
319
+ async function reclaimTree(target, uid, gid) {
320
+ const reclaimed = [];
321
+ const walk = (path) => {
322
+ let stats;
323
+ try {
324
+ stats = lstatSync(path);
325
+ } catch {
326
+ return;
327
+ }
328
+ try {
329
+ lchownSync(path, uid, gid);
330
+ reclaimed.push(path);
331
+ } catch {
332
+ }
333
+ if (!stats.isDirectory())
334
+ return;
335
+ let entries;
336
+ try {
337
+ entries = readdirSync(path);
338
+ } catch {
339
+ return;
340
+ }
341
+ for (const entry of entries)
342
+ walk(join(path, entry));
343
+ };
344
+ walk(target);
345
+ return reclaimed;
346
+ }
347
+ async function trustLocalCaWithSystemTools(caddyBin) {
348
+ try {
349
+ const { stdout, stderr } = await execFileAsync(
350
+ caddyBin,
351
+ ["trust", "--ca", "local", "--address", "127.0.0.1:2019"],
352
+ { timeout: 12e4 }
353
+ );
354
+ return { trusted: true, output: `${stdout}${stderr}`.trim() };
355
+ } catch (error) {
356
+ const output = String(error.stdout ?? "") + String(error.stderr ?? "");
357
+ if (/already|not modified|no changes/i.test(output))
358
+ return { trusted: true, output: output.trim() };
359
+ throw new Error(output.trim() || error.message);
360
+ }
361
+ }
362
+ function createNodeHelperDeps(options) {
363
+ const hostsPath = options.hostsPath ?? HOSTS_PATH;
364
+ const log = options.log ?? ((message) => process.stdout.write(`${message}
365
+ `));
366
+ let caddy;
367
+ return {
368
+ readHosts: () => readFileSync(hostsPath, "utf8"),
369
+ writeHosts: (content) => {
370
+ writeFileSync(hostsPath, content);
371
+ },
372
+ flushDns: async () => {
373
+ const candidates = FLUSH_COMMANDS[platform()] ?? [...FLUSH_FALLBACK];
374
+ let lastError;
375
+ for (const candidate of candidates) {
376
+ try {
377
+ await execFileAsync(candidate.file, candidate.args);
378
+ return;
379
+ } catch (error) {
380
+ lastError = error;
381
+ }
382
+ }
383
+ throw lastError ?? new Error("no DNS cache flush command for this platform");
384
+ },
385
+ freePort: async (port) => {
386
+ const pids = platform() === "win32" ? parseWindowsListeningPids((await execFileAsync("netstat", ["-ano"])).stdout, port) : parseListeningPids((await execFileQuiet("lsof", [`-ti:${port}`, "-sTCP:LISTEN"])).stdout);
387
+ const killed = [];
388
+ for (const pid of pids) {
389
+ if (pid === process.pid)
390
+ continue;
391
+ try {
392
+ process.kill(pid, "SIGTERM");
393
+ killed.push(pid);
394
+ } catch {
395
+ }
396
+ }
397
+ return killed;
398
+ },
399
+ caddyBin: options.caddyBin,
400
+ caddyConfigPath: options.caddyConfigPath,
401
+ caddyCwd: options.caddyCwd ?? process.cwd(),
402
+ caddyEnv: options.caddyEnv ?? {},
403
+ trustLocalCa: () => trustLocalCaWithSystemTools(options.caddyBin),
404
+ caddyStoragePath: options.caddyStoragePath,
405
+ storageOwner: options.storageOwner,
406
+ startCaddy: (emit) => {
407
+ var _a, _b;
408
+ const child = spawn(options.caddyBin, ["run", "--adapter", "caddyfile", "--config", options.caddyConfigPath], {
409
+ cwd: options.caddyCwd ?? process.cwd(),
410
+ env: { ...process.env, ...options.caddyEnv },
411
+ stdio: ["ignore", "pipe", "pipe"]
412
+ });
413
+ (_a = child.stdout) == null ? void 0 : _a.on("data", (data) => emit({ type: "event", kind: "caddy.stdout", data: data.toString() }));
414
+ (_b = child.stderr) == null ? void 0 : _b.on("data", (data) => emit({ type: "event", kind: "caddy.stderr", data: data.toString() }));
415
+ child.on("exit", (code, signal) => emit({ type: "event", kind: "caddy.exit", code, signal }));
416
+ caddy = child;
417
+ return caddy;
418
+ },
419
+ stopCaddy: async () => {
420
+ const current = caddy;
421
+ caddy = void 0;
422
+ if (!current)
423
+ return;
424
+ try {
425
+ current.kill("SIGTERM");
426
+ } catch {
427
+ }
428
+ },
429
+ log
430
+ };
431
+ }
432
+ function attachSocket(socket, runtime, log) {
433
+ let buffer = "";
434
+ runtime.attach(socket);
435
+ socket.on("data", (chunk) => {
436
+ buffer += chunk.toString("utf8");
437
+ let index = buffer.indexOf("\n");
438
+ while (index >= 0) {
439
+ const line = buffer.slice(0, index);
440
+ buffer = buffer.slice(index + 1);
441
+ index = buffer.indexOf("\n");
442
+ if (!line.trim())
443
+ continue;
444
+ let request;
445
+ try {
446
+ request = JSON.parse(line);
447
+ } catch (error) {
448
+ log(`invalid request: ${error.message}`);
449
+ continue;
450
+ }
451
+ void runtime.handle(request).then((response) => {
452
+ if (!socket.destroyed)
453
+ socket.write(serializeMessage(response));
454
+ });
455
+ }
456
+ });
457
+ socket.on("error", (error) => {
458
+ log(`connection error: ${error.message}`);
459
+ });
460
+ socket.on("close", () => {
461
+ void runtime.detach(socket);
462
+ });
463
+ }
464
+
465
+ // src/privileged/bootstrap.ts
466
+ var HANDSHAKE_TIMEOUT_MS = 12e4;
467
+ async function startHelper(options) {
468
+ const { socketPath, token } = options;
469
+ const log = options.log ?? ((message) => process2.stdout.write(`[uhrp-helper] ${message}
470
+ `));
471
+ const deps = options.deps ?? createNodeHelperDeps({
472
+ hostsPath: options.hostsPath,
473
+ log,
474
+ caddyBin: options.caddyBin,
475
+ caddyConfigPath: options.caddyConfigPath,
476
+ caddyCwd: options.caddyCwd,
477
+ caddyEnv: options.caddyEnv,
478
+ caddyStoragePath: options.caddyStoragePath,
479
+ storageOwner: typeof options.ownerUid === "number" ? { uid: options.ownerUid, gid: options.ownerGid ?? options.ownerUid } : void 0
480
+ });
481
+ const runtime = createHelperRuntime(deps);
482
+ await runtime.sweepStaleEntries().catch(() => {
483
+ });
484
+ try {
485
+ rmSync(socketPath, { force: true });
486
+ } catch {
487
+ }
488
+ const connections = /* @__PURE__ */ new Set();
489
+ const server = createServer((socket) => {
490
+ var _a;
491
+ let buffer = "";
492
+ let authenticated = false;
493
+ connections.add(socket);
494
+ socket.on("close", () => connections.delete(socket));
495
+ const timer = setTimeout(() => {
496
+ if (!authenticated)
497
+ socket.destroy();
498
+ }, HANDSHAKE_TIMEOUT_MS);
499
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
500
+ const onData = (chunk) => {
501
+ buffer += chunk.toString("utf8");
502
+ const index = buffer.indexOf("\n");
503
+ if (index < 0)
504
+ return;
505
+ const line = buffer.slice(0, index);
506
+ const rest = buffer.slice(index + 1);
507
+ let parsed;
508
+ try {
509
+ parsed = JSON.parse(line);
510
+ } catch {
511
+ socket.destroy();
512
+ return;
513
+ }
514
+ if (parsed.type !== "handshake" || typeof parsed.token !== "string") {
515
+ socket.destroy();
516
+ return;
517
+ }
518
+ clearTimeout(timer);
519
+ socket.removeListener("data", onData);
520
+ if (parsed.token !== token) {
521
+ socket.end(`${JSON.stringify({ type: "handshake", ok: false, reason: "invalid token" })}
522
+ `);
523
+ return;
524
+ }
525
+ authenticated = true;
526
+ socket.write(`${JSON.stringify({ type: "handshake", ok: true, pid: process2.pid })}
527
+ `);
528
+ attachSocket(socket, runtime, log);
529
+ if (rest.trim() !== "")
530
+ socket.emit("data", Buffer.from(rest));
531
+ };
532
+ socket.on("data", onData);
533
+ socket.on("error", () => {
534
+ });
535
+ });
536
+ await new Promise((resolve, reject) => {
537
+ server.once("error", reject);
538
+ server.listen(socketPath, () => resolve());
539
+ });
540
+ try {
541
+ if (typeof options.ownerUid === "number") {
542
+ chownSync(socketPath, options.ownerUid, options.ownerGid ?? options.ownerUid);
543
+ chmodSync(socketPath, 384);
544
+ } else {
545
+ chmodSync(socketPath, 384);
546
+ }
547
+ } catch (error) {
548
+ log(`could not set socket ownership: ${error.message}`);
549
+ }
550
+ return {
551
+ runtime,
552
+ async close() {
553
+ for (const socket of connections)
554
+ socket.destroy();
555
+ connections.clear();
556
+ await new Promise((resolve) => {
557
+ server.close(() => resolve());
558
+ });
559
+ }
560
+ };
561
+ }
562
+
563
+ // src/privileged-helper.ts
564
+ function parseArgs(argv) {
565
+ let socket = "";
566
+ let token = "";
567
+ let hosts;
568
+ let caddyBin = "";
569
+ let caddyConfig = "";
570
+ let ownerUid;
571
+ let ownerGid;
572
+ let caddyStorage;
573
+ for (let index = 0; index < argv.length; index++) {
574
+ const current = argv[index];
575
+ if (current === "--socket")
576
+ socket = argv[++index] ?? "";
577
+ else if (current === "--token")
578
+ token = argv[++index] ?? "";
579
+ else if (current === "--hosts")
580
+ hosts = argv[++index] ?? void 0;
581
+ else if (current === "--caddy")
582
+ caddyBin = argv[++index] ?? "";
583
+ else if (current === "--caddy-config")
584
+ caddyConfig = argv[++index] ?? "";
585
+ else if (current === "--owner-uid")
586
+ ownerUid = Number(argv[++index]);
587
+ else if (current === "--owner-gid")
588
+ ownerGid = Number(argv[++index]);
589
+ else if (current === "--caddy-storage")
590
+ caddyStorage = argv[++index] ?? void 0;
591
+ }
592
+ if (!socket || !token)
593
+ throw new Error("usage: privileged-helper --socket <path> --token <token> --caddy <bin> --caddy-config <path>");
594
+ if (!caddyBin || !caddyConfig)
595
+ throw new Error("privileged-helper requires --caddy and --caddy-config");
596
+ return {
597
+ socket,
598
+ token,
599
+ hosts,
600
+ caddyBin,
601
+ caddyConfig,
602
+ ownerUid: Number.isInteger(ownerUid) ? ownerUid : void 0,
603
+ ownerGid: Number.isInteger(ownerGid) ? ownerGid : void 0,
604
+ caddyStorage
605
+ };
606
+ }
607
+ async function main() {
608
+ const args = parseArgs(process3.argv.slice(2));
609
+ const helper = await startHelper({
610
+ socketPath: args.socket,
611
+ token: args.token,
612
+ hostsPath: args.hosts,
613
+ caddyBin: args.caddyBin,
614
+ caddyConfigPath: args.caddyConfig,
615
+ ownerUid: args.ownerUid,
616
+ ownerGid: args.ownerGid,
617
+ caddyStoragePath: args.caddyStorage,
618
+ caddyEnv: {
619
+ HOME: process3.env.HOME ?? "",
620
+ // Keep Caddy's storage next to the invoking user's home even though the
621
+ // process runs as root.
622
+ XDG_DATA_HOME: process3.env.XDG_DATA_HOME ?? ""
623
+ }
624
+ });
625
+ process3.on("SIGINT", () => {
626
+ });
627
+ const shutdown = () => {
628
+ void helper.runtime.dispose().finally(() => process3.exit(0));
629
+ };
630
+ process3.on("SIGTERM", shutdown);
631
+ process3.on("SIGHUP", shutdown);
632
+ process3.stdout.on("error", () => {
633
+ });
634
+ process3.stderr.on("error", () => {
635
+ });
636
+ }
637
+ main().catch((error) => {
638
+ process3.stderr.write(`[uhrp-helper] fatal: ${error.message}
639
+ `);
640
+ process3.exit(1);
641
+ });