export interface DockerOrchestratorConfig { version: string; port?: number; maxPortRetries?: number; adminKey?: string; username?: string; password?: string; dataDir?: string; cacheDir?: string; containerName?: string; /** * Set this when the `docker` binary is actually a rootless Podman install * (e.g. via the `podman-docker` package). Rootless container runtimes only * identity-map container UID 0 back to the invoking host user - any other * UID (including the `--user`-forced host UID below) otherwise maps * through an arbitrary /etc/subuid subordinate range instead, breaking * bind-mount ownership. `--userns=keep-id` (Podman-specific) fixes that. * Default false: no behavior change for a plain rootful Docker install. */ rootless?: boolean; /** * Optional hook to customize the `docker run` invocation (see * {@link https://github.com/TheFehr/foundry-playwright/issues/110}). * Receives the full argument array {@link DockerFoundryOrchestrator.getRunCommand} * would otherwise pass to `execFileSync("docker", ...)` - every flag this * orchestrator sets by default (`--user`, `-p`, `--restart`, * `--userns=keep-id`, etc.) plus the image tag as the last element - and * must return the array to actually use. Append, remove, or replace * anything, including the defaults; nothing here is protected, so a * conflicting override (e.g. a different `--user`) can reintroduce bugs * this class's defaults exist to avoid. The image tag must stay the last * element of whatever you return - Docker treats anything after it as a * command for the container's entrypoint, not a `docker run` flag. * * Example - joining a caller-managed network so a sibling container can * reach Foundry directly by `containerName`, instead of `--network=host` * or the published host port: * ```ts * buildRunArgs: (args) => [...args.slice(0, -1), "--network", "my-net", args.at(-1)!] * ``` * * If your override changes the `--name` or `-p` this orchestrator would * otherwise set, you must also provide {@link onRunArgsChanged} - see * that option for why. Changing anything else (adding `--network`, * `--restart`, etc.) needs no companion. */ buildRunArgs?: (defaultArgs: string[]) => string[]; /** * Required companion to {@link buildRunArgs} whenever your override * changes the container's effective `--name` or `-p` mapping. * `stopAndRemove()`, `copyToContainer()`, `waitForReady()`, and the URL * `start()` returns all need to target the *actual* running container, * not just the pre-override default - this orchestrator can't safely * parse the changed value back out of a `buildRunArgs` result (Docker * accepts both `--name foo` and `--name=foo`; naively parsing one form * silently misses the other and reintroduces the exact bug this option * exists to prevent), so it asks you to state the effective values * directly instead of guessing. * * `start()` throws immediately if `buildRunArgs` changed `--name` or `-p` * and this doesn't return the corresponding field - a loud failure at * the moment of misuse, instead of a silently orphaned container * (wrong name → `stopAndRemove()` can't find it, so a caller that then * deletes its data directory does so out from under a container still * using it) or a readiness check that spins until it times out against * the wrong port. * * If you don't want to supply `readyUrl` here at all (e.g. you dropped * `-p` for a pure container-to-container setup with no published port), * subclass {@link DockerFoundryOrchestrator} and override BOTH * `protected` `waitForReady()` and `getUrl()` - overriding only one * still throws, since the other would otherwise silently use a stale or * meaningless value. That combination is intentionally outside what * this config surface supports. */ onRunArgsChanged?: () => { containerName?: string; readyUrl?: string; }; } /** * Host uid/gid for Docker's --user / FOUNDRY_UID+FOUNDRY_GID bind-mount * ownership matching. Returns null on Windows (process.getuid/getgid are * POSIX-only, undefined there) - Docker Desktop for Windows has no * host-side POSIX ownership to preserve on a bind mount in the first * place, so there's nothing to match and the container should just run * as its own default user. */ export declare function getHostUidGid(): { uid: number; gid: number; } | null; /** * Whether the `docker` binary is actually a Podman install (e.g. via the * `podman-docker` package). `--userns=keep-id` is Podman-specific syntax - * real Docker (including rootless Docker) doesn't understand it, so the * `rootless` config option must only add it when this is true. */ export declare function isPodmanRuntime(): boolean; /** * Programmatic orchestrator for Foundry VTT Docker containers. * Uses direct docker commands instead of docker-compose for better control and zero-config for users. */ export declare class DockerFoundryOrchestrator { protected config: Required> & Pick; private effectiveContainerName; private effectiveReadyUrl; constructor(config: DockerOrchestratorConfig); /** * Starts the Foundry VTT container. */ start(): Promise; /** * The URL callers should use to reach the container - the readiness * check above polls this same value. Pulled into its own overridable * method (rather than inlined in `start()`) so a subclass with a * fundamentally different reachability story (see * {@link DockerOrchestratorConfig.onRunArgsChanged}'s docs - e.g. no * published host port at all) only needs to override this and * {@link waitForReady}, not reimplement `start()`. */ protected getUrl(): string; /** * Generates the arguments for `docker run` (excluding the "docker" binary * itself) as an array, for execFileSync - never shell-joined, so none of * these values (container name, resolved paths, version-derived image tag) * can be interpreted as shell metacharacters. * * As a side effect, resolves and caches the effective container name / * ready-check URL that `stopAndRemove()`, `copyToContainer()`, * `waitForReady()`, and `getUrl()` use - see * {@link DockerOrchestratorConfig.onRunArgsChanged}. `envPath` only needs * to be a real path for the actual `docker run` invocation; calling this * purely to (re-)resolve that identity (e.g. before pre-run cleanup, when * no real env file exists yet) is safe with any placeholder string, * since `buildRunArgs`/`onRunArgsChanged` must be pure functions of the * current config, not of the env file's path. * @internal */ getRunCommand(envPath: string): string[]; /** * Populates `effectiveContainerName`/`effectiveReadyUrl` from the args * `buildRunArgs` actually produced, falling back to `onRunArgsChanged` * when the default `--name`/`-p` this orchestrator set are no longer * the *effective* ones - and throwing if that fallback isn't provided. * See {@link DockerOrchestratorConfig.onRunArgsChanged} for the full * rationale (in short: parsing a changed value back out of the args * array isn't reliable - e.g. `--name=foo` vs `--name foo` - so this * asks for the effective value directly instead of guessing at it). * * Checks the *last* occurrence of `--name`/`-p`, not just whether the * default pair is present anywhere: Docker and Podman both resolve a * repeated flag to its last value (confirmed directly against real * Docker - it does not reject the duplicate), so a `buildRunArgs` bug * that appends a second `--name` instead of replacing the first would * otherwise leave this orchestrator still trusting the original default * while Docker/Podman actually run under the new one. */ private resolveEffectiveIdentity; /** * Ensures a bind-mount directory exists and its top-level entries are * actually owned by the current process, best-effort fixing any that * aren't (only possible when this process already has permission to - * e.g. after switching automation users, it typically won't). Only checks * top-level entries, matching the known shape of this cache directory * (a handful of files, not deeply nested) rather than a full recursive * walk of potentially large cached content. */ private ensureWritableDir; /** * Stops and removes the container. */ stopAndRemove(): void; /** * Copies a local path into the container. */ copyToContainer(localPath: string, containerPath: string): void; /** * Polls {@link getUrl} until Foundry responds or the timeout elapses. * `protected`, not `private`, alongside `getUrl()` - see * {@link DockerOrchestratorConfig.onRunArgsChanged}'s docs for the one * scenario (no published host port at all) meant to be handled by * subclassing and overriding both, rather than through config. */ protected waitForReady(): Promise; private findAvailablePort; private isPortAvailable; }