@catheadowl/ukp 0.1.1 → 0.2.1
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 +34 -24
- package/bin/ukp.js +82 -0
- package/package.json +4 -5
- package/src/capabilities/nav.ts +4 -2
- package/src/capabilities/read.ts +33 -3
- package/src/capabilities/remote-client.ts +351 -60
- package/src/capabilities/rg.ts +2 -0
- package/src/capabilities/search.ts +25 -5
- package/src/capabilities/tls-identity.ts +172 -30
- package/src/cli.ts +1 -1
- package/src/commands/guide-content/client.txt +12 -0
- package/src/commands/guide-content/remote.txt +64 -0
- package/src/commands/guide-content/service-qmd.txt +1 -1
- package/src/commands/guide-content/service.txt +33 -32
- package/src/commands/guide.ts +11 -4
- package/src/commands/init.ts +9 -12
- package/src/commands/inventory.ts +189 -48
- package/src/commands/nav.ts +1 -1
- package/src/commands/read.ts +48 -9
- package/src/commands/rg.ts +4 -1
- package/src/commands/search.ts +4 -1
- package/src/commands/serve.ts +86 -12
- package/src/config/manifest.ts +6 -0
- package/src/registry.ts +104 -16
- package/src/server.ts +276 -46
- package/bun.lock +0 -35
- package/tsconfig.json +0 -12
package/src/server.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readFileSync, writeFileSync, fstatSync, constants as fsConstants } from "node:fs";
|
|
3
|
+
import { createServer as netCreateServer, connect as netConnect, type Server as NetServer } from "node:net";
|
|
3
4
|
import { ENDPOINT_NAME, loadManifest, type LoadedManifest } from "./config/manifest.ts";
|
|
4
5
|
import { localPathOf, readRegistry } from "./registry.ts";
|
|
5
6
|
import { FILE_NATIVE_CAPABILITIES, resolveFileNativeCapability } from "./config/file-native.ts";
|
|
7
|
+
import { EXTERNAL_PROVIDER, EXTERNAL_TOOL_CAPABILITIES } from "./config/external-tool.ts";
|
|
6
8
|
import {
|
|
7
9
|
PROPOSE_SLUG,
|
|
8
10
|
ProposeBusyError,
|
|
@@ -83,11 +85,24 @@ export interface ServeConfig {
|
|
|
83
85
|
* identity under the Service folder (`.ukp/tls/`) — in door mode under the
|
|
84
86
|
* registry's directory, since a door has no single Service folder;
|
|
85
87
|
* `certificates` serves operator-provided PEM files (Let's Encrypt IP
|
|
86
|
-
* certs, mkcert, private CA). `
|
|
87
|
-
*
|
|
88
|
+
* certs, mkcert, private CA). `sanEntries` (the `--tls-san` flag, already
|
|
89
|
+
* normalized `IP:x`/`DNS:y`) merges extra coverage into the self-signed
|
|
90
|
+
* SAN — a NAT/EIP public address no NIC carries; a persisted certificate
|
|
91
|
+
* lacking a requested entry is re-signed over the same key (pin unchanged,
|
|
92
|
+
* clients re-anchor). `opensslCommand` is a test injection point for the
|
|
93
|
+
* self-signing. */
|
|
88
94
|
tls?:
|
|
89
|
-
| { mode: "self-signed"; opensslCommand?: readonly string[] }
|
|
95
|
+
| { mode: "self-signed"; opensslCommand?: readonly string[]; sanEntries?: readonly string[] }
|
|
90
96
|
| { mode: "certificates"; certPath: string; keyPath: string };
|
|
97
|
+
/** W9 / ADR-REM-006: self-reap after this many seconds without requests.
|
|
98
|
+
* The orphan backstop for on-demand-woken doors — a no-TTY remote command
|
|
99
|
+
* can escape SIGHUP on an abrupt disconnect, so the door bounds its own
|
|
100
|
+
* lifetime instead of trusting the session to reap it. */
|
|
101
|
+
maxIdleSeconds?: number;
|
|
102
|
+
/** W10 / ADR-REM-006: serve on the systemd socket-activation listener
|
|
103
|
+
* (LISTEN_FDS fd 3) instead of binding a port — the port belongs to the
|
|
104
|
+
* socket unit; the process only exists while in use. Linux-only. */
|
|
105
|
+
systemdSocket?: boolean;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
export interface DiscoveryDocument {
|
|
@@ -136,11 +151,57 @@ export interface ServeInfo {
|
|
|
136
151
|
port: number;
|
|
137
152
|
authRequired: boolean;
|
|
138
153
|
/** TLS identity summary (W5' / D-079) when serving over HTTPS; the pin is
|
|
139
|
-
* what remote clients TOFU-pin at registration.
|
|
154
|
+
* what remote clients TOFU-pin at registration. `source` "re-signed" marks
|
|
155
|
+
* the one start that grew SAN coverage over the existing key (--tls-san
|
|
156
|
+
* added an entry the persisted certificate lacked) — pin unchanged. */
|
|
140
157
|
tls?: {
|
|
141
158
|
pin: string;
|
|
142
159
|
san: string;
|
|
143
|
-
source: "generated" | "persisted" | "operator";
|
|
160
|
+
source: "generated" | "re-signed" | "persisted" | "operator";
|
|
161
|
+
};
|
|
162
|
+
/** Present when --max-idle armed the self-reap timer (W9). */
|
|
163
|
+
maxIdleSeconds?: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** W9 / ADR-REM-006 (--max-idle): wrap a serve handler so the listener stops
|
|
167
|
+
* itself after `maxIdleSeconds` without requests. Requests re-arm the timer
|
|
168
|
+
* on entry and again once the response settles; the exit mirrors the SIGINT
|
|
169
|
+
* path (stderr line + server.stop(true)), after which the process exits with
|
|
170
|
+
* its already-set code. The timer is unref'd so a SIGINT/SIGTERM stop is
|
|
171
|
+
* immediate — the listener, not the timer, is what holds the serve process
|
|
172
|
+
* open. `stopped` fences the settle-rearm of a request that outlived the
|
|
173
|
+
* window (no second fire after the first stop). The bind indirection exists
|
|
174
|
+
* because Bun.serve hands back the server only after `fetch` is fixed. */
|
|
175
|
+
function armIdleExit(
|
|
176
|
+
handler: (request: Request) => Response | Promise<Response>,
|
|
177
|
+
maxIdleSeconds: number,
|
|
178
|
+
): { fetch: (request: Request) => Response | Promise<Response>; bind: (stop: () => void) => void } {
|
|
179
|
+
const control = { stop: (): void => {} };
|
|
180
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
181
|
+
let stopped = false;
|
|
182
|
+
const rearm = () => {
|
|
183
|
+
if (stopped) return;
|
|
184
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
185
|
+
timer = setTimeout(() => {
|
|
186
|
+
stopped = true;
|
|
187
|
+
console.error(`ukp serve: idle exit (--max-idle ${maxIdleSeconds}s without requests)`);
|
|
188
|
+
control.stop();
|
|
189
|
+
}, maxIdleSeconds * 1000);
|
|
190
|
+
// Best-effort unref: present in Bun and Node; a missing no-op keeps this
|
|
191
|
+
// portable without changing the firing behavior while serving.
|
|
192
|
+
(timer as { unref?: () => void }).unref?.();
|
|
193
|
+
};
|
|
194
|
+
rearm();
|
|
195
|
+
return {
|
|
196
|
+
fetch: (request: Request) => {
|
|
197
|
+
rearm();
|
|
198
|
+
const response = handler(request);
|
|
199
|
+
Promise.resolve(response).then(rearm, () => {});
|
|
200
|
+
return response;
|
|
201
|
+
},
|
|
202
|
+
bind(stop: () => void) {
|
|
203
|
+
control.stop = stop;
|
|
204
|
+
},
|
|
144
205
|
};
|
|
145
206
|
}
|
|
146
207
|
|
|
@@ -171,13 +232,19 @@ function readInstanceUid(serviceFolder: string): string {
|
|
|
171
232
|
}
|
|
172
233
|
|
|
173
234
|
/** Capability projection shared by endpoint and door documents: derived
|
|
174
|
-
* file-native defaults (read/nav) are always present
|
|
175
|
-
*
|
|
235
|
+
* file-native defaults (read/nav) are always present, then the external-tool
|
|
236
|
+
* base tier (rg, ADR-RG-003 — served on /v1/rg for every endpoint; a missing
|
|
237
|
+
* binary degrades to per-endpoint availability data per the W6 wire
|
|
238
|
+
* contract, never an undeclared capability); declared capabilities override
|
|
239
|
+
* or extend both (a declared nav or rg replaces the default entry). */
|
|
176
240
|
function projectCapabilities(loaded: LoadedManifest): DiscoveryDocument["capabilities"] {
|
|
177
241
|
const capabilities: DiscoveryDocument["capabilities"] = {};
|
|
178
242
|
for (const [name, spec] of Object.entries(FILE_NATIVE_CAPABILITIES)) {
|
|
179
243
|
if (spec.derived) capabilities[name] = { provider: "file", derived: true };
|
|
180
244
|
}
|
|
245
|
+
for (const name of Object.keys(EXTERNAL_TOOL_CAPABILITIES)) {
|
|
246
|
+
capabilities[name] = { provider: EXTERNAL_PROVIDER, derived: true };
|
|
247
|
+
}
|
|
181
248
|
for (const name of Object.keys(loaded.manifest.capabilities).sort()) {
|
|
182
249
|
const declared = loaded.manifest.capabilities[name]!;
|
|
183
250
|
capabilities[name] = { provider: declared.provider ?? "file" };
|
|
@@ -449,45 +516,202 @@ function parseRgParams(
|
|
|
449
516
|
export interface StartedServe {
|
|
450
517
|
server: ReturnType<typeof Bun.serve>;
|
|
451
518
|
info: ServeInfo;
|
|
519
|
+
/** Composite stop: in socket-activation mode this also closes the fd3
|
|
520
|
+
* acceptor (Bun.serve's own stop alone would leave the process alive
|
|
521
|
+
* holding the systemd-passed listener). Safe to call from the SIGINT/
|
|
522
|
+
* SIGTERM/SIGHUP handlers and the idle-exit path alike. */
|
|
523
|
+
stopAll: () => void;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** W10 / ADR-REM-006: parse the systemd LISTEN_FDS convention — exactly one
|
|
527
|
+
* listening fd expected (fd 3), PID-guarded, and the variables are CONSUMED
|
|
528
|
+
* (unset) so any child process does not double-count inherited activation
|
|
529
|
+
* fds (the podman fd-counting pitfall, knowledge §5.2). */
|
|
530
|
+
const S_ISSOCK = fsConstants.S_IFSOCK;
|
|
531
|
+
|
|
532
|
+
export function parseListenFds(env: NodeJS.ProcessEnv): { fd: number } | { error: string } {
|
|
533
|
+
if ((env.LISTEN_FDS ?? "") === "" || (env.LISTEN_PID ?? "") === "") {
|
|
534
|
+
return {
|
|
535
|
+
error:
|
|
536
|
+
"--systemd-socket requires systemd socket activation: LISTEN_FDS/LISTEN_PID are not set (run under a .socket unit or systemd-socket-activate)",
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
if (Number(env.LISTEN_PID) !== process.pid) {
|
|
540
|
+
return { error: `LISTEN_PID ${env.LISTEN_PID} does not match this process (${process.pid}) — inherited activation variables belong to another process` };
|
|
541
|
+
}
|
|
542
|
+
const count = Number(env.LISTEN_FDS);
|
|
543
|
+
if (!Number.isSafeInteger(count) || count < 1) {
|
|
544
|
+
return { error: `LISTEN_FDS=${String(env.LISTEN_FDS)} is not a positive integer` };
|
|
545
|
+
}
|
|
546
|
+
if (count !== 1) {
|
|
547
|
+
return { error: `expected exactly one listening fd (LISTEN_FDS=1), got ${count} — run one socket unit per serve instance` };
|
|
548
|
+
}
|
|
549
|
+
delete env.LISTEN_FDS;
|
|
550
|
+
delete env.LISTEN_PID;
|
|
551
|
+
return { fd: 3 };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** W10 / ADR-REM-006: the in-process bridge. Bun.serve cannot adopt a raw
|
|
555
|
+
* listening fd (no public API; ali probe 2026-09-19), but node:net can
|
|
556
|
+
* (`createServer().listen({fd: 3})` verified under bun). Each connection the
|
|
557
|
+
* systemd-held listener accepts is piped to the real Bun.serve listener on
|
|
558
|
+
* an ephemeral loopback port — the fake-ssh proxy pattern, in-process. */
|
|
559
|
+
export function createSocketBridge(acceptor: NetServer, targetPort: number): { destroyAll: () => void } {
|
|
560
|
+
// Live connections are tracked so stopAll can force-close the pairs — a
|
|
561
|
+
// peer that receives FIN but never closes (half-open keep-alive shape)
|
|
562
|
+
// would otherwise hold the process open after the idle exit forever
|
|
563
|
+
// (review P2: probe-confirmed liveness hang), letting stale serve
|
|
564
|
+
// processes accumulate beside systemd's fresh spawns.
|
|
565
|
+
const live = new Set<{ socket: import("node:net").Socket; upstream: import("node:net").Socket }>();
|
|
566
|
+
acceptor.on("connection", (socket) => {
|
|
567
|
+
const upstream = netConnect({ host: "127.0.0.1", port: targetPort });
|
|
568
|
+
const pair = { socket, upstream };
|
|
569
|
+
live.add(pair);
|
|
570
|
+
socket.pipe(upstream);
|
|
571
|
+
upstream.pipe(socket);
|
|
572
|
+
const drop = () => {
|
|
573
|
+
live.delete(pair);
|
|
574
|
+
socket.destroy();
|
|
575
|
+
upstream.destroy();
|
|
576
|
+
};
|
|
577
|
+
socket.on("error", drop);
|
|
578
|
+
upstream.on("error", drop);
|
|
579
|
+
socket.on("close", () => {
|
|
580
|
+
live.delete(pair);
|
|
581
|
+
upstream.destroy();
|
|
582
|
+
});
|
|
583
|
+
upstream.on("close", () => {
|
|
584
|
+
live.delete(pair);
|
|
585
|
+
socket.destroy();
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
return {
|
|
589
|
+
destroyAll() {
|
|
590
|
+
for (const pair of live) {
|
|
591
|
+
pair.socket.destroy();
|
|
592
|
+
pair.upstream.destroy();
|
|
593
|
+
}
|
|
594
|
+
live.clear();
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** One serve listener, both modes (W10): plain binding by default; under
|
|
600
|
+
* `systemdSocket` the systemd-passed fd 3 is adopted via node:net and
|
|
601
|
+
* bridged to the real Bun.serve listener on an ephemeral loopback port. */
|
|
602
|
+
function startServeListener(
|
|
603
|
+
handler: (request: Request) => Response | Promise<Response>,
|
|
604
|
+
options: {
|
|
605
|
+
host: string;
|
|
606
|
+
port?: number;
|
|
607
|
+
tlsMaterial?: { cert: string; key: string };
|
|
608
|
+
maxIdleSeconds?: number;
|
|
609
|
+
systemdSocket?: boolean;
|
|
610
|
+
},
|
|
611
|
+
): { server: ReturnType<typeof Bun.serve>; stopAll: () => void; publicUrl: string; port: number } {
|
|
612
|
+
const idle = options.maxIdleSeconds !== undefined ? armIdleExit(handler, options.maxIdleSeconds) : undefined;
|
|
613
|
+
const fetch = idle?.fetch ?? handler;
|
|
614
|
+
const tls = options.tlsMaterial !== undefined ? { tls: { cert: options.tlsMaterial.cert, key: options.tlsMaterial.key } } : {};
|
|
615
|
+
const scheme = options.tlsMaterial !== undefined ? "https" : "http";
|
|
616
|
+
|
|
617
|
+
if (options.systemdSocket !== true) {
|
|
618
|
+
const server = Bun.serve({ hostname: options.host, port: options.port ?? 8570, ...tls, fetch });
|
|
619
|
+
idle?.bind(() => server.stop(true));
|
|
620
|
+
const port = server.port ?? (options.port ?? 8570);
|
|
621
|
+
return { server, stopAll: () => server.stop(true), publicUrl: `${scheme}://${options.host}:${port}`, port };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (process.platform !== "linux") {
|
|
625
|
+
throw new ServeSetupError("--systemd-socket is Linux-only (systemd socket activation)");
|
|
626
|
+
}
|
|
627
|
+
const parsed = parseListenFds(process.env);
|
|
628
|
+
if ("error" in parsed) throw new ServeSetupError(parsed.error);
|
|
629
|
+
// Validate the fd synchronously: systemd passes a bound LISTENING socket;
|
|
630
|
+
// anything else (EBADF, wrong type) must fail setup, not crash later.
|
|
631
|
+
let isSocket = false;
|
|
632
|
+
try {
|
|
633
|
+
const stats = fstatSync(parsed.fd);
|
|
634
|
+
isSocket = (stats.mode & S_ISSOCK) === S_ISSOCK;
|
|
635
|
+
} catch {
|
|
636
|
+
isSocket = false;
|
|
637
|
+
}
|
|
638
|
+
if (!isSocket) {
|
|
639
|
+
throw new ServeSetupError(`fd ${parsed.fd} is not an open socket — socket activation must pass a bound listening socket`);
|
|
640
|
+
}
|
|
641
|
+
const server = Bun.serve({ hostname: "127.0.0.1", port: 0, ...tls, fetch });
|
|
642
|
+
const acceptor = netCreateServer();
|
|
643
|
+
const bridge = createSocketBridge(acceptor, server.port ?? 0);
|
|
644
|
+
acceptor.on("error", (error) => {
|
|
645
|
+
// Async setup failure channel (e.g. fd 3 is a CONNECTED socket — passes
|
|
646
|
+
// the S_IFSOCK pre-check but cannot listen): no hard exit from library
|
|
647
|
+
// code — report, stop both listeners, and let the loop drain with a
|
|
648
|
+
// failing exit code.
|
|
649
|
+
console.error(`ukp serve: fd${parsed.fd} acceptor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
650
|
+
process.exitCode = 1;
|
|
651
|
+
acceptor.close();
|
|
652
|
+
bridge.destroyAll();
|
|
653
|
+
server.stop(true);
|
|
654
|
+
});
|
|
655
|
+
acceptor.listen({ fd: parsed.fd });
|
|
656
|
+
const stopAll = () => {
|
|
657
|
+
acceptor.close();
|
|
658
|
+
bridge.destroyAll();
|
|
659
|
+
server.stop(true);
|
|
660
|
+
};
|
|
661
|
+
idle?.bind(stopAll);
|
|
662
|
+
return {
|
|
663
|
+
server,
|
|
664
|
+
stopAll,
|
|
665
|
+
publicUrl: `systemd:fd${parsed.fd} (bridge ${scheme}://127.0.0.1:${server.port})`,
|
|
666
|
+
port: server.port ?? 0,
|
|
667
|
+
};
|
|
452
668
|
}
|
|
453
669
|
|
|
454
670
|
/** TLS material resolution (W5' / D-079), shared by both serve modes: a
|
|
455
|
-
* self-signed identity is generated/persisted under `identityDir`
|
|
456
|
-
* Service folder's `.ukp/tls/` in endpoint mode, the registry directory
|
|
457
|
-
* door mode — a door has no single Service folder), explicit certificates
|
|
671
|
+
* self-signed identity is generated/re-signed/persisted under `identityDir`
|
|
672
|
+
* (the Service folder's `.ukp/tls/` in endpoint mode, the registry directory
|
|
673
|
+
* in door mode — a door has no single Service folder), explicit certificates
|
|
458
674
|
* are read from the operator's paths. Bun.serve takes PEM contents
|
|
459
|
-
* (path-string handling is platform-dependent).
|
|
675
|
+
* (path-string handling is platform-dependent). Every failure — unreadable
|
|
676
|
+
* operator files, an unusable persisted identity, openssl unable to sign —
|
|
677
|
+
* surfaces as one ServeSetupError naming the certificate path. */
|
|
460
678
|
function resolveTlsMaterial(
|
|
461
679
|
config: ServeConfig,
|
|
462
680
|
identityDir: string,
|
|
463
|
-
): { cert: string; key: string; pin: string; san: string; source: "generated" | "persisted" | "operator" } | undefined {
|
|
681
|
+
): { cert: string; key: string; pin: string; san: string; source: "generated" | "re-signed" | "persisted" | "operator" } | undefined {
|
|
464
682
|
if (config.tls === undefined) return undefined;
|
|
465
683
|
const tls = config.tls;
|
|
466
|
-
|
|
467
|
-
? (() => {
|
|
468
|
-
const identity = ensureSelfSignedTlsFiles(
|
|
469
|
-
identityDir,
|
|
470
|
-
tls.mode === "self-signed" && tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {},
|
|
471
|
-
);
|
|
472
|
-
return {
|
|
473
|
-
certPath: identity.certPath,
|
|
474
|
-
keyPath: identity.keyPath,
|
|
475
|
-
source: identity.created ? ("generated" as const) : ("persisted" as const),
|
|
476
|
-
};
|
|
477
|
-
})()
|
|
478
|
-
: { certPath: tls.certPath, keyPath: tls.keyPath, source: "operator" as const };
|
|
684
|
+
let certPath: string | undefined;
|
|
479
685
|
try {
|
|
480
|
-
|
|
686
|
+
let keyPath: string;
|
|
687
|
+
let source: "generated" | "re-signed" | "persisted" | "operator";
|
|
688
|
+
if (tls.mode === "self-signed") {
|
|
689
|
+
const identity = ensureSelfSignedTlsFiles(
|
|
690
|
+
identityDir,
|
|
691
|
+
{
|
|
692
|
+
...(tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {}),
|
|
693
|
+
...(tls.sanEntries !== undefined ? { extraSanEntries: tls.sanEntries } : {}),
|
|
694
|
+
},
|
|
695
|
+
);
|
|
696
|
+
certPath = identity.certPath;
|
|
697
|
+
keyPath = identity.keyPath;
|
|
698
|
+
source = identity.source;
|
|
699
|
+
} else {
|
|
700
|
+
certPath = tls.certPath;
|
|
701
|
+
keyPath = tls.keyPath;
|
|
702
|
+
source = "operator";
|
|
703
|
+
}
|
|
704
|
+
const cert = readFileSync(certPath, "utf8");
|
|
481
705
|
return {
|
|
482
706
|
cert,
|
|
483
|
-
key: readFileSync(
|
|
707
|
+
key: readFileSync(keyPath, "utf8"),
|
|
484
708
|
pin: spkiPinOf(cert),
|
|
485
709
|
san: certSanOf(cert),
|
|
486
|
-
source
|
|
710
|
+
source,
|
|
487
711
|
};
|
|
488
712
|
} catch (error) {
|
|
489
713
|
const reason = error instanceof Error ? error.message : String(error);
|
|
490
|
-
throw new ServeSetupError(`TLS material is unusable (${
|
|
714
|
+
throw new ServeSetupError(`TLS material is unusable (${certPath ?? join(identityDir, "cert.pem")}): ${reason}`);
|
|
491
715
|
}
|
|
492
716
|
}
|
|
493
717
|
|
|
@@ -650,12 +874,14 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
|
|
|
650
874
|
return doorNotFound(undefined, config.registryPath);
|
|
651
875
|
};
|
|
652
876
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
port: config.port
|
|
656
|
-
...(tlsMaterial !== undefined ? {
|
|
657
|
-
|
|
877
|
+
const listener = startServeListener(handler, {
|
|
878
|
+
host,
|
|
879
|
+
...(config.port !== undefined ? { port: config.port } : {}),
|
|
880
|
+
...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
|
|
881
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
882
|
+
...(config.systemdSocket === true ? { systemdSocket: true } : {}),
|
|
658
883
|
});
|
|
884
|
+
const { server } = listener;
|
|
659
885
|
|
|
660
886
|
const info: ServeInfo = {
|
|
661
887
|
mode: "door",
|
|
@@ -663,15 +889,16 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
|
|
|
663
889
|
endpoints: listDoorEndpoints(config.registryPath).map((endpoint) => endpoint.name),
|
|
664
890
|
write: listWriteEndpoints(config.registryPath),
|
|
665
891
|
},
|
|
666
|
-
url:
|
|
892
|
+
url: listener.publicUrl,
|
|
667
893
|
host,
|
|
668
|
-
port:
|
|
894
|
+
port: listener.port,
|
|
669
895
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
670
896
|
...(tlsMaterial !== undefined
|
|
671
897
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
672
898
|
: {}),
|
|
899
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
673
900
|
};
|
|
674
|
-
return { server, info };
|
|
901
|
+
return { server, info, stopAll: listener.stopAll };
|
|
675
902
|
}
|
|
676
903
|
|
|
677
904
|
export function startUkpServer(config: ServeConfig): StartedServe {
|
|
@@ -781,27 +1008,30 @@ export function startUkpServer(config: ServeConfig): StartedServe {
|
|
|
781
1008
|
);
|
|
782
1009
|
};
|
|
783
1010
|
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
port: config.port
|
|
787
|
-
...(tlsMaterial !== undefined ? {
|
|
788
|
-
|
|
1011
|
+
const listener = startServeListener(handler, {
|
|
1012
|
+
host,
|
|
1013
|
+
...(config.port !== undefined ? { port: config.port } : {}),
|
|
1014
|
+
...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
|
|
1015
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
1016
|
+
...(config.systemdSocket === true ? { systemdSocket: true } : {}),
|
|
789
1017
|
});
|
|
1018
|
+
const { server } = listener;
|
|
790
1019
|
|
|
791
1020
|
const info: ServeInfo = {
|
|
792
1021
|
mode: "endpoint",
|
|
793
1022
|
endpoint: binding.name,
|
|
794
1023
|
folder: serviceFolder,
|
|
795
1024
|
instanceUid: readInstanceUid(serviceFolder),
|
|
796
|
-
url:
|
|
1025
|
+
url: listener.publicUrl,
|
|
797
1026
|
host,
|
|
798
|
-
port:
|
|
1027
|
+
port: listener.port,
|
|
799
1028
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
800
1029
|
...(tlsMaterial !== undefined
|
|
801
1030
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
802
1031
|
: {}),
|
|
1032
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
803
1033
|
};
|
|
804
|
-
return { server, info };
|
|
1034
|
+
return { server, info, stopAll: listener.stopAll };
|
|
805
1035
|
}
|
|
806
1036
|
|
|
807
1037
|
async function handleSearch(
|
package/bun.lock
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "@catheadowl/ukp",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"commander": "15.0.0",
|
|
9
|
-
"smol-toml": "1.7.1",
|
|
10
|
-
"zod": "4.4.3",
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"@types/bun": "1.3.5",
|
|
14
|
-
"typescript": "5.9.3",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
"packages": {
|
|
19
|
-
"@types/bun": ["@types/bun@1.3.5", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.5.tgz", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
|
20
|
-
|
|
21
|
-
"@types/node": ["@types/node@26.1.1", "https://registry.npmmirror.com/@types/node/-/node-26.1.1.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
|
22
|
-
|
|
23
|
-
"bun-types": ["bun-types@1.3.5", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.5.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
24
|
-
|
|
25
|
-
"commander": ["commander@15.0.0", "https://registry.npmmirror.com/commander/-/commander-15.0.0.tgz", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="],
|
|
26
|
-
|
|
27
|
-
"smol-toml": ["smol-toml@1.7.1", "https://registry.npmmirror.com/smol-toml/-/smol-toml-1.7.1.tgz", {}, "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ=="],
|
|
28
|
-
|
|
29
|
-
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
30
|
-
|
|
31
|
-
"undici-types": ["undici-types@8.3.0", "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
32
|
-
|
|
33
|
-
"zod": ["zod@4.4.3", "https://registry.npmmirror.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
|
34
|
-
}
|
|
35
|
-
}
|
package/tsconfig.json
DELETED