UNPKG

@stacksjs/rpx

Version:

A modern and smart reverse proxy.

143 lines (142 loc) 5.95 kB
import { OnDemandCertManager } from './on-demand'; import type { OnDemandSitesConfig, OnDemandTlsConfig, ProductionTlsConfig, TlsOption } from './types'; import type { ProxyRoute } from './proxy-handler'; import type { RegistryEntry } from './registry'; import type { SiteSnapshot } from './site-supervisor'; export declare function getDaemonRpxDir(): string; export declare function getDaemonPidPath(rpxDir?: string): string; /** * Read the PID stored in `daemon.pid`, or `null` if no file / unparseable. */ export declare function readDaemonPid(rpxDir?: string): Promise<number | null>; /** * True if `daemon.pid` points at a process that is still alive. */ export declare function isDaemonRunning(rpxDir?: string): Promise<boolean>; /** * Acquire the daemon's single-instance lock by atomically creating * `daemon.pid`. If the file exists but holds a stale PID we take it over; * otherwise we throw. * * `O_CREAT | O_EXCL` (`'wx'`) guarantees only one process wins the create * race, so we don't need an external lock library. */ export declare function acquireDaemonLock(rpxDir?: string): Promise<string>; export declare function releaseDaemonLock(rpxDir?: string): Promise<void>; /** * Translate a registry entry into the routing shape consumed by the proxy * fetch handler. The entry's `from` is normalized to `host:port`, or (for a * multi-upstream `from`) load-balanced via a per-entry-id cached pool. * * Exported for tests, which exercise the entry-id-keyed pool cache/reconcile * logic directly rather than through a full daemon + live HTTP round trip. */ export declare function entryToRoute(entry: RegistryEntry): ProxyRoute; /** * The shared `:80` handler: serve ACME http-01 challenges, kick off on-demand * issuance for an approved-but-uncovered host, then 301 to HTTPS. The request * target is parsed defensively — scanners constantly send malformed/relative * targets, and a thrown `new URL` would reject the fetch handler and make Bun * drop the connection with no response. A bad target becomes a 400 instead. */ export declare function handleHttpRedirect(req: Request, onDemand: OnDemandCertManager | null, acmeChallengeWebroot?: string): Response; // `opts` IS used throughout; pickier's no-unused-vars mis-fires on this fn after // the on-demand serve refactor (its --fix would wrongly rename to `_opts`). // eslint-disable-next-line pickier/no-unused-vars export declare function runDaemon(opts?: DaemonOptions): Promise<DaemonHandle>; /** * A cluster worker: binds :443 with `reusePort`, serves the proxy handler, keeps * its routing table in sync with the registry, and reloads its TLS certs from * the coordinator-published file on `SIGHUP`. It owns none of the singletons * (lock, DNS, hosts, :80, ACME issuance) — the coordinator does. */ export declare function runDaemonWorker(ctx: WorkerCtx): Promise<DaemonHandle>; /** * Best-effort default for the spawn command used by lazy-spawn. Compiled * binaries (`bun build --compile`) self-invoke; source-mode executions invoke * the same Bun + script that's running now. * * Library consumers should not rely on this — pass `spawnCommand` explicitly. */ export declare function defaultDaemonSpawnCommand(): string[]; /** * Make sure a daemon is running, starting one as a detached child if needed. * * - If the pid file exists and points at a live process, returns immediately * with `spawned: false`. * - Otherwise cleans any stale pid file, spawns the configured command with * `detached: true` + `stdio: 'ignore'` + `unref()` so it survives the caller * exiting, and polls the pid file until the new daemon registers itself. * * Throws if the daemon never appears within `startupTimeoutMs`. */ export declare function ensureDaemonRunning(opts?: EnsureDaemonOptions): Promise<EnsureDaemonResult>; /** * Stop a running daemon by reading its pid and sending SIGTERM. Polls until * the process is gone (or escalates to SIGKILL if `forceAfterTimeout`). The * pid file is removed by the daemon's own SIGTERM handler — we clean up only * if we had to SIGKILL. */ export declare function stopDaemon(opts?: StopDaemonOptions): Promise<StopDaemonResult>; /** * When the daemon is not running, ensure no stale macOS resolver overrides remain. */ export declare function reconcileDevelopmentDnsOnIdle(opts?: { rpxDir?: string, verbose?: boolean }): Promise<void>; export declare interface DaemonOptions { verbose?: boolean rpxDir?: string registryDir?: string httpsPort?: number httpPort?: number hostname?: string https?: TlsOption productionCerts?: ProductionTlsConfig onDemandTls?: OnDemandTlsConfig onDemandSites?: OnDemandSitesConfig acmeChallengeWebroot?: string gcIntervalMs?: number workers?: number } export declare interface DaemonHandle { stop: () => Promise<void> done: Promise<void> httpsPort: number httpPort: number pidPath: string ensureCert: (host: string) => Promise<boolean> listSites: () => SiteSnapshot[] } // ───────────────────────── cluster: coordinator + workers ───────────────────── declare interface WorkerCtx { rpxDir: string registryDir: string httpsPort: number hostname: string verbose: boolean } export declare interface EnsureDaemonOptions { rpxDir?: string spawnCommand?: string[] spawnCwd?: string spawnEnv?: Record<string, string> startupTimeoutMs?: number pollIntervalMs?: number verbose?: boolean } export declare interface EnsureDaemonResult { pid: number spawned: boolean } export declare interface StopDaemonOptions { rpxDir?: string timeoutMs?: number pollIntervalMs?: number forceAfterTimeout?: boolean verbose?: boolean } export declare interface StopDaemonResult { stopped: boolean pid: number | null forced: boolean } declare type SniEntry = { serverName: string, cert: string, key: string }