import type { ContainerConfig, ContainerRuntimeInfo, RuntimePreference, SelfDeploymentResult, SelfDeploymentStatus, SetupSnippetFormat, SetupSnippetResult } from "./types.js"; import { type ContainerClient, type ResolvedClient } from "./client.js"; /** * Outcome of an image-compliance probe. `ok` is the headline; `output` * and `error` give the operator enough to fix a failure. */ export interface ImageProbeResult { ok: boolean; /** * Combined stdout/stderr of the probe container. Always non-null — * empty string when nothing was printed (a successful run prints `"ok"`). */ output: string; /** Reason the probe failed; absent on `ok: true`. */ error?: string; } /** * Run the image as the host caller (via the existing `userMappingFlags` * matrix), execute `touch /tmp/x && echo ok`, and report whether the * image is non-root-friendly enough for the ownership-aligned mount * model. * * Failure modes: * - Image has a non-writable `/tmp` for the host UID. * - Image has no `HOME` writable for the host UID and the entrypoint * touches `~`. * - Image's `USER` directive doesn't grant write on the paths * downstream code will need. * * Implementation deliberately reuses `userMappingFlags` so the probe * runs with the *same* uid mapping `ensureRunning` would use for the * managed container — `ok` here means "the live mapping works", not * "any mapping works". * * Returns `ok: false` (never throws) on: * - non-zero exit * - exit 0 but stdout doesn't contain `"ok"` (the probe shell short- * circuited via `&&` and printed nothing — the touch failed silently) * - exec layer failure (timeout, binary missing) */ export declare function imageRunsAsUser(runtime: ContainerRuntimeInfo, image: string, user: ContainerConfig["user"], client?: ContainerClient): Promise; /** * Probe-injection bag for `selfDeployment`. Production omits the arg * and the real helpers are used; tests pass fakes so no real podman / * docker / filesystem access happens. */ export interface SelfDeploymentProbes { isContainerized?: () => boolean; /** * Resolve the dockerode client + its socket path, or `null` when no * socket answers. Replaces the CLI-era `findBinary`/`readBinaryVersion` * pair: "no runtime" now means "no socket answered the Docker API", not * "no binary on PATH". Defaults to the real `resolveClient` from * `client.ts`; tests inject a fake to drive the no-socket / * socket-reachable branches without a live daemon. */ resolveClient?: () => Promise; /** Read an env var, returning undefined when unset. */ readEnv?: (key: string) => string | undefined; /** * Read the cgroup v2 controllers delegated to the current cgroup * (production: `/sys/fs/cgroup/cgroup.controllers`). Returns the list * of controller names, or `null` when the file is unreadable * (cgroup v1 host, non-Linux, unusual mount layout) — in which case * the doctor skips the cgroup status escalation entirely. */ readCgroupControllers?: () => Promise; /** * Read the kernel boot cmdline (production: `/proc/cmdline`). Used to * detect the `cgroup_disable=memory` Raspberry-Pi-OS-Trixie quirk: the * firmware-injected cmdline disables the memory cgroup controller * before systemd ever gets a chance to delegate it. systemd's * `Delegate=memory` then has nothing to delegate, and the "missing * controller" symptom looks identical to plain missing delegation — * the systemd-snippet remediation won't help. When this probe returns * a cmdline containing the disable token, the remediation block * surfaces the cmdline fix instead. */ readKernelCmdline?: () => Promise; /** * Read `/proc/mounts` (or equivalent) as parsed entries. Returns * `null` when the file is unreadable (non-Linux, sandboxed). Used by * the rootless-Podman storage-driver probe to determine the * filesystem backing `~/.local/share/containers` and warn on * filesystems known to interact poorly with `--userns=keep-id`. */ readMounts?: () => Promise; /** * Resolve the path of the rootless container-storage root for the * current user. Defaults to `$XDG_DATA_HOME/containers` (or * `$HOME/.local/share/containers`) — tests can override. */ resolveContainerStoragePath?: () => string | null; /** * List the usernames with systemd linger enabled (production: the * entries of `/var/lib/systemd/linger`, one file per lingering user). * Returns `"absent"` when the directory does not exist — systemd * creates it on the first `enable-linger`, so on a bare-metal host * absence means "nobody lingers", while inside a container it means * the host directory simply isn't bind-mounted in. Returns `null` on * any other read error (unreadable, non-Linux). */ readLingerDir?: () => Promise; /** * Resolve the username whose linger file to look for. Defaults to * `os.userInfo().username`; only consulted on bare-metal (inside a * container the in-container username says nothing about the host * user owning the runtime). */ resolveLingerUser?: () => string | null; } /** * One row from `/proc/mounts`. Only the fields the doctor cares about. */ export interface MountEntry { /** Mount point. */ mountPoint: string; /** Filesystem type token. */ fstype: string; } /** * Diagnose whether this Signal K deployment can drive the container * runtime at all, and (when SK is itself containerized) whether the * prereqs for the in-container deployment path are met. * * Algorithm: * 1. Note `isContainerized()` (advisory — checks run regardless). * 2. Resolve a socket that answers the Docker API (`resolveClient`). * "No runtime" now means "no socket answered", not "no binary on * PATH" — the plugin talks the API, not a CLI. * 3. Probe the daemon with `version()` + `info()`; on failure classify * off the `CategorizedError.kind` from `safe()` (socket-unreachable * / permission / etc). * 4. When both 2 and 3 succeed AND we're containerized, run the * `findSelfContainerId` cascade. * * Each failure short-circuits and produces a copy-pasteable * `remediation`. Never throws. * * `client` is a test-injection override that takes precedence over the * `resolveClient` probe; production omits it and the resolved socket's * client is used. `getClient()` is NOT the default here (unlike the * post-detection helpers) because the doctor runs precisely when runtime * detection may have failed — eagerly calling `getClient()` would throw * before the no-runtime path could report. */ export declare function selfDeployment(preference: RuntimePreference, client?: ContainerClient | null, probes?: SelfDeploymentProbes): Promise; /** * Produce a ready-to-paste compose fragment or `podman/docker run` * command line tailored to the detected deployment shape, plus a * minimal Dockerfile sidecar showing image-side prereqs. * * Pure templating — runs no probes. All inputs come from a previously * gathered `SelfDeploymentResult` plus an optional `hostUser`. When * `hostUser` is omitted, snippets use `$(id -u):$(id -g)` placeholders * so the result stays portable across operator machines. * * Falls back to the recommended default (rootless podman) when the * input has no detected binary — the generator's job is to always * produce something usable. */ export declare function generateSetupSnippet(result: SelfDeploymentResult, format?: SetupSnippetFormat, hostUser?: { uid: number; gid: number; } | null): SetupSnippetResult; /** * True when a doctor status reached after successful runtime detection * still warrants a dashboard error (degraded-but-running deployment). */ export declare function isDashboardDeploymentError(status: SelfDeploymentStatus): boolean; //# sourceMappingURL=doctor.d.ts.map