@kolu/surface-daemon reference
The daemon binary half — the code that runs inside the long-lived process. It
carries no @kolu/* app dependency; the client half lives separately in
@kolu/surface-daemon-supervisor. Single entry
point, no subpaths.
The lifecycle
daemonMain(spec) is the gate → serve → teardown skeleton. It claims a
single-instance gate, serves the router over a unix socket, waits for the
lifetime to end, then closes the socket, releases the gate, and returns a
DaemonExit — it never calls process.exit, so the whole lifecycle is drivable
in-process from a test.
const exit = await daemonMain({
gatePath: GATE_PATH, // the single-instance scope key
socketPath: SOCKET_PATH, // where the surface is served
router, // implement(surface.contract).router({ ...fragment.router })
lifetime: { kind: "forever" }, // or { kind: "idleTimeout", ms, isIdle }
log: stderrLogger(),
signal: controller.signal,
onReady: ({ socketPath, pid }) =>
process.stderr.write(`listening on ${socketPath} (pid ${pid})\n`),
});
process.exit(daemonExitCode(exit));
| Export | Role |
|---|---|
daemonMain(spec) |
the gate → serve → teardown skeleton; resolves a DaemonExit |
acquirePidGate(gatePath) |
atomic single-instance claim via link(2); returns { kind: "acquired", release } or { kind: "held", pid } |
gatePid(gatePath) · isHolderLive(pid) |
the gate-file primitives (pid parse · liveness probe) the supervisor composes |
daemonExitCode(exit) · stderrLogger() · Logger · DaemonExit |
exit-code classification and the structural logging contract |
lifetime is the policy that distinguishes the two tenants: kaval (a PTY daemon)
uses { kind: "forever" }; odu serve uses { kind: "idleTimeout", ms, isIdle }.
Serving the surface
The listener is serveOverUnixSocket({ socketPath, router, log }) from
@kolu/surface/unix-socket — a @kolu/surface subpath, not this package. It
never crashes: every failure mode resolves to a no-op listener with a
machine-readable outcome.
outcome.kind |
Meaning |
|---|---|
listening |
bound and serving |
dir-not-private · not-a-socket · bind-failed |
could not bind |
already-served · probe-failed |
another holder is (or may be) live |
Fronting over stdio
frontDaemonOverStdio(opts) is the front half: it adopts-or-spawns the
gate-held daemon and raw-byte-relays this process’s stdio onto its socket, so a
remote (ssh) session survives the link — the durable counterpart to
serveOverStdio. It relays with node:net only, no surface import, keeping the
daemon closure contract-blind.
return frontDaemonOverStdio({
socketPath: SOCKET_PATH,
spawnDaemon: () => reExecAsDetachedDaemon({ stripArgs: ["--stdio"] }),
log: (msg) => process.stderr.write(`--stdio: ${msg}\n`),
});
reExecAsDetachedDaemon({ stripArgs, stderrLog? }) re-execs this binary minus
the front flag as a detached, unref’d process, so it survives the SIGHUP that
drops the link.
Baked identity
readBakedIdentity(prefix) reads a Nix-baked env pair
(<PREFIX>_BUILD_ID / <PREFIX>_COMMIT_HASH) into { staleKey, navigableCommit },
and returns empty strings off-nix rather than inventing an identity. See
How to bake an identity.