import * as fs from 'fs/promises'; import * as T from '../../../base/lib/types'; import * as cp from 'child_process'; import { Buffer } from 'node:buffer'; import { Drop } from '../../../base/lib/util/Drop'; import { Mounts } from '../mainFn/Mounts'; import { BackupEffects } from '../backup/Backups'; import { PathBase } from './Volume'; export declare const execFile: typeof cp.execFile.__promisify__; export type ExecOptions = { input?: string | Buffer; }; /** * Interface representing an isolated container environment for running service processes. * * Provides methods for executing commands, spawning processes, mounting filesystems, * and writing files within the container's rootfs. Comes in two flavors: * {@link SubContainerOwned} (owns the underlying filesystem) and * {@link SubContainerRc} (reference-counted handle to a shared container). */ export interface SubContainer extends Drop, PathBase { readonly imageId: keyof Manifest['images'] & T.ImageId; readonly rootfs: string; readonly guid: T.Guid; /** * Get the absolute path to a file or directory within this subcontainer's rootfs * @param path Path relative to the rootfs */ subpath(path: string): string; /** * Apply filesystem mounts (volumes, assets, dependencies, backups) to this subcontainer. * @param mounts - The Mounts configuration to apply * @returns This subcontainer instance for chaining */ mount(mounts: Effects extends BackupEffects ? Mounts : Mounts): Promise; /** Destroy this subcontainer and clean up its filesystem */ destroy: () => Promise; /** * @description run a command inside this subcontainer * DOES NOT THROW ON NONZERO EXIT CODE (see execFail) * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ throw: () => { stdout: string | Buffer; stderr: string | Buffer; }; exitCode: number | null; exitSignal: NodeJS.Signals | null; stdout: string | Buffer; stderr: string | Buffer; }>; /** * @description run a command inside this subcontainer, throwing on non-zero exit status * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ stdout: string | Buffer; stderr: string | Buffer; }>; /** * Launch a command as the init (PID 1) process of the subcontainer. * Replaces the current leader process. * @param command - The command and arguments to execute * @param options - Optional environment, working directory, and user overrides */ launch(command: string[], options?: CommandOptions): Promise; /** * Spawn a command inside the subcontainer as a non-init process. * @param command - The command and arguments to execute * @param options - Optional environment, working directory, user, and stdio overrides */ spawn(command: string[], options?: CommandOptions & StdioOptions): Promise; /** * @description Write a file to the subcontainer's filesystem * @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json") * @param data The data to write * @param options Optional write options (same as node:fs/promises writeFile) */ writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable | AsyncIterable, options?: Parameters[2]): Promise; /** * Create a reference-counted handle to this subcontainer. * The underlying container is only destroyed when all handles are released. */ rc(): SubContainerRc; /** Returns true if this is an owned subcontainer (not a reference-counted handle) */ isOwned(): this is SubContainerOwned; } /** * Want to limit what we can do in a container, so we want to launch a container with a specific image and the mounts. */ export declare class SubContainerOwned extends Drop implements SubContainer { readonly effects: Effects; readonly imageId: keyof Manifest['images'] & T.ImageId; readonly rootfs: string; readonly guid: T.Guid; private destroyed; rcs: number; private leader; private leaderExited; private waitProc; private constructor(); static of(effects: Effects, image: { imageId: keyof Manifest['images'] & T.ImageId; sharedRun?: boolean; }, mounts: (Effects extends BackupEffects ? Mounts : Mounts) | null, name: string): Promise>; static withTemp(effects: Effects, image: { imageId: keyof Manifest['images'] & T.ImageId; sharedRun?: boolean; }, mounts: (Effects extends BackupEffects ? Mounts : Mounts) | null, name: string, fn: (subContainer: SubContainer) => Promise): Promise; subpath(path: string): string; mount(mounts: Effects extends BackupEffects ? Mounts : Mounts): Promise; private killLeader; get destroy(): () => Promise; onDrop(): void; /** * @description run a command inside this subcontainer * DOES NOT THROW ON NONZERO EXIT CODE (see execFail) * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ throw: () => { stdout: string | Buffer; stderr: string | Buffer; }; exitCode: number | null; exitSignal: NodeJS.Signals | null; stdout: string | Buffer; stderr: string | Buffer; }>; /** * @description run a command inside this subcontainer, throwing on non-zero exit status * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ stdout: string | Buffer; stderr: string | Buffer; }>; launch(command: string[], options?: CommandOptions): Promise; spawn(command: string[], options?: CommandOptions & StdioOptions): Promise; /** * @description Write a file to the subcontainer's filesystem * @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json") * @param data The data to write * @param options Optional write options (same as node:fs/promises writeFile) */ writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable | AsyncIterable, options?: Parameters[2]): Promise; rc(): SubContainerRc; isOwned(): this is SubContainerOwned; } /** * A reference-counted handle to a {@link SubContainerOwned}. * * Multiple `SubContainerRc` instances can share one underlying subcontainer. * The subcontainer is destroyed only when the last reference is released via `destroy()`. */ export declare class SubContainerRc extends Drop implements SubContainer { private readonly subcontainer; get imageId(): keyof Manifest["images"] & string; get rootfs(): string; get guid(): string; subpath(path: string): string; private destroyed; private destroying; constructor(subcontainer: SubContainerOwned); static of(effects: Effects, image: { imageId: keyof Manifest['images'] & T.ImageId; sharedRun?: boolean; }, mounts: (Effects extends BackupEffects ? Mounts : Mounts) | null, name: string): Promise>; static withTemp(effects: Effects, image: { imageId: keyof Manifest['images'] & T.ImageId; sharedRun?: boolean; }, mounts: (Effects extends BackupEffects ? Mounts : Mounts) | null, name: string, fn: (subContainer: SubContainer) => Promise): Promise; mount(mounts: Effects extends BackupEffects ? Mounts : Mounts): Promise; get destroy(): () => Promise; onDrop(): void; /** * @description run a command inside this subcontainer * DOES NOT THROW ON NONZERO EXIT CODE (see execFail) * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ exec(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ throw: () => { stdout: string | Buffer; stderr: string | Buffer; }; exitCode: number | null; exitSignal: NodeJS.Signals | null; stdout: string | Buffer; stderr: string | Buffer; }>; /** * @description run a command inside this subcontainer, throwing on non-zero exit status * @param commands an array representing the command and args to execute * @param options * @param timeoutMs how long to wait before killing the command in ms * @returns */ execFail(command: string[], options?: CommandOptions & ExecOptions, timeoutMs?: number | null, abort?: AbortController): Promise<{ stdout: string | Buffer; stderr: string | Buffer; }>; launch(command: string[], options?: CommandOptions): Promise; spawn(command: string[], options?: CommandOptions & StdioOptions): Promise; /** * @description Write a file to the subcontainer's filesystem * @param path Path relative to the subcontainer rootfs (e.g. "/etc/config.json") * @param data The data to write * @param options Optional write options (same as node:fs/promises writeFile) */ writeFile(path: string, data: string | NodeJS.ArrayBufferView | Iterable | AsyncIterable, options?: Parameters[2]): Promise; rc(): SubContainerRc; isOwned(): this is SubContainerOwned; } export type CommandOptions = { /** * Environment variables to set for this command */ env?: { [variable in string]?: string; }; /** * the working directory to run this command in */ cwd?: string; /** * the user to run this command as */ user?: string; }; export type StdioOptions = { stdio?: cp.IOType; }; /** UID/GID mapping for mount id-remapping (see kernel idmappings docs) */ export type IdMap = { fromId: number; toId: number; range: number; }; /** Union of all mount option types supported by the subcontainer runtime */ export type MountOptions = MountOptionsVolume | MountOptionsAssets | MountOptionsPointer | MountOptionsBackup; /** Mount options for binding a service volume into a subcontainer */ export type MountOptionsVolume = { type: 'volume'; volumeId: string; subpath: string | null; readonly: boolean; filetype: 'file' | 'directory' | 'infer'; idmap: IdMap[]; }; /** Mount options for binding packaged static assets into a subcontainer */ export type MountOptionsAssets = { type: 'assets'; subpath: string | null; filetype: 'file' | 'directory' | 'infer'; idmap: { fromId: number; toId: number; range: number; }[]; }; /** Mount options for binding a dependency package's volume into a subcontainer */ export type MountOptionsPointer = { type: 'pointer'; packageId: string; volumeId: string; subpath: string | null; readonly: boolean; idmap: { fromId: number; toId: number; range: number; }[]; }; /** Mount options for binding the backup directory into a subcontainer */ export type MountOptionsBackup = { type: 'backup'; subpath: string | null; filetype: 'file' | 'directory' | 'infer'; idmap: { fromId: number; toId: number; range: number; }[]; }; /** * Error thrown when a subcontainer command exits with a non-zero code or signal. * Contains the full result including stdout, stderr, exit code, and exit signal. */ export declare class ExitError extends Error { readonly command: string; readonly result: { exitCode: number | null; exitSignal: T.Signals | null; stdout: string | Buffer; stderr: string | Buffer; }; constructor(command: string, result: { exitCode: number | null; exitSignal: T.Signals | null; stdout: string | Buffer; stderr: string | Buffer; }); }