import type { JinnConfig } from "../shared/types.js"; export { resolveLocalGatewayConnection, type LocalGatewayConnection } from "./local-gateway-connection.js"; export declare function startForeground(config: JinnConfig): Promise; export declare function startDaemon(config: JinnConfig): void; /** * Restart the gateway from a DETACHED, reparented helper process. * * The whole point: a `jinn stop && jinn start` run from *inside* a gateway * session fails because `stop` kills the gateway, whose shutdown kills all PTYs * — including the very session running the command — so `start` never executes. * This spawns a helper (restart-entry.js) detached + unref'd with no IPC, so it * is reparented to launchd/init and SURVIVES the gateway's killAll(). The * helper does stop → waitForPortFree → startDaemon out of band. The returning * gateway then resumes the interrupted session. */ export interface LifecycleKillOptions { takePort?: boolean; } export interface RestartDetachedOptions extends LifecycleKillOptions { port?: number; } export declare const CONTAINER_RESTART_UNSUPPORTED_MESSAGE = "Gateway self-restart is unsupported inside Docker; use docker compose restart jinn instead."; export declare function restartDetached(options?: RestartDetachedOptions): void; export declare function buildGatewayChildEnv(config: JinnConfig, baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv; /** * Send SIGTERM to the running gateway. Returns the PID that was signaled (or * was found already gone), or null if nothing was running. * * Deliberately does NOT delete the PID file after a successful SIGTERM: the * gateway keeps shutting down (gracefully, up to ~5s) after the signal, and * unlinking early opens a race window where the gateway is still running and * holding the port but a concurrent start/status sees "not running". The file * self-heals once the process exits: getStatus() and stop() both verify * liveness with kill(pid, 0) and treat a dead PID as stale, and startDaemon() * overwrites the file. stopAndWait() removes it once the process has exited. */ export type PortOwnerLookup = { status: "found"; pid: number; } | { status: "none"; } | { status: "unknown"; }; export type ProcessJinnHomeLookup = { status: "found"; jinnHome: string; identity: string; } | { status: "none"; } | { status: "unknown"; }; export declare function selectPortOwnerPid(pids: number[], commandLooksLikeGateway?: (pid: number) => boolean): number | undefined; export declare class PortOwnershipError extends Error { constructor(port: number, ownerJinnHome: string); } export declare class UnsafeContainerTakeoverError extends Error { constructor(); } export declare function formatPortOwnedByAnotherInstanceError(port: number, ownerJinnHome: string): string; export declare function shouldSignalPidFileProcess(pid: number, portOwner: PortOwnerLookup, commandLooksLikeGateway: boolean): boolean; export declare function assertPortTakeoverAllowed(port: number, options?: LifecycleKillOptions): void; export declare function readProcessJinnHome(pid: number): ProcessJinnHomeLookup; export declare function stop(port?: number, options?: LifecycleKillOptions): boolean; /** * Stop the gateway and wait until the process has actually exited (bounded by * `timeoutMs`), then remove the PID file. This is the race-free variant used * by the restart path: the PID file stays on disk for the whole shutdown so a * concurrent start/status keeps seeing "running" until the port is truly * released. Returns true if something was signaled, false if nothing was * running. */ export declare function stopAndWait(port?: number, timeoutMs?: number, options?: LifecycleKillOptions): Promise; /** Split a `netstat -ano` address column into host and port. Windows renders * IPv6 as `[::1]:7777` and IPv4 as `127.0.0.1:7777`. */ export declare function parseNetstatAddress(address: string): { host: string; port: number; } | undefined; /** Pick the pid listening on `port` at an address that overlaps `host`. * * The previous Windows lookup took the pid from the first `findstr LISTENING` * row and ignored the address entirely, so a listener bound to a DIFFERENT * interface (a proxy on ::1, say) was reported as owning the configured * loopback address — the same defect that was fixed for lsof but never here. * * Listening rows are identified by their wildcard foreign address rather than * the state word, which netstat localises. */ export declare function selectNetstatPortOwnerPid(output: string, host: string, port: number): PortOwnerLookup; export declare function lookupPidOnPort(port: number): PortOwnerLookup; /** * Poll until nothing is listening on `port`, or `timeoutMs` elapses. * Returns true if the port became free, false on timeout (caller should start * anyway — startGateway will surface EADDRINUSE if it's still bound). This is * the race-killer: it prevents a fresh daemon from racing the old one's * graceful shutdown (up to 5s) into an EADDRINUSE crash. */ export declare function waitForPortFree(port: number, timeoutMs?: number): Promise; export declare function waitForPortListening(port: number, host?: string, timeoutMs?: number): Promise; export declare function waitForDashboardReady(port: number, host?: string, timeoutMs?: number): Promise; export interface GatewayStatus { running: boolean; pid: number | null; } export declare function getStatus(): GatewayStatus; //# sourceMappingURL=lifecycle.d.ts.map