/** * Verify transferred files by hash. * * One approach for every case: file names go in as arguments, the server * prints hashes, the comparison happens on our side. This works on both * coreutils and BusyBox — long options (`--quiet`) and taking a manifest on * stdin (`sha256sum -c -`) aren't available everywhere, and embedded systems * have neither at all. * * There are exactly three outcomes, and they never mix: matched, mismatched, * nothing to verify with. "Nothing to verify with" arrives as exit code 127 * with text on stderr, not on stdout — mistaking that for a mismatch would * make a transfer to a server without sha256sum look corrupted. */ import type { SSHConfig } from '../utils/ssh-config.js'; import type { SSHExecutor } from './ssh-executor.js'; export interface VerifyEntry { path: string; hash: string; } export interface VerifyOptions { sudo?: boolean; /** * Ceiling on hashing, in milliseconds. Zero means no ceiling, and that's * the default here: verification time is set by the data volume, not the * network. A blanket 30 seconds for commands would cut off a tree of * several gigabytes — after the transfer had already succeeded. */ timeoutMs?: number; } export type VerifyOutcome = { status: 'matched'; } | { status: 'mismatched'; paths: string[]; } | { status: 'unavailable'; reason: string; }; /** * Verify files on the server against locally computed hashes. * * Does not throw on a failed check: a mismatch is an answer the caller * decides what to do with (one caller needs to roll back the install, * another just needs a warning). */ export declare function verifyRemoteFiles(executor: SSHExecutor, config: SSHConfig, entries: VerifyEntry[], options: VerifyOptions): Promise; /** * The copy on the far side differed from the source. * * Carries the path the caller named, not the temporary one the comparison ran * at: a name the caller never chose explains nothing to whoever reads the * failure. */ export declare class VerificationMismatchError extends Error { /** The diagnosis is already complete: there is nothing here to route around */ readonly noExecHint: true; readonly path: string; readonly differing: number; readonly total: number; /** Names inside the tree, at most the first few; empty for a single file */ readonly names: string[]; constructor(message: string, path: string, differing: number, total: number, names?: string[]); } /** * The mismatch behind a failure, however it was wrapped on the way up: the * installer wraps whatever verification threw whenever it has warnings of its * own to report. */ export declare function mismatchOf(error: unknown): VerificationMismatchError | null;