paratix 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/moduleFailure.ts","../src/types.ts","../src/modules/aptKeyHelpers.ts","../src/modules/moduleHelpers.ts","../src/modules/apt.ts","../src/modules/fileHelpers.ts","../src/modules/archive.ts","../src/modules/command.ts","../src/modules/compose.ts","../src/modules/cron.ts","../src/modules/download.ts","../src/modules/netHelpers.ts","../src/modules/file.ts","../src/template.ts","../src/modules/fileExtra.ts","../src/modules/git.ts","../src/modules/group.ts","../src/modules/hostname.ts","../src/modules/mount.ts","../src/modules/net.ts","../src/modules/op.ts","../src/totp.ts","../src/modules/package.ts","../src/modules/releaseUpgrade.ts","../src/modules/rsync.ts","../src/modules/script.ts","../src/modules/service.ts","../src/modules/sshAuthorizedKeysHelpers.ts","../src/modules/ssh.ts","../src/modules/sshd.ts","../src/modules/sysctl.ts","../src/modules/system.ts","../src/modules/systemd.ts","../src/modules/ufw.ts","../src/modules/user.ts"],"sourcesContent":["import type { ExecResult, ModuleResult } from \"./types.js\"\n\nimport { CommandError } from \"./sshHelpers.js\"\n\nfunction firstNonEmptyLine(text: string): null | string {\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim()\n if (trimmed.length > 0) return trimmed\n }\n return null\n}\n\nexport function failed(message: string): ModuleResult {\n return { error: new Error(message), status: \"failed\" }\n}\n\nexport function failedCommand(message: string, result: ExecResult): ModuleResult {\n const detail = firstNonEmptyLine(result.stderr) ?? firstNonEmptyLine(result.stdout)\n const summary = `${message} (exit code ${String(result.code)})`\n const errorMessage = detail == null ? summary : `${summary}\\n${detail}`\n return {\n error: new CommandError(errorMessage, result.stdout, result.stderr),\n status: \"failed\",\n }\n}\n","/**\n * A scalar env value, or a lazy function that returns one.\n * Functions may be async, allowing secrets to be fetched on demand.\n */\nexport type EnvironmentValue =\n | (() => boolean | number | string)\n | (() => Promise<boolean | number | string>)\n | boolean\n | number\n | string\n\n/** A key-value map of environment values available to modules and templates. */\nexport type Environment = Record<string, EnvironmentValue>\n\n/** Environment values that can be emitted through the typed meta system. */\nexport type MetaEnvironmentValue = EnvironmentValue\n\n/** Generic meta entry that propagates a value into the downstream environment. */\nexport type EnvironmentMetaEntry = {\n kind: \"env\"\n name: string\n resolve: () => Promise<boolean | number | string>\n valueType: \"boolean\" | \"number\" | \"string\"\n}\n\n/** Runner control-plane meta entry emitted when sshd changed its listen port. */\nexport type SshdPortMetaEntry = {\n kind: \"sshd.port\"\n port: number\n}\n\n/** Runner control-plane meta entry emitted when the target host changed. */\nexport type SystemHostMetaEntry = {\n host: string\n kind: \"system.host\"\n}\n\n/** Runner control-plane meta entry emitted when a reboot should trigger reconnect logic. */\nexport type SystemRebootMetaEntry = {\n kind: \"system.reboot\"\n}\n\n/** Any meta entry that modules may emit. */\nexport type ModuleMetaEntry =\n | EnvironmentMetaEntry\n | SshdPortMetaEntry\n | SystemHostMetaEntry\n | SystemRebootMetaEntry\n\n/** Check result indicating the module's desired state is not yet present. */\nexport const NEEDS_APPLY = \"needs-apply\" as const\n\n/** Execution status emitted by a module apply step. */\nexport type ModuleStatus = \"changed\" | \"failed\" | \"ok\" | \"skipped\"\n\n/** The outcome of a module's apply step. */\nexport type ModuleResult = {\n /**\n * Optional internal dry-run detail shown instead of the generic `(dry-run)`\n * suffix when a module performed custom dry-run verification.\n * @internal\n */\n _dryRunDetail?: string\n /**\n * Optional internal control-plane marker that tells the current scope to\n * execute all pending signals immediately at this point in the run.\n * @internal\n */\n _flushSignals?: true\n /**\n * Optional internal control-plane marker that tells the runner to stop the\n * current run successfully after this module completed.\n * @internal\n */\n _stopRun?: true\n /** Optional error details consumed by the runner for centralized CLI output. */\n error?: Error\n /** Optional typed meta entries for env propagation and runner control-plane updates. */\n meta?: ModuleMetaEntry[]\n /** Execution status of the module. */\n status: ModuleStatus\n}\n\n/**\n * Internal orchestration step shape shared between runner and recipe execution.\n * Contains the merged downstream environment after a single apply step.\n * @internal\n */\nexport type OrchestrationStep = {\n /** @internal */\n _flushSignals?: true\n /** @internal */\n _stopRun?: true\n env: Environment\n meta?: ModuleMetaEntry[]\n status: ModuleStatus\n}\n\n/**\n * A single idempotent unit of work that can be checked and applied.\n * Modules form the building blocks of a server recipe.\n */\nexport type Module = {\n /**\n * Optional internal dry-run apply hook for modules that need custom dry-run\n * execution semantics beyond the generic blocker/meta-producer markers.\n * @internal\n */\n _applyDryRun?: (ssh: null | SshConnection, environment: Environment) => Promise<ModuleResult>\n /**\n * Internal marker for modules that must still execute their apply step in dry-run mode\n * because they act as run blockers rather than mutating state.\n * @internal\n */\n _dryRunBlocker?: true\n /**\n * Internal marker for non-mutating modules whose apply step emits meta that must\n * still be materialized during dry-run so downstream modules see the same environment.\n * @internal\n */\n _dryRunMetaProducer?: true\n /**\n * Enforce the desired state.\n * @returns A {@link ModuleResult} describing what happened.\n */\n apply: (ssh: null | SshConnection, environment: Environment) => Promise<ModuleResult>\n /**\n * Determine whether the module needs to run.\n * @returns `\"ok\"` if the desired state is already present, `\"needs-apply\"` otherwise.\n */\n check: (ssh: null | SshConnection, environment: Environment) => Promise<\"needs-apply\" | \"ok\">\n /**\n * When true the module runs locally instead of over SSH.\n * The `ssh` parameter will be `null` in check/apply.\n */\n local?: boolean\n /** Human-readable name shown in the run output. */\n name: string\n}\n\n/** Raw output from a remote or local command execution. */\nexport type ExecResult = {\n /** Exit code of the process. */\n code: number\n /** Captured standard error. */\n stderr: string\n /** Captured standard output. */\n stdout: string\n}\n\n/** Options that control how a command is executed. */\nexport type ExecOptions = {\n /** Additional environment variables to inject into the process. */\n env?: Record<string, string>\n /** Return a result even when the exit code is non-zero instead of throwing. */\n ignoreExitCode?: boolean\n /** Strings to mask in error messages (e.g. tokens, passwords). */\n secrets?: string[]\n /** Suppress stdout/stderr from the console while running. */\n silent?: boolean\n /** Abort the command after this many milliseconds. */\n timeout?: number\n}\n\n/**\n * Abstraction over an active SSH session.\n * All methods that accept a `command` string run it on the remote host.\n */\nexport type SshConnection = {\n /** Register an additional port that was opened on the remote host. */\n addPort: (port: number) => void\n /** Close the SSH connection and free resources. */\n disconnect: () => void\n /** Download a remote file to the local filesystem. */\n downloadFile: (remotePath: string, localPath: string) => Promise<void>\n /** Run a command and return the full result including exit code and output. */\n exec: (command: string, options?: ExecOptions) => Promise<ExecResult>\n /** Return `true` if the remote path exists. */\n exists: (remotePath: string) => Promise<boolean>\n /**\n * Return the low-level connection parameters for this session.\n * `privateKeyPath` and `agentSocket` reflect the authentication method that\n * was actually used for the current session. `privateKeyPath` is returned as\n * an expanded filesystem path.\n */\n getConnectionInfo: () => {\n agentSocket?: string\n authMethod?: \"agent\" | \"password\" | \"privateKey\"\n host: string\n port: number\n privateKeyPath?: string\n user: string\n verifiedHostPublicKey?: string\n }\n /** Run a command and return stdout split into lines. */\n lines: (command: string) => Promise<string[]>\n /** Run a command and return trimmed stdout. */\n output: (command: string) => Promise<string>\n /** Probe whether passwordless sudo works; prompt interactively if not and cache the password. */\n probeSudo: () => Promise<void>\n /** Read the full contents of a remote file as a string. */\n readFile: (remotePath: string) => Promise<string>\n /** Remove a previously registered port from the reconnect candidate list. */\n removePort: (port: number) => void\n /** Return the SHA-256 hex digest of a remote file, or `null` if not found. */\n sha256: (remotePath: string) => Promise<null | string>\n /** Run a command and return `true` if the exit code is zero. */\n test: (command: string) => Promise<boolean>\n /** Update the target host address (e.g. after a reboot with new IP). */\n updateHost: (host: string) => void\n /** Upload a local file to the remote host via SFTP. */\n uploadFile: (localPath: string, remotePath: string, options?: { mode?: string }) => Promise<void>\n /** Write a string to a remote file, creating or overwriting it. */\n writeFile: (remotePath: string, content: string, options: { mode: string }) => Promise<void>\n}\n\n/**\n * Write a file only if its content has not changed since it was read.\n * Re-reads the file before writing and throws if the current content\n * differs from `originalContent`, preventing lost updates from concurrent modifications.\n *\n * @param ssh - The SSH connection to the remote host.\n * @param parameters - Parameters for the guarded write operation.\n * @param parameters.mode - Chmod mode string for the written file.\n * @param parameters.newContent - The transformed content to write.\n * @param parameters.originalContent - The content that was read before the transformation.\n * @param parameters.remotePath - Path to the file on the remote host.\n */\nexport async function guardedWriteFile(\n ssh: SshConnection,\n parameters: {\n mode: string\n newContent: string\n originalContent: string\n remotePath: string\n }\n): Promise<void> {\n const currentContent = await ssh.readFile(parameters.remotePath)\n if (currentContent !== parameters.originalContent) {\n throw new Error(\n `Concurrent modification detected on ${parameters.remotePath}: ` +\n \"file content changed between read and write. Aborting to prevent data loss.\"\n )\n }\n await ssh.writeFile(parameters.remotePath, parameters.newContent, { mode: parameters.mode })\n}\n\n/** SSH connection parameters for a server. */\nexport type SshConfig = {\n /** Forward the local SSH agent to the remote host. */\n agentForward?: boolean\n /** Expected SHA256 host fingerprint used as a pinned trust anchor. */\n expectedHostFingerprint?: string\n /** Expected OpenSSH public key (`\"<algorithm> <base64>\"`) used as a pinned trust anchor. */\n expectedHostPublicKey?: string\n /** Maximum number of reconnection attempts before giving up. */\n maxReconnectAttempts?: number\n /** Fall back to password authentication if key auth fails. */\n passwordFallback?: boolean\n /** Ordered list of candidate ports -- the runner tries each until one connects. */\n ports: number[]\n /**\n * Absolute path to the private key file used for authentication.\n * When omitted, the SSH agent referenced by `SSH_AUTH_SOCK` is used instead.\n * Exactly one of `privateKey` or a running SSH agent must be available.\n */\n privateKey?: string\n /** Maximum time in milliseconds to spend attempting reconnection before giving up. */\n reconnectTimeout?: number\n /**\n * Host key verification strategy.\n * - `\"accept-new\"` — explicit TOFU opt-in: accept unknown keys and append them to `~/.ssh/known_hosts`.\n * - `\"yes\"` — reject unknown keys; only connect when the key is already in `known_hosts`.\n * - `\"no\"` — skip host key verification entirely.\n *\n * When omitted, Paratix now defaults to `\"yes\"`. To connect to a new host\n * safely without TOFU, set `expectedHostFingerprint` or `expectedHostPublicKey`.\n */\n strictHostKeyChecking?: \"accept-new\" | \"no\" | \"yes\"\n /** Password used for `sudo` escalation on the remote host. */\n sudoPassword?: string\n /** Username to authenticate as. */\n user: string\n}\n\n/** Top-level definition of a server and the modules to run on it. */\nexport type ServerDefinition = {\n /** Server-level env values merged with global env before running modules. */\n env?: Environment\n /** Hostname or IP address. */\n host: string\n /** Display name for the server. */\n name: string\n /** Ordered list of modules (or recipes) to apply. */\n run: Module[]\n /**\n * Modules triggered as signals after the run completes with status `\"changed\"`.\n * Typically used for service reloads or notifications.\n */\n signals?: Module[]\n /** SSH connection parameters. */\n ssh: SshConfig\n}\n","import type { ModuleResult, SshConnection } from \"../types.js\"\n\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\n\n// cspell:ignore OPENPGP\nconst OPENPGP_FINGERPRINT_RE = /^[A-F0-9]{40,64}$/v\n\nexport function normalizeOpenPgpFingerprint(fingerprint: string): string {\n const normalized = fingerprint.replaceAll(/\\s+/gv, \"\").toUpperCase()\n if (!OPENPGP_FINGERPRINT_RE.test(normalized)) {\n throw new Error(\n `apt.key requires an OpenPGP fingerprint with 40-64 hexadecimal characters, got: ${fingerprint}`\n )\n }\n return normalized\n}\n\nfunction parseOpenPgpFingerprint(stdout: string): null | string {\n for (const line of stdout.split(\"\\n\")) {\n const parts = line.trim().split(\":\")\n const fingerprint = parts[0] === \"fpr\" ? parts[9] : null\n if (fingerprint != null && fingerprint.length > 0) {\n return normalizeOpenPgpFingerprint(fingerprint)\n }\n }\n return null\n}\n\nexport function validateAptKeyUrl(url: string): void {\n let parsedUrl: URL\n try {\n parsedUrl = new URL(url)\n } catch {\n throw new Error(`apt.key requires a valid URL, got: ${url}`)\n }\n if (parsedUrl.protocol !== \"https:\") {\n throw new Error(`apt.key requires an https URL, got: ${url}`)\n }\n}\n\nasync function inspectOpenPgpFingerprint(\n ssh: SshConnection,\n path: string\n): Promise<{ fingerprint: string; result: ModuleResult }> {\n const fingerprintResult = await ssh.exec(`gpg --show-keys --with-colons ${shellQuote(path)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (fingerprintResult.code !== 0) {\n return {\n fingerprint: \"\",\n result: failedCommand(\"[apt.key] failed to inspect key material\", fingerprintResult),\n }\n }\n const fingerprint = parseOpenPgpFingerprint(fingerprintResult.stdout)\n if (fingerprint == null) {\n return { fingerprint: \"\", result: failed(\"[apt.key] failed to parse key fingerprint\") }\n }\n return { fingerprint, result: { status: \"changed\" } }\n}\n\nexport async function verifyAptKeyFingerprint(parameters: {\n expectedFingerprint: string\n name: string\n path: string\n ssh: SshConnection\n}): Promise<\"ok\" | ModuleResult> {\n const { expectedFingerprint, name, path, ssh } = parameters\n const inspection = await inspectOpenPgpFingerprint(ssh, path)\n if (inspection.result.status === \"failed\") return inspection.result\n if (inspection.fingerprint !== expectedFingerprint) {\n return failed(\n `[apt.key] fingerprint mismatch for ${name}: expected ${expectedFingerprint}, got ${inspection.fingerprint}`\n )\n }\n return \"ok\"\n}\n\nexport async function applyAptKey(\n ssh: SshConnection,\n parameters: {\n expectedFingerprint: string\n keyringPath: string\n name: string\n url: string\n }\n): Promise<ModuleResult> {\n const { expectedFingerprint, keyringPath, name, url } = parameters\n const temporaryTemplate = `/tmp/apt-key-${name}.XXXXXX`\n const temporaryPath = await ssh.output(`mktemp ${shellQuote(temporaryTemplate)}`)\n try {\n const download = await ssh.exec(\n `curl -fsSL ${shellQuote(url)} -o ${shellQuote(temporaryPath)}`,\n {\n ignoreExitCode: true,\n silent: true,\n }\n )\n if (download.code !== 0) return failedCommand(`[apt.key] failed to download ${name}`, download)\n const fingerprintCheck = await verifyAptKeyFingerprint({\n expectedFingerprint,\n name,\n path: temporaryPath,\n ssh,\n })\n if (fingerprintCheck !== \"ok\") return fingerprintCheck\n const importResult = await ssh.exec(\n `gpg --dearmor --yes -o ${shellQuote(keyringPath)} ${shellQuote(temporaryPath)}`,\n { ignoreExitCode: true, silent: true }\n )\n if (importResult.code !== 0) {\n return failedCommand(`[apt.key] failed to import ${name}`, importResult)\n }\n return { status: \"changed\" }\n } finally {\n await ssh.exec(`rm -f ${shellQuote(temporaryPath)}`, { silent: true })\n }\n}\n","import type { SshConnection } from \"../types.js\"\n\nimport { shellQuote } from \"../ssh.js\"\n\nexport const FLAGS_DIRECTORY = \"/var/lib/paratix/flags\"\n\nconst FLAG_NAME_PATTERN = /^[\\w.\\-]+$/v\n\nfunction validateFlagName(value: string, label: string): void {\n if (!FLAG_NAME_PATTERN.test(value)) {\n throw new Error(\n `${label} must match ${String(FLAG_NAME_PATTERN)}, got: ${JSON.stringify(value)}`\n )\n }\n}\n\nexport async function ensureFlagsDirectory(ssh: SshConnection): Promise<void> {\n await ssh.exec(`mkdir -p ${FLAGS_DIRECTORY}`, { silent: true })\n}\n\nexport async function hasFlag(ssh: SshConnection, flagName: string): Promise<boolean> {\n validateFlagName(flagName, \"flagName\")\n return ssh.test(`[ -f ${FLAGS_DIRECTORY}/${shellQuote(flagName)} ]`)\n}\n\nexport async function setVersionedFlag(\n ssh: SshConnection,\n flagName: string,\n flagPrefix: string\n): Promise<void> {\n validateFlagName(flagName, \"flagName\")\n validateFlagName(flagPrefix, \"flagPrefix\")\n await ensureFlagsDirectory(ssh)\n const glob = shellQuote(`${flagPrefix}*`)\n await ssh.exec(\n `find ${FLAGS_DIRECTORY} -maxdepth 1 -name ${glob} -delete && touch ${FLAGS_DIRECTORY}/${shellQuote(flagName)}`,\n { silent: true }\n )\n}\n\nexport async function setFlag(ssh: SshConnection, flagName: string): Promise<void> {\n await ensureFlagsDirectory(ssh)\n await ssh.exec(`touch ${FLAGS_DIRECTORY}/${shellQuote(flagName)}`, { silent: true })\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport {\n applyAptKey,\n normalizeOpenPgpFingerprint,\n validateAptKeyUrl,\n verifyAptKeyFingerprint,\n} from \"./aptKeyHelpers.js\"\nimport { hasFlag, setVersionedFlag } from \"./moduleHelpers.js\"\n\nconst NONINTERACTIVE = \"DEBIAN_FRONTEND=noninteractive\"\nconst APT_REPOSITORY_MODE = \"0644\"\n\nconst PPA_PREFIX = \"ppa:\"\n/**\n * Parse the output of `debconf-show` into a question-to-value map.\n *\n * Each line has the form `[*] <question>: <value>`. The leading asterisk\n * (marking the currently active value) is stripped, and blank lines are\n * ignored.\n *\n * @param stdout - Raw stdout of `debconf-show <package>`.\n * @returns A map of debconf question names to their current values.\n */\nfunction parseDebconfOutput(stdout: string): Record<string, string> {\n const values: Record<string, string> = {}\n for (const line of stdout.split(\"\\n\")) {\n const colonIndex = line.indexOf(\":\")\n if (colonIndex === -1) continue\n const rawQuestion = line.slice(0, colonIndex).replace(/^\\s*\\*?\\s*/v, \"\")\n if (rawQuestion) {\n values[rawQuestion] = line.slice(colonIndex + 1).trim()\n }\n }\n return values\n}\n\n/**\n * Build a Module that adds a Launchpad PPA via `add-apt-repository`.\n *\n * The check phase queries `/etc/apt/sources.list.d/` for the PPA path\n * so that the command is not re-run if the repository is already present.\n *\n * @param ppa - PPA identifier in the form `\"ppa:user/name\"`.\n * @returns A Module that registers the PPA.\n */\nfunction buildPpaRepository(ppa: string): Module {\n const ppaPath = ppa.slice(PPA_PREFIX.length)\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[apt.repository] SSH connection is required for ${ppa}`)\n const result = await ssh.exec(`add-apt-repository -y ${shellQuote(ppa)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (result.code !== 0) return failedCommand(`[apt.repository] failed to add ${ppa}`, result)\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`grep -rq ${shellQuote(ppaPath)} /etc/apt/sources.list.d/`))\n ? \"ok\"\n : NEEDS_APPLY\n },\n name: `apt.repository: ${ppa}`,\n }\n}\n\n/**\n * Query the debconf database for the type of a question (e.g. `select`, `string`).\n * Falls back to `\"string\"` when the type cannot be determined.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @param question - Fully-qualified debconf question key.\n * @returns Debconf type string (defaults to `\"string\"`).\n */\nasync function resolveDebconfType(ssh: SshConnection, question: string): Promise<string> {\n const metagetCommand = `METAGET ${question} type`\n const typeResult = await ssh.exec(`echo ${shellQuote(metagetCommand)} | debconf-communicate`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (typeResult.code === 0 && typeResult.stdout.trim()) {\n const parts = typeResult.stdout.trim().split(/\\s+/v)\n if (parts.length >= 2 && parts[0] === \"0\") {\n return parts[1]\n }\n }\n return \"string\"\n}\n\nconst BRACKETED_SOURCE_RE = /^(?<prefix>deb(?:-src)?)\\s+\\[(?<opts>[^\\]]*)\\](?<rest>.*)$/v\nconst PLAIN_SOURCE_RE = /^(?<prefix>deb(?:-src)?)\\s(?<rest>.+)$/v\n\n/**\n * Inject a `signed-by=<keyPath>` option into a deb source line.\n *\n * Handles both the bracketed form (`deb [arch=amd64] ...`) and the plain\n * form (`deb https://...`). If the line does not match either pattern it is\n * returned unchanged.\n *\n * @param sourceLine - A single deb/deb-src source line.\n * @param keyPath - Absolute path to the GPG keyring file on the remote host.\n * @returns The source line with the `signed-by` option inserted.\n */\nfunction injectSignedBy(sourceLine: string, keyPath: string): string {\n const withBrackets = BRACKETED_SOURCE_RE.exec(sourceLine)\n if (withBrackets?.groups) {\n if (withBrackets.groups.opts.includes(\"signed-by=\")) return sourceLine\n return `${withBrackets.groups.prefix} [${withBrackets.groups.opts} signed-by=${keyPath}]${withBrackets.groups.rest}`\n }\n const withoutBrackets = PLAIN_SOURCE_RE.exec(sourceLine)\n if (withoutBrackets?.groups) {\n return `${withoutBrackets.groups.prefix} [signed-by=${keyPath}] ${withoutBrackets.groups.rest}`\n }\n return sourceLine\n}\n\n/**\n * Modules for Debian/Ubuntu-specific apt configuration.\n *\n * Provides four apt-specific helpers:\n * - `debconf` — pre-seed debconf answers for non-interactive installs\n * - `distUpgrade` — run `apt-get dist-upgrade` with full dependency resolution\n * - `key` — import a GPG key into `/etc/apt/keyrings/`\n * - `repository` — add a PPA or custom `.list` source file\n *\n * For installing, removing and upgrading packages use the distro-agnostic\n * `package` module instead.\n *\n * @see pkg\n */\nexport const apt = {\n /**\n * Set debconf selections for a package so that installs/upgrades\n * can proceed non-interactively with the desired answers.\n *\n * @param packageName - The package name whose debconf questions to pre-seed.\n * @param selections - A map of `question -> value` entries where the key is\n * the full debconf question name (e.g. `\"postfix/main_mailer_type\"`).\n * @returns A Module that applies the debconf selections.\n *\n * @example\n * apt.debconf(\"postfix\", {\n * \"postfix/main_mailer_type\": \"Internet Site\",\n * \"postfix/mailname\": \"example.com\",\n * })\n */\n debconf(packageName: string, selections: Record<string, string>): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[apt.debconf] SSH connection is required for ${packageName}`)\n\n const lines: string[] = []\n for (const [question, value] of Object.entries(selections)) {\n if (question.includes(\"\\n\") || value.includes(\"\\n\")) {\n return failed(\n `[apt.debconf] selections for ${packageName} must not contain newline characters`\n )\n }\n // eslint-disable-next-line no-await-in-loop\n const type = await resolveDebconfType(ssh, question)\n lines.push(`${packageName} ${question} ${type} ${value}`)\n }\n\n const selectionsText = lines.join(\"\\n\")\n const result = await ssh.exec(\n `echo ${shellQuote(selectionsText)} | debconf-set-selections`,\n { ignoreExitCode: true, silent: true }\n )\n if (result.code !== 0)\n return failedCommand(`[apt.debconf] failed to set selections for ${packageName}`, result)\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const result = await ssh.exec(`debconf-show ${shellQuote(packageName)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (result.code !== 0) return NEEDS_APPLY\n\n const currentValues = parseDebconfOutput(result.stdout)\n for (const [question, value] of Object.entries(selections)) {\n if (currentValues[question] !== value) return NEEDS_APPLY\n }\n return \"ok\"\n },\n name: `apt.debconf: ${packageName}`,\n }\n },\n\n /**\n * Run `apt-get update && apt-get dist-upgrade` once per dated flag.\n * Performs a full distribution upgrade with dependency resolution.\n *\n * @param date - A date string used as the idempotency key (e.g. `\"2024-01-15\"`).\n * @returns A Module that performs the dist-upgrade.\n */\n distUpgrade(date: string): Module {\n const flagName = `apt-dist-upgrade-${date}`\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[apt.distUpgrade] SSH connection is required for ${date}`)\n const update = await ssh.exec(`${NONINTERACTIVE} apt-get update`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (update.code !== 0)\n return failedCommand(\"[apt.distUpgrade] apt-get update failed\", update)\n\n const configure = await ssh.exec(`${NONINTERACTIVE} dpkg --configure -a`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (configure.code !== 0)\n return failedCommand(\"[apt.distUpgrade] dpkg --configure -a failed\", configure)\n\n const upgrade = await ssh.exec(`${NONINTERACTIVE} apt-get dist-upgrade -y`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (upgrade.code !== 0)\n return failedCommand(\"[apt.distUpgrade] apt-get dist-upgrade failed\", upgrade)\n\n await setVersionedFlag(ssh, flagName, \"apt-dist-upgrade-\")\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await hasFlag(ssh, flagName)) ? \"ok\" : NEEDS_APPLY\n },\n name: `apt.distUpgrade: ${date}`,\n }\n },\n\n /**\n * Import a GPG key into `/etc/apt/keyrings/` for use with signed repositories.\n * @param name - Key file name (without `.gpg` extension).\n * @param url - URL to download the key from.\n * @param options - Required trust anchor for the downloaded key.\n * @param options.fingerprint - Expected OpenPGP fingerprint of the repository key.\n * @returns A Module that imports the GPG key.\n */\n key(name: string, url: string, options: { fingerprint: string }): Module {\n validateAptKeyUrl(url)\n const expectedFingerprint = normalizeOpenPgpFingerprint(options.fingerprint)\n const keyringPath = `/etc/apt/keyrings/${name}.gpg`\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[apt.key] SSH connection is required for ${name}`)\n\n const mkdirResult = await ssh.exec(\"mkdir -p /etc/apt/keyrings\", {\n ignoreExitCode: true,\n silent: true,\n })\n if (mkdirResult.code !== 0)\n return failedCommand(\"[apt.key] failed to create /etc/apt/keyrings\", mkdirResult)\n\n return applyAptKey(ssh, { expectedFingerprint, keyringPath, name, url })\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const keyExists = await ssh.test(`[ -f ${shellQuote(keyringPath)} ]`)\n if (!keyExists) return NEEDS_APPLY\n\n return (await verifyAptKeyFingerprint({\n expectedFingerprint,\n name,\n path: keyringPath,\n ssh,\n })) === \"ok\"\n ? \"ok\"\n : NEEDS_APPLY\n },\n name: `apt.key: ${name}`,\n }\n },\n\n /**\n * Add an apt repository source, either as a PPA or a custom source line.\n *\n * **PPA form**: pass `\"ppa:user/name\"` as the first argument (no `source`).\n * Uses `add-apt-repository` internally.\n *\n * **Standard form**: pass a name and a deb source line. The source is\n * written to `/etc/apt/sources.list.d/<name>.list`. The `signed-by` option\n * is automatically derived from `name` (pointing to\n * `/etc/apt/keyrings/<name>.gpg`) unless overridden.\n *\n * @param nameOrPpa - Repository name or PPA identifier (e.g. `\"ppa:user/name\"`).\n * @param source - The deb source line (omit for PPA form).\n * @param options - Optional settings.\n * @param options.signedBy - Override the key name used for `signed-by`\n * (`false` to disable auto-derivation, a string to use a different key name).\n * @returns A Module that configures the repository.\n *\n * @example\n * // PPA form\n * apt.repository(\"ppa:ondrej/php\")\n *\n * @example\n * // Standard form – signed-by is auto-derived from \"nodejs\"\n * apt.repository(\n * \"nodejs\",\n * \"deb https://deb.nodesource.com/node_20.x nodistro main\",\n * )\n *\n * @example\n * // Standard form with signed-by disabled\n * apt.repository(\n * \"internal\",\n * \"deb [arch=amd64] https://packages.example.com stable main\",\n * { signedBy: false },\n * )\n */\n repository(nameOrPpa: string, source?: string, options?: { signedBy?: false | string }): Module {\n if (nameOrPpa.startsWith(PPA_PREFIX) && source === undefined) {\n return buildPpaRepository(nameOrPpa)\n }\n\n if (source === undefined) {\n throw new Error(\"apt.repository: source is required for non-PPA repositories\")\n }\n\n const name = nameOrPpa\n const signedBy = options?.signedBy\n let expectedContent = source\n\n if (signedBy !== false) {\n const keyName = typeof signedBy === \"string\" ? signedBy : name\n expectedContent = injectSignedBy(expectedContent, `/etc/apt/keyrings/${keyName}.gpg`)\n }\n\n const filePath = `/etc/apt/sources.list.d/${name}.list`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[apt.repository] SSH connection is required for ${name}`)\n await ssh.writeFile(filePath, `${expectedContent}\\n`, { mode: APT_REPOSITORY_MODE })\n const result = await ssh.exec(`${NONINTERACTIVE} apt-get update`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (result.code !== 0)\n return failedCommand(`[apt.repository] apt-get update failed for ${name}`, result)\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.test(`[ -f ${shellQuote(filePath)} ]`)\n if (!exists) return NEEDS_APPLY\n const content = await ssh.readFile(filePath)\n return content.trim() === expectedContent.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `apt.repository: ${name}`,\n }\n },\n}\n","import { createHash, timingSafeEqual } from \"node:crypto\"\nimport { readFile } from \"node:fs/promises\"\n\n/**\n * Constant-time comparison of two hex-encoded hashes.\n *\n * @param a - First hex-encoded hash (or `null` when unavailable).\n * @param b - Second hex-encoded hash.\n * @returns `true` when both hashes are equal.\n */\nexport function hexHashesEqual(a: null | string, b: string): boolean {\n if (a == null) return false\n const bufA = Buffer.from(a, \"hex\")\n const bufB = Buffer.from(b, \"hex\")\n return bufA.length === bufB.length && timingSafeEqual(bufA, bufB)\n}\n\nexport async function localSha256(filePath: string): Promise<string> {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const content = await readFile(filePath)\n return createHash(\"sha256\").update(content).digest(\"hex\")\n}\n\nexport function sha256String(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\")\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { localSha256, sha256String } from \"./fileHelpers.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst SILENT = { silent: true } as const\nconst FLAGS_DIR = \"/var/lib/paratix/flags\"\nconst ARCHIVE_MARKER_MODE = \"0644\"\n\n/**\n * Derive the marker file path from the source and destination paths.\n *\n * @param source - The source archive path used as part of the stable key.\n * @param destination - The extraction target path used as part of the stable key.\n * @returns The absolute path to the marker file.\n */\nfunction markerPath(source: string, destination: string): string {\n const hash = sha256String(`${source}\\n${destination}`)\n return `${FLAGS_DIR}/archive-${hash}.sha256`\n}\n\n/**\n * Build the extract command based on the file extension of the original source.\n *\n * @param source - The original archive path used for format detection.\n * @param archivePath - The actual archive path on the remote host.\n * @param destination - The target directory for extraction.\n * @returns The shell command to extract the archive, or null if unsupported.\n */\nfunction extractCommand(source: string, archivePath: string, destination: string): null | string {\n const lower = source.toLowerCase()\n if (lower.endsWith(\".tar.gz\") || lower.endsWith(\".tgz\")) {\n return `tar xzf ${shellQuote(archivePath)} -C ${shellQuote(destination)}`\n }\n if (lower.endsWith(\".tar.bz2\")) {\n return `tar xjf ${shellQuote(archivePath)} -C ${shellQuote(destination)}`\n }\n if (lower.endsWith(\".tar.xz\")) {\n return `tar xJf ${shellQuote(archivePath)} -C ${shellQuote(destination)}`\n }\n if (lower.endsWith(\".tar\")) {\n return `tar xf ${shellQuote(archivePath)} -C ${shellQuote(destination)}`\n }\n if (lower.endsWith(\".zip\")) {\n return `unzip -o ${shellQuote(archivePath)} -d ${shellQuote(destination)}`\n }\n return null\n}\n\n/**\n * Resolve the remote archive path, uploading a local file if needed.\n *\n * @param conn - The SSH connection.\n * @param source - The source archive path.\n * @param upload - Whether to upload the local file first.\n * @returns The remote path to the archive.\n */\nasync function resolveRemoteSource(\n conn: SshConnection,\n source: string,\n upload: boolean\n): Promise<string> {\n if (!upload) return source\n const uploadHash = sha256String(source)\n const remoteSource = `/tmp/paratix-upload-${uploadHash}`\n await conn.uploadFile(source, remoteSource)\n return remoteSource\n}\n\n/**\n * Write the marker file and optionally clean up the uploaded archive.\n *\n * @param conn - The SSH connection.\n * @param remoteSource - The remote archive path.\n * @param options - Marker path and upload flag.\n * @param options.marker - The marker file path.\n * @param options.upload - Whether a temporary upload file should be removed.\n * @returns True if the marker was written successfully.\n */\nasync function writeMarkerAndCleanup(\n conn: SshConnection,\n remoteSource: string,\n options: { marker: string; upload: boolean }\n): Promise<boolean> {\n const sha = await conn.sha256(remoteSource)\n if (sha === null) return false\n await conn.exec(`mkdir -p ${shellQuote(FLAGS_DIR)}`, SILENT)\n await conn.writeFile(options.marker, sha, { mode: ARCHIVE_MARKER_MODE })\n if (options.upload) {\n await conn.exec(`rm -f ${shellQuote(remoteSource)}`, SILENT)\n }\n return true\n}\n\n/** Parameters for the apply helper. */\ntype ApplyParameters = {\n /** The destination directory on the remote host. */\n destination: string\n /** The marker file path. */\n marker: string\n /** Optional owner for chown -R. */\n owner: string | undefined\n /** The source archive path. */\n source: string\n /** Whether to upload a local file first. */\n upload: boolean\n}\n\n/**\n * Execute the archive extraction on the remote host.\n *\n * @param conn - The SSH connection.\n * @param parameters - The extraction parameters.\n * @returns The module result.\n */\nasync function applyExtract(\n conn: SshConnection,\n parameters: ApplyParameters\n): Promise<ModuleResult> {\n const { destination, marker, owner, source, upload } = parameters\n\n const remoteSource = await resolveRemoteSource(conn, source, upload)\n await conn.exec(`mkdir -p ${shellQuote(destination)}`, SILENT)\n\n const cmd = extractCommand(source, remoteSource, destination)\n if (cmd === null) {\n if (upload) await conn.exec(`rm -f ${shellQuote(remoteSource)}`, SILENT)\n return failed(`[archive.extract] unsupported archive format for ${source}`)\n }\n const result = await conn.exec(cmd, EXEC_OPTS)\n if (result.code !== 0) {\n if (upload) await conn.exec(`rm -f ${shellQuote(remoteSource)}`, SILENT)\n return failedCommand(`[archive.extract] failed to extract ${source}`, result)\n }\n\n if (owner !== undefined && owner !== \"\") {\n await conn.exec(`chown -R ${shellQuote(owner)} ${shellQuote(destination)}`, SILENT)\n }\n\n const markerWritten = await writeMarkerAndCleanup(conn, remoteSource, { marker, upload })\n return markerWritten\n ? { status: \"changed\" }\n : failed(`[archive.extract] failed to write marker for ${source}`)\n}\n\n/**\n * Modules for managing archive extraction on the remote host.\n */\nexport const archive = {\n /**\n * Extract an archive to a destination directory on the remote host.\n *\n * Supports tar, tar.gz, tgz, tar.bz2, tar.xz, and zip formats.\n * Uses a marker file with SHA256 checksum for idempotency.\n *\n * @param source - Path to the archive (remote path, or local path when upload is true).\n * @param destination - The destination directory on the remote host.\n * @param options - Optional settings.\n * @param options.owner - Run chown -R after extraction.\n * @param options.upload - Upload a local file to the remote host before extracting.\n * @returns A Module that manages the archive extraction.\n */\n extract(\n source: string,\n destination: string,\n options?: { owner?: string; upload?: boolean }\n ): Module {\n const marker = markerPath(source, destination)\n const upload = options?.upload === true\n const owner = options?.owner\n const parameters: ApplyParameters = { destination, marker, owner, source, upload }\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[archive.extract] SSH connection is required for ${destination}`)\n return applyExtract(conn, parameters)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n // 1. Does the destination directory exist?\n const destinationExists = await conn.test(`test -d ${shellQuote(destination)}`)\n if (!destinationExists) return NEEDS_APPLY\n\n // 2. Does the marker file exist?\n const markerExists = await conn.test(`test -f ${shellQuote(marker)}`)\n if (!markerExists) return NEEDS_APPLY\n\n // 3. Compare SHA256 of the archive with the marker file content.\n const markerResult = await conn.exec(`cat ${shellQuote(marker)}`, SILENT)\n const markerContent = markerResult.stdout.trim()\n\n if (upload) {\n // Compute local SHA256 without uploading.\n const localHash = await localSha256(source)\n return localHash === markerContent ? \"ok\" : NEEDS_APPLY\n }\n\n const remoteSha = await conn.sha256(source)\n return remoteSha === markerContent ? \"ok\" : NEEDS_APPLY\n },\n name: `archive.extract: ${destination}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { maskSecrets } from \"../sshHelpers.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\n/**\n * Modules for running arbitrary shell commands on the remote host.\n */\nexport const command = {\n /**\n * Run an arbitrary shell command on the remote host.\n *\n * By default the module always applies (no idempotency). Provide `options.check`\n * with a shell expression that exits `0` when the desired state is already\n * present -- in that case the command is skipped.\n *\n * @param cmd - The shell command to execute.\n * @param options - Optional configuration for the command.\n * @param options.check - An optional shell expression used as the idempotency guard.\n * If it exits `0`, the command is considered already done.\n * @param options.name - An optional display name shown in the run output.\n * @param options.secrets - Secret values that must be redacted from command output.\n * @returns A Module that executes the shell command.\n *\n * @example\n * command.shell(\"curl -fsSL https://example.com/install.sh | bash\", {\n * check: \"which my-tool\",\n * name: \"install my-tool\",\n * })\n */\n shell(cmd: string, options?: { check?: string; name?: string; secrets?: string[] }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const moduleName = options?.name ?? \"command.shell\"\n if (!ssh) return failed(`[${moduleName}] SSH connection is required`)\n const secrets = options?.secrets ?? []\n const result = await ssh.exec(cmd, {\n ignoreExitCode: true,\n secrets,\n silent: true,\n })\n if (result.code !== 0) {\n return failedCommand(`[${moduleName}] command failed`, {\n ...result,\n stderr: maskSecrets(result.stderr, secrets),\n stdout: maskSecrets(result.stdout, secrets),\n })\n }\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n if (options?.check == null) return NEEDS_APPLY\n return (await ssh.test(options.check)) ? \"ok\" : NEEDS_APPLY\n },\n name: options?.name ?? \"command.shell\",\n }\n },\n}\n","/* eslint-disable max-lines -- compose module intentionally keeps related lifecycle helpers together */\nimport { readFile } from \"node:fs/promises\"\nimport { basename } from \"node:path\"\n\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst UNIT_NAME_PATTERN = /^[\\w@.\\-]+$/v\nconst COMPOSE_CONFIG_MODE = \"0600\"\nconst SYSTEMD_UNIT_MODE = \"0644\"\n\n// cspell:ignore podman\ntype ComposeRuntime = \"docker\" | \"podman\"\n\nfunction requireComposeSsh(\n ssh: null | SshConnection,\n action: string,\n projectDirectory: string\n): ModuleResult | SshConnection {\n return ssh ?? failed(`[compose.${action}] SSH connection is required for ${projectDirectory}`)\n}\n\nasync function requireComposeRuntime(parameters: {\n action: string\n explicitRuntime?: ComposeRuntime\n projectDirectory: string\n ssh: SshConnection\n}): Promise<ComposeRuntime | ModuleResult> {\n const runtime = await getRuntime(parameters.ssh, parameters.explicitRuntime)\n return (\n runtime ??\n failed(\n `[compose.${parameters.action}] no container runtime found for ${parameters.projectDirectory}`\n )\n )\n}\n\n/**\n * Detect whether `podman` or `docker` is available on the remote host.\n * Podman is preferred when both are installed.\n *\n * @param ssh - The SSH connection to the remote host.\n * @returns The first available runtime, or `null` if neither is found.\n */\nasync function detectRuntime(ssh: SshConnection): Promise<ComposeRuntime | null> {\n if (await ssh.test(\"command -v podman\")) return \"podman\"\n if (await ssh.test(\"command -v docker\")) return \"docker\"\n return null\n}\n\n/**\n * Resolve the container runtime to use, preferring the explicit override.\n *\n * @param ssh - The SSH connection to the remote host.\n * @param explicit - An optional runtime override that skips auto-detection.\n * @returns The resolved runtime, or `null` if none could be determined.\n */\nasync function getRuntime(\n ssh: SshConnection,\n explicit?: ComposeRuntime\n): Promise<ComposeRuntime | null> {\n return explicit ?? (await detectRuntime(ssh))\n}\n\n/**\n * Build the base `docker compose` or `podman compose` command string for a\n * given project directory.\n *\n * @param runtime - The container runtime to use.\n * @param projectDirectory - The project directory passed via `--project-directory`.\n * @returns The base compose command string, ready for subcommand concatenation.\n */\nfunction composeCommand(runtime: ComposeRuntime, projectDirectory: string): string {\n return `${runtime} compose --project-directory ${shellQuote(projectDirectory)}`\n}\n\nasync function resolveDesiredComposeContent(options: {\n content?: string\n src?: string\n}): Promise<null | string> {\n if (options.src !== undefined && options.src !== \"\") {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- path from module config, not user input\n return readFile(options.src, \"utf8\")\n }\n if (options.content !== undefined && options.content !== \"\") {\n return options.content\n }\n return null\n}\n\n/**\n * Parse the container state strings from the JSON output of `compose ps --format json`.\n *\n * Both array JSON (Docker >= 2.x) and newline-delimited JSON (older Docker / Podman)\n * are supported. Each entry is expected to have a `State` property.\n *\n * @param stdout - The raw stdout string from the `compose ps` command.\n * @returns An array of state strings (e.g. `\"running\"`, `\"exited\"`). Returns an\n * empty array when parsing fails or the output is not in a recognised format.\n */\nfunction parseContainerStates(stdout: string): string[] {\n try {\n const parsed: unknown = stdout.startsWith(\"[\")\n ? JSON.parse(stdout)\n : stdout\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\")\n .map((line) => JSON.parse(line) as unknown)\n\n if (!Array.isArray(parsed)) return []\n\n return parsed.map((entry: unknown) => {\n if (typeof entry === \"object\" && entry !== null && \"State\" in entry) {\n const state = (entry as { State: unknown }).State\n return typeof state === \"string\" ? state : \"\"\n }\n return \"\"\n })\n } catch {\n return []\n }\n}\n\n/**\n * Strip newline characters from a value to prevent injection in systemd unit files.\n *\n * @param value - The string to sanitize.\n * @returns The sanitized string with all newline characters removed.\n */\nfunction sanitizeUnitValue(value: string): string {\n return value.replaceAll(/[\\n\\r]/gv, \"\")\n}\n\n/**\n * Generate the content of a systemd service unit file that manages a compose\n * stack via `ExecStart` / `ExecStop`.\n *\n * The generated unit depends on `docker.service` when the runtime is `docker`,\n * and only on `network-online.target` for `podman`.\n *\n * @param projectDirectory - The working directory for the compose commands.\n * @param name - The human-readable service description and unit name.\n * @param runtime - The container runtime (`docker` or `podman`).\n * @returns The full systemd unit file content as a string.\n */\nfunction generateSystemdUnit(\n projectDirectory: string,\n name: string,\n runtime: ComposeRuntime\n): string {\n const safeName = sanitizeUnitValue(name)\n const safeDirectory = sanitizeUnitValue(projectDirectory)\n const lines = [\"[Unit]\", `Description=Compose stack: ${safeName}`]\n\n if (runtime === \"docker\") {\n lines.push(\"After=network-online.target docker.service\")\n lines.push(\"Requires=docker.service\")\n } else {\n lines.push(\"After=network-online.target\")\n }\n\n lines.push(\n \"\",\n \"[Service]\",\n \"Type=oneshot\",\n \"RemainAfterExit=yes\",\n `WorkingDirectory=${safeDirectory}`,\n `ExecStart=/usr/bin/env ${runtime} compose up -d`,\n `ExecStop=/usr/bin/env ${runtime} compose down`,\n \"\",\n \"[Install]\",\n \"WantedBy=multi-user.target\",\n \"\"\n )\n\n return lines.join(\"\\n\")\n}\n\n/**\n * Modules for managing Docker Compose / Podman Compose stacks on a remote host.\n *\n * All methods auto-detect the container runtime (`docker` or `podman`) unless\n * an explicit `runtime` option is provided.\n */\nexport const compose = {\n /**\n * Ensure a compose-file is present at `<projectDirectory>/compose.yml`.\n *\n * Provide either `src` (a local file path to upload) or `content` (a string\n * to write). The check phase compares the remote file content with the\n * desired content and skips the apply if they match.\n *\n * @param options - Configuration for the compose file.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.src - Local file path to upload as `compose.yml`.\n * @param options.content - String content to write as `compose.yml`.\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that ensures the compose file is present and valid.\n */\n config(options: {\n content?: string\n projectDirectory: string\n runtime?: ComposeRuntime\n src?: string\n }): Module {\n const { projectDirectory, runtime: explicitRuntime } = options\n const remotePath = `${projectDirectory}/compose.yml`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"config\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"config\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n if (options.src !== undefined && options.src !== \"\") {\n await connection.uploadFile(options.src, remotePath)\n } else if (options.content !== undefined && options.content !== \"\") {\n await connection.writeFile(remotePath, options.content, { mode: COMPOSE_CONFIG_MODE })\n } else {\n return failed(`[compose.config] content or src is required for ${projectDirectory}`)\n }\n\n const validate = await connection.exec(\n `${composeCommand(runtime, projectDirectory)} config --quiet`,\n EXEC_OPTS\n )\n if (validate.code !== 0)\n return failedCommand(\n `[compose.config] validation failed for ${projectDirectory}`,\n validate\n )\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const exists = await ssh.exists(remotePath)\n if (!exists) return NEEDS_APPLY\n\n const desiredContent = await resolveDesiredComposeContent(options)\n if (desiredContent == null) return NEEDS_APPLY\n\n const remoteContent = await ssh.readFile(remotePath)\n return remoteContent.trim() === desiredContent.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `compose.config: ${projectDirectory}`,\n }\n },\n\n /**\n * Tear down all containers in the compose stack. Optionally remove volumes.\n *\n * @param options - Configuration for the down operation.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.volumes - When `true`, also remove named volumes.\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that ensures all containers are stopped and removed.\n */\n down(options: { projectDirectory: string; runtime?: ComposeRuntime; volumes?: boolean }): Module {\n const { projectDirectory, runtime: explicitRuntime, volumes } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"down\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"down\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n const volumesFlag = volumes === true ? \" --volumes\" : \"\"\n const result = await connection.exec(\n `${composeCommand(runtime, projectDirectory)} down${volumesFlag}`,\n EXEC_OPTS\n )\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[compose.down] failed for ${projectDirectory}`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const rt = await getRuntime(ssh, explicitRuntime)\n if (!rt) return NEEDS_APPLY\n\n const result = await ssh.exec(\n `${composeCommand(rt, projectDirectory)} ps --format json`,\n EXEC_OPTS\n )\n if (result.code !== 0) return \"ok\"\n\n const stdout = result.stdout.trim()\n if (stdout === \"\") return \"ok\"\n\n return NEEDS_APPLY\n },\n name: `compose.down: ${projectDirectory}`,\n }\n },\n\n /**\n * Pull the latest images for all services in the compose stack.\n * Signal-style: always applies since an efficient up-to-date check is not\n * feasible.\n *\n * @param options - Configuration for the pull operation.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that pulls the latest images.\n */\n pull(options: { projectDirectory: string; runtime?: ComposeRuntime }): Module {\n const { projectDirectory, runtime: explicitRuntime } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"pull\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"pull\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n const result = await connection.exec(\n `${composeCommand(runtime, projectDirectory)} pull 2>&1`,\n EXEC_OPTS\n )\n if (result.code !== 0)\n return failedCommand(`[compose.pull] failed for ${projectDirectory}`, result)\n\n const output = result.stdout\n if (output.includes(\"Pulling\") || output.includes(\"Downloaded\")) {\n return { status: \"changed\" }\n }\n return { status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `compose.pull: ${projectDirectory}`,\n }\n },\n\n /**\n * Restart all containers by running `down` followed by `up -d`.\n * Signal-style: always applies.\n *\n * @param options - Configuration for the restart operation.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that restarts the compose stack.\n */\n restart(options: { projectDirectory: string; runtime?: ComposeRuntime }): Module {\n const { projectDirectory, runtime: explicitRuntime } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"restart\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"restart\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n const cmd = composeCommand(runtime, projectDirectory)\n const result = await connection.exec(`${cmd} down && ${cmd} up -d`, EXEC_OPTS)\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[compose.restart] failed for ${projectDirectory}`, result)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `compose.restart: ${projectDirectory}`,\n }\n },\n\n /**\n * Generate and write a systemd service unit that manages the compose stack\n * via `ExecStart` / `ExecStop`.\n *\n * The service name defaults to `compose-<basename(projectDirectory)>` when not\n * explicitly provided.\n *\n * @param options - Configuration for the systemd unit.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.name - Optional service name (without `.service` suffix).\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that ensures the systemd unit file is present and up-to-date.\n */\n systemd(options: { name?: string; projectDirectory: string; runtime?: ComposeRuntime }): Module {\n const { projectDirectory, runtime: explicitRuntime } = options\n const serviceName = options.name ?? `compose-${basename(projectDirectory)}`\n if (!UNIT_NAME_PATTERN.test(serviceName)) {\n throw new Error(\n `compose.systemd: name must match ${String(UNIT_NAME_PATTERN)}, got: ${serviceName}`\n )\n }\n const unitFileName = `${serviceName}.service`\n const filePath = `/etc/systemd/system/${unitFileName}`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"systemd\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"systemd\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n const content = generateSystemdUnit(projectDirectory, serviceName, runtime)\n await connection.writeFile(filePath, content, { mode: SYSTEMD_UNIT_MODE })\n\n const result = await connection.exec(\"systemctl daemon-reload\", EXEC_OPTS)\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[compose.systemd] daemon-reload failed for ${unitFileName}`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const rt = await getRuntime(ssh, explicitRuntime)\n if (!rt) return NEEDS_APPLY\n\n const exists = await ssh.exists(filePath)\n if (!exists) return NEEDS_APPLY\n\n const content = generateSystemdUnit(projectDirectory, serviceName, rt)\n const remoteContent = await ssh.readFile(filePath)\n return remoteContent.trim() === content.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `compose.systemd: ${unitFileName}`,\n }\n },\n\n /**\n * Ensure all services in the compose stack are running.\n *\n * The check phase inspects each container's state via `ps --format json`\n * and only skips when every service reports `\"running\"`.\n *\n * @param options - Configuration for the up operation.\n * @param options.projectDirectory - The project directory on the remote host.\n * @param options.services - Optional list of specific services to start.\n * @param options.runtime - Explicit container runtime override.\n * @returns A Module that ensures the compose stack is up.\n */\n up(options: { projectDirectory: string; runtime?: ComposeRuntime; services?: string[] }): Module {\n const { projectDirectory, runtime: explicitRuntime, services } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n const connection = requireComposeSsh(ssh, \"up\", projectDirectory)\n if (\"status\" in connection) return connection\n\n const runtime = await requireComposeRuntime({\n action: \"up\",\n explicitRuntime,\n projectDirectory,\n ssh: connection,\n })\n if (typeof runtime !== \"string\") return runtime\n\n const serviceArguments = services?.map((s) => shellQuote(s)).join(\" \") ?? \"\"\n const suffix = serviceArguments === \"\" ? \"\" : ` ${serviceArguments}`\n const result = await connection.exec(\n `${composeCommand(runtime, projectDirectory)} up -d${suffix}`,\n EXEC_OPTS\n )\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[compose.up] failed for ${projectDirectory}`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const rt = await getRuntime(ssh, explicitRuntime)\n if (!rt) return NEEDS_APPLY\n\n const serviceFilter = services?.map((s) => shellQuote(s)).join(\" \") ?? \"\"\n const filterSuffix = serviceFilter === \"\" ? \"\" : ` ${serviceFilter}`\n const result = await ssh.exec(\n `${composeCommand(rt, projectDirectory)} ps --format json${filterSuffix}`,\n EXEC_OPTS\n )\n if (result.code !== 0) return NEEDS_APPLY\n\n const stdout = result.stdout.trim()\n if (stdout === \"\") return NEEDS_APPLY\n\n const states = parseContainerStates(stdout)\n if (states.length === 0) return NEEDS_APPLY\n\n return states.every((s) => s === \"running\") ? \"ok\" : NEEDS_APPLY\n },\n name: `compose.up: ${projectDirectory}`,\n }\n },\n}\n","import { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\n/**\n * Read the current crontab lines for a user.\n *\n * @param ssh - The active SSH connection.\n * @param user - The target user whose crontab is read.\n * @returns The crontab split into lines, or an empty array if no crontab exists.\n */\nasync function readCrontab(ssh: SshConnection, user: string): Promise<string[]> {\n const result = await ssh.exec(`crontab -u ${shellQuote(user)} -l`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0 ? result.stdout.trimEnd().split(\"\\n\") : []\n}\n\n/**\n * Write a new crontab for a user, or remove it entirely when the content is empty.\n *\n * @param ssh - The active SSH connection.\n * @param user - The target user whose crontab is written.\n * @param lines - The crontab lines to write. An empty array removes the crontab.\n */\nasync function writeCrontab(ssh: SshConnection, user: string, lines: string[]): Promise<void> {\n if (lines.length === 0) {\n await ssh.exec(`crontab -u ${shellQuote(user)} -r`, { ignoreExitCode: true, silent: true })\n return\n }\n const content = `${lines.join(\"\\n\")}\\n`\n await ssh.exec(`printf '%s' ${shellQuote(content)} | crontab -u ${shellQuote(user)} -`, {\n silent: true,\n })\n}\n\n/**\n * Check whether a marker-job pair is correctly present in crontab lines.\n *\n * @param lines - The crontab lines to inspect.\n * @param marker - The marker comment to search for.\n * @param cronJob - The expected job line after the marker.\n * @returns `true` if the marker exists and is followed by the expected job line.\n */\nfunction hasMarkedJob(lines: string[], marker: string, cronJob: string): boolean {\n const index = lines.indexOf(marker)\n return index !== -1 && index + 1 < lines.length && lines[index + 1] === cronJob\n}\n\n/** Options for `cron.job`. */\ntype CronJobOptions = {\n /** The crontab line to manage (e.g. `\"0 * * * * /usr/bin/backup\"`). */\n job: string\n /** Whether the job should be `\"present\"` or `\"absent\"`. Defaults to `\"present\"`. */\n state?: \"absent\" | \"present\"\n}\n\n/**\n * Modules for managing cron jobs in user crontabs.\n *\n * Each managed entry is identified by a `# paratix: <name>` marker comment\n * written on the line directly above the job line, making all changes\n * idempotent and safely repeatable.\n */\nexport const cron = {\n /**\n * Ensure a cron job is present in (or absent from) a user's crontab.\n *\n * Each managed entry is tracked via a `# paratix: <name>` marker comment\n * placed on the line directly above the job line. When the marker already\n * exists, the job line is updated in place. When `state` is `\"absent\"`,\n * both the marker and the job line are removed.\n *\n * @param user - The target user whose crontab is managed.\n * @param name - Unique identifier for this cron job, used in the marker line.\n * @param options - Job content and desired state.\n * @param options.job - The crontab line to manage (e.g. `\"0 * * * * /usr/bin/backup\"`).\n * @param options.state - Whether the job should be `\"present\"` or `\"absent\"`. Defaults to `\"present\"`.\n * @returns A Module that manages the cron job entry.\n */\n job(user: string, name: string, options: CronJobOptions): Module {\n if (/[\\n\\r]/v.test(name)) {\n throw new Error(`cron.job: name must not contain newlines: ${JSON.stringify(name)}`)\n }\n if (/[\\n\\r]/v.test(options.job)) {\n throw new Error(`cron.job: job must not contain newlines: ${JSON.stringify(options.job)}`)\n }\n\n const state = options.state ?? \"present\"\n const cronJob = options.job\n const marker = `# paratix: ${name}`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[cron.job: ${name} (${user})] SSH connection is required`)\n\n const lines = await readCrontab(ssh, user)\n const markerIndex = lines.indexOf(marker)\n\n if (state === \"present\") {\n if (markerIndex === -1) {\n lines.push(marker, cronJob)\n } else {\n lines[markerIndex + 1] = cronJob\n }\n } else if (markerIndex === -1) {\n return { status: \"ok\" }\n } else {\n lines.splice(markerIndex, 2)\n }\n\n await writeCrontab(ssh, user, lines)\n return { status: \"changed\" }\n },\n\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const lines = await readCrontab(ssh, user)\n const found = hasMarkedJob(lines, marker, cronJob)\n\n if (state === \"present\") {\n return found ? \"ok\" : NEEDS_APPLY\n }\n\n // state === \"absent\": ok when marker is not found\n return lines.includes(marker) ? NEEDS_APPLY : \"ok\"\n },\n\n name: `cron.job: ${name} (${user})`,\n }\n },\n}\n","import { createHash, timingSafeEqual } from \"node:crypto\"\n\n/* eslint-disable max-lines */\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote, validateMode } from \"../ssh.js\"\nimport { maskSecrets } from \"../sshHelpers.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { hasFlag, setFlag } from \"./moduleHelpers.js\"\nimport { isValidHeaderName, isValidHeaderValue, validateHttpUrl } from \"./netHelpers.js\"\n\n/**\n * Options shared by all download methods.\n */\ntype BaseDownloadOptions = {\n /** Allow unencrypted `http://` downloads explicitly. */\n allowInsecureHttp?: boolean\n /** Explicitly opt out of integrity verification for trusted sources. */\n allowUnverifiedDownload?: boolean\n /** Group owner to set on the downloaded file via `chown`. */\n group?: string\n /** File mode to set via `chmod` (e.g. `\"0755\"`). */\n mode?: string\n /** User owner to set on the downloaded file via `chown`. */\n owner?: string\n /** Expected SHA-256 hex digest for integrity verification. */\n sha256?: string\n}\n\n/**\n * Full set of parameters for the internal download helpers.\n * Combines {@link BaseDownloadOptions} with the required download coordinates.\n */\ntype DownloadParameters = {\n /** Absolute path on the remote server where the file is written. */\n destination: string\n /** Additional HTTP headers sent with the curl request. */\n headers?: Record<string, string>\n /** Strings to mask in error messages (e.g. tokens). */\n secrets?: string[]\n /** The URL to download from. */\n url: string\n} & BaseDownloadOptions\n\ntype DownloadOwnership = {\n group: string\n mode: string\n owner: string\n}\n\nfunction hasSensitiveQueryParameters(url: URL): boolean {\n const sensitiveTokens = new Set([\n \"auth\",\n \"credential\",\n \"key\",\n \"passwd\",\n \"password\",\n \"secret\",\n \"sig\",\n \"signature\",\n \"token\",\n ])\n for (const [name] of url.searchParams) {\n const parts = name\n .toLowerCase()\n .replaceAll(\".\", \" \")\n .replaceAll(\"_\", \" \")\n .replaceAll(\"-\", \" \")\n .split(\" \")\n if (parts.some((part) => sensitiveTokens.has(part))) return true\n }\n return false\n}\n\nfunction extractUrlSecrets(url: string): string[] {\n const parsed = new URL(url)\n if (parsed.username.length > 0 || parsed.password.length > 0) {\n throw new Error(\n \"Download URLs must not embed credentials. Pass credentials via headers instead.\"\n )\n }\n return hasSensitiveQueryParameters(parsed) ? [url] : []\n}\n\nfunction buildDownloadParameters(\n destination: string,\n options: { headers?: Record<string, string> } & BaseDownloadOptions,\n url: string\n): DownloadParameters {\n const urlSecrets = extractUrlSecrets(url)\n return {\n ...options,\n destination,\n secrets: [...Object.values(options.headers ?? {}), ...urlSecrets],\n url,\n }\n}\n\nfunction canonicalizeHeaders(headers?: Record<string, string>): string {\n return JSON.stringify(\n Object.entries(headers ?? {}).sort(([leftName], [rightName]) =>\n leftName.localeCompare(rightName)\n )\n )\n}\n\nfunction buildLargeDownloadFlagName(\n parameters: Pick<DownloadParameters, \"destination\" | \"headers\" | \"url\">\n): string {\n const flagKey = JSON.stringify({\n destination: parameters.destination,\n headers: canonicalizeHeaders(parameters.headers),\n url: parameters.url,\n })\n const flagHash = createHash(\"sha256\").update(flagKey).digest(\"hex\")\n return `download-${flagHash}`\n}\n\nfunction validateIntegrityConfiguration(\n moduleName: \"download.github\" | \"download.large\" | \"download.url\",\n options: BaseDownloadOptions\n): void {\n if (options.sha256 != null || options.allowUnverifiedDownload === true) return\n throw new Error(\n `${moduleName} requires options.sha256 for integrity verification. ` +\n \"If you intentionally trust the remote artifact, set allowUnverifiedDownload: true explicitly.\"\n )\n}\n\nasync function readDownloadOwnership(\n conn: SshConnection,\n destination: string\n): Promise<DownloadOwnership> {\n const raw = await conn.output(`stat -c '%a %U %G' ${shellQuote(destination)}`)\n const [mode = \"\", owner = \"\", group = \"\"] = raw.trim().split(\" \")\n return { group, mode, owner }\n}\n\nfunction downloadOwnershipMatches(\n current: DownloadOwnership,\n options: BaseDownloadOptions\n): boolean {\n if (options.mode != null && current.mode !== options.mode.replace(/^0+/v, \"\")) return false\n if (options.owner != null && current.owner !== options.owner) return false\n if (options.group != null && current.group !== options.group) return false\n return true\n}\n\nasync function metadataMatches(\n conn: SshConnection,\n destination: string,\n options: BaseDownloadOptions\n): Promise<boolean> {\n if (options.mode == null && options.owner == null && options.group == null) return true\n const ownership = await readDownloadOwnership(conn, destination)\n return downloadOwnershipMatches(ownership, options)\n}\n\nfunction buildTemporaryDownloadPathCommand(destination: string): string {\n return `mktemp \"$(dirname ${shellQuote(destination)})/.paratix-download.XXXXXX\"`\n}\n\n// cspell:ignore redir\nfunction buildCurlProtocolFlags(parameters: Pick<DownloadParameters, \"allowInsecureHttp\">): string {\n const allowedProtocols = parameters.allowInsecureHttp === true ? \"http,https\" : \"https\"\n return `--proto '=${allowedProtocols}' --proto-redir '=${allowedProtocols}'`\n}\n\n/**\n * Build the curl command string including optional headers.\n *\n * @param parameters - Download parameters containing destination, url, and optional headers.\n * @returns The assembled curl shell command.\n */\nfunction buildCurlCommand(parameters: DownloadParameters): string {\n const headerFlags = Object.entries(parameters.headers ?? {})\n .map(([name, value]) => {\n if (!isValidHeaderName(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n if (!isValidHeaderValue(value)) {\n throw new Error(`Invalid HTTP header value for ${name}: value contains newline characters`)\n }\n const header = `${name}: ${value}`\n return `-H ${shellQuote(header)}`\n })\n .join(\" \")\n const headerPart = headerFlags.length > 0 ? `${headerFlags} ` : \"\"\n const protocolFlags = buildCurlProtocolFlags(parameters)\n return `curl -fsSL -o ${shellQuote(parameters.destination)} ${protocolFlags} ${headerPart}${shellQuote(parameters.url)}`\n}\n\n/**\n * Verify the SHA-256 digest of a downloaded file.\n *\n * @param conn - Active SSH connection.\n * @param parameters - Download parameters containing destination and expected sha256.\n * @returns `true` if verification passed or was skipped, `false` on mismatch.\n */\nasync function verifyChecksum(\n conn: SshConnection,\n parameters: DownloadParameters\n): Promise<boolean> {\n if (parameters.sha256 == null) return true\n const actualHash = await conn.sha256(parameters.destination)\n if (hashMatches(actualHash, parameters.sha256)) return true\n return false\n}\n\n/**\n * Apply file ownership and permission settings after download.\n *\n * @param conn - Active SSH connection.\n * @param parameters - Download parameters containing destination, mode, owner, and group.\n */\nasync function applyFileAttributes(\n conn: SshConnection,\n parameters: DownloadParameters\n): Promise<void> {\n if (parameters.mode != null) {\n validateMode(parameters.mode)\n await conn.exec(`chmod ${shellQuote(parameters.mode)} ${shellQuote(parameters.destination)}`, {\n silent: true,\n })\n }\n if (parameters.owner != null || parameters.group != null) {\n const ownerSpec = `${parameters.owner ?? \"\"}:${parameters.group ?? \"\"}`\n await conn.exec(`chown ${shellQuote(ownerSpec)} ${shellQuote(parameters.destination)}`, {\n silent: true,\n })\n }\n}\n\nasync function cleanupTemporaryDownloadFile(\n conn: SshConnection,\n parameters: Pick<DownloadParameters, \"destination\" | \"secrets\">\n): Promise<void> {\n try {\n await conn.exec(`rm -f ${shellQuote(parameters.destination)}`, { silent: true })\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${parameters.destination}: ${maskSecrets(String(cleanupError), parameters.secrets ?? [])}\\n`\n )\n }\n}\n\n/**\n * Execute the download, verify integrity, and set ownership/permissions.\n * Shared implementation behind both `download.url()` and `download.github()`.\n *\n * @param conn - SSH connection or `null` for dry-run mode.\n * @param parameters - Download parameters including destination, url, and options.\n * @returns A {@link ModuleResult} indicating the outcome.\n */\nasync function performDownload(\n conn: null | SshConnection,\n parameters: DownloadParameters\n): Promise<ModuleResult> {\n if (!conn) return failed(`[download] SSH connection is required for ${parameters.destination}`)\n\n await conn.exec(`mkdir -p \"$(dirname ${shellQuote(parameters.destination)})\"`, { silent: true })\n const temporaryDestination = await conn.output(\n buildTemporaryDownloadPathCommand(parameters.destination)\n )\n const downloadParameters = { ...parameters, destination: temporaryDestination }\n let shouldCleanupTemporaryFile = true\n\n try {\n await conn.exec(buildCurlCommand(downloadParameters), {\n secrets: downloadParameters.secrets,\n silent: true,\n })\n\n if (!(await verifyChecksum(conn, downloadParameters))) {\n return failed(`[download] checksum verification failed for ${parameters.destination}`)\n }\n await applyFileAttributes(conn, downloadParameters)\n await conn.exec(\n `mv ${shellQuote(downloadParameters.destination)} ${shellQuote(parameters.destination)}`,\n { silent: true }\n )\n shouldCleanupTemporaryFile = false\n\n return { status: \"changed\" }\n } finally {\n if (shouldCleanupTemporaryFile) {\n await cleanupTemporaryDownloadFile(conn, downloadParameters)\n }\n }\n}\n\n/**\n * Check whether a download needs to be applied based on SHA-256, file\n * existence, and the `force` flag.\n *\n * @param conn - SSH connection or `null` for dry-run mode.\n * @param destination - Absolute path of the target file on the remote server.\n * @param options - Options containing sha256 and force settings.\n * @returns `\"ok\"` if the desired state is already present, `\"needs-apply\"` otherwise.\n */\nasync function checkDownload(\n conn: null | SshConnection,\n destination: string,\n options: { force?: boolean } & BaseDownloadOptions\n): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n if (options.force === true) return NEEDS_APPLY\n\n if (options.sha256 != null) {\n const actualHash = await conn.sha256(destination)\n if (!hashMatches(actualHash, options.sha256)) return NEEDS_APPLY\n return (await metadataMatches(conn, destination, options)) ? \"ok\" : NEEDS_APPLY\n }\n\n const fileExists = await conn.exists(destination)\n if (!fileExists) return NEEDS_APPLY\n return (await metadataMatches(conn, destination, options)) ? \"ok\" : NEEDS_APPLY\n}\n\n/**\n * Compare an actual remote SHA-256 hash against an expected digest using\n * timing-safe comparison.\n *\n * @param actualHash - The hex digest returned by the remote `sha256` command, or `undefined`.\n * @param expectedHash - The expected hex digest.\n * @returns `true` when hashes match, `false` otherwise.\n */\nfunction hashMatches(actualHash: null | string | undefined, expectedHash: string): boolean {\n if (actualHash?.length !== expectedHash.length) return false\n const actual = Buffer.from(actualHash, \"hex\")\n const expected = Buffer.from(expectedHash, \"hex\")\n return actual.length === expected.length && timingSafeEqual(actual, expected)\n}\n\n/**\n * Validate that a string is a 64-character lowercase hex SHA-256 digest.\n *\n * @param value - The string to validate.\n * @throws {Error} If the value is not a valid SHA-256 hex digest.\n */\nfunction validateSha256(value: string): void {\n if (!/^[\\da-f]{64}$/v.test(value)) {\n throw new Error(\n `Invalid SHA-256 hex digest: expected 64 lowercase hex characters, got \"${value}\"`\n )\n }\n}\n\n/**\n * Validate GitHub download options and return the parsed `[owner, repo]` parts.\n *\n * @param options - The GitHub download options to validate.\n * @param options.asset - GitHub release asset filename.\n * @param options.repo - GitHub repository in `owner/repo` format.\n * @param options.tag - Release tag.\n * @returns A two-element array `[owner, repo]`.\n * @throws {Error} If repo, tag, or asset are invalid.\n */\nfunction validateGithubOptions(options: {\n asset: string\n repo: string\n tag: string\n}): [string, string] {\n const parts = options.repo.split(\"/\")\n if (parts.length !== 2 || parts.some((p) => p.length === 0 || p.includes(\"..\"))) {\n throw new Error(`Invalid GitHub repo format: ${options.repo} (expected \"owner/repo\")`)\n }\n if (options.tag.length === 0 || options.tag.includes(\"..\")) {\n throw new Error(`Invalid GitHub release tag: ${options.tag}`)\n }\n if (options.asset.length === 0 || options.asset.includes(\"..\")) {\n throw new Error(`Invalid GitHub release asset: ${options.asset}`)\n }\n return [parts[0], parts[1]]\n}\n\n/**\n * Modules for downloading files to remote servers via `curl`.\n */\nexport const download = {\n /**\n * Download a release asset from a GitHub repository.\n *\n * Builds the download URL from the repository, tag, and asset name. For\n * private repositories, provide a `token` which is sent as an\n * `Authorization` header.\n *\n * Idempotency follows the same rules as {@link download.url}: SHA-256\n * comparison when a digest is given, otherwise file-existence check.\n *\n * @param destination - Absolute path on the remote server where the asset is saved.\n * @param options - Repository coordinates and optional download settings.\n * @param options.repo - GitHub repository in `owner/repo` format (e.g. `\"hashicorp/terraform\"`).\n * @param options.tag - Release tag (e.g. `\"v1.5.0\"`).\n * @param options.asset - GitHub release asset filename (e.g. `\"terraform_1.5.0_linux_amd64.zip\"`).\n * @param options.token - GitHub Personal Access Token for private repositories.\n * @param options.sha256 - Expected SHA-256 hex digest for integrity verification.\n * @param options.allowUnverifiedDownload - Explicitly opt out of integrity verification.\n * @param options.mode - File mode to set via `chmod` (e.g. `\"0755\"`).\n * @param options.owner - User owner to set on the downloaded file via `chown`.\n * @param options.group - Group owner to set on the downloaded file via `chown`.\n * @returns A Module that manages the GitHub release download.\n */\n github(\n destination: string,\n options: {\n /** GitHub release asset filename (e.g. `\"terraform_1.5.0_linux_amd64.zip\"`). */\n asset: string\n /** GitHub repository in `owner/repo` format (e.g. `\"hashicorp/terraform\"`). */\n repo: string\n /** Release tag (e.g. `\"v1.5.0\"`). */\n tag: string\n /** GitHub Personal Access Token for private repositories. */\n token?: string\n } & BaseDownloadOptions\n ): Module {\n const parts = validateGithubOptions(options)\n if (options.sha256 != null) validateSha256(options.sha256)\n validateIntegrityConfiguration(\"download.github\", options)\n\n const url = `https://github.com/${encodeURIComponent(parts[0])}/${encodeURIComponent(parts[1])}/releases/download/${encodeURIComponent(options.tag)}/${encodeURIComponent(options.asset)}`\n const headers: Record<string, string> = {}\n\n if (options.token != null) {\n headers.Authorization = `token ${options.token}`\n headers.Accept = \"application/octet-stream\"\n }\n\n const { group, mode, owner, sha256 } = options\n const downloadParameters: DownloadParameters = {\n ...buildDownloadParameters(destination, { group, headers, mode, owner, sha256 }, url),\n secrets: options.token == null ? undefined : [options.token, ...extractUrlSecrets(url)],\n }\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n return performDownload(conn, downloadParameters)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n return checkDownload(conn, destination, options)\n },\n name: `download.github: ${options.repo}@${options.tag}/${options.asset}`,\n }\n },\n\n /**\n * Download a large file that should only be fetched once.\n *\n * Tracks whether the download has been performed by writing a flag file\n * under `/var/lib/paratix/flags/`. The flag name is derived from a SHA-256\n * hash of the URL. When `sha256` is provided, the check additionally verifies\n * the remote file's digest, and the downloaded file is verified after transfer.\n *\n * @param destination - Absolute path on the remote server where the file is saved.\n * @param url - The URL to download from.\n * @param options - Optional settings for ownership, permissions, and headers.\n * @param options.allowInsecureHttp - Allow unencrypted `http://` downloads explicitly.\n * @param options.group - Group owner to set on the downloaded file via `chown`.\n * @param options.mode - File mode to set via `chmod` (e.g. `\"0755\"`).\n * @param options.owner - User owner to set on the downloaded file via `chown`.\n * @param options.sha256 - Expected SHA-256 hex digest for integrity verification.\n * @param options.allowUnverifiedDownload - Explicitly opt out of integrity verification.\n * @param options.headers - Additional HTTP headers sent with the curl request.\n * @returns A Module that manages the large file download.\n */\n large(\n destination: string,\n url: string,\n options?: {\n /** Allow unencrypted `http://` downloads explicitly. */\n allowInsecureHttp?: boolean\n /** Explicitly opt out of integrity verification for trusted sources. */\n allowUnverifiedDownload?: boolean\n /** Group owner to set on the downloaded file via `chown`. */\n group?: string\n /** Additional HTTP headers sent with the curl request. */\n headers?: Record<string, string>\n /** File mode to set via `chmod` (e.g. `\"0755\"`). */\n mode?: string\n /** User owner to set on the downloaded file via `chown`. */\n owner?: string\n /** Expected SHA-256 hex digest for integrity verification. */\n sha256?: string\n }\n ): Module {\n const resolvedOptions = options ?? {}\n validateHttpUrl(url, { allowHttp: resolvedOptions.allowInsecureHttp })\n if (resolvedOptions.sha256 != null) validateSha256(resolvedOptions.sha256)\n validateIntegrityConfiguration(\"download.large\", resolvedOptions)\n const downloadParameters = buildDownloadParameters(destination, resolvedOptions, url)\n const flagName = buildLargeDownloadFlagName(downloadParameters)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[download.large: ${destination}] SSH connection is required`)\n\n const result = await performDownload(conn, downloadParameters)\n\n if (result.status === \"changed\") {\n await setFlag(conn, flagName)\n }\n\n return result\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const flagExists = await hasFlag(conn, flagName)\n if (!flagExists) return NEEDS_APPLY\n\n const destinationExists = await conn.exists(destination)\n if (!destinationExists) return NEEDS_APPLY\n\n if (resolvedOptions.sha256 != null) {\n const actualHash = await conn.sha256(destination)\n if (!hashMatches(actualHash, resolvedOptions.sha256)) return NEEDS_APPLY\n }\n\n return (await metadataMatches(conn, destination, resolvedOptions)) ? \"ok\" : NEEDS_APPLY\n },\n name: `download.large: ${destination}`,\n }\n },\n\n /**\n * Download a file from a URL to the remote server via `curl -fsSL`.\n *\n * Idempotency is determined by SHA-256 comparison (when `sha256` is\n * provided), file existence (when no digest is given), or skipped entirely\n * when `force` is `true`.\n *\n * @param destination - Absolute path on the remote server where the file is saved.\n * @param url - The URL to download from.\n * @param options - Optional settings for integrity, ownership, and headers.\n * @param options.allowUnverifiedDownload - Explicitly opt out of integrity verification.\n * @returns A Module that manages the file download.\n */\n url(\n destination: string,\n url: string,\n options?: {\n /** Allow unencrypted `http://` downloads explicitly. */\n allowInsecureHttp?: boolean\n /** Explicitly opt out of integrity verification for trusted sources. */\n allowUnverifiedDownload?: boolean\n /** Force re-download even if the file already exists. */\n force?: boolean\n /** Additional HTTP headers sent with the curl request. */\n headers?: Record<string, string>\n } & BaseDownloadOptions\n ): Module {\n validateHttpUrl(url, { allowHttp: options?.allowInsecureHttp })\n if (options?.sha256 != null) validateSha256(options.sha256)\n const resolvedOptions = options ?? {}\n validateIntegrityConfiguration(\"download.url\", resolvedOptions)\n const downloadParameters = buildDownloadParameters(destination, resolvedOptions, url)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n return performDownload(conn, downloadParameters)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n return checkDownload(conn, destination, resolvedOptions)\n },\n name: `download.url: ${destination}`,\n }\n },\n}\n","import type { SshConnection } from \"../types.js\"\n\nimport { shellQuote } from \"../ssh.js\"\n\n/** Wait-for condition and timing options. */\nexport type WaitForOptions = {\n /** String that must appear in `file` for the condition to be satisfied. Requires `file`. */\n contains?: string\n /** Absolute path to a file whose existence (or content) is checked. */\n file?: string\n /** Host address used for port checks (default: `\"127.0.0.1\"`). */\n host?: string\n /** Poll interval in milliseconds between condition checks (default: `2000`). */\n interval?: number\n /** TCP port to probe with `nc -z`. */\n port?: number\n /** Maximum time in milliseconds to wait before failing (default: `60000`). */\n timeout?: number\n}\n\n/** Precomputed curl command parts for an HTTP request check. */\nexport type HttpCheckParameters = {\n /** Expected substring in the response body, or `undefined` to skip body verification. */\n expectedBody: string | undefined\n /** HTTP status code the response must return (e.g. `200`). */\n expectedStatus: number\n /** Pre-built `-H` curl flags string with trailing space, or empty string. */\n headerFlags: string\n /** Pre-built `-X METHOD ` curl flag string with trailing space, or empty string for GET. */\n methodFlag: string\n /** The URL to request. */\n url: string\n}\n\n/**\n * Build the shell command used to test a wait-for condition.\n *\n * @param options - The wait-for condition options.\n * @param host - The resolved host address for port checks.\n * @returns The shell test command string.\n */\nexport function buildWaitForTestCommand(options: WaitForOptions, host: string): string {\n if (options.port != null) {\n return `nc -z ${shellQuote(host)} ${shellQuote(String(options.port))}`\n }\n if (options.file != null && options.contains != null) {\n return `grep -q ${shellQuote(options.contains)} ${shellQuote(options.file)}`\n }\n if (options.file != null) {\n return `test -f ${shellQuote(options.file)}`\n }\n throw new Error(\"net.waitFor requires either port or file option\")\n}\n\n/**\n * Build the display name for a wait-for module instance.\n *\n * @param options - The wait-for condition options.\n * @returns The formatted module display name.\n */\nexport function buildWaitForName(options: WaitForOptions): string {\n if (options.port != null) return `net.waitFor: port ${options.port}`\n if (options.file != null && options.contains != null) {\n return `net.waitFor: ${options.file} contains ${options.contains}`\n }\n if (options.file != null) return `net.waitFor: file ${options.file}`\n return \"net.waitFor\"\n}\n\n/** Last ASCII control character (U+001F). */\nconst LAST_CONTROL_CHAR = 0x1f\n/** ASCII DEL character (U+007F). */\nconst DEL_CHAR = 0x7f\n\n/**\n * Check whether a string is a valid HTTP header name per RFC 7230 (token chars).\n * Rejects control characters, DEL, colons, and any non-printable ASCII.\n *\n * @param name - The header name to validate.\n * @returns `true` if the name contains only valid token characters, `false` otherwise.\n */\nexport function isValidHeaderName(name: string): boolean {\n for (let index = 0; index < name.length; index++) {\n const code = name.charCodeAt(index)\n if (code <= LAST_CONTROL_CHAR || code >= DEL_CHAR || name[index] === \":\") return false\n }\n return name.length > 0\n}\n\n/**\n * Check whether a string is safe to use as an HTTP header value.\n * Rejects values containing CR, LF, or null bytes to prevent HTTP header injection.\n *\n * @param value - The header value to validate.\n * @returns `true` if the value contains no newline characters, `false` otherwise.\n */\nexport function isValidHeaderValue(value: string): boolean {\n return !value.includes(\"\\r\") && !value.includes(\"\\n\") && !value.includes(\"\\0\")\n}\n\n/**\n * Build curl `-H` flags from a headers record for use in shell commands.\n * Validates header names and values to prevent HTTP header injection.\n *\n * @param headers - The HTTP headers to convert into curl flags.\n * @returns The formatted curl header flags string with trailing space, or empty string.\n */\nexport function buildCurlHeaderFlags(headers: Record<string, string>): string {\n const flags = Object.entries(headers)\n .map(([name, value]) => {\n if (!isValidHeaderName(name)) {\n throw new Error(`Invalid HTTP header name: ${name}`)\n }\n if (!isValidHeaderValue(value)) {\n throw new Error(`Invalid HTTP header value for ${name}: value contains newline characters`)\n }\n const header = `${name}: ${value}`\n return `-H ${shellQuote(header)}`\n })\n .join(\" \")\n return flags.length > 0 ? `${flags} ` : \"\"\n}\n\n/**\n * Check whether an HTTP endpoint matches the expected status code and body content.\n *\n * @param conn - Active SSH connection to execute curl commands on.\n * @param parameters - Precomputed request parameters including URL and expectations.\n * @returns `true` if the endpoint matches all expectations.\n */\nexport async function checkHttpCondition(\n conn: SshConnection,\n parameters: HttpCheckParameters\n): Promise<boolean> {\n try {\n const statusCommand = `curl -s -o /dev/null -w '%{http_code}' ${parameters.methodFlag}${parameters.headerFlags}${shellQuote(parameters.url)}`\n const statusOutput = await conn.output(statusCommand)\n if (statusOutput.trim() !== String(parameters.expectedStatus)) return false\n\n if (parameters.expectedBody != null) {\n const bodyCommand = `curl -s ${parameters.methodFlag}${parameters.headerFlags}${shellQuote(parameters.url)}`\n const bodyOutput = await conn.output(bodyCommand)\n if (!bodyOutput.includes(parameters.expectedBody)) return false\n }\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Validate that a URL string is a well-formed HTTPS URL by default.\n * HTTP can be allowed explicitly via `allowHttp`.\n *\n * @param url - The URL string to validate.\n * @param options - Validation options.\n * @param options.allowHttp - When `true`, also allow `http://` URLs.\n * @throws {Error} If the URL is malformed or the scheme is not allowed.\n */\nexport function validateHttpUrl(url: string, options?: { allowHttp?: boolean }): void {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n throw new Error(`Invalid URL '${url}': expected an http or https URL`)\n }\n if (parsed.protocol === \"https:\") return\n if (parsed.protocol === \"http:\" && options?.allowHttp === true) return\n if (parsed.protocol === \"http:\") {\n throw new Error(`Insecure URL scheme 'http' in '${url}': only https is allowed by default`)\n }\n if (parsed.protocol !== \"https:\") {\n throw new Error(\n `Unsupported URL scheme '${parsed.protocol.replace(/:$/v, \"\")}' in '${url}': only http and https are allowed`\n )\n }\n}\n\n/**\n * Create a delay promise for use in polling loops.\n *\n * @param ms - The delay duration in milliseconds.\n * @returns A promise that resolves after the specified delay.\n */\nexport async function delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n","import { readFile } from \"node:fs/promises\"\nimport { posix } from \"node:path\"\n\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote, validateMode } from \"../ssh.js\"\nimport { renderTemplate } from \"../template.js\"\nimport {\n type Environment,\n guardedWriteFile,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\nimport { assemble, block, properties, replace, stat } from \"./fileExtra.js\"\nimport { hexHashesEqual, localSha256, sha256String } from \"./fileHelpers.js\"\n\nexport type { BlockOptions } from \"./fileExtra.js\"\n\ntype FileOwnership = {\n group: string\n mode: string\n owner: string\n}\n\nconst DEFAULT_FILE_WRITE_MODE = \"0644\"\n\nasync function readOwnership(ssh: SshConnection, remotePath: string): Promise<FileOwnership> {\n const raw = await ssh.output(`stat -c '%a %U %G' ${shellQuote(remotePath)}`)\n const [mode = \"\", owner = \"\", group = \"\"] = raw.trim().split(\" \")\n return { group, mode, owner }\n}\n\nfunction normalizeMode(mode: string): string {\n return mode.startsWith(\"0\") ? mode : `0${mode}`\n}\n\nasync function resolveWriteMode(\n ssh: SshConnection,\n remotePath: string,\n explicitMode?: string\n): Promise<string> {\n if (explicitMode != null) return explicitMode\n const exists = await ssh.exists(remotePath)\n if (!exists) return DEFAULT_FILE_WRITE_MODE\n const ownership = await readOwnership(ssh, remotePath)\n return normalizeMode(ownership.mode)\n}\n\nasync function applyFileMetadata(\n ssh: SshConnection,\n remotePath: string,\n options?: { mode?: string; owner?: string }\n): Promise<void> {\n if (options?.mode != null) {\n validateMode(options.mode)\n await ssh.exec(`chmod ${shellQuote(options.mode)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n if (options?.owner != null) {\n await ssh.exec(`chown ${shellQuote(options.owner)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n}\n\nfunction ownershipMatches(\n current: FileOwnership,\n options?: { mode?: string; owner?: string }\n): boolean {\n if (options?.mode != null && current.mode !== options.mode.replace(/^0+/v, \"\")) return false\n if (options?.owner == null) return true\n\n const expectsGroup = options.owner.includes(\":\")\n const [expectedOwner, expectedGroup = \"\"] = options.owner.split(\":\", 2)\n if (current.owner !== expectedOwner) return false\n if (expectsGroup && current.group !== expectedGroup) return false\n return true\n}\n\nfunction splitLines(content: string): string[] {\n return content.split(/\\r?\\n/v)\n}\n\nfunction validateAbsentPath(remotePath: string): void {\n const trimmedPath = remotePath.trim()\n if (trimmedPath.length === 0) {\n throw new Error(\"file.absent: remotePath must not be empty\")\n }\n\n if (posix.normalize(trimmedPath) === \"/\") {\n throw new Error(`file.absent: refusing to remove destructive path: ${remotePath}`)\n }\n}\n\nasync function templateStateMatches(input: {\n options?: { mode?: string; owner?: string }\n remotePath: string\n rendered: string\n ssh: SshConnection\n}): Promise<boolean> {\n const remoteHash = await input.ssh.sha256(input.remotePath)\n const localHash = sha256String(input.rendered)\n if (!hexHashesEqual(remoteHash, localHash)) return false\n\n return ownershipMatches(await readOwnership(input.ssh, input.remotePath), input.options)\n}\n\n/**\n * Modules for managing remote files and directories.\n *\n * Idempotency is enforced via SHA-256 checksums for file content and\n * existence checks for directories and individual lines.\n */\nexport const file = {\n /**\n * Ensure a remote path does not exist, removing it recursively if present.\n *\n * @param remotePath - Path to remove on the remote host.\n * @returns A Module that ensures the path is absent.\n */\n absent(remotePath: string): Module {\n validateAbsentPath(remotePath)\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.absent: ${remotePath}] SSH connection is required`)\n await ssh.exec(`rm -rf ${shellQuote(remotePath)}`, { silent: true })\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`[ -e ${shellQuote(remotePath)} ]`)) ? \"needs-apply\" : \"ok\"\n },\n name: `file.absent: ${remotePath}`,\n }\n },\n\n assemble,\n\n block,\n\n /**\n * Upload a local file to the remote host.\n * The file is only transferred when the remote SHA-256 differs from the local one.\n *\n * @param remotePath - Destination path on the remote host.\n * @param localPath - Source path on the local filesystem.\n * @param options - Optional file attributes.\n * @param options.mode - Optional chmod mode string (e.g. `\"0644\"`).\n * @param options.owner - Optional chown owner string (e.g. `\"www-data:www-data\"`).\n * @returns A Module that copies the file to the remote host.\n */\n copy(remotePath: string, localPath: string, options?: { mode?: string; owner?: string }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.copy: ${remotePath}] SSH connection is required`)\n await ssh.uploadFile(localPath, remotePath)\n\n if (options?.mode != null) {\n validateMode(options.mode)\n await ssh.exec(`chmod ${shellQuote(options.mode)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n if (options?.owner != null) {\n await ssh.exec(`chown ${shellQuote(options.owner)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.exists(remotePath)\n if (!exists) return NEEDS_APPLY\n\n const remoteHash = await ssh.sha256(remotePath)\n const localHash = await localSha256(localPath)\n if (!hexHashesEqual(remoteHash, localHash)) return NEEDS_APPLY\n\n const metadataMatches = ownershipMatches(await readOwnership(ssh, remotePath), options)\n return metadataMatches ? \"ok\" : NEEDS_APPLY\n },\n name: `file.copy: ${remotePath}`,\n }\n },\n\n /**\n * Ensure a remote directory exists, creating it recursively if needed.\n *\n * @param remotePath - Path of the directory to create on the remote host.\n * @param options - Optional directory attributes.\n * @param options.mode - Optional chmod mode string.\n * @param options.owner - Optional chown owner string.\n * @returns A Module that ensures the directory exists.\n */\n directory(remotePath: string, options?: { mode?: string; owner?: string }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.directory: ${remotePath}] SSH connection is required`)\n await ssh.exec(`mkdir -p ${shellQuote(remotePath)}`, { silent: true })\n\n if (options?.mode != null) {\n validateMode(options.mode)\n await ssh.exec(`chmod ${shellQuote(options.mode)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n if (options?.owner != null) {\n await ssh.exec(`chown ${shellQuote(options.owner)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.test(`[ -d ${shellQuote(remotePath)} ]`)\n if (!exists) return NEEDS_APPLY\n\n const metadataMatches = ownershipMatches(await readOwnership(ssh, remotePath), options)\n return metadataMatches ? \"ok\" : NEEDS_APPLY\n },\n name: `file.directory: ${remotePath}`,\n }\n },\n\n /**\n * Ensure a specific line is present in a remote file.\n * When `options.match` is provided, the first line matching the regex is replaced\n * with the new `line` value instead of appending.\n *\n * @param remotePath - Path to the file on the remote host.\n * @param line - The exact line content to ensure is present.\n * @param options - Optional match configuration.\n * @param options.match - A regex pattern; when matched, the line is replaced rather than appended.\n * @returns A Module that ensures the line is present.\n */\n line(remotePath: string, line: string, options?: { match?: string }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.line: ${remotePath}] SSH connection is required`)\n\n if (options?.match == null) {\n // Append line using printf to avoid shell interpretation\n await ssh.exec(`printf '%s\\\\n' ${shellQuote(line)} >> ${shellQuote(remotePath)}`, {\n silent: true,\n })\n } else {\n // Replace matching line with the new line (client-side to avoid sed escaping issues)\n const content = await ssh.readFile(remotePath)\n // eslint-disable-next-line security/detect-non-literal-regexp\n const pattern = new RegExp(options.match, \"mu\")\n if (!pattern.test(content)) {\n return failed(\n `[file.line: ${remotePath}] No line matching ${options.match} found for replacement`\n )\n }\n const newContent = content.replace(pattern, line)\n const ownership = await readOwnership(ssh, remotePath)\n await guardedWriteFile(ssh, {\n mode: normalizeMode(ownership.mode),\n newContent,\n originalContent: content,\n remotePath,\n })\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.exists(remotePath)\n if (!exists) return NEEDS_APPLY\n\n const content = await ssh.readFile(remotePath)\n const lines = splitLines(content)\n\n if (options?.match != null) {\n // eslint-disable-next-line security/detect-non-literal-regexp\n const matchPattern = new RegExp(options.match, \"mu\")\n const matchedLine = lines.find((candidateLine) => matchPattern.test(candidateLine))\n return matchedLine === line ? \"ok\" : NEEDS_APPLY\n }\n\n return lines.includes(line) ? \"ok\" : NEEDS_APPLY\n },\n name: `file.line: ${remotePath}`,\n }\n },\n\n properties,\n\n replace,\n\n stat,\n\n /**\n * Render a local template file with env values and write the result to the remote host.\n * Uses SHA-256 comparison of the rendered output to avoid unnecessary writes.\n *\n * @param remotePath - Destination path on the remote host.\n * @param templatePath - Path to the local template file containing `{{key}}` placeholders.\n * @param options - Optional file attributes.\n * @param options.mode - Optional chmod mode string.\n * @param options.owner - Optional chown owner string.\n * @param options.strict - When `true`, every template placeholder must use an explicit modifier.\n * @returns A Module that renders and writes the template.\n */\n template(\n remotePath: string,\n templatePath: string,\n options?: { mode?: string; owner?: string; strict?: boolean }\n ): Module {\n let cachedContent: string | undefined\n\n async function getTemplateContent(): Promise<string> {\n // eslint-disable-next-line security/detect-non-literal-fs-filename, require-atomic-updates -- single-threaded; runner calls check() then apply() sequentially\n cachedContent ??= await readFile(templatePath, \"utf8\")\n return cachedContent\n }\n\n return {\n async apply(ssh: null | SshConnection, environment: Environment): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.template: ${remotePath}] SSH connection is required`)\n\n const templateContent = await getTemplateContent()\n const rendered = await renderTemplate(templateContent, environment, {\n strict: options?.strict,\n })\n await ssh.writeFile(remotePath, rendered, {\n mode: await resolveWriteMode(ssh, remotePath, options?.mode),\n })\n await applyFileMetadata(ssh, remotePath, options)\n\n return { status: \"changed\" }\n },\n async check(\n ssh: null | SshConnection,\n environment: Environment\n ): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.exists(remotePath)\n if (!exists) return NEEDS_APPLY\n\n const templateContent = await getTemplateContent()\n const rendered = await renderTemplate(templateContent, environment, {\n strict: options?.strict,\n })\n return (await templateStateMatches({ options, remotePath, rendered, ssh }))\n ? \"ok\"\n : NEEDS_APPLY\n },\n name: `file.template: ${remotePath}`,\n }\n },\n}\n","import type { Environment } from \"./types.js\"\n\nimport { resolveEnvironment } from \"./environment.js\"\nimport { shellQuote } from \"./sshHelpers.js\"\n\n/** Options for controlling template rendering behavior. */\nexport type RenderOptions = {\n /** When `true`, every placeholder must use an explicit modifier (e.g. `|shell` or `|raw`). */\n strict?: boolean\n}\n\n/** Registry of supported template modifiers. */\nconst modifiers: Partial<Record<string, (value: string) => string>> = {\n raw: (value: string) => value,\n shell: shellQuote,\n}\n\n/**\n * Apply a template modifier to a resolved value.\n *\n * @param value - The stringified resolved value.\n * @param modifier - The modifier name extracted from the placeholder, or `undefined` if none.\n * @returns The value after applying the modifier transformation.\n */\nfunction applyModifier(value: string, modifier: string | undefined): string {\n if (modifier === undefined) return value\n const transform = modifiers[modifier]\n if (!transform) throw new Error(`Unknown template modifier \"${modifier}\"`)\n return transform(value)\n}\n\n/**\n * Throw if any placeholder in {@link matches} lacks an explicit modifier.\n *\n * @param matches - The regex matches to validate.\n */\nfunction enforceStrictModifiers(matches: RegExpExecArray[]): void {\n for (const match of matches) {\n if (match.groups?.modifier === undefined) {\n throw new Error(\n `Strict mode: placeholder \"{{${match.groups?.varName}}}\" requires an explicit modifier (e.g. |shell or |raw)`\n )\n }\n }\n}\n\n/**\n * Render a template string by replacing all `\\{\\{key\\}\\}` (or `\\{\\{key|modifier\\}\\}`)\n * placeholders with the corresponding resolved env values.\n *\n * **Security: No default escaping.** Values are inserted verbatim unless a modifier\n * is applied. When the rendered output is used in a shell context (e.g. a script or\n * shell config file), always use the `|shell` modifier on every user-controlled\n * placeholder to prevent shell injection: `\\{\\{VALUE|shell\\}\\}`.\n *\n * Supported modifiers:\n * - `shell` — wraps the resolved value with {@link shellQuote} for safe shell interpolation.\n * - `raw` — passes the value through unchanged (explicit verbatim insertion).\n *\n * When `options.strict` is `true` (the default), every placeholder **must** specify\n * a modifier; bare `\\{\\{KEY\\}\\}` placeholders will throw an error. Pass `strict: false`\n * to disable this check.\n *\n * Placeholders are resolved concurrently via Promise.all; insertion order is preserved.\n *\n * @param template - The template string containing placeholders.\n * @param environment - The env map used to resolve placeholder values.\n * @param options - Optional rendering options.\n * @returns The rendered string with all placeholders replaced.\n * @throws {Error} When a placeholder key is not found in `environment`.\n * @throws {Error} When `strict` is `true` and a placeholder has no modifier.\n */\nexport async function renderTemplate(\n template: string,\n environment: Environment,\n options?: RenderOptions\n): Promise<string> {\n // Handle escaped \\{{ by replacing with a placeholder\n const escapedBraceMarker = \"\\x00ESCAPED_BRACE\\x00\"\n const result = template.replaceAll(\"\\\\{{\", escapedBraceMarker)\n\n // Find all {{key}} or {{key|modifier}} patterns and resolve values in parallel\n // eslint-disable-next-line security/detect-unsafe-regex -- modifier group is consumed via match.groups\n const pattern = /\\{\\{(?<varName>\\w+)(?:\\|(?<modifier>\\w*))?\\}\\}/gv\n const matches = [...result.matchAll(pattern)]\n\n // In strict mode, validate that all placeholders have explicit modifiers before resolving values\n if (options?.strict ?? true) enforceStrictModifiers(matches)\n\n const resolvedValues = await Promise.all(\n matches.map(async (match) => resolveEnvironment(environment, match.groups?.varName ?? \"\"))\n )\n\n // Build result from segments between matches\n let cursor = 0\n let output = \"\"\n\n for (const [index, match] of matches.entries()) {\n const matchIndex = match.index\n const value = applyModifier(String(resolvedValues[index]), match.groups?.modifier)\n output += result.slice(cursor, matchIndex) + value\n cursor = matchIndex + match[0].length\n }\n\n output += result.slice(cursor)\n\n // Restore escaped braces\n return output.replaceAll(escapedBraceMarker, \"{{\")\n}\n","import { readFile } from \"node:fs/promises\"\n\nimport { environmentToMetaEntries } from \"../meta.js\"\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote, validateMode } from \"../ssh.js\"\nimport {\n guardedWriteFile,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\nimport { hexHashesEqual, sha256String } from \"./fileHelpers.js\"\n\n/** Index where the file-type field starts in `stat -c '%s %a %U %G %F %Y'` output. */\nconst STAT_TYPE_START_INDEX = 4\nconst DEFAULT_FILE_WRITE_MODE = \"0644\"\n\nasync function concatFragments(fragments: string[]): Promise<string> {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- paths from module config, not user input\n const contents = await Promise.all(fragments.map(async (f) => readFile(f, \"utf8\")))\n return contents.join(\"\")\n}\n\nfunction normalizeMode(mode: string): string {\n return mode.startsWith(\"0\") ? mode : `0${mode}`\n}\n\nasync function resolveWriteMode(\n ssh: SshConnection,\n remotePath: string,\n explicitMode?: string\n): Promise<string> {\n if (explicitMode != null) return explicitMode\n const exists = await ssh.exists(remotePath)\n if (!exists) return DEFAULT_FILE_WRITE_MODE\n const mode = await ssh.output(`stat -c '%a' ${shellQuote(remotePath)}`)\n return normalizeMode(mode.trim())\n}\n\ntype BlockMarkers = { begin: string; end: string; full: string }\n\nfunction replaceBlock(text: string, markers: BlockMarkers): string {\n const lines = text.split(\"\\n\")\n const result: string[] = []\n let insideBlock = false\n for (const line of lines) {\n if (line.includes(markers.begin)) {\n result.push(markers.full)\n insideBlock = true\n } else if (insideBlock && line.includes(markers.end)) {\n insideBlock = false\n } else if (!insideBlock) {\n result.push(line)\n }\n }\n return result.join(\"\\n\")\n}\n\nfunction extractBlockContent(text: string, beginMarker: string, endMarker: string): string {\n const lines = text.split(\"\\n\")\n const blockLines: string[] = []\n let insideBlock = false\n for (const line of lines) {\n if (line.includes(beginMarker)) {\n insideBlock = true\n } else if (insideBlock && line.includes(endMarker)) {\n insideBlock = false\n } else if (insideBlock) {\n blockLines.push(line)\n }\n }\n return blockLines.join(\"\\n\")\n}\n\n/** Options for the {@link block} module. */\nexport type BlockOptions = {\n /** The desired content between the markers. */\n content: string\n /** Unique identifier for the managed block. Used in the marker comment lines. */\n name: string\n /** Comment prefix for the marker lines (default `\"#\"`). */\n prefix?: string\n}\n\n/**\n * Concatenate local fragment files and write the result to the remote host.\n * The file is only transferred when the remote SHA-256 differs from the\n * combined local fragments.\n *\n * @param remotePath - Destination path on the remote host.\n * @param fragments - Array of local file paths whose contents are concatenated.\n * @param options - Optional file attributes.\n * @param options.mode - Optional chmod mode string (e.g. `\"0644\"`).\n * @param options.owner - Optional chown owner string (e.g. `\"www-data:www-data\"`).\n * @returns A Module that assembles the fragments on the remote host.\n */\nexport function assemble(\n remotePath: string,\n fragments: string[],\n options?: { mode?: string; owner?: string }\n): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.assemble: ${remotePath}] SSH connection is required`)\n\n await ssh.writeFile(remotePath, await concatFragments(fragments), {\n mode: await resolveWriteMode(ssh, remotePath, options?.mode),\n })\n\n if (options?.mode != null) {\n validateMode(options.mode)\n await ssh.exec(`chmod ${shellQuote(options.mode)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n if (options?.owner != null) {\n await ssh.exec(`chown ${shellQuote(options.owner)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.exists(remotePath)\n if (!exists) return NEEDS_APPLY\n\n const localHash = sha256String(await concatFragments(fragments))\n const remoteHash = await ssh.sha256(remotePath)\n return hexHashesEqual(remoteHash, localHash) ? \"ok\" : NEEDS_APPLY\n },\n name: `file.assemble: ${remotePath}`,\n }\n}\n\n/**\n * Ensure a managed block of text is present in a remote file.\n * The block is delimited by marker comments of the form\n * `<prefix> BEGIN paratix: <name>` / `<prefix> END paratix: <name>`.\n * If the markers are not yet present, the block is appended to the file.\n * If the markers already exist, the content between them is replaced in place.\n *\n * @param remotePath - Path to the file on the remote host.\n * @param options - Block configuration.\n * @param options.content - The desired content between the begin and end markers.\n * @param options.name - Unique identifier for the managed block, used in the marker lines.\n * @param options.prefix - Comment prefix for the marker lines (default `\"#\"`).\n * @returns A Module that ensures the block is present with the correct content.\n */\nexport function block(remotePath: string, options: BlockOptions): Module {\n const prefix = options.prefix ?? \"#\"\n const beginMarker = `${prefix} BEGIN paratix: ${options.name}`\n const endMarker = `${prefix} END paratix: ${options.name}`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh)\n return failed(`[file.block: ${remotePath} (${options.name})] SSH connection is required`)\n\n const fullBlock = `${beginMarker}\\n${options.content}\\n${endMarker}`\n const exists = await ssh.exists(remotePath)\n\n if (exists) {\n const existing = await ssh.readFile(remotePath)\n const hasMarker = existing.includes(beginMarker)\n\n if (hasMarker) {\n const markers: BlockMarkers = { begin: beginMarker, end: endMarker, full: fullBlock }\n await guardedWriteFile(ssh, {\n mode: await resolveWriteMode(ssh, remotePath),\n newContent: replaceBlock(existing, markers),\n originalContent: existing,\n remotePath,\n })\n } else {\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\"\n await guardedWriteFile(ssh, {\n mode: await resolveWriteMode(ssh, remotePath),\n newContent: `${existing}${separator}${fullBlock}\\n`,\n originalContent: existing,\n remotePath,\n })\n }\n } else {\n await ssh.writeFile(remotePath, `${fullBlock}\\n`, {\n mode: await resolveWriteMode(ssh, remotePath),\n })\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const hasMarker = await ssh.test(\n `grep -qF ${shellQuote(beginMarker)} ${shellQuote(remotePath)}`\n )\n if (!hasMarker) return NEEDS_APPLY\n\n const fileContent = await ssh.readFile(remotePath)\n const current = extractBlockContent(fileContent, beginMarker, endMarker)\n return current === options.content ? \"ok\" : NEEDS_APPLY\n },\n name: `file.block: ${remotePath} (${options.name})`,\n }\n}\n\n/**\n * Set file or directory ownership and permissions on the remote host.\n * Only the attributes specified in `options` are checked and applied.\n *\n * @param remotePath - Path to the file or directory on the remote host.\n * @param options - Attributes to enforce.\n * @param options.group - Optional group name.\n * @param options.mode - Optional chmod mode string (e.g. `\"0644\"`).\n * @param options.owner - Optional owner name.\n * @returns A Module that ensures the properties match.\n */\nexport function properties(\n remotePath: string,\n options: { group?: string; mode?: string; owner?: string }\n): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.properties: ${remotePath}] SSH connection is required`)\n\n let changed = false\n\n if (options.mode != null) {\n await ssh.exec(`chmod ${shellQuote(options.mode)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n changed = true\n }\n if (options.owner != null && options.group != null) {\n const ownerGroup = `${options.owner}:${options.group}`\n await ssh.exec(`chown ${shellQuote(ownerGroup)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n changed = true\n } else if (options.owner != null) {\n await ssh.exec(`chown ${shellQuote(options.owner)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n changed = true\n } else if (options.group != null) {\n // cspell:disable-next-line\n await ssh.exec(`chgrp ${shellQuote(options.group)} ${shellQuote(remotePath)}`, {\n silent: true,\n })\n changed = true\n }\n\n return { status: changed ? \"changed\" : \"ok\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const raw = await ssh.output(`stat -c '%a %U %G' ${shellQuote(remotePath)}`)\n const [mode, owner, group] = raw.trim().split(\" \")\n\n if (options.mode != null && mode !== options.mode.replace(/^0+/v, \"\")) return NEEDS_APPLY\n if (options.owner != null && owner !== options.owner) return NEEDS_APPLY\n if (options.group != null && group !== options.group) return NEEDS_APPLY\n\n return \"ok\"\n },\n name: `file.properties: ${remotePath}`,\n }\n}\n\n/**\n * Replace all occurrences of a regex pattern in a remote file.\n * `check` uses `grep -E` to detect whether the pattern still exists in the file.\n * `apply` reads the file, performs the replacement in TypeScript and writes it back.\n *\n * @param remotePath - Path to the file on the remote host.\n * @param pattern - Extended regex pattern to match.\n * @param replacement - Replacement string.\n * @returns A Module that performs the substitution.\n */\nexport function replace(remotePath: string, pattern: string, replacement: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.replace: ${remotePath}] SSH connection is required`)\n\n const content = await ssh.readFile(remotePath)\n // eslint-disable-next-line security/detect-non-literal-regexp -- pattern from module config, not user input\n const updated = content.replaceAll(new RegExp(pattern, \"gu\"), replacement)\n await guardedWriteFile(ssh, {\n mode: await resolveWriteMode(ssh, remotePath),\n newContent: updated,\n originalContent: content,\n remotePath,\n })\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const found = await ssh.test(`grep -qE ${shellQuote(pattern)} ${shellQuote(remotePath)}`)\n return found ? NEEDS_APPLY : \"ok\"\n },\n name: `file.replace: ${remotePath}`,\n }\n}\n\n/**\n * Read metadata about a remote file via `stat`.\n * This is a read-only module: `check` always reports `\"needs-apply\"` so the runner\n * invokes `apply`, which populates the result's `meta` map with the following keys:\n * - `file.stat.size` — file size in bytes\n * - `file.stat.mode` — octal permission bits (e.g. `\"644\"`)\n * - `file.stat.owner` — owning user name\n * - `file.stat.group` — owning group name\n * - `file.stat.type` — file type string (e.g. `\"regular file\"`, `\"directory\"`)\n * - `file.stat.mtime` — last modification time as a Unix timestamp string\n *\n * @param remotePath - Path to the file on the remote host.\n * @returns A Module that reads file metadata into `result.meta`.\n */\nexport function stat(remotePath: string): Module {\n return {\n _dryRunMetaProducer: true,\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[file.stat: ${remotePath}] SSH connection is required`)\n\n const raw = await ssh.output(`stat -c '%s %a %U %G %F %Y' ${shellQuote(remotePath)}`)\n const parts = raw.trim().split(\" \")\n const [size, mode, owner, group] = parts\n const mtime = parts.at(-1)\n const type = parts.slice(STAT_TYPE_START_INDEX, -1).join(\" \")\n\n return {\n meta: environmentToMetaEntries({\n \"file.stat.group\": group,\n \"file.stat.mode\": mode,\n \"file.stat.mtime\": mtime ?? \"\",\n \"file.stat.owner\": owner,\n \"file.stat.size\": size,\n \"file.stat.type\": type,\n }),\n status: \"ok\",\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: `file.stat: ${remotePath}`,\n }\n}\n","import { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst SILENT = { silent: true } as const\n\n/** Parameters for a git clone or update operation. */\ntype GitCloneParameters = {\n /** The destination path on the remote host. */\n destination: string\n /** An optional branch, tag, or commit SHA to check out. */\n reference?: string\n /** The repository URL to clone. */\n repo: string\n}\n\n/**\n * Clone a repository into a new directory, optionally at a specific ref.\n *\n * @param conn - The SSH connection to the remote host.\n * @param parameters - Clone parameters including repo, destination, and optional reference.\n * @returns A promise that resolves when the clone is complete.\n */\nasync function cloneRepo(conn: SshConnection, parameters: GitCloneParameters): Promise<boolean> {\n const { destination, reference, repo } = parameters\n if (reference !== undefined && reference !== \"\") {\n // Try --branch first (works for branches and tags, not bare SHAs).\n const result = await conn.exec(\n `git clone --branch ${shellQuote(reference)} ${shellQuote(repo)} ${shellQuote(destination)}`,\n EXEC_OPTS\n )\n // Fallback: clone without --branch then checkout (handles bare commit SHAs).\n if (result.code !== 0) {\n const fallback = await conn.exec(\n `git clone ${shellQuote(repo)} ${shellQuote(destination)}`,\n EXEC_OPTS\n )\n if (fallback.code !== 0) return false\n const checkout = await conn.exec(\n `git -C ${shellQuote(destination)} checkout ${shellQuote(reference)}`,\n EXEC_OPTS\n )\n return checkout.code === 0\n }\n return true\n }\n const cloneResult = await conn.exec(\n `git clone ${shellQuote(repo)} ${shellQuote(destination)}`,\n EXEC_OPTS\n )\n return cloneResult.code === 0\n}\n\n/**\n * Update an existing repository to a specific ref, or pull latest if no ref given.\n *\n * When a reference is provided, the function fetches all tags and then checks\n * out the reference. It first attempts `reset --hard origin/<reference>` to\n * handle tracking branches. If that fails (e.g. for tags or bare commit SHAs\n * that have no remote-tracking counterpart), it falls back to\n * `reset --hard <reference>` directly.\n *\n * @param conn - The SSH connection to the remote host.\n * @param parameters - Clone parameters including destination and optional reference.\n * @returns A promise that resolves when the update is complete.\n */\nasync function updateRepo(conn: SshConnection, parameters: GitCloneParameters): Promise<boolean> {\n const { destination, reference } = parameters\n if (reference !== undefined && reference !== \"\") {\n const fetch = await conn.exec(\n `git -C ${shellQuote(destination)} fetch origin --tags --force`,\n EXEC_OPTS\n )\n if (fetch.code !== 0) return false\n const checkout = await conn.exec(\n `git -C ${shellQuote(destination)} checkout ${shellQuote(reference)}`,\n EXEC_OPTS\n )\n if (checkout.code !== 0) return false\n const branchReset = await conn.exec(\n `git -C ${shellQuote(destination)} reset --hard origin/${shellQuote(reference)}`,\n EXEC_OPTS\n )\n // Fallback for tags and bare commit SHAs that have no remote-tracking ref.\n if (branchReset.code !== 0) {\n const directReset = await conn.exec(\n `git -C ${shellQuote(destination)} reset --hard ${shellQuote(reference)}`,\n EXEC_OPTS\n )\n return directReset.code === 0\n }\n return true\n }\n const pull = await conn.exec(`git -C ${shellQuote(destination)} pull`, EXEC_OPTS)\n return pull.code === 0\n}\n\n/**\n * Resolve a reference to a commit SHA by querying the remote via `git ls-remote`.\n *\n * For tags, the dereferenced line (`refs/tags/<ref>^{}`) is preferred because it\n * contains the commit SHA rather than the tag object SHA. When `ls-remote`\n * returns no output (e.g. because the reference is already a bare commit SHA),\n * the reference string is returned as-is.\n *\n * @param conn - The SSH connection to the remote host.\n * @param destination - The repository path on the remote host.\n * @param reference - The branch, tag, or commit SHA to resolve.\n * @returns The resolved commit SHA.\n */\nasync function resolveRemoteReference(\n conn: SshConnection,\n destination: string,\n reference: string\n): Promise<string> {\n const result = await conn.exec(\n `git -C ${shellQuote(destination)} ls-remote origin ${shellQuote(reference)}`,\n EXEC_OPTS\n )\n\n const output = result.stdout.trim()\n if (output === \"\") return reference\n\n const lines = output.split(\"\\n\")\n\n // Prefer the dereferenced tag line (^{}) when present.\n for (const line of lines) {\n if (line.includes(\"^{}\")) {\n return line.split(\"\\t\")[0]\n }\n }\n\n return lines[0].split(\"\\t\")[0]\n}\n\n/**\n * Modules for managing Git repositories on the remote host.\n */\nexport const git = {\n /**\n * Ensure a Git repository is cloned to the given destination and optionally\n * checked out at a specific ref (branch, tag, or commit).\n *\n * If the destination directory does not yet contain a `.git` folder, the\n * repository is cloned from scratch. If it already exists, the repository is\n * updated instead (fetch + checkout + reset). When no `ref` is specified, a\n * plain `git pull` is performed on an existing clone.\n *\n * @param repo - The repository URL to clone.\n * @param destination - The destination path on the remote host.\n * @param options - Optional settings.\n * @param options.ref - A branch, tag, or commit SHA to check out.\n * @returns A Module that manages the cloned repository.\n */\n clone(repo: string, destination: string, options?: { ref?: string }): Module {\n const reference = options?.ref\n const gitDirectory = `${destination}/.git`\n const parameters: GitCloneParameters = { destination, reference, repo }\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[git.clone: ${destination}] SSH connection is required`)\n\n const directoryExists = await conn.test(`test -d ${shellQuote(gitDirectory)}`)\n const success = await (directoryExists\n ? updateRepo(conn, parameters)\n : cloneRepo(conn, parameters))\n\n return success\n ? { status: \"changed\" }\n : failed(`[git.clone: ${destination}] git clone or update failed`)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const gitDirectoryExists = await conn.test(`test -d ${shellQuote(gitDirectory)}`)\n if (!gitDirectoryExists) return NEEDS_APPLY\n\n if (reference === undefined || reference === \"\") return \"ok\"\n\n const headResult = await conn.exec(\n `git -C ${shellQuote(destination)} rev-parse HEAD`,\n SILENT\n )\n const head = headResult.stdout.trim()\n\n const resolved = await resolveRemoteReference(conn, destination, reference)\n\n return head === resolved ? \"ok\" : NEEDS_APPLY\n },\n name: `git.clone: ${destination}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst GETENT_GROUP = \"getent group\"\n\n/**\n * Modules for managing Linux groups.\n */\nexport const group = {\n /**\n * Ensure a group does not exist. Removes it via `groupdel` if present.\n *\n * @param name - The group name to remove.\n * @returns A Module that ensures the group is absent.\n */\n absent(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[group.absent: ${name}] SSH connection is required`)\n const result = await ssh.exec(`groupdel ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[group.absent: ${name}] groupdel failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`${GETENT_GROUP} ${shellQuote(name)}`)) ? \"needs-apply\" : \"ok\"\n },\n name: `group.absent: ${name}`,\n }\n },\n\n /**\n * Ensure a group exists. Creates it via `groupadd` if absent.\n *\n * @param name - The group name.\n * @param options - Optional group configuration.\n * @param options.gid - Desired numeric GID.\n * @returns A Module that ensures the group is present.\n */\n present(name: string, options?: { gid?: number }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[group.present: ${name}] SSH connection is required`)\n const gidArgument = options?.gid == null ? \"\" : `--gid ${String(options.gid)}`\n const result = await ssh.exec(`groupadd ${gidArgument} ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[group.present: ${name}] groupadd failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`${GETENT_GROUP} ${shellQuote(name)}`)) ? \"ok\" : NEEDS_APPLY\n },\n name: `group.present: ${name}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\n/**\n * Modules for managing the system hostname.\n */\nexport const hostname = {\n /**\n * Set the system hostname via `hostnamectl set-hostname`.\n * Checks the current hostname first and skips the command when it already matches.\n *\n * @param name - The desired hostname.\n * @returns A Module that sets the hostname.\n */\n set(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[hostname.set: ${name}] SSH connection is required`)\n const result = await ssh.exec(`hostnamectl set-hostname ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[hostname.set: ${name}] hostnamectl set-hostname failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const current = await ssh.output(\"hostname\")\n return current === name ? \"ok\" : NEEDS_APPLY\n },\n name: `hostname.set: ${name}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport {\n guardedWriteFile,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst FSTAB_PATH = \"/etc/fstab\"\nconst FSTAB_MODE = \"0644\"\n// cspell:ignore fstype mountpoint noheadings noexec nosuid nodev tmpfs umount findmnt\n\n/**\n * Build a single fstab line from the given mount parameters.\n *\n * @param entry - The mount entry fields.\n * @param entry.fstype - The filesystem type.\n * @param entry.opts - The mount options string.\n * @param entry.path - The mountpoint path.\n * @param entry.src - The device or virtual filesystem source.\n * @returns A formatted fstab line with dump and pass fields set to `0`.\n */\nfunction buildFstabLine(entry: {\n fstype: string\n opts: string\n path: string\n src: string\n}): string {\n return `${entry.src} ${entry.path} ${entry.fstype} ${entry.opts} 0 0`\n}\n\n/**\n * Check whether a given fstab line already exists in the fstab content.\n *\n * @param fstabContent - The full contents of `/etc/fstab`.\n * @param path - The mountpoint to search for.\n * @returns The matching fstab line, or `null` if no entry exists for this mountpoint.\n */\nfunction findFstabEntry(fstabContent: string, path: string): null | string {\n const lines = fstabContent.split(\"\\n\")\n for (const line of lines) {\n const trimmed = line.trim()\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) continue\n const fields = trimmed.split(/\\s+/v)\n if (fields[1] === path) return trimmed\n }\n return null\n}\n\n/**\n * Replace or append a fstab entry for the given mountpoint.\n *\n * @param fstabContent - The current full contents of `/etc/fstab`.\n * @param path - The mountpoint to match against.\n * @param newLine - The new fstab line to insert or replace with.\n * @returns The updated fstab content.\n */\nfunction upsertFstabEntry(fstabContent: string, path: string, newLine: string): string {\n const lines = fstabContent.split(\"\\n\")\n const index = lines.findIndex((line) => {\n const trimmed = line.trim()\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) return false\n const fields = trimmed.split(/\\s+/v)\n return fields[1] === path\n })\n\n if (index === -1) {\n while (lines.length > 0 && lines.at(-1)?.trim() === \"\") {\n lines.pop()\n }\n lines.push(newLine)\n } else {\n lines[index] = newLine\n }\n\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/**\n * Remove the fstab entry for the given mountpoint.\n *\n * @param fstabContent - The current full contents of `/etc/fstab`.\n * @param path - The mountpoint whose entry should be removed.\n * @returns The updated fstab content with the entry removed.\n */\nfunction removeFstabEntry(fstabContent: string, path: string): string {\n const lines = fstabContent.split(\"\\n\")\n const result = lines.filter((line) => {\n const trimmed = line.trim()\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) return true\n const fields = trimmed.split(/\\s+/v)\n return fields[1] !== path\n })\n return `${result.join(\"\\n\")}\\n`\n}\n\nasync function removePersistedMountIfPresent(ssh: SshConnection, path: string): Promise<boolean> {\n const fstabContent = await ssh.readFile(FSTAB_PATH)\n const entry = findFstabEntry(fstabContent, path)\n if (entry === null) return false\n const newContent = removeFstabEntry(fstabContent, path)\n await guardedWriteFile(ssh, {\n mode: FSTAB_MODE,\n newContent,\n originalContent: fstabContent,\n remotePath: FSTAB_PATH,\n })\n return true\n}\n\nasync function unmountIfNeeded(ssh: SshConnection, path: string): Promise<boolean | ModuleResult> {\n const isMounted = await ssh.test(`findmnt --noheadings ${shellQuote(path)}`)\n if (!isMounted) return false\n\n const umountResult = await ssh.exec(`umount ${shellQuote(path)}`, EXEC_OPTS)\n if (umountResult.code !== 0) {\n return failedCommand(`[mount.absent: ${path}] umount failed`, umountResult)\n }\n return true\n}\n\n/**\n * Ensure the fstab entry for a mount matches the desired line.\n * Reads, compares, and writes back only when a change is needed.\n *\n * @param ssh - The SSH connection to the remote host.\n * @param path - The mountpoint to match against.\n * @param desiredLine - The expected fstab line.\n * @returns `true` if the fstab was updated, `false` if it already matched.\n */\nasync function ensureFstabEntry(\n ssh: SshConnection,\n path: string,\n desiredLine: string\n): Promise<boolean> {\n const fstabContent = await ssh.readFile(FSTAB_PATH)\n const existingEntry = findFstabEntry(fstabContent, path)\n if (existingEntry === desiredLine) return false\n const newContent = upsertFstabEntry(fstabContent, path, desiredLine)\n await guardedWriteFile(ssh, {\n mode: FSTAB_MODE,\n newContent,\n originalContent: fstabContent,\n remotePath: FSTAB_PATH,\n })\n return true\n}\n\n/**\n * Modules for managing filesystem mounts and `/etc/fstab` entries on a remote host.\n */\nexport const mount = {\n /**\n * Ensure a mountpoint is not mounted. Optionally removes the `/etc/fstab` entry.\n *\n * The check phase verifies both whether the path is currently mounted and, when\n * `persist` is `true`, whether a corresponding fstab entry exists. The apply\n * phase always returns `\"changed\"` even if only one of the two conditions required\n * action.\n *\n * @param options - Configuration for unmounting.\n * @param options.path - The mountpoint to unmount.\n * @param options.persist - When `true` (default), also remove the fstab entry.\n * @returns A Module that ensures the mountpoint is absent.\n */\n absent(options: { path: string; persist?: boolean }): Module {\n const { path, persist = true } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[mount.absent: ${path}] SSH connection is required`)\n\n let changed = false\n\n const unmountResult = await unmountIfNeeded(ssh, path)\n if (typeof unmountResult !== \"boolean\") {\n return unmountResult\n }\n if (unmountResult) {\n changed = true\n }\n\n if (persist && (await removePersistedMountIfPresent(ssh, path))) {\n changed = true\n }\n\n return { status: changed ? \"changed\" : \"ok\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const isMounted = await ssh.test(`findmnt --noheadings ${shellQuote(path)}`)\n if (isMounted) return NEEDS_APPLY\n\n if (persist) {\n const fstabContent = await ssh.readFile(FSTAB_PATH)\n const entry = findFstabEntry(fstabContent, path)\n if (entry !== null) return NEEDS_APPLY\n }\n\n return \"ok\"\n },\n name: `mount.absent: ${path}`,\n }\n },\n\n /**\n * Ensure a filesystem is mounted at the given path. Creates the mountpoint\n * directory if it does not exist. Optionally persists the mount in `/etc/fstab`.\n *\n * The check phase verifies that the mountpoint is active (via `findmnt`) and,\n * when `persist` is `true`, that the fstab entry matches the desired line\n * exactly. It does **not** compare the currently mounted source, filesystem\n * type, or options against the desired values — a remount is only triggered\n * when the mountpoint is absent entirely.\n *\n * @param options - Configuration for the mount.\n * @param options.fstype - The filesystem type (e.g. `\"ext4\"`, `\"tmpfs\"`, `\"nfs\"`).\n * @param options.opts - Mount options string (e.g. `\"noexec,nosuid,nodev,size=512m\"`).\n * @param options.path - The mountpoint path.\n * @param options.persist - When `true` (default), add or update the fstab entry.\n * @param options.src - The device or virtual filesystem source (e.g. `\"tmpfs\"`, `\"/dev/sdb1\"`).\n * @returns A Module that ensures the filesystem is mounted.\n */\n present(options: {\n fstype: string\n opts: string\n path: string\n persist?: boolean\n src: string\n }): Module {\n const { fstype, opts, path, persist = true, src } = options\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[mount.present: ${path}] SSH connection is required`)\n\n let changed = false\n\n await ssh.exec(`mkdir -p ${shellQuote(path)}`, EXEC_OPTS)\n\n if (persist) {\n const desiredLine = buildFstabLine({ fstype, opts, path, src })\n if (await ensureFstabEntry(ssh, path, desiredLine)) changed = true\n }\n\n const isMounted = await ssh.test(`findmnt --noheadings ${shellQuote(path)}`)\n if (!isMounted) {\n const mountResult = await ssh.exec(\n `mount -t ${shellQuote(fstype)} -o ${shellQuote(opts)} ${shellQuote(src)} ${shellQuote(path)}`,\n EXEC_OPTS\n )\n if (mountResult.code !== 0) {\n return failedCommand(`[mount.present: ${path}] mount failed`, mountResult)\n }\n changed = true\n }\n\n return { status: changed ? \"changed\" : \"ok\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const findmntResult = await ssh.exec(\n `findmnt --noheadings --output SOURCE,FSTYPE,OPTIONS ${shellQuote(path)}`,\n EXEC_OPTS\n )\n if (findmntResult.code !== 0) return NEEDS_APPLY\n\n if (persist) {\n const fstabContent = await ssh.readFile(FSTAB_PATH)\n const desiredLine = buildFstabLine({ fstype, opts, path, src })\n const existingEntry = findFstabEntry(fstabContent, path)\n if (existingEntry !== desiredLine) return NEEDS_APPLY\n }\n\n return \"ok\"\n },\n name: `mount.present: ${path}`,\n }\n },\n}\n","/* eslint-disable max-lines */\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport {\n guardedWriteFile,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\nimport {\n buildCurlHeaderFlags,\n buildWaitForName,\n buildWaitForTestCommand,\n checkHttpCondition,\n delay,\n type HttpCheckParameters,\n type WaitForOptions,\n} from \"./netHelpers.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst HOSTS_FILE = \"/etc/hosts\"\nconst HOSTS_FILE_MODE = \"0644\"\nconst NET_CONFIG_FILE_MODE = \"0644\"\nconst NETWORKCTL_RELOAD = \"networkctl reload\"\nconst DEFAULT_POLL_INTERVAL_MS = 2000\nconst DEFAULT_POLL_TIMEOUT_MS = 60_000\nconst DEFAULT_EXPECTED_STATUS = 200\n\n/**\n * Sanitize a destination string for use in a filename.\n * Replaces `/` and `:` with dashes and strips leading dashes.\n *\n * @param value - The string to sanitize.\n * @returns The sanitized filename-safe string.\n */\nfunction sanitizeForFilename(value: string): string {\n return value.replaceAll(\"/\", \"-\").replaceAll(\":\", \"-\").replace(/^-+/v, \"\")\n}\n\n/**\n * Build the expected hosts line for an IP and its hostnames.\n *\n * @param ip - The IP address.\n * @param hostnames - The hostnames to associate.\n * @returns The formatted hosts line.\n */\nfunction buildHostsLine(ip: string, hostnames: string[]): string {\n return `${ip} ${hostnames.join(\" \")}`\n}\n\n/**\n * Generate the content of a resolv.conf file.\n *\n * @param nameservers - List of nameserver addresses.\n * @param search - Optional list of search domains.\n * @returns The resolv.conf file content.\n */\nfunction buildResolvConfig(nameservers: string[], search?: string[]): string {\n const lines: string[] = []\n if (search && search.length > 0) {\n lines.push(`search ${search.join(\" \")}`)\n }\n for (const ns of nameservers) {\n lines.push(`nameserver ${ns}`)\n }\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/**\n * Generate a systemd-networkd drop-in for a static route.\n *\n * @param destination - The route destination CIDR.\n * @param gateway - The gateway IP address.\n * @param device - Optional network device name.\n * @returns The drop-in file content.\n */\nfunction buildRouteDropin(destination: string, gateway: string, device?: string): string {\n const lines = [\n \"[Match]\",\n `Name=${device ?? \"*\"}`,\n \"\",\n \"[Route]\",\n `Destination=${destination}`,\n `Gateway=${gateway}`,\n ]\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/** Interface configuration options shared by Netplan and networkd builders. */\ntype InterfaceOptions = {\n addresses?: string[]\n dhcp?: boolean\n gateway?: string\n nameservers?: string[]\n}\n\n/**\n * Generate a Netplan YAML configuration for a network interface.\n *\n * @param name - The network interface name.\n * @param options - The interface configuration.\n * @returns The Netplan YAML file content.\n */\nfunction buildNetplanYaml(name: string, options: InterfaceOptions): string {\n const lines: string[] = [\n \"network:\",\n \" version: 2\",\n \" ethernets:\",\n ` ${name}:`,\n ` dhcp4: ${options.dhcp === true ? \"true\" : \"false\"}`,\n ]\n if (options.addresses && options.addresses.length > 0) {\n lines.push(\" addresses:\")\n for (const addr of options.addresses) {\n lines.push(` - ${addr}`)\n }\n }\n appendNetplanGateway(lines, options.gateway)\n appendNetplanNameservers(lines, options.nameservers)\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/**\n * Append gateway route lines to a Netplan config.\n *\n * @param lines - The lines array to append to.\n * @param gateway - Optional gateway address.\n */\nfunction appendNetplanGateway(lines: string[], gateway?: string): void {\n if (gateway !== undefined && gateway !== \"\") {\n lines.push(\" routes:\")\n lines.push(\" - to: default\")\n lines.push(` via: ${gateway}`)\n }\n}\n\n/**\n * Append nameserver lines to a Netplan config.\n *\n * @param lines - The lines array to append to.\n * @param nameservers - Optional list of nameserver addresses.\n */\nfunction appendNetplanNameservers(lines: string[], nameservers?: string[]): void {\n if (nameservers && nameservers.length > 0) {\n lines.push(\" nameservers:\")\n lines.push(\" addresses:\")\n for (const ns of nameservers) {\n lines.push(` - ${ns}`)\n }\n }\n}\n\n/**\n * Generate a systemd-networkd configuration for a network interface.\n *\n * @param name - The network interface name.\n * @param options - The interface configuration.\n * @returns The networkd .network file content.\n */\nfunction buildNetworkdConfig(name: string, options: InterfaceOptions): string {\n const lines: string[] = [\n \"[Match]\",\n `Name=${name}`,\n \"\",\n \"[Network]\",\n `DHCP=${options.dhcp === true ? \"yes\" : \"no\"}`,\n ]\n appendNetworkdEntries(lines, options)\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/**\n * Append Address, DNS, and Route entries to a networkd config.\n *\n * @param lines - The lines array to append to.\n * @param options - The interface configuration.\n */\nfunction appendNetworkdEntries(lines: string[], options: InterfaceOptions): void {\n if (options.addresses) {\n for (const addr of options.addresses) {\n lines.push(`Address=${addr}`)\n }\n }\n if (options.nameservers) {\n for (const ns of options.nameservers) {\n lines.push(`DNS=${ns}`)\n }\n }\n if (options.gateway !== undefined && options.gateway !== \"\") {\n lines.push(\"\")\n lines.push(\"[Route]\")\n lines.push(`Gateway=${options.gateway}`)\n }\n}\n\n/**\n * Modules for managing network configuration on the remote host.\n */\nexport const net = {\n /**\n * Manage entries in /etc/hosts.\n *\n * @param ip - The IP address for the hosts entry.\n * @param hostnames - One or more hostnames to associate with the IP.\n * @param options - Optional settings.\n * @param options.state - Whether the entry should be \"present\" (default) or \"absent\".\n * @returns A Module that manages the hosts entry.\n */\n hosts(ip: string, hostnames: string[], options?: { state?: \"absent\" | \"present\" }): Module {\n const state = options?.state ?? \"present\"\n const expectedLine = buildHostsLine(ip, hostnames)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) {\n return failed(`[net.hosts: ${ip} ${hostnames.join(\" \")}] SSH connection is required`)\n }\n\n const content = await conn.readFile(HOSTS_FILE)\n const lines = content.split(\"\\n\")\n\n if (state === \"present\") {\n const alreadyPresent = lines.some((line) => line.trim() === expectedLine)\n if (alreadyPresent) return { status: \"ok\" }\n const suffix = content.endsWith(\"\\n\") ? \"\" : \"\\n\"\n const newContent = `${content}${suffix}${expectedLine}\\n`\n await guardedWriteFile(conn, {\n mode: HOSTS_FILE_MODE,\n newContent,\n originalContent: content,\n remotePath: HOSTS_FILE,\n })\n } else {\n const newContent = lines.filter((line) => line.trim() !== expectedLine).join(\"\\n\")\n await guardedWriteFile(conn, {\n mode: HOSTS_FILE_MODE,\n newContent,\n originalContent: content,\n remotePath: HOSTS_FILE,\n })\n }\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const content = await conn.readFile(HOSTS_FILE)\n const lines = content.split(\"\\n\")\n const found = lines.some((line) => line.trim() === expectedLine)\n\n if (state === \"present\") {\n return found ? \"ok\" : NEEDS_APPLY\n }\n return found ? NEEDS_APPLY : \"ok\"\n },\n name: `net.hosts: ${ip} ${hostnames.join(\" \")}`,\n }\n },\n\n /**\n * Configure a network interface via Netplan (when available) or systemd-networkd.\n *\n * @param name - The interface name (e.g. \"eth0\").\n * @param options - Network configuration options.\n * @returns A Module that manages the interface configuration.\n */\n interface(name: string, options: InterfaceOptions): Module {\n const netplanPath = `/etc/netplan/60-paratix-${name}.yaml`\n const networkdPath = `/etc/systemd/network/60-paratix-${name}.network`\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[net.interface: ${name}] SSH connection is required`)\n\n const useNetplan = await conn.test(\"test -d '/etc/netplan'\")\n\n if (useNetplan) {\n const content = buildNetplanYaml(name, options)\n await conn.writeFile(netplanPath, content, { mode: NET_CONFIG_FILE_MODE })\n await conn.exec(\"netplan apply\", EXEC_OPTS)\n } else {\n const content = buildNetworkdConfig(name, options)\n await conn.writeFile(networkdPath, content, { mode: NET_CONFIG_FILE_MODE })\n await conn.exec(NETWORKCTL_RELOAD, EXEC_OPTS)\n }\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const useNetplan = await conn.test(\"test -d '/etc/netplan'\")\n const configPath = useNetplan ? netplanPath : networkdPath\n const expectedContent = useNetplan\n ? buildNetplanYaml(name, options)\n : buildNetworkdConfig(name, options)\n\n const existsResult = await conn.exec(`test -f ${shellQuote(configPath)}`, EXEC_OPTS)\n if (existsResult.code !== 0) return NEEDS_APPLY\n\n const currentContent = await conn.readFile(configPath)\n return currentContent.trim() === expectedContent.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `net.interface: ${name}`,\n }\n },\n\n /**\n * Check that an HTTP endpoint returns the expected status code and/or body.\n *\n * @param url - The URL to request.\n * @param options - Optional request settings.\n * @param options.body - Expected string in the response body.\n * @param options.headers - Additional HTTP headers.\n * @param options.method - HTTP method (default: `\"GET\"`).\n * @param options.status - Expected HTTP status code (default: `200`).\n * @returns A Module that checks the HTTP endpoint.\n */\n request(\n url: string,\n options?: { body?: string; headers?: Record<string, string>; method?: string; status?: number }\n ): Module {\n const method = options?.method ?? \"GET\"\n const parameters: HttpCheckParameters = {\n expectedBody: options?.body,\n expectedStatus: options?.status ?? DEFAULT_EXPECTED_STATUS,\n headerFlags: buildCurlHeaderFlags(options?.headers ?? {}),\n methodFlag: method === \"GET\" ? \"\" : `-X ${shellQuote(method)} `,\n url,\n }\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[net.request: ${method} ${url}] SSH connection is required`)\n\n const ok = await checkHttpCondition(conn, parameters)\n return ok\n ? { status: \"ok\" }\n : failed(`[net.request: ${method} ${url}] HTTP request did not match expectations`)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const ok = await checkHttpCondition(conn, parameters)\n return ok ? \"ok\" : NEEDS_APPLY\n },\n name: `net.request: ${method} ${url}`,\n }\n },\n\n /**\n * Manage /etc/resolv.conf (nameservers and search domains).\n *\n * @param options - Resolver configuration.\n * @param options.nameservers - List of nameserver IP addresses.\n * @param options.search - Optional list of DNS search domains.\n * @returns A Module that manages /etc/resolv.conf.\n */\n resolv(options: { nameservers: string[]; search?: string[] }): Module {\n const expectedContent = buildResolvConfig(options.nameservers, options.search)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(\"[net.resolv] SSH connection is required\")\n\n await conn.exec(\"rm -f /etc/resolv.conf\", EXEC_OPTS)\n await conn.writeFile(\"/etc/resolv.conf\", expectedContent, { mode: NET_CONFIG_FILE_MODE })\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const content = await conn.readFile(\"/etc/resolv.conf\")\n return content.trim() === expectedContent.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `net.resolv: nameservers ${options.nameservers.join(\",\")}`,\n }\n },\n\n /**\n * Manage persistent static routes via `ip route` and a systemd-networkd drop-in.\n *\n * @param destination - The route destination (e.g. \"10.0.0.0/24\").\n * @param gateway - The gateway IP address.\n * @param options - Optional settings.\n * @param options.device - The network device to use (e.g. \"eth0\").\n * @param options.state - Whether the route should be \"present\" (default) or \"absent\".\n * @returns A Module that manages the static route.\n */\n route(\n destination: string,\n gateway: string,\n options?: { device?: string; state?: \"absent\" | \"present\" }\n ): Module {\n const state = options?.state ?? \"present\"\n const device = options?.device\n const sanitized = sanitizeForFilename(destination)\n const dropinPath = `/etc/systemd/network/50-paratix-route-${sanitized}.network`\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) {\n return failed(\n `[net.route: ${state} ${destination} via ${gateway}] SSH connection is required`\n )\n }\n\n if (state === \"present\") {\n const devicePart =\n device !== undefined && device !== \"\" ? ` dev ${shellQuote(device)}` : \"\"\n await conn.exec(\n `ip route replace ${shellQuote(destination)} via ${shellQuote(gateway)}${devicePart}`,\n EXEC_OPTS\n )\n const dropinContent = buildRouteDropin(destination, gateway, device)\n await conn.writeFile(dropinPath, dropinContent, { mode: NET_CONFIG_FILE_MODE })\n await conn.exec(NETWORKCTL_RELOAD, EXEC_OPTS)\n } else {\n await conn.exec(`ip route del ${shellQuote(destination)}`, EXEC_OPTS)\n await conn.exec(`rm -f ${shellQuote(dropinPath)}`, EXEC_OPTS)\n await conn.exec(NETWORKCTL_RELOAD, EXEC_OPTS)\n }\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const result = await conn.exec(`ip route show ${shellQuote(destination)}`, EXEC_OPTS)\n const output = result.stdout.trim()\n const hasRoute = output.includes(`via ${gateway}`)\n\n if (state === \"present\") {\n return hasRoute ? \"ok\" : NEEDS_APPLY\n }\n return hasRoute ? NEEDS_APPLY : \"ok\"\n },\n name: `net.route: ${state} ${destination} via ${gateway}`,\n }\n },\n\n /**\n * Wait for a condition to become true on the remote host.\n *\n * @param options - Wait condition and timing options.\n * @returns A Module that waits for the condition.\n */\n waitFor(options: WaitForOptions): Module {\n const interval = options.interval ?? DEFAULT_POLL_INTERVAL_MS\n const timeout = options.timeout ?? DEFAULT_POLL_TIMEOUT_MS\n const host = options.host ?? \"127.0.0.1\"\n const testCommand = buildWaitForTestCommand(options, host)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[${buildWaitForName(options)}] SSH connection is required`)\n\n const start = Date.now()\n while (Date.now() - start < timeout) {\n // eslint-disable-next-line no-await-in-loop\n const success = await conn.test(testCommand)\n if (success) return { status: \"changed\" }\n // eslint-disable-next-line no-await-in-loop\n await delay(interval)\n }\n\n return failed(`[${buildWaitForName(options)}] condition was not met within ${timeout}ms`)\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n const success = await conn.test(testCommand)\n return success ? \"ok\" : NEEDS_APPLY\n },\n name: buildWaitForName(options),\n }\n },\n}\n","import { type ChildProcess, spawn } from \"node:child_process\"\n\nimport type { Environment, Module, ModuleResult } from \"../types.js\"\n\nimport { environmentToMetaEntries } from \"../meta.js\"\nimport { failed } from \"../moduleFailure.js\"\nimport { generateTotpCode } from \"../totp.js\"\n\n/**\n * Spawn a command, write `input` to its stdin, and collect stdout.\n *\n * @param command - The executable to run.\n * @param commandArguments - Arguments for the command.\n * @param input - Data to write to stdin before closing it.\n * @returns The stdout output as a string.\n */\nasync function spawnWithInput(\n command: string,\n commandArguments: string[],\n input: string\n): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const child: ChildProcess = spawn(command, commandArguments, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n let stdout = \"\"\n let stderr = \"\"\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString()\n })\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString()\n })\n child.on(\"error\", reject)\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolve(stdout)\n } else {\n reject(new Error(`${command} exited with code ${String(code)}: ${stderr}`))\n }\n })\n\n child.stdin?.end(input)\n })\n}\n\nconst OTP_SUFFIX_PATTERN = /\\/(?:one-time-password|otp)$/iv\n\nconst REFERENCE_PREFIX = \"op://\"\n\n/**\n * Validate that all reference values start with `op://`.\n *\n * @param references - Map of logical names to 1Password references.\n * @throws {Error} If any reference does not start with `op://`.\n */\nfunction validateReferences(references: Record<string, string>): void {\n for (const [name, reference] of Object.entries(references)) {\n if (!reference.startsWith(REFERENCE_PREFIX)) {\n throw new Error(\n `Invalid 1Password reference for \"${name}\": must start with \"${REFERENCE_PREFIX}\"`\n )\n }\n }\n}\n\n/**\n * Split references into regular secrets and OTP references.\n *\n * @param references - Map of logical names to 1Password references.\n * @returns A tuple of [regularEntries, otpEntries].\n */\nfunction splitReferences(\n references: Record<string, string>\n): [Record<string, string>, Record<string, string>] {\n const regularEntries: Record<string, string> = {}\n const otpEntries: Record<string, string> = {}\n\n for (const [name, reference] of Object.entries(references)) {\n if (OTP_SUFFIX_PATTERN.test(reference)) {\n otpEntries[name] = reference\n } else {\n regularEntries[name] = reference\n }\n }\n\n return [regularEntries, otpEntries]\n}\n\n/**\n * Resolve regular (non-OTP) 1Password references in bulk via `op inject`.\n *\n * @param entries - Map of logical names to 1Password references.\n * @returns Resolved key-value pairs.\n * @throws {Error} If the `op` CLI is not available or the session is not authenticated.\n */\nasync function resolveRegularReferences(\n entries: Record<string, string>\n): Promise<Record<string, string>> {\n if (Object.keys(entries).length === 0) return {}\n\n const stdout = await spawnWithInput(\"op\", [\"inject\"], JSON.stringify(entries))\n\n const parsed: unknown = JSON.parse(stdout)\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"op inject returned unexpected non-object JSON\")\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validated: non-null, non-array object\n const record = parsed as Record<string, unknown>\n if (!Object.values(record).every((v) => typeof v === \"string\")) {\n throw new Error(\"op inject returned object with non-string values\")\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- all values validated as strings above\n return record as Record<string, string>\n}\n\n/**\n * Resolve OTP 1Password references into lazy TOTP code generators.\n *\n * Each reference is resolved immediately via `op read` to obtain the\n * `otpauth://totp/...` URI, then wrapped in a lazy function so the TOTP\n * code is computed fresh on every access.\n *\n * @param entries - Map of logical names to OTP 1Password references.\n * @returns Map of logical names to lazy functions that compute fresh TOTP codes.\n * @throws {Error} If the `op` CLI is not available or the session is not authenticated.\n */\nasync function resolveOtpReferences(entries: Record<string, string>): Promise<Environment> {\n const result: Environment = {}\n\n for (const [name, reference] of Object.entries(entries)) {\n // eslint-disable-next-line no-await-in-loop\n const stdout = await spawnWithInput(\"op\", [\"read\", reference], \"\")\n\n const otpauthUri = stdout.trim()\n result[name] = () => generateTotpCode(otpauthUri)\n }\n\n return result\n}\n\n/**\n * Modules for resolving secrets from 1Password on the local controller.\n */\nexport const op = {\n /**\n * Resolve 1Password secret references into environment values.\n *\n * Regular references are resolved in bulk via `op inject`.\n * References ending in `/one-time-password` or `/otp` are resolved individually\n * via `op read` and returned as lazy functions that compute a fresh TOTP code\n * on each call.\n *\n * This module runs locally (`local: true`) and never touches the remote host.\n * `check` always returns `\"needs-apply\"` so the runner executes `apply`\n * unconditionally and propagates the resolved meta values.\n * If any CLI call fails the module returns `{ status: \"failed\", error }`.\n *\n * @param references - A map of logical names to 1Password secret references\n * (e.g. `op://vault/item/field`). References ending in `/one-time-password`\n * or `/otp` are treated as TOTP sources.\n * @returns A Module that resolves the references and emits them as meta values.\n *\n * @example\n * ```ts\n * op.resolve({\n * DB_PASSWORD: \"op://prod/database/password\",\n * API_TOKEN: \"op://prod/api/credential\",\n * MFA_CODE: \"op://prod/authenticator/one-time-password\",\n * })\n * ```\n */\n resolve(references: Record<string, string>): Module {\n validateReferences(references)\n\n return {\n _dryRunMetaProducer: true,\n async apply(): Promise<ModuleResult> {\n try {\n const [regularEntries, otpEntries] = splitReferences(references)\n const resolvedRegular = await resolveRegularReferences(regularEntries)\n const resolvedOtp = await resolveOtpReferences(otpEntries)\n\n return {\n meta: environmentToMetaEntries({ ...resolvedRegular, ...resolvedOtp }),\n status: \"ok\",\n }\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error)\n return failed(`Failed to resolve 1Password references: ${detail}`)\n }\n },\n // eslint-disable-next-line @typescript-eslint/require-await\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return \"needs-apply\"\n },\n local: true,\n name: `op.resolve: ${Object.keys(references).join(\", \")}`,\n }\n },\n}\n","import { createHmac } from \"node:crypto\"\n\n/** Number of bits per Base32 character. */\nconst BASE32_BITS_PER_CHAR = 5\n\n/** Number of bits per byte. */\nconst BITS_PER_BYTE = 8\n\n/** Size of the HMAC counter buffer in bytes (64-bit big-endian). */\nconst COUNTER_BUFFER_SIZE = 8\n\n/** Bitmask to extract the lower 4 bits for the HMAC offset. */\nconst OFFSET_MASK = 0x0f\n\n/** Bitmask to clear the sign bit on the first truncated byte. */\nconst SIGN_BIT_MASK = 0x7f\n\n/** Bitmask for a full byte. */\nconst BYTE_MASK = 0xff\n\n/** Bit shift for the first byte in the truncated 32-bit value. */\nconst SHIFT_24 = 24\n\n/** Bit shift for the second byte in the truncated 32-bit value. */\nconst SHIFT_16 = 16\n\n/** Byte offset constants for dynamic truncation. */\nconst TRUNCATION_BYTE_1 = 1\nconst TRUNCATION_BYTE_2 = 2\nconst TRUNCATION_BYTE_3 = 3\n\n/** Milliseconds per second. */\nconst MILLISECONDS_PER_SECOND = 1000\n\n/** Base for decimal digit extraction. */\nconst DECIMAL_BASE = 10\n\n/** Default TOTP period in seconds. */\nconst DEFAULT_PERIOD = 30\n\n/** Default number of TOTP digits. */\nconst DEFAULT_DIGITS = 6\n\n/** Maximum allowed number of TOTP digits (prevents integer overflow in 10^digits). */\nconst MAX_DIGITS = 8\n\n/** Mapping from otpauth URI algorithm names to Node.js crypto hash names. */\nconst SUPPORTED_ALGORITHMS: Record<string, string> = {\n SHA1: \"sha1\",\n SHA256: \"sha256\",\n SHA512: \"sha512\",\n}\n\n/**\n * Decode a Base32-encoded string (RFC 4648) into a Buffer.\n *\n * Padding characters (`=`) and whitespace are stripped before decoding.\n * The alphabet used is `A–Z` and `2–7` (standard Base32, not Base32hex).\n *\n * @param encoded - The Base32 string to decode (case-insensitive, padding optional).\n * @returns A Buffer containing the decoded bytes.\n * @throws {Error} If the string contains a character outside the Base32 alphabet.\n */\nfunction decodeBase32(encoded: string): Buffer {\n const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\n const stripped = encoded.toUpperCase().replaceAll(/[=\\s]/gv, \"\")\n\n let bits = \"\"\n for (const character of stripped) {\n const index = alphabet.indexOf(character)\n if (index === -1) {\n throw new Error(`Invalid Base32 character: ${character}`)\n }\n bits += index.toString(2).padStart(BASE32_BITS_PER_CHAR, \"0\")\n }\n\n const bytes: number[] = []\n for (let index = 0; index + BITS_PER_BYTE <= bits.length; index += BITS_PER_BYTE) {\n bytes.push(Number.parseInt(bits.slice(index, index + BITS_PER_BYTE), 2))\n }\n\n return Buffer.from(bytes)\n}\n\n/**\n * Perform HMAC dynamic truncation on the digest to produce a numeric code.\n *\n * @param hmacDigest - The raw HMAC digest buffer.\n * @param digits - Number of digits for the output code.\n * @returns The zero-padded TOTP code string.\n */\nfunction truncateHmac(hmacDigest: Buffer, digits: number): string {\n const offset = (hmacDigest.at(-1) ?? 0) & OFFSET_MASK\n const binaryCode =\n (((hmacDigest.at(offset) ?? 0) & SIGN_BIT_MASK) << SHIFT_24) |\n (((hmacDigest.at(offset + TRUNCATION_BYTE_1) ?? 0) & BYTE_MASK) << SHIFT_16) |\n (((hmacDigest.at(offset + TRUNCATION_BYTE_2) ?? 0) & BYTE_MASK) << BITS_PER_BYTE) |\n ((hmacDigest.at(offset + TRUNCATION_BYTE_3) ?? 0) & BYTE_MASK)\n\n const otp = binaryCode % DECIMAL_BASE ** digits\n return otp.toString().padStart(digits, \"0\")\n}\n\n/**\n * Parse and validate the TOTP algorithm parameter from a URL.\n *\n * @param url - The parsed otpauth URL.\n * @returns The Node.js crypto hash name (e.g. \"sha1\", \"sha256\", \"sha512\").\n * @throws {Error} If the algorithm is not one of SHA1, SHA256, or SHA512.\n */\nfunction parseAlgorithm(url: URL): string {\n const algorithmParameter = (url.searchParams.get(\"algorithm\") ?? \"SHA1\").toUpperCase()\n if (!(algorithmParameter in SUPPORTED_ALGORITHMS)) {\n throw new Error(\n `Unsupported TOTP algorithm '${algorithmParameter}'. Supported: ${Object.keys(SUPPORTED_ALGORITHMS).join(\", \")}`\n )\n }\n return SUPPORTED_ALGORITHMS[algorithmParameter]\n}\n\n/**\n * Parse a decimal integer parameter from the otpauth URI without accepting trailing junk.\n *\n * @param rawValue - Raw query parameter value.\n * @param parameterName - Parameter name used in validation errors.\n * @returns The parsed integer.\n * @throws {Error} If the value is not a decimal integer.\n */\nfunction parseDecimalInteger(rawValue: string, parameterName: \"digits\" | \"period\"): number {\n if (!/^\\d+$/v.test(rawValue)) {\n if (parameterName === \"period\") {\n throw new Error(\"TOTP 'period' must be a positive integer\")\n }\n throw new Error(\"TOTP 'digits' must be an integer between 1 and 8\")\n }\n\n return Number.parseInt(rawValue, DECIMAL_BASE)\n}\n\n/**\n * Validate that a URI is an otpauth TOTP URI.\n *\n * @param url - The parsed URI to validate.\n * @throws {Error} If the scheme is not `otpauth://` or the type is not `totp`.\n */\nfunction validateTotpUri(url: URL): void {\n if (url.protocol !== \"otpauth:\") {\n throw new Error(\"TOTP URI must use the otpauth:// scheme\")\n }\n if (url.hostname !== \"totp\") {\n throw new Error(\"TOTP URI must use the otpauth://totp/... type\")\n }\n}\n\n/**\n * Parse and validate TOTP parameters from an otpauth URI.\n *\n * @param otpauthUri - The otpauth URI to parse.\n * @returns The parsed secret, period, digits, and algorithm.\n */\nfunction parseTotpParameters(otpauthUri: string): {\n algorithm: string\n digits: number\n period: number\n secret: string\n} {\n const url = new URL(otpauthUri)\n validateTotpUri(url)\n\n const secret = url.searchParams.get(\"secret\")\n if (secret == null || secret === \"\") {\n throw new Error(\"TOTP URI is missing the 'secret' parameter\")\n }\n\n const period = parseDecimalInteger(\n url.searchParams.get(\"period\") ?? String(DEFAULT_PERIOD),\n \"period\"\n )\n const digits = parseDecimalInteger(\n url.searchParams.get(\"digits\") ?? String(DEFAULT_DIGITS),\n \"digits\"\n )\n\n if (!Number.isFinite(period) || period <= 0) {\n throw new Error(\"TOTP 'period' must be a positive integer\")\n }\n if (!Number.isFinite(digits) || digits < 1 || digits > MAX_DIGITS) {\n throw new Error(\"TOTP 'digits' must be an integer between 1 and 8\")\n }\n\n const algorithm = parseAlgorithm(url)\n\n return { algorithm, digits, period, secret }\n}\n\n/**\n * Generate a TOTP code from an `otpauth://totp/...` URI according to RFC 6238.\n *\n * The URI is parsed for `secret`, `period` (default 30), `digits` (default 6),\n * and `algorithm` (default SHA1).\n * Uses HMAC with the algorithm specified in the URI (default SHA1) and dynamic\n * truncation to produce a numeric one-time password.\n *\n * @param otpauthUri - A fully-qualified `otpauth://totp/...` URI containing at\n * least a `secret` query parameter with a Base32-encoded shared secret.\n * @returns The zero-padded TOTP code as a string (length determined by `digits`).\n * @throws {Error} If the URI is missing the `secret` parameter.\n * @throws {Error} If the `secret` contains characters outside the Base32 alphabet.\n * @throws {Error} If `period` is not a positive integer.\n * @throws {Error} If `digits` is not an integer between 1 and 8.\n * @throws {Error} If `algorithm` is not one of SHA1, SHA256, or SHA512.\n * @see {@link https://datatracker.ietf.org/doc/html/rfc6238 RFC 6238 – TOTP}\n * @see {@link https://datatracker.ietf.org/doc/html/rfc4226 RFC 4226 – HOTP}\n */\nexport function generateTotpCode(otpauthUri: string): string {\n const { algorithm, digits, period, secret } = parseTotpParameters(otpauthUri)\n\n const key = decodeBase32(secret)\n\n // Calculate the time-based counter\n const counter = Math.floor(Date.now() / MILLISECONDS_PER_SECOND / period)\n\n // Encode counter as big-endian 8-byte buffer\n const counterBuffer = Buffer.alloc(COUNTER_BUFFER_SIZE)\n counterBuffer.writeBigUInt64BE(BigInt(counter))\n\n // Compute HMAC and apply dynamic truncation\n const hmacDigest = createHmac(algorithm, key).update(counterBuffer).digest()\n const code = truncateHmac(hmacDigest, digits)\n\n // Zero-fill sensitive buffers to reduce exposure window in memory\n key.fill(0)\n counterBuffer.fill(0)\n hmacDigest.fill(0)\n\n return code\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { hasFlag, setVersionedFlag } from \"./moduleHelpers.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\n\n/** Supported system package managers. */\ntype PackageManager = \"apk\" | \"apt\" | \"dnf\" | \"yum\"\n\n/** Per-connection cache for the detected package manager (avoids repeated SSH roundtrips). */\nconst pmCache = new WeakMap<SshConnection, null | PackageManager>()\n\nconst INSTALL_COMMANDS = {\n apk: (pkgs: string) => `apk add ${pkgs}`,\n apt: (pkgs: string) => `DEBIAN_FRONTEND=noninteractive apt-get install -y ${pkgs}`,\n dnf: (pkgs: string) => `dnf install -y ${pkgs}`,\n yum: (pkgs: string) => `yum install -y ${pkgs}`,\n} as const\n\nconst REMOVE_COMMANDS = {\n apk: (pkgs: string) => `apk del ${pkgs}`,\n apt: (pkgs: string) => `DEBIAN_FRONTEND=noninteractive apt-get remove -y ${pkgs}`,\n dnf: (pkgs: string) => `dnf remove -y ${pkgs}`,\n yum: (pkgs: string) => `yum remove -y ${pkgs}`,\n} as const\n\nconst UPDATE_COMMANDS = {\n apk: \"apk update\",\n apt: \"apt-get update\",\n dnf: \"dnf makecache\",\n yum: \"yum makecache\",\n} as const\n\nconst UPGRADE_COMMANDS = {\n apk: \"apk update && apk upgrade\",\n apt: \"DEBIAN_FRONTEND=noninteractive dpkg --configure -a && DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y\",\n dnf: \"dnf upgrade -y\",\n yum: \"yum update -y\",\n} as const\n\nfunction missingPackageManager(moduleName: string): ModuleResult {\n return failed(`[${moduleName}] No supported package manager found (apt, dnf, yum, apk)`)\n}\n\n/**\n * Detect the system package manager by probing for known binaries.\n *\n * Checks in order: apt, dnf, yum, apk. The result is cached per\n * {@link SshConnection} instance so subsequent calls avoid extra SSH roundtrips.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @returns The detected package manager, or `null` when none is found.\n */\nexport async function detectPackageManager(ssh: SshConnection): Promise<null | PackageManager> {\n const cached = pmCache.get(ssh)\n if (cached !== undefined) return cached\n\n let result: null | PackageManager = null\n if (await ssh.test(\"which apt-get\")) result = \"apt\"\n else if (await ssh.test(\"which dnf\")) result = \"dnf\"\n else if (await ssh.test(\"which yum\")) result = \"yum\"\n else if (await ssh.test(\"which apk\")) result = \"apk\"\n\n pmCache.set(ssh, result)\n return result\n}\n\n/**\n * Check whether a single package is installed using the appropriate\n * command for the detected package manager.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @param pm - The detected package manager.\n * @param packageName - Name of the package to check.\n * @returns `true` if the package is installed.\n */\nexport async function isPackageInstalled(\n ssh: SshConnection,\n pm: PackageManager,\n packageName: string\n): Promise<boolean> {\n const quoted = shellQuote(packageName)\n switch (pm) {\n case \"apk\": {\n return ssh.test(`apk info -e ${quoted}`)\n }\n case \"apt\": {\n return ssh.test(\n `dpkg-query -W -f='\\${Status}' ${quoted} 2>/dev/null | grep -q 'install ok installed'`\n )\n }\n case \"dnf\":\n case \"yum\": {\n return ssh.test(`rpm -q ${quoted}`)\n }\n }\n}\n\n/**\n * Distro-agnostic package management module.\n *\n * Automatically detects the system package manager (apt, dnf, yum, apk)\n * and delegates to the appropriate commands. All methods are idempotent.\n *\n * For Debian/Ubuntu-specific configuration (debconf pre-seeding, GPG keys,\n * apt repositories) use the `apt` module instead.\n *\n * @example\n * // Install packages\n * pkg.installed(\"git\", \"curl\")\n *\n * @example\n * // Refresh package lists and upgrade all packages on a specific date\n * pkg.update(\"2024-01-15\")\n * pkg.upgrade(\"2024-01-15\")\n */\n// eslint-disable-next-line unicorn/prevent-abbreviations -- `package` is a JS reserved word; re-exported as `package` in index.ts\nexport const pkg = {\n /**\n * Ensure the given packages are not installed.\n *\n * The check phase queries the package database for each package individually;\n * the remove command is only executed when at least one package is present.\n *\n * @param packages - One or more package names to remove.\n * @returns A Module that removes the packages if any are present.\n *\n * @example\n * pkg.absent(\"vim\", \"nano\")\n */\n absent(...packages: string[]): Module {\n if (packages.length === 0) {\n throw new Error(\"package.absent: at least one package name is required\")\n }\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh)\n return failed(`[package.absent: ${packages.join(\", \")}] SSH connection is required`)\n const pm = await detectPackageManager(ssh)\n if (!pm) return missingPackageManager(`package.absent: ${packages.join(\", \")}`)\n const quoted = packages.map((p) => shellQuote(p)).join(\" \")\n const result = await ssh.exec(REMOVE_COMMANDS[pm](quoted), EXEC_OPTS)\n if (result.code !== 0) {\n return failedCommand(\n `[package.absent: ${packages.join(\", \")}] package removal failed`,\n result\n )\n }\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const pm = await detectPackageManager(ssh)\n if (!pm) return NEEDS_APPLY\n for (const p of packages) {\n // eslint-disable-next-line no-await-in-loop\n if (await isPackageInstalled(ssh, pm, p)) return NEEDS_APPLY\n }\n return \"ok\"\n },\n name: `package.absent: ${packages.join(\", \")}`,\n }\n },\n\n /**\n * Ensure the given packages are installed.\n *\n * The check phase queries the package database for each package individually;\n * the install command is only executed when at least one package is missing.\n *\n * @param packages - One or more package names to install.\n * @returns A Module that installs missing packages.\n *\n * @example\n * pkg.installed(\"git\", \"curl\", \"unzip\")\n */\n installed(...packages: string[]): Module {\n if (packages.length === 0) {\n throw new Error(\"package.installed: at least one package name is required\")\n }\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) {\n return failed(`[package.installed: ${packages.join(\", \")}] SSH connection is required`)\n }\n const pm = await detectPackageManager(ssh)\n if (!pm) return missingPackageManager(`package.installed: ${packages.join(\", \")}`)\n const quoted = packages.map((p) => shellQuote(p)).join(\" \")\n const result = await ssh.exec(INSTALL_COMMANDS[pm](quoted), EXEC_OPTS)\n if (result.code !== 0) {\n return failedCommand(\n `[package.installed: ${packages.join(\", \")}] package installation failed`,\n result\n )\n }\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const pm = await detectPackageManager(ssh)\n if (!pm) return NEEDS_APPLY\n for (const p of packages) {\n // eslint-disable-next-line no-await-in-loop\n if (!(await isPackageInstalled(ssh, pm, p))) return NEEDS_APPLY\n }\n return \"ok\"\n },\n name: `package.installed: ${packages.join(\", \")}`,\n }\n },\n\n /**\n * Refresh the package manager's package lists once per dated flag.\n *\n * A flag file at `${FLAGS_DIRECTORY}/package-update-<date>` is created after\n * a successful run. On the next run the flag is detected and the module\n * reports `\"ok\"` without running the update again. Changing `date` to a new\n * value invalidates all previous flags for this operation.\n *\n * @param date - A date string used as the idempotency key (e.g. `\"2024-01-15\"`).\n * @returns A Module that refreshes package lists.\n *\n * @example\n * pkg.update(\"2024-01-15\")\n */\n update(date: string): Module {\n const flagName = `package-update-${date}`\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[package.update: ${date}] SSH connection is required`)\n const pm = await detectPackageManager(ssh)\n if (!pm) return missingPackageManager(`package.update: ${date}`)\n const result = await ssh.exec(UPDATE_COMMANDS[pm], EXEC_OPTS)\n if (result.code !== 0) {\n return failedCommand(`[package.update: ${date}] package index refresh failed`, result)\n }\n\n await setVersionedFlag(ssh, flagName, \"package-update-\")\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await hasFlag(ssh, flagName)) ? \"ok\" : NEEDS_APPLY\n },\n name: `package.update: ${date}`,\n }\n },\n\n /**\n * Upgrade all installed packages once per dated flag.\n *\n * A flag file at `${FLAGS_DIRECTORY}/package-upgrade-<date>` is created after\n * a successful run. On the next run the flag is detected and the module\n * reports `\"ok\"` without running the upgrade again. Changing `date` to a new\n * value invalidates all previous flags for this operation.\n *\n * On apt systems this runs `apt-get update && apt-get upgrade -y` (not\n * `dist-upgrade`); use `apt.distUpgrade` for full dependency resolution.\n *\n * @param date - A date string used as the idempotency key (e.g. `\"2024-01-15\"`).\n * @returns A Module that upgrades all packages.\n *\n * @example\n * pkg.upgrade(\"2024-01-15\")\n *\n * @see apt.distUpgrade\n */\n upgrade(date: string): Module {\n const flagName = `package-upgrade-${date}`\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[package.upgrade: ${date}] SSH connection is required`)\n const pm = await detectPackageManager(ssh)\n if (!pm) return missingPackageManager(`package.upgrade: ${date}`)\n const result = await ssh.exec(UPGRADE_COMMANDS[pm], EXEC_OPTS)\n if (result.code !== 0) {\n return failedCommand(`[package.upgrade: ${date}] package upgrade failed`, result)\n }\n\n await setVersionedFlag(ssh, flagName, \"package-upgrade-\")\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await hasFlag(ssh, flagName)) ? \"ok\" : NEEDS_APPLY\n },\n name: `package.upgrade: ${date}`,\n }\n },\n}\n","import { meta } from \"../meta.js\"\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport {\n guardedWriteFile,\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\n\nconst NONINTERACTIVE = \"DEBIAN_FRONTEND=noninteractive\"\nconst CODENAME_RE = /^[a-z]{3,20}$/v\nconst APT_SOURCES_MODE = \"0644\"\n\n/**\n * Options for the {@link releaseUpgrade.upgrade} module.\n */\ntype ReleaseUpgradeOptions = {\n /**\n * When `true`, only check whether an upgrade is available without applying\n * any changes. The module returns `\"ok\"` regardless of what is found.\n */\n dryRun?: boolean\n /**\n * Optional async function to resolve the new host address after the\n * post-upgrade reboot. Useful when the server's IP address may change\n * (e.g. DHCP or cloud environments). The resolved value is emitted as\n * `system.host` meta so the runner can reconnect to the correct address.\n */\n resolveHost?: () => Promise<string>\n}\n\ntype Distro = \"debian\" | \"ubuntu\"\n\n/**\n * Detect the Linux distribution of the remote host by reading `/etc/os-release`.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @returns `\"debian\"`, `\"ubuntu\"`, or `null` when the distribution cannot be\n * identified from the `ID=` field.\n */\nasync function detectDistro(ssh: SshConnection): Promise<Distro | null> {\n const osRelease = await ssh.readFile(\"/etc/os-release\")\n for (const line of osRelease.split(\"\\n\")) {\n const match = /^ID=(?<value>.*)$/v.exec(line)\n if (match?.groups) {\n const id = match.groups.value.replaceAll('\"', \"\").trim()\n if (id === \"ubuntu\") return \"ubuntu\"\n if (id === \"debian\") return \"debian\"\n return null\n }\n }\n return null\n}\n\n/**\n * Return the current Debian/Ubuntu release codename via `lsb_release -cs`.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @returns The codename string (e.g. `\"bookworm\"` or `\"noble\"`).\n */\nasync function getDebianCurrentCodename(ssh: SshConnection): Promise<string> {\n const codename = await ssh.output(\"lsb_release -cs\")\n if (!CODENAME_RE.test(codename)) {\n throw new Error(`Invalid codename from lsb_release: ${JSON.stringify(codename)}`)\n }\n return codename\n}\n\n/**\n * Fetch the codename of the current Debian stable release from the official\n * Debian mirrors by downloading the `Release` metadata file.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @returns The stable codename (e.g. `\"bookworm\"`).\n * @throws {Error} When the `Codename:` field is absent from the Release file.\n */\nasync function getDebianStableCodename(ssh: SshConnection): Promise<string> {\n const result = await ssh.exec(\"curl -fsSL https://deb.debian.org/debian/dists/stable/Release\", {\n ignoreExitCode: true,\n silent: true,\n })\n for (const line of result.stdout.split(\"\\n\")) {\n const match = /^Codename:\\s+(?<name>\\S+)$/v.exec(line)\n if (match?.groups) {\n const codename = match.groups.name\n if (!CODENAME_RE.test(codename)) {\n throw new Error(`Invalid stable codename from Debian mirrors: ${JSON.stringify(codename)}`)\n }\n return codename\n }\n }\n throw new Error(\"Could not determine Debian stable codename\")\n}\n\n/**\n * Replace all occurrences of `currentCodename` with `targetCodename` in\n * `/etc/apt/sources.list` and every `.list` and `.sources` file under\n * `/etc/apt/sources.list.d/`.\n *\n * This is the core step for upgrading Debian: pointing apt at the new release\n * suite before running `apt-get full-upgrade`.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @param currentCodename - The codename that is currently in use (e.g. `\"bullseye\"`).\n * @param targetCodename - The codename to upgrade to (e.g. `\"bookworm\"`).\n */\nasync function replaceCodenameInSourcesList(\n ssh: SshConnection,\n currentCodename: string,\n targetCodename: string\n): Promise<void> {\n const sourcesContent = await ssh.readFile(\"/etc/apt/sources.list\")\n const updatedContent = sourcesContent.replaceAll(currentCodename, targetCodename)\n await guardedWriteFile(ssh, {\n mode: APT_SOURCES_MODE,\n newContent: updatedContent,\n originalContent: sourcesContent,\n remotePath: \"/etc/apt/sources.list\",\n })\n\n const listFilesResult = await ssh.exec(\n \"find /etc/apt/sources.list.d/ \\\\( -name '*.list' -o -name '*.sources' \\\\) -type f\",\n { ignoreExitCode: true, silent: true }\n )\n if (listFilesResult.code === 0 && listFilesResult.stdout.trim()) {\n for (const filePath of listFilesResult.stdout.trim().split(\"\\n\")) {\n const trimmedPath = filePath.trim()\n if (!trimmedPath) continue\n // eslint-disable-next-line no-await-in-loop\n const content = await ssh.readFile(trimmedPath)\n const updated = content.replaceAll(currentCodename, targetCodename)\n if (updated !== content) {\n // eslint-disable-next-line no-await-in-loop\n await guardedWriteFile(ssh, {\n mode: APT_SOURCES_MODE,\n newContent: updated,\n originalContent: content,\n remotePath: trimmedPath,\n })\n }\n }\n }\n}\n\n/**\n * Build the meta signal map that triggers a runner reboot and optional host\n * re-resolution after the upgrade completes.\n *\n * Always sets `system.reboot` to `\"true\"`. If `options.resolveHost` is\n * provided and resolves successfully, `system.host` is set to the returned\n * address. Failures from `resolveHost` are silently ignored so the runner\n * falls back to the current host.\n *\n * @param options - Upgrade options containing an optional `resolveHost` callback.\n * @returns A meta map suitable for inclusion in a `ModuleResult`.\n */\nasync function buildRebootMeta(options: ReleaseUpgradeOptions): Promise<ModuleMetaEntry[]> {\n const entries: ModuleMetaEntry[] = [meta.systemReboot()]\n if (options.resolveHost != null) {\n try {\n const newHost = await options.resolveHost()\n entries.push(meta.systemHost(newHost))\n } catch {\n // resolveHost failed — reconnect will use current host\n }\n }\n return entries\n}\n\nasync function runReleaseUpgradeCommand(\n ssh: SshConnection,\n command: string,\n failureMessage: string\n): Promise<ModuleResult | null> {\n const result = await ssh.exec(command, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0 ? null : failedCommand(failureMessage, result)\n}\n\n/**\n * Run the Ubuntu release upgrade via `do-release-upgrade`.\n *\n * Executes `apt-get update` first, then invokes `do-release-upgrade` in\n * non-interactive mode. When `dryRun` is set, only the check flag (`-c`) is\n * passed and no changes are made.\n *\n * On success, returns `status: \"changed\"` with reboot meta so the runner\n * can reconnect after the post-upgrade restart.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @param options - Upgrade options (see {@link ReleaseUpgradeOptions}).\n * @returns A `ModuleResult` — `\"changed\"` with reboot meta on success,\n * `\"ok\"` on dry-run, or `\"failed\"` when any command returns a non-zero\n * exit code.\n */\nasync function applyUbuntu(\n ssh: SshConnection,\n options: ReleaseUpgradeOptions\n): Promise<ModuleResult> {\n const updateFailure = await runReleaseUpgradeCommand(\n ssh,\n `${NONINTERACTIVE} apt-get update`,\n \"[releaseUpgrade.upgrade] apt-get update failed\"\n )\n if (updateFailure != null) return updateFailure\n\n if (options.dryRun === true) {\n await ssh.exec(\"do-release-upgrade -c\", {\n ignoreExitCode: true,\n silent: true,\n })\n return { status: \"ok\" }\n }\n\n const upgradeFailure = await runReleaseUpgradeCommand(\n ssh,\n \"do-release-upgrade -f DistUpgradeViewNonInteractive\",\n \"[releaseUpgrade.upgrade] do-release-upgrade failed\"\n )\n if (upgradeFailure != null) return upgradeFailure\n\n const entries = await buildRebootMeta(options)\n return { meta: entries, status: \"changed\" }\n}\n\n/**\n * Run the Debian release upgrade by rewriting sources and running\n * `apt-get full-upgrade`.\n *\n * Determines the current and target (stable) codenames, rewrites all\n * apt sources to point at the new suite, then executes the four-step\n * upgrade sequence: `apt-get update`, `dpkg --configure -a` (to resolve\n * any previously interrupted package configurations), `apt-get full-upgrade`,\n * and `apt-get autoremove`. When `dryRun` is set, the upgrade is skipped\n * entirely and `\"ok\"` is returned.\n *\n * On success, returns `status: \"changed\"` with reboot meta so the runner\n * can reconnect after the post-upgrade restart.\n *\n * @param ssh - Active SSH connection to the remote host.\n * @param options - Upgrade options (see {@link ReleaseUpgradeOptions}).\n * @returns A `ModuleResult` — `\"changed\"` with reboot meta on success,\n * `\"ok\"` on dry-run, or `\"failed\"` when any command returns a non-zero\n * exit code.\n */\nasync function applyDebian(\n ssh: SshConnection,\n options: ReleaseUpgradeOptions\n): Promise<ModuleResult> {\n const currentCodename = await getDebianCurrentCodename(ssh)\n const targetCodename = await getDebianStableCodename(ssh)\n\n if (options.dryRun === true) {\n return { status: \"ok\" }\n }\n\n await replaceCodenameInSourcesList(ssh, currentCodename, targetCodename)\n\n const updateFailure = await runReleaseUpgradeCommand(\n ssh,\n `${NONINTERACTIVE} apt-get update`,\n \"[releaseUpgrade.upgrade] apt-get update failed\"\n )\n if (updateFailure != null) return updateFailure\n\n const configureFailure = await runReleaseUpgradeCommand(\n ssh,\n `${NONINTERACTIVE} dpkg --configure -a`,\n \"[releaseUpgrade.upgrade] dpkg --configure -a failed\"\n )\n if (configureFailure != null) return configureFailure\n\n const upgradeFailure = await runReleaseUpgradeCommand(\n ssh,\n `${NONINTERACTIVE} apt-get full-upgrade -y`,\n \"[releaseUpgrade.upgrade] apt-get full-upgrade failed\"\n )\n if (upgradeFailure != null) return upgradeFailure\n\n const autoremoveFailure = await runReleaseUpgradeCommand(\n ssh,\n `${NONINTERACTIVE} apt-get autoremove -y`,\n \"[releaseUpgrade.upgrade] apt-get autoremove failed\"\n )\n if (autoremoveFailure != null) return autoremoveFailure\n\n const entries = await buildRebootMeta(options)\n return { meta: entries, status: \"changed\" }\n}\n\n/**\n * Modules for upgrading the operating system to the next major release.\n *\n * Supports Ubuntu (via `do-release-upgrade`) and Debian (via sources.list\n * rewrite + `apt-get full-upgrade`). After a successful upgrade the module\n * signals the runner to reboot and optionally reconnect to a new host address\n * via the `system.reboot` / `system.host` meta keys.\n */\nexport const releaseUpgrade = {\n /**\n * Upgrade the remote host to the next major OS release.\n *\n * The distribution is auto-detected from `/etc/os-release`. Ubuntu hosts\n * are upgraded with `do-release-upgrade`; Debian hosts are upgraded by\n * rewriting apt sources to the current stable suite and running\n * `apt-get full-upgrade`.\n *\n * The `check` phase returns `\"needs-apply\"` when an upgrade is available\n * (Ubuntu: `do-release-upgrade -c` exits 0; Debian: current codename differs\n * from stable codename) and `\"ok\"` when the host is already up to date.\n *\n * @param options - Optional settings.\n * @param options.dryRun - When `true`, only inspect whether an upgrade is\n * available without modifying the system.\n * @param options.resolveHost - Async callback invoked after the upgrade to\n * determine the new host address before the runner reconnects.\n * @returns A Module that performs the OS release upgrade.\n *\n * @example\n * // Simple upgrade — auto-detect distro and apply\n * releaseUpgrade.upgrade()\n *\n * @example\n * // Dry-run: check availability without making changes\n * releaseUpgrade.upgrade({ dryRun: true })\n *\n * @example\n * // Resolve the new host address after reboot (e.g. dynamic DNS or DHCP)\n * releaseUpgrade.upgrade({\n * resolveHost: async () => {\n * const ip = await myDns.resolve(\"my-server.example.com\")\n * return ip\n * },\n * })\n */\n upgrade(options: ReleaseUpgradeOptions = {}): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[releaseUpgrade.upgrade] SSH connection is required\")\n\n const distro = await detectDistro(ssh)\n if (distro == null) return failed(\"[releaseUpgrade.upgrade] Unsupported distribution\")\n\n if (distro === \"ubuntu\") return applyUbuntu(ssh, options)\n return applyDebian(ssh, options)\n },\n\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const distro = await detectDistro(ssh)\n if (distro == null) return NEEDS_APPLY\n\n if (distro === \"ubuntu\") {\n const result = await ssh.exec(\"do-release-upgrade -c\", {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0 ? NEEDS_APPLY : \"ok\"\n }\n\n // Debian: compare current codename to stable\n try {\n const currentCodename = await getDebianCurrentCodename(ssh)\n const targetCodename = await getDebianStableCodename(ssh)\n return currentCodename === targetCodename ? \"ok\" : NEEDS_APPLY\n } catch {\n return NEEDS_APPLY\n }\n },\n\n name: \"releaseUpgrade.upgrade\",\n }\n },\n}\n","import { execFile, type ExecFileException } from \"node:child_process\"\nimport { randomUUID } from \"node:crypto\"\nimport { unlinkSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { join } from \"node:path\"\n\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { CommandError } from \"../sshHelpers.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\ntype RsyncPhase = \"apply\" | \"check\"\nconst DEFAULT_SSH_PORT = 22\n\ntype SyncOptions = {\n /** Permission mode applied via `--chmod`, e.g. `\"Du=rwx,go=rx,Fu=rw,go=r\"`. */\n chmod?: string\n /** Remove files on the remote that are absent from the source (`--delete`). */\n delete?: boolean\n /** Absolute destination path on the remote host. */\n dest: string\n /** Patterns passed to rsync `--exclude` in order, applied after includes. */\n exclude?: string[]\n /** Group name for `--chown`; defaults to `owner` when only `owner` is set. */\n group?: string\n /** Patterns passed to rsync `--include` in order, applied before excludes. */\n include?: string[]\n /** Owner name for `--chown`. */\n owner?: string\n /** Local source path (file or directory) to synchronize. */\n src: string\n /**\n * SSH `StrictHostKeyChecking` option passed to the `-o` flag.\n *\n * Defaults to `\"yes\"`. Use `\"accept-new\"` for explicit TOFU when first-time\n * connections must be auto-accepted, or `\"no\"` to disable checking entirely\n * (not recommended for production).\n */\n strictHostKeyChecking?: \"accept-new\" | \"no\" | \"off\" | \"yes\"\n}\n\n/**\n * Build the rsync filter arguments for include/exclude patterns and deletion.\n *\n * Includes are appended before excludes so that rsync evaluates them in the\n * correct order (first matching rule wins).\n *\n * @param options - Sync options containing include, exclude, and delete settings.\n * @returns An array of rsync arguments for filter rules.\n */\nfunction buildFilterArguments(options: SyncOptions): string[] {\n const result: string[] = []\n\n for (const pattern of options.include ?? []) {\n result.push(\"--include\", pattern)\n }\n\n for (const pattern of options.exclude ?? []) {\n result.push(\"--exclude\", pattern)\n }\n\n if (options.delete) {\n result.push(\"--delete\")\n }\n\n return result\n}\n\n/**\n * Build the rsync ownership arguments for `--chown` and `--chmod`.\n *\n * When only `owner` is set, the group defaults to the same value so that\n * rsync receives a valid `owner:group` pair.\n *\n * @param options - Sync options containing owner, group, and chmod settings.\n * @returns An array of rsync arguments for ownership and permissions.\n */\nfunction buildOwnershipArguments(options: SyncOptions): string[] {\n const result: string[] = []\n\n if (options.owner != null || options.group != null) {\n const ownerPart = options.owner ?? \"\"\n const groupPart = options.group ?? options.owner ?? \"\"\n result.push(`--chown=${ownerPart}:${groupPart}`)\n }\n\n if (options.chmod != null) {\n result.push(`--chmod=${options.chmod}`)\n }\n\n return result\n}\n\nfunction buildRemoteSpec(\n connectionInfo: {\n host: string\n user: string\n },\n destination: string\n): string {\n const remoteHost = connectionInfo.host.includes(\":\")\n ? `[${connectionInfo.host}]`\n : connectionInfo.host\n return `${connectionInfo.user}@${remoteHost}:${destination}`\n}\n\nfunction formatKnownHostsLabel(host: string, port: number): string {\n return port === DEFAULT_SSH_PORT ? host : `[${host}]:${port}`\n}\n\nfunction createVerifiedKnownHostsFile(connectionInfo: {\n host: string\n port: number\n verifiedHostPublicKey?: string\n}): null | string {\n if (connectionInfo.verifiedHostPublicKey == null) return null\n\n const filePath = join(tmpdir(), `paratix-rsync-known-hosts-${randomUUID()}`)\n const content = `${formatKnownHostsLabel(connectionInfo.host, connectionInfo.port)} ${connectionInfo.verifiedHostPublicKey}\\n`\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n writeFileSync(filePath, content, { mode: 0o600 })\n return filePath\n}\n\nfunction cleanupVerifiedKnownHostsFile(path: null | string): void {\n if (path == null) return\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n unlinkSync(path)\n } catch {\n // local cleanup is best-effort\n }\n}\n\n/**\n * Assemble the full rsync argument list for a transfer.\n *\n * Always enables archive mode (`-a`), compression (`-z`), and itemized\n * output (`--itemize-changes`). The SSH transport is configured from\n * the connection info with host-key checking set to `yes` by default.\n * Use `strictHostKeyChecking: \"accept-new\"` for explicit TOFU when\n * first-time connections must be auto-accepted.\n *\n * @param parameters - Argument bundle for the rsync command construction.\n * @param parameters.options - Sync options describing source, destination, and filters.\n * @param parameters.connectionInfo - SSH connection details obtained from `SshConnection.getConnectionInfo`.\n * @param parameters.connectionInfo.agentSocket - SSH agent socket path (`SSH_AUTH_SOCK`), used when no private key is configured.\n * @param parameters.connectionInfo.authMethod - Authentication method that established the current SSH session.\n * @param parameters.connectionInfo.host - The remote host address.\n * @param parameters.connectionInfo.port - The SSH port number.\n * @param parameters.connectionInfo.privateKeyPath - Absolute path to the SSH private key.\n * @param parameters.connectionInfo.user - The SSH username.\n * @param parameters.dryRun - When `true`, adds `--dry-run` so no files are transferred.\n * @param parameters.verifiedKnownHostsPath - Optional temporary known_hosts file containing the verified session host key.\n * @returns The complete list of arguments to pass to the `rsync` binary.\n */\nfunction buildArguments(parameters: {\n connectionInfo: {\n agentSocket?: string\n authMethod?: \"agent\" | \"password\" | \"privateKey\"\n host: string\n port: number\n privateKeyPath?: string\n user: string\n }\n dryRun: boolean\n options: SyncOptions\n verifiedKnownHostsPath?: string\n}): string[] {\n const { connectionInfo, dryRun, options, verifiedKnownHostsPath } = parameters\n const result: string[] = [\"-az\", \"--itemize-changes\"]\n\n if (dryRun) {\n result.push(\"--dry-run\")\n }\n\n let sshFlags = \"\"\n if (connectionInfo.privateKeyPath != null) {\n sshFlags = ` -i ${shellQuote(connectionInfo.privateKeyPath)}`\n } else if (connectionInfo.agentSocket != null) {\n sshFlags = ` -o IdentityAgent=${shellQuote(connectionInfo.agentSocket)}`\n }\n const strictHostKeyChecking =\n verifiedKnownHostsPath == null ? (options.strictHostKeyChecking ?? \"yes\") : \"yes\"\n const knownHostsFlags =\n verifiedKnownHostsPath == null\n ? \"\"\n : ` -o UserKnownHostsFile=${shellQuote(verifiedKnownHostsPath)} -o GlobalKnownHostsFile=/dev/null`\n result.push(\n \"-e\",\n `ssh -p ${connectionInfo.port}${sshFlags}${knownHostsFlags} -o StrictHostKeyChecking=${strictHostKeyChecking}`\n )\n result.push(...buildFilterArguments(options))\n result.push(...buildOwnershipArguments(options))\n result.push(\"--\", options.src, buildRemoteSpec(connectionInfo, options.dest))\n\n return result\n}\n\ntype RsyncFailureDetails = {\n code?: number | string\n error: unknown\n stderr: string\n stdout: string\n}\n\nfunction firstNonEmptyLine(text: string): null | string {\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim()\n if (trimmed.length > 0) return trimmed\n }\n return null\n}\n\nfunction createRsyncError(\n options: SyncOptions,\n phase: RsyncPhase,\n details: RsyncFailureDetails\n): Error {\n const exitCodeSuffix = details.code == null ? \"\" : ` (exit code ${String(details.code)})`\n const messageDetails =\n firstNonEmptyLine(details.stderr) ??\n firstNonEmptyLine(details.stdout) ??\n (details.error instanceof Error ? details.error.message : String(details.error))\n\n return new CommandError(\n `[rsync.sync] ${phase} failed for ${options.src} -> ${options.dest}${exitCodeSuffix}\\n${messageDetails}`,\n details.stdout,\n details.stderr\n )\n}\n\nasync function executeRsync(parameters: {\n dryRun: boolean\n options: SyncOptions\n phase: RsyncPhase\n ssh: SshConnection\n}): Promise<string> {\n const { dryRun, options, phase, ssh } = parameters\n const connectionInfo = ssh.getConnectionInfo()\n if (connectionInfo.authMethod === \"password\") {\n throw new Error(\n `[rsync.sync] ${phase} requires agent or private-key SSH authentication; password fallback sessions are not supported`\n )\n }\n const verifiedKnownHostsPath = createVerifiedKnownHostsFile(connectionInfo)\n const rsyncArguments = buildArguments({\n connectionInfo,\n dryRun,\n options,\n verifiedKnownHostsPath: verifiedKnownHostsPath ?? undefined,\n })\n\n try {\n return await new Promise((resolve, reject) => {\n execFile(\"rsync\", rsyncArguments, (error: ExecFileException | null, stdout, stderr) => {\n if (error != null) {\n reject(\n createRsyncError(options, phase, {\n code: error.code ?? undefined,\n error,\n stderr,\n stdout,\n })\n )\n return\n }\n resolve(stdout)\n })\n })\n } finally {\n cleanupVerifiedKnownHostsFile(verifiedKnownHostsPath)\n }\n}\n\n/**\n * Modules for synchronizing files to a remote host using rsync.\n */\nexport const rsync = {\n /**\n * Synchronize a local path to a remote destination using rsync over SSH.\n *\n * The `check` phase runs rsync with `--dry-run` and reports `needs-apply`\n * when the itemized output is non-empty. The `apply` phase returns\n * `\"changed\"` when rsync reports transferred items, or `\"ok\"` when the\n * destination was already in sync.\n *\n * @param options - Sync configuration including source, destination, and optional filters.\n * @returns A Module that manages the rsync synchronization.\n *\n * @example\n * ```ts\n * rsync.sync({\n * src: \"./dist/\",\n * dest: \"/var/www/app\",\n * delete: true,\n * exclude: [\"*.map\"],\n * owner: \"www-data\",\n * })\n * ```\n *\n * @example Enforce strict host-key checking for a host that is already known:\n * ```ts\n * rsync.sync({\n * src: \"./dist/\",\n * dest: \"/var/www/app\",\n * strictHostKeyChecking: \"yes\",\n * })\n * ```\n */\n sync(options: SyncOptions): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[rsync.sync] SSH connection is required\")\n\n try {\n const stdout = await executeRsync({ dryRun: false, options, phase: \"apply\", ssh })\n return { status: stdout.trim().length > 0 ? \"changed\" : \"ok\" }\n } catch (error) {\n return {\n error: error instanceof Error ? error : new Error(String(error)),\n status: \"failed\",\n }\n }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const stdout = await executeRsync({ dryRun: true, options, phase: \"check\", ssh })\n return stdout.trim().length > 0 ? NEEDS_APPLY : \"ok\"\n },\n name: `rsync.sync: ${options.src} -> ${options.dest}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { hasFlag, setVersionedFlag } from \"./moduleHelpers.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst NAME_PATTERN = /^[\\w.\\-]+$/iv\n\n/**\n * Modules for executing scripts on the remote host.\n */\nexport const script = {\n /**\n * Upload and run a local script on the remote host exactly once.\n * Idempotency is tracked via a versioned flag file; bumping the version\n * causes the script to run again.\n *\n * @param name - A unique identifier for this script execution (alphanumeric, dots, hyphens, underscores).\n * @param localPath - Path to the script on the local filesystem.\n * @param options - Optional settings.\n * @param options.args - Arguments to pass to the script (each element is shell-quoted).\n * @param options.version - Version string for the flag file (default: `\"1\"`).\n * @returns A Module that manages the one-time script execution.\n */\n once(name: string, localPath: string, options?: { args?: string[]; version?: string }): Module {\n if (!NAME_PATTERN.test(name)) {\n throw new Error(`script.once: name must match ${String(NAME_PATTERN)}, got: ${name}`)\n }\n\n const version = options?.version ?? \"1\"\n const scriptArguments = options?.args\n const flagName = `script-${name}-${version}`\n const remotePath = `/tmp/paratix-script-${name}`\n const flagPrefix = `script-${name}-`\n\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[script.once: ${name}] SSH connection is required`)\n\n await ssh.uploadFile(localPath, remotePath)\n\n try {\n await ssh.exec(`chmod +x ${shellQuote(remotePath)}`, { silent: true })\n\n const cmd =\n scriptArguments != null && scriptArguments.length > 0\n ? `${shellQuote(remotePath)} ${scriptArguments.map((a) => shellQuote(a)).join(\" \")}`\n : shellQuote(remotePath)\n const result = await ssh.exec(cmd, EXEC_OPTS)\n\n if (result.code !== 0) {\n return failedCommand(`[script.once: ${name}] script execution failed`, result)\n }\n\n await setVersionedFlag(ssh, flagName, flagPrefix)\n\n return { status: \"changed\" }\n } finally {\n await ssh.exec(`rm -f ${shellQuote(remotePath)}`, { silent: true })\n }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await hasFlag(ssh, flagName)) ? \"ok\" : NEEDS_APPLY\n },\n name: `script.once: ${name} (v${version})`,\n }\n },\n}\n","import { environmentToMetaEntries } from \"../meta.js\"\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst SYSTEMCTL = \"systemctl\"\n\n/**\n * Modules for managing systemd services.\n *\n * State-checking methods (`running`, `stopped`, `enabled`, `disabled`) are\n * idempotent. Signal-style methods (`restart`, `reload`) always apply.\n */\nexport const service = {\n /**\n * Ensure a systemd service is disabled and will not start on boot.\n * @param name - The systemd unit name.\n * @returns A Module that ensures the service is disabled.\n */\n disabled(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.disabled: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} disable ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.disabled: ${name}] systemctl disable failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const enabled = await ssh.test(`${SYSTEMCTL} is-enabled --quiet ${shellQuote(name)}`)\n return enabled ? \"needs-apply\" : \"ok\"\n },\n name: `service.disabled: ${name}`,\n }\n },\n\n /**\n * Ensure a systemd service is enabled to start on boot.\n * @param name - The systemd unit name.\n * @returns A Module that ensures the service is enabled.\n */\n enabled(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.enabled: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} enable ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.enabled: ${name}] systemctl enable failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`${SYSTEMCTL} is-enabled --quiet ${shellQuote(name)}`))\n ? \"ok\"\n : NEEDS_APPLY\n },\n name: `service.enabled: ${name}`,\n }\n },\n\n /**\n * Collect status information for all systemd services and expose them as\n * meta entries. Does not change anything on the server.\n * @returns A Module that gathers service facts into `service.<name>` meta keys.\n */\n facts(): Module {\n return {\n _dryRunMetaProducer: true,\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[service.facts] SSH connection is required\")\n const result = await ssh.exec(\n `${SYSTEMCTL} list-units --type=service --all --no-pager --no-legend`,\n { ignoreExitCode: true, silent: true }\n )\n if (result.code !== 0) {\n return failedCommand(\"[service.facts] systemctl list-units failed\", result)\n }\n const facts: Record<string, string> = {}\n for (const line of result.stdout.split(\"\\n\")) {\n // Strip leading Unicode bullet (● or ○) that systemd prepends to failed units\n const trimmed = line.trim().replace(/^[\\u25CF\\u25CB]\\s*/v, \"\")\n if (!trimmed) continue\n const parts = trimmed.split(/\\s+/v)\n const unit = parts[0]\n const active = parts[2]\n if (!unit || !active) continue\n const name = unit.replace(/\\.service$/v, \"\")\n facts[`service.${name}`] = active\n }\n return { meta: environmentToMetaEntries(facts), status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: \"service.facts\",\n }\n },\n\n /**\n * Reload a systemd service. Intended as a recipe signal -- always applies.\n * @param name - The systemd unit name.\n * @returns A Module that reloads the service.\n */\n reload(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.reload: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} reload ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.reload: ${name}] systemctl reload failed`, result)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n // Signal: always needs apply\n return NEEDS_APPLY\n },\n name: `service.reload: ${name}`,\n }\n },\n\n /**\n * Restart a systemd service. Intended as a recipe signal -- always applies.\n * @param name - The systemd unit name.\n * @returns A Module that restarts the service.\n */\n restart(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.restart: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} restart ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.restart: ${name}] systemctl restart failed`, result)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n // Signal: always needs apply\n return NEEDS_APPLY\n },\n name: `service.restart: ${name}`,\n }\n },\n\n /**\n * Ensure a systemd service is running. Starts the service if inactive.\n * @param name - The systemd unit name (e.g. `\"nginx\"`).\n * @returns A Module that ensures the service is running.\n */\n running(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.running: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} start ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.running: ${name}] systemctl start failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`${SYSTEMCTL} is-active --quiet ${shellQuote(name)}`))\n ? \"ok\"\n : NEEDS_APPLY\n },\n name: `service.running: ${name}`,\n }\n },\n\n /**\n * Ensure a systemd service is stopped. Stops the service if active.\n * @param name - The systemd unit name.\n * @returns A Module that ensures the service is stopped.\n */\n stopped(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[service.stopped: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} stop ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[service.stopped: ${name}] systemctl stop failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const active = await ssh.test(`${SYSTEMCTL} is-active --quiet ${shellQuote(name)}`)\n return active ? \"needs-apply\" : \"ok\"\n },\n name: `service.stopped: ${name}`,\n }\n },\n}\n","import { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nasync function resolveHome(conn: SshConnection, user: string): Promise<string> {\n const home = await conn.output(`getent passwd ${shellQuote(user)} | cut -d: -f6`)\n if (home.length === 0 || home === \"/\") {\n throw new Error(`[ssh.authorizedKeys: ${user}] failed to resolve a safe home directory`)\n }\n return home\n}\n\nfunction isUnsafeAuthorizedKeysHomeError(error: unknown, user: string): boolean {\n return (\n error instanceof Error &&\n error.message === `[ssh.authorizedKeys: ${user}] failed to resolve a safe home directory`\n )\n}\n\nasync function createAuthorizedKeysTemporaryPath(conn: SshConnection): Promise<string> {\n await conn.exec(\"install -d -m 700 /run/paratix\", { silent: true })\n return conn.output(\"mktemp /run/paratix/authorized-keys.XXXXXX\")\n}\n\nasync function ensureAuthorizedKeysIsNotSymlink(\n conn: SshConnection,\n authorizedKeysPath: string\n): Promise<void> {\n await conn.exec(\n `[ ! -L ${shellQuote(authorizedKeysPath)} ] || { echo 'authorized_keys must not be a symlink' >&2; exit 1; }`,\n { silent: true }\n )\n}\n\nasync function authorizedKeysSecurityStateIsValid(\n conn: SshConnection,\n parameters: {\n authorizedKeysPath: string\n sshDirectoryPath: string\n user: string\n }\n): Promise<boolean> {\n const { authorizedKeysPath, sshDirectoryPath, user } = parameters\n\n const isAuthorizedKeysSymlink = await conn.test(`[ -L ${shellQuote(authorizedKeysPath)} ]`)\n if (isAuthorizedKeysSymlink) return false\n\n const sshDirectoryState = await conn.output(\n `stat -c '%a %U %G %F' ${shellQuote(sshDirectoryPath)}`\n )\n if (sshDirectoryState.trim() !== `700 ${user} ${user} directory`) return false\n\n const authorizedKeysState = await conn.output(\n `stat -c '%a %U %G %F' ${shellQuote(authorizedKeysPath)}`\n )\n return authorizedKeysState.trim() === `600 ${user} ${user} regular file`\n}\n\nasync function rewriteAuthorizedKeys(\n conn: SshConnection,\n parameters: {\n authorizedKeysPath: string\n key: string\n state: \"absent\" | \"present\"\n user: string\n }\n): Promise<void> {\n const { authorizedKeysPath, key, state, user } = parameters\n const temporaryPath = await createAuthorizedKeysTemporaryPath(conn)\n\n try {\n if (state === \"present\") {\n await conn.exec(\n `{ if [ -f ${shellQuote(authorizedKeysPath)} ]; then cat ${shellQuote(authorizedKeysPath)}; grep -qxF -- ${shellQuote(key)} ${shellQuote(authorizedKeysPath)} || printf '%s\\\\n' ${shellQuote(key)}; else printf '%s\\\\n' ${shellQuote(key)}; fi; } > ${shellQuote(temporaryPath)}`,\n { silent: true }\n )\n } else {\n await conn.exec(\n `{ if [ -f ${shellQuote(authorizedKeysPath)} ]; then grep -vF -- ${shellQuote(key)} ${shellQuote(authorizedKeysPath)} || true; fi; } > ${shellQuote(temporaryPath)}`,\n { silent: true }\n )\n }\n\n await conn.exec(\n `chmod 600 ${shellQuote(temporaryPath)} && chown ${shellQuote(user)}:${shellQuote(user)} ${shellQuote(temporaryPath)} && mv ${shellQuote(temporaryPath)} ${shellQuote(authorizedKeysPath)} && chmod 600 ${shellQuote(authorizedKeysPath)} && chown ${shellQuote(user)}:${shellQuote(user)} ${shellQuote(authorizedKeysPath)}`,\n { silent: true }\n )\n } finally {\n await conn.exec(`rm -f ${shellQuote(temporaryPath)}`, { silent: true })\n }\n}\n\nexport async function applyAuthorizedKeys(\n conn: null | SshConnection,\n parameters: {\n key: string\n state: \"absent\" | \"present\"\n user: string\n }\n): Promise<ModuleResult> {\n const { key, state, user } = parameters\n if (!conn) {\n return failed(`[ssh.authorizedKeys: ${user} (${state})] SSH connection is required`)\n }\n\n const home = await resolveHome(conn, user)\n const directory = shellQuote(`${home}/.ssh`)\n const authorizedKeysPath = `${home}/.ssh/authorized_keys`\n\n await conn.exec(\n `mkdir -p ${directory} && chmod 700 ${directory} && chown ${shellQuote(user)}:${shellQuote(user)} ${directory}`,\n { silent: true }\n )\n await ensureAuthorizedKeysIsNotSymlink(conn, authorizedKeysPath)\n await rewriteAuthorizedKeys(conn, { authorizedKeysPath, key, state, user })\n\n return { status: \"changed\" }\n}\n\nfunction checkMissingAuthorizedKeysUser(\n error: unknown,\n user: string,\n state: \"absent\" | \"present\"\n): \"needs-apply\" | \"ok\" | null {\n if (!isUnsafeAuthorizedKeysHomeError(error, user)) return null\n return state === \"present\" ? NEEDS_APPLY : \"ok\"\n}\n\nfunction checkResultForMissingAuthorizedKeysFiles(\n state: \"absent\" | \"present\"\n): \"needs-apply\" | \"ok\" {\n return state === \"present\" ? NEEDS_APPLY : \"ok\"\n}\n\nfunction finalizeAuthorizedKeysCheck(\n state: \"absent\" | \"present\",\n keyExists: boolean,\n securityStateIsValid: boolean\n): \"needs-apply\" | \"ok\" {\n if (state === \"present\") {\n return keyExists && securityStateIsValid ? \"ok\" : NEEDS_APPLY\n }\n return keyExists || !securityStateIsValid ? NEEDS_APPLY : \"ok\"\n}\n\nasync function resolveAuthorizedKeysCheckPaths(\n conn: SshConnection,\n user: string,\n state: \"absent\" | \"present\"\n): Promise<\n | \"needs-apply\"\n | \"ok\"\n | {\n authorizedKeysPath: string\n sshDirectoryPath: string\n }\n> {\n try {\n const home = await resolveHome(conn, user)\n return {\n authorizedKeysPath: `${home}/.ssh/authorized_keys`,\n sshDirectoryPath: `${home}/.ssh`,\n }\n } catch (error) {\n const missingUserResult = checkMissingAuthorizedKeysUser(error, user, state)\n if (missingUserResult != null) return missingUserResult\n throw error\n }\n}\n\nasync function checkAuthorizedKeysPathsExist(\n conn: SshConnection,\n parameters: {\n authorizedKeysPath: string\n sshDirectoryPath: string\n state: \"absent\" | \"present\"\n }\n): Promise<\"needs-apply\" | \"ok\" | null> {\n const { authorizedKeysPath, sshDirectoryPath, state } = parameters\n const sshDirectoryExists = await conn.exists(sshDirectoryPath)\n const authorizedKeysExists = await conn.exists(authorizedKeysPath)\n\n if (sshDirectoryExists && authorizedKeysExists) return null\n return checkResultForMissingAuthorizedKeysFiles(state)\n}\n\nexport async function checkAuthorizedKeys(\n conn: null | SshConnection,\n parameters: {\n key: string\n state: \"absent\" | \"present\"\n user: string\n }\n): Promise<\"needs-apply\" | \"ok\"> {\n const { key, state, user } = parameters\n if (!conn) return NEEDS_APPLY\n\n const pathResolution = await resolveAuthorizedKeysCheckPaths(conn, user, state)\n if (pathResolution === \"needs-apply\" || pathResolution === \"ok\") return pathResolution\n\n const { authorizedKeysPath, sshDirectoryPath } = pathResolution\n const authKeysPath = shellQuote(authorizedKeysPath)\n const missingPathResult = await checkAuthorizedKeysPathsExist(conn, {\n authorizedKeysPath,\n sshDirectoryPath,\n state,\n })\n if (missingPathResult != null) return missingPathResult\n\n const keyExists = await conn.test(`grep -qF -- ${shellQuote(key)} ${authKeysPath}`)\n const securityStateIsValid = await authorizedKeysSecurityStateIsValid(conn, {\n authorizedKeysPath,\n sshDirectoryPath,\n user,\n })\n\n return finalizeAuthorizedKeysCheck(state, keyExists, securityStateIsValid)\n}\n","import { computeFingerprint } from \"../knownHosts.js\"\nimport { failed } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { applyAuthorizedKeys, checkAuthorizedKeys } from \"./sshAuthorizedKeysHelpers.js\"\n\ntype KnownHostsOptions = {\n expectedFingerprint?: string\n port?: number\n publicKey?: string\n state?: \"absent\" | \"present\"\n}\n\nconst SSH_KEYSCAN_MIN_FIELDS = 3\nconst DEFAULT_SSH_PORT = 22\n\nfunction normalizePublicKey(publicKey: string): string {\n const parts = publicKey.trim().split(/\\s+/v)\n if (parts.length < 2) {\n throw new Error(\n \"ssh.knownHosts requires a full public key in the format '<algorithm> <base64>'\"\n )\n }\n const [algorithm, key] = parts\n return `${algorithm} ${key}`\n}\n\nfunction parseHostKeyLines(output: string): string[] {\n return output\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"))\n}\n\nfunction scannedLinePublicKey(line: string): string {\n const parts = line.split(/\\s+/v)\n if (parts.length < SSH_KEYSCAN_MIN_FIELDS) {\n throw new Error(`ssh.knownHosts received invalid ssh-keyscan output: ${line}`)\n }\n const [, algorithm, key] = parts\n return `${algorithm} ${key}`\n}\n\nfunction scannedLineFingerprint(line: string): string {\n const publicKey = scannedLinePublicKey(line)\n const [, key] = publicKey.split(\" \")\n return computeFingerprint(Buffer.from(key, \"base64\"))\n}\n\nfunction lineMatchesTrustAnchor(line: string, options: KnownHostsOptions): boolean {\n const normalizedExpectedKey =\n options.publicKey == null ? null : normalizePublicKey(options.publicKey)\n const expectedFingerprint = options.expectedFingerprint\n\n const publicKeyMatches =\n normalizedExpectedKey != null && scannedLinePublicKey(line) === normalizedExpectedKey\n const fingerprintMatches =\n expectedFingerprint != null && scannedLineFingerprint(line) === expectedFingerprint\n return publicKeyMatches || fingerprintMatches\n}\n\nfunction getVerifiedScannedHostKeyLines(\n host: string,\n scannedLines: string[],\n options: KnownHostsOptions\n): string[] {\n const normalizedExpectedKey =\n options.publicKey == null ? null : normalizePublicKey(options.publicKey)\n const expectedFingerprint = options.expectedFingerprint\n\n if (normalizedExpectedKey == null && expectedFingerprint == null) {\n throw new Error(\n `ssh.knownHosts(${host}) requires expectedFingerprint or publicKey before accepting ssh-keyscan output`\n )\n }\n\n const verifiedLines = scannedLines.filter((line) => lineMatchesTrustAnchor(line, options))\n\n if (verifiedLines.length === 0) {\n throw new Error(\n `ssh.knownHosts(${host}) could not verify the scanned host key against the provided trust anchor`\n )\n }\n\n return verifiedLines\n}\n\nfunction hasKnownHostsTrustAnchor(options?: KnownHostsOptions): boolean {\n return options?.expectedFingerprint != null || options?.publicKey != null\n}\n\nfunction knownHostsLookupTarget(host: string, options?: KnownHostsOptions): string {\n const port = options?.port\n if (port == null || port === DEFAULT_SSH_PORT) return host\n return `[${host}]:${port}`\n}\n\nfunction sshKeyscanCommand(host: string, options?: KnownHostsOptions): string {\n const port = options?.port\n if (port == null || port === DEFAULT_SSH_PORT) {\n return `ssh-keyscan -H ${shellQuote(host)} 2>/dev/null`\n }\n return `ssh-keyscan -p ${port} -H ${shellQuote(host)} 2>/dev/null`\n}\n\nasync function hasMatchingKnownHostTrustAnchor(\n conn: SshConnection,\n host: string,\n options: KnownHostsOptions\n): Promise<boolean> {\n const knownHostOutput = await conn.output(\n `ssh-keygen -F ${shellQuote(knownHostsLookupTarget(host, options))}`\n )\n const knownHostLines = parseHostKeyLines(knownHostOutput)\n\n if (knownHostLines.length === 0) return false\n\n return knownHostLines.some((line) => lineMatchesTrustAnchor(line, options))\n}\n\n/**\n * Modules for managing SSH client-side resources such as known hosts\n * and authorized keys.\n */\nexport const ssh = {\n /**\n * Ensure a public key is present in (or absent from) a user's `authorized_keys` file.\n *\n * When `state` is `\"present\"` (the default), the key is appended if missing and the\n * `.ssh` directory and `authorized_keys` file are created with correct ownership\n * and permissions. When `state` is `\"absent\"`, the matching line is removed.\n *\n * @param user - The target user whose `authorized_keys` file is managed.\n * @param key - The full public key string (e.g. `\"ssh-ed25519 AAAA... comment\"`).\n * @param options - Optional settings.\n * @param options.state - Whether the key should be `\"present\"` or `\"absent\"`. Defaults to `\"present\"`.\n * @returns A Module that manages the authorized key entry.\n */\n authorizedKeys(user: string, key: string, options?: { state?: \"absent\" | \"present\" }): Module {\n const state = options?.state ?? \"present\"\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n return applyAuthorizedKeys(conn, { key, state, user })\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n return checkAuthorizedKeys(conn, { key, state, user })\n },\n name: `ssh.authorizedKeys: ${user} (${state})`,\n }\n },\n\n /**\n * Ensure a host is present in (or absent from) the connecting user's `~/.ssh/known_hosts`.\n *\n * When `state` is `\"present\"` (the default), the host's public keys are fetched\n * via `ssh-keyscan` and appended to `~/.ssh/known_hosts`. When `state` is\n * `\"absent\"`, the host entry is removed via `ssh-keygen -R`.\n *\n * @param host - The hostname or IP address to manage.\n * @param options - Optional settings.\n * @param options.state - Whether the host should be `\"present\"` or `\"absent\"`. Defaults to `\"present\"`.\n * @returns A Module that manages the known hosts entry.\n */\n knownHosts(host: string, options?: KnownHostsOptions): Module {\n const state = options?.state ?? \"present\"\n const lookupTarget = knownHostsLookupTarget(host, options)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[ssh.knownHosts: ${host} (${state})] SSH connection is required`)\n\n if (state === \"present\") {\n const scannedOutput = await conn.output(sshKeyscanCommand(host, options))\n const scannedLines = parseHostKeyLines(scannedOutput)\n const verifiedLines = getVerifiedScannedHostKeyLines(host, scannedLines, options ?? {})\n await conn.exec(\"mkdir -p ~/.ssh && chmod 700 ~/.ssh\", { silent: true })\n await conn.exec(\n `printf '%s\\\\n' ${verifiedLines.map((line) => shellQuote(line)).join(\" \")} >> ~/.ssh/known_hosts`,\n { silent: true }\n )\n } else {\n await conn.exec(`ssh-keygen -R ${shellQuote(lookupTarget)}`, { silent: true })\n }\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n if (state === \"present\" && hasKnownHostsTrustAnchor(options)) {\n return (await hasMatchingKnownHostTrustAnchor(conn, host, options ?? {}))\n ? \"ok\"\n : NEEDS_APPLY\n }\n\n const hostKnown = await conn.test(`ssh-keygen -F ${shellQuote(lookupTarget)}`)\n\n if (state === \"present\") {\n return hostKnown ? \"ok\" : NEEDS_APPLY\n }\n return hostKnown ? NEEDS_APPLY : \"ok\"\n },\n name: `ssh.knownHosts: ${host} (${state})`,\n }\n },\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport { sshdPortMeta } from \"../meta.js\"\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport { isValidTcpPort } from \"../serverDefinitionValidation.js\"\nimport {\n guardedWriteFile,\n type Module,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\n\nconst DEFAULT_SSH_PORT = 22\nconst PRIVILEGE_SEPARATION_DIRECTORY = \"/run/sshd\"\nconst SSHD_CONFIG_PATH = \"/etc/ssh/sshd_config\"\nconst SSHD_CONFIG_MODE = \"0644\"\nconst SYSTEMCTL = \"systemctl\"\n\n// prettier-ignore\nconst REGEXP_SPECIAL = new Set([\"?\", \".\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\", \"*\", \"\\\\\", \"^\", \"+\", \"|\", \"$\"])\n\nfunction escapeRegExp(s: string): string {\n let result = \"\"\n for (const ch of s) {\n result += REGEXP_SPECIAL.has(ch) ? `\\\\${ch}` : ch\n }\n return result\n}\n\nasync function validateSshdConfig(ssh: SshConnection, originalConfig: string): Promise<void> {\n await ensurePrivilegeSeparationDirectory(ssh)\n const result = await ssh.exec(\"sshd -t\", { ignoreExitCode: true, silent: true })\n if (result.code !== 0) {\n // Intentional: unguarded write — restoring the original config is more\n // important than concurrency safety during a failed validation rollback.\n await ssh.writeFile(SSHD_CONFIG_PATH, originalConfig, { mode: SSHD_CONFIG_MODE })\n throw new Error(\n `sshd config validation failed (sshd -t), rolled back to previous config:\\n${result.stderr}`\n )\n }\n}\n\nasync function ensurePrivilegeSeparationDirectory(ssh: SshConnection): Promise<void> {\n await ssh.exec(`mkdir -p '${PRIVILEGE_SEPARATION_DIRECTORY}'`, {\n ignoreExitCode: false,\n silent: true,\n })\n}\n\nasync function disableSocketActivatedSsh(ssh: SshConnection): Promise<void> {\n const socketExists = await ssh.exec(\"systemctl cat ssh.socket >/dev/null 2>&1\", {\n ignoreExitCode: true,\n silent: true,\n })\n if (socketExists.code !== 0) return\n\n await ssh.exec(\"systemctl disable --now ssh.socket\", {\n ignoreExitCode: false,\n silent: true,\n })\n}\n\nasync function resolveSshServiceUnit(ssh: SshConnection): Promise<\"ssh\" | \"sshd\"> {\n const sshdExists = await ssh.exec(`${SYSTEMCTL} cat sshd.service >/dev/null 2>&1`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (sshdExists.code === 0) {\n return \"sshd\"\n }\n\n const sshExists = await ssh.exec(`${SYSTEMCTL} cat ssh.service >/dev/null 2>&1`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (sshExists.code === 0) {\n return \"ssh\"\n }\n\n throw new Error(\n \"[sshd] could not find a systemd SSH service unit (tried sshd.service and ssh.service)\"\n )\n}\n\nasync function reloadSshd(ssh: SshConnection): Promise<ModuleResult> {\n const serviceUnit = await resolveSshServiceUnit(ssh)\n const result = await ssh.exec(`${SYSTEMCTL} reload ${serviceUnit}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[sshd.config] systemctl reload ${serviceUnit} failed`, result)\n}\n\nasync function validateProspectiveSshdConfig(\n ssh: SshConnection,\n content: string\n): Promise<ModuleResult | undefined> {\n const temporaryConfigPath = `/tmp/paratix-sshd-dry-run-${randomUUID()}.conf`\n try {\n await ssh.writeFile(temporaryConfigPath, content, { mode: SSHD_CONFIG_MODE })\n await ensurePrivilegeSeparationDirectory(ssh)\n const result = await ssh.exec(`sshd -t -f '${temporaryConfigPath}'`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (result.code === 0) {\n return undefined\n }\n return failedCommand(\"[sshd dry-run] sshd -t failed for prospective config\", result)\n } finally {\n await ssh.exec(`rm -f '${temporaryConfigPath}'`, {\n ignoreExitCode: true,\n silent: true,\n })\n }\n}\n\nasync function dryRunSshdConfig(\n ssh: SshConnection,\n newContent: string,\n successDetail: string\n): Promise<ModuleResult> {\n const validationFailure = await validateProspectiveSshdConfig(ssh, newContent)\n if (validationFailure != null) {\n return validationFailure\n }\n\n return {\n _dryRunDetail: successDetail,\n status: \"changed\",\n }\n}\n\nfunction applySshdSettingToContent(content: string, key: string, value: string): string {\n // eslint-disable-next-line security/detect-non-literal-regexp\n const pattern = new RegExp(`^${escapeRegExp(key)}\\\\s.*`, \"gmv\")\n const replaced = content.replace(pattern, `${key} ${value}`)\n\n if (replaced !== content) {\n return replaced\n }\n // eslint-disable-next-line security/detect-non-literal-regexp\n if (new RegExp(`^${escapeRegExp(key)}\\\\s`, \"mv\").test(content)) {\n return content\n }\n return content.endsWith(\"\\n\") ? `${content}${key} ${value}\\n` : `${content}\\n${key} ${value}\\n`\n}\n\nfunction isRestartDisconnect(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error)\n return (\n message.includes(\"SSH connection closed\") ||\n message.includes(\"ECONNRESET\") ||\n message.includes(\"Connection reset\")\n )\n}\n\nfunction buildSshdConfigContent(\n originalConfig: string,\n settings: Record<string, string>\n): { didChange: boolean; newContent: string } {\n let newContent = originalConfig\n for (const [key, value] of Object.entries(settings)) {\n newContent = applySshdSettingToContent(newContent, key, value)\n }\n return { didChange: newContent !== originalConfig, newContent }\n}\n\nfunction buildSshdPortContent(\n originalConfig: string,\n targetPort: number\n): { didChange: boolean; newContent: string } {\n return buildSshdConfigContent(originalConfig, { Port: String(targetPort) })\n}\n\nasync function applySshdPort(ssh: SshConnection, targetPort: number): Promise<ModuleResult> {\n const originalConfig = await ssh.readFile(SSHD_CONFIG_PATH)\n const { didChange, newContent } = buildSshdPortContent(originalConfig, targetPort)\n if (!didChange) {\n return { status: \"ok\" }\n }\n\n await guardedWriteFile(ssh, {\n mode: SSHD_CONFIG_MODE,\n newContent,\n originalContent: originalConfig,\n remotePath: SSHD_CONFIG_PATH,\n })\n await validateSshdConfig(ssh, originalConfig)\n ssh.addPort(targetPort)\n try {\n await disableSocketActivatedSsh(ssh)\n const serviceUnit = await resolveSshServiceUnit(ssh)\n await ssh.exec(`${SYSTEMCTL} restart ${serviceUnit}`, { silent: true })\n } catch (error) {\n if (!isRestartDisconnect(error)) {\n ssh.removePort(targetPort)\n }\n throw error\n }\n\n return {\n meta: [sshdPortMeta(targetPort)],\n status: \"changed\",\n }\n}\n\n/**\n * Modules for managing the OpenSSH daemon configuration (`/etc/ssh/sshd_config`).\n * All methods restart or reload `sshd` after applying changes.\n */\nexport const sshd = {\n /**\n * Apply one or more key-value settings to `sshd_config`.\n * Existing directives are updated in-place; missing directives are appended.\n *\n * @param settings - A map of sshd_config directive names to their desired values\n * (e.g. `{ PasswordAuthentication: \"no\" }`).\n * @returns A Module that applies the sshd configuration settings.\n */\n config(settings: Record<string, string>): Module {\n const settingNames = Object.keys(settings).join(\", \")\n return {\n async _applyDryRun(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[sshd.config: ${settingNames}] SSH connection is required`)\n\n const originalConfig = await ssh.readFile(SSHD_CONFIG_PATH)\n const { didChange, newContent } = buildSshdConfigContent(originalConfig, settings)\n if (!didChange) {\n return { status: \"ok\" }\n }\n\n return dryRunSshdConfig(ssh, newContent, \"(dry-run, sshd -t ok; reload not executed)\")\n },\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[sshd.config: ${settingNames}] SSH connection is required`)\n\n const originalConfig = await ssh.readFile(SSHD_CONFIG_PATH)\n const { didChange, newContent } = buildSshdConfigContent(originalConfig, settings)\n if (didChange) {\n await guardedWriteFile(ssh, {\n mode: SSHD_CONFIG_MODE,\n newContent,\n originalContent: originalConfig,\n remotePath: SSHD_CONFIG_PATH,\n })\n }\n\n await validateSshdConfig(ssh, originalConfig)\n\n if (!didChange) {\n return { status: \"ok\" }\n }\n\n return reloadSshd(ssh)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const content = await ssh.readFile(SSHD_CONFIG_PATH)\n for (const [key, value] of Object.entries(settings)) {\n // eslint-disable-next-line security/detect-non-literal-regexp\n const pattern = new RegExp(`^${escapeRegExp(key)}\\\\s+${escapeRegExp(value)}$`, \"mv\")\n if (!pattern.test(content)) {\n return NEEDS_APPLY\n }\n }\n return \"ok\"\n },\n name: `sshd.config: ${settingNames}`,\n }\n },\n\n /**\n * Set the SSH daemon listen port.\n * Updates the `Port` directive in `sshd_config` and restarts `sshd`.\n * The new port is exported as `sshd.port` in the module result meta so\n * the runner can reconnect on the updated port.\n *\n * @param targetPort - The port number sshd should listen on.\n * @returns A Module that sets the sshd listen port.\n */\n port(targetPort: number): Module {\n if (!isValidTcpPort(targetPort)) {\n throw new Error(\n `sshd.port requires an integer port between 1 and 65535, got ${String(targetPort)}`\n )\n }\n\n return {\n async _applyDryRun(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[sshd.port: ${targetPort}] SSH connection is required`)\n\n const originalConfig = await ssh.readFile(SSHD_CONFIG_PATH)\n const { didChange, newContent } = buildSshdPortContent(originalConfig, targetPort)\n if (!didChange) {\n return { status: \"ok\" }\n }\n\n return dryRunSshdConfig(\n ssh,\n newContent,\n \"(dry-run, sshd -t ok; restart, port switch, firewall and reconnect not verified)\"\n )\n },\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[sshd.port: ${targetPort}] SSH connection is required`)\n return applySshdPort(ssh, targetPort)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const content = await ssh.readFile(SSHD_CONFIG_PATH)\n // eslint-disable-next-line security/detect-non-literal-regexp\n const pattern = new RegExp(`^Port\\\\s+${String(targetPort)}$`, \"mv\")\n if (pattern.test(content)) return \"ok\"\n // When no Port directive exists, sshd defaults to port 22\n if (targetPort === DEFAULT_SSH_PORT && !/^Port\\s/mv.test(content)) return \"ok\"\n return NEEDS_APPLY\n },\n name: `sshd.port: ${targetPort}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\nconst SYSCTL_DIR = \"/etc/sysctl.d\"\nconst SYSCTL_CONFIG_MODE = \"0644\"\n\n/**\n * Sanitize a sysctl key for safe use in a filesystem path.\n * Replaces `.` with `-` so the key can be embedded in a filename without\n * creating accidental sub-directories or conflicting with file extensions.\n *\n * @param key - The sysctl key (e.g. \"net.ipv4.ip_forward\").\n * @returns The sanitized string (e.g. \"net-ipv4-ip_forward\").\n */\nfunction sanitizeKey(key: string): string {\n return key.replaceAll(\".\", \"-\")\n}\n\n/**\n * Build the content of a sysctl.d configuration file.\n * The trailing newline is required by the sysctl.d(5) format.\n *\n * @param key - The sysctl parameter name (e.g. \"net.ipv4.ip_forward\").\n * @param value - The desired value to assign to the parameter.\n * @returns The file content in `key = value\\n` format.\n */\nfunction buildSysctlConfig(key: string, value: string): string {\n return `${key} = ${value}\\n`\n}\n\n/**\n * Modules for managing kernel parameters via sysctl on the remote host.\n */\nexport const sysctl = {\n /**\n * Set a sysctl kernel parameter and persist it across reboots.\n *\n * The live value is applied immediately via `sysctl -w` and a configuration\n * file is written to `/etc/sysctl.d/99-paratix-<sanitized-key>.conf` for\n * persistence.\n *\n * When `state` is `\"absent\"`, the persistence file is removed but the live\n * value is not reverted (a reboot will restore the default).\n *\n * @param key - The sysctl key (e.g. \"net.ipv4.ip_forward\").\n * @param value - The desired value (e.g. \"1\").\n * @param options - Optional settings.\n * @param options.state - Whether the parameter should be \"present\" (default) or \"absent\".\n * @returns A Module that manages the sysctl parameter.\n */\n set(key: string, value: string, options?: { state?: \"absent\" | \"present\" }): Module {\n const state = options?.state ?? \"present\"\n const configPath = `${SYSCTL_DIR}/99-paratix-${sanitizeKey(key)}.conf`\n const expectedContent = buildSysctlConfig(key, value)\n\n return {\n async apply(conn: null | SshConnection): Promise<ModuleResult> {\n if (!conn) return failed(`[sysctl.set: ${key}] SSH connection is required`)\n\n if (state === \"present\") {\n const assignment = `${key}=${value}`\n const result = await conn.exec(`sysctl -w ${shellQuote(assignment)}`, EXEC_OPTS)\n if (result.code !== 0) {\n return failedCommand(`[sysctl.set: ${key}] sysctl -w failed`, result)\n }\n await conn.writeFile(configPath, expectedContent, { mode: SYSCTL_CONFIG_MODE })\n } else {\n await conn.exec(`rm -f ${shellQuote(configPath)}`, EXEC_OPTS)\n }\n\n return { status: \"changed\" }\n },\n async check(conn: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!conn) return NEEDS_APPLY\n\n if (state === \"present\") {\n const result = await conn.exec(`sysctl -n ${shellQuote(key)}`, EXEC_OPTS)\n const currentValue = result.stdout.trim()\n if (currentValue !== value) return NEEDS_APPLY\n\n const configExists = await conn.exec(`test -f ${shellQuote(configPath)}`, EXEC_OPTS)\n if (configExists.code !== 0) return NEEDS_APPLY\n\n const fileContent = await conn.readFile(configPath)\n return fileContent.trim() === expectedContent.trim() ? \"ok\" : NEEDS_APPLY\n }\n\n const fileExists = await conn.exec(`test -f ${shellQuote(configPath)}`, EXEC_OPTS)\n return fileExists.code === 0 ? NEEDS_APPLY : \"ok\"\n },\n name: state === \"present\" ? `sysctl.set: ${key}=${value}` : `sysctl.set: absent ${key}`,\n }\n },\n}\n","import { environmentToMetaEntries, meta } from \"../meta.js\"\nimport { failed, failedCommand } from \"../moduleFailure.js\"\nimport {\n type Module,\n type ModuleMetaEntry,\n type ModuleResult,\n NEEDS_APPLY,\n type SshConnection,\n} from \"../types.js\"\n\n/**\n * Options for the reboot module.\n */\nexport type RebootOptions = {\n /**\n * Optional async function to resolve the new host address after a reboot.\n * Useful when the server's IP address may change (e.g. DHCP or cloud environments).\n */\n resolveHost?: () => Promise<string>\n}\n\n/**\n * Parse the contents of /etc/os-release into a key-value record.\n *\n * @param content - The raw file content.\n * @returns A record of key-value pairs.\n */\nfunction parseOsRelease(content: string): Partial<Record<string, string>> {\n const result: Record<string, string> = {}\n for (const line of content.split(\"\\n\")) {\n const eqIndex = line.indexOf(\"=\")\n if (eqIndex === -1) continue\n const key = line.slice(0, eqIndex)\n const value = line.slice(eqIndex + 1).replaceAll(/^\"|\"$/gv, \"\")\n result[key] = value\n }\n return result\n}\n\n/**\n * Find the first RFC-1918 private IP address in `ip -4 addr` output.\n *\n * @param output - The raw output of `ip -4 addr`.\n * @returns The first private IP found, or `undefined`.\n */\nfunction findPrivateIp(output: string): string | undefined {\n const inetPattern = /inet\\s+(?<addr>\\d+\\.\\d+\\.\\d+\\.\\d+)/gv\n let match\n while ((match = inetPattern.exec(output)) !== null) {\n const ip = match.groups?.addr\n if (ip == null) continue\n if (\n ip.startsWith(\"10.\") ||\n ip.startsWith(\"192.168.\") ||\n /^172\\.(?:1[6-9]|2\\d|3[01])\\./v.test(ip)\n ) {\n return ip\n }\n }\n return undefined\n}\n\nconst FACTS_EXEC_OPTS = { ignoreExitCode: true, silent: true } as const\n\n/**\n * Execute a command and return its stdout, or `null` on failure.\n *\n * @param ssh - Active SSH connection.\n * @param cmd - The command to execute.\n * @returns The raw stdout or `null` on non-zero exit.\n */\nasync function execFact(ssh: SshConnection, cmd: string): Promise<null | string> {\n const result = await ssh.exec(cmd, FACTS_EXEC_OPTS)\n return result.code === 0 ? result.stdout : null\n}\n\n/**\n * Run all fact commands and return an array of raw outputs in order, or `null`\n * if any command fails.\n *\n * @param ssh - Active SSH connection.\n * @returns The raw outputs array or `null` on failure.\n */\nasync function runFactCommands(ssh: SshConnection): Promise<null | string[]> {\n const commands = [\n \"cat /etc/os-release\",\n \"uname -m\",\n \"hostname\",\n \"uname -r\",\n \"free -m\",\n \"nproc\",\n \"ip -4 route get 1.1.1.1\",\n \"ip -4 addr\",\n \"df -m /\",\n ]\n\n const results: string[] = []\n for (const cmd of commands) {\n // eslint-disable-next-line no-await-in-loop\n const output = await execFact(ssh, cmd)\n if (output === null) return null\n results.push(output)\n }\n\n return results\n}\n\n/**\n * Extract the total RAM in MB from `free -m` output.\n *\n * @param freeOutput - Raw output of `free -m`.\n * @returns The total RAM string or empty string.\n */\nfunction parseRamTotal(freeOutput: string): string {\n const memLine = freeOutput.split(\"\\n\").find((line) => line.startsWith(\"Mem:\"))\n return memLine?.trim().split(/\\s+/v)[1] ?? \"\"\n}\n\n/**\n * Extract the root disk size in MB from `df -m /` output.\n *\n * @param dfOutput - Raw output of `df -m /`.\n * @returns The disk size string or empty string.\n */\nfunction parseDiskRoot(dfOutput: string): string {\n const dataLine = dfOutput.split(\"\\n\")[1] ?? \"\"\n return dataLine.trim().split(/\\s+/v)[1] ?? \"\"\n}\n\n/**\n * Extract the public IP from `ip -4 route get` output.\n *\n * @param routeOutput - Raw output of `ip -4 route get 1.1.1.1`.\n * @returns The public IP string or empty string.\n */\nfunction parsePublicIp(routeOutput: string): string {\n const sourceMatch = /src\\s+(?<addr>\\d+\\.\\d+\\.\\d+\\.\\d+)/v.exec(routeOutput)\n return sourceMatch?.groups?.addr ?? \"\"\n}\n\n/**\n * Parse raw fact command outputs into a meta record.\n *\n * @param outputs - The 9 raw command outputs in order.\n * @returns A record of system fact meta keys.\n */\nfunction parseFacts(outputs: string[]): Record<string, string> {\n const osInfo = parseOsRelease(outputs[0])\n\n return {\n \"system.arch\": outputs[1].trim(),\n \"system.cpu.cores\": outputs[5].trim(),\n \"system.disk.root\": parseDiskRoot(outputs[8]),\n \"system.hostname\": outputs[2].trim(),\n \"system.ip.private\": findPrivateIp(outputs[7]) ?? \"\",\n \"system.ip.public\": parsePublicIp(outputs[6]),\n \"system.kernel\": outputs[3].trim(),\n \"system.os\": osInfo.ID ?? \"\",\n \"system.os.codename\": osInfo.VERSION_CODENAME ?? \"\",\n \"system.os.version\": osInfo.VERSION_ID ?? \"\",\n \"system.ram.total\": parseRamTotal(outputs[4]),\n }\n}\n\n/**\n * Modules for managing system-level operations.\n */\nexport const system = {\n /**\n * Collect system facts (OS, architecture, hostname, kernel, RAM, CPU, IPs, disk).\n * Always applies because it is informational and should always report.\n *\n * All gathered values are emitted as `system.*` meta keys.\n *\n * @returns A Module that collects system facts.\n */\n facts(): Module {\n return {\n _dryRunMetaProducer: true,\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[system.facts] SSH connection is required\")\n\n const outputs = await runFactCommands(ssh)\n if (outputs === null) return failed(\"[system.facts] failed to collect system facts\")\n\n return { meta: environmentToMetaEntries(parseFacts(outputs)), status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: \"system.facts\",\n }\n },\n\n /**\n * Reboot the remote system via `shutdown -r now`.\n * Always applies because a reboot is an imperative action.\n *\n * When `resolveHost` is provided, the resolved address is emitted as\n * `system.host` meta so the runner can update the SSH connection.\n * The `system.reboot` meta signal is always set to `\"true\"`.\n *\n * @param options - Optional settings including a host resolver.\n * @returns A Module that reboots the system.\n */\n reboot(options: RebootOptions = {}): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[system.reboot] SSH connection is required\")\n\n try {\n const result = await ssh.exec(\"shutdown -r now\", { ignoreExitCode: true, silent: true })\n if (result.code !== 0) {\n return failedCommand(\"[system.reboot] shutdown -r now failed\", result)\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return failed(`[system.reboot] shutdown -r now failed\\n${message}`)\n }\n\n const entries: ModuleMetaEntry[] = [meta.systemReboot()]\n\n if (options.resolveHost != null) {\n try {\n const newHost = await options.resolveHost()\n entries.push(meta.systemHost(newHost))\n } catch {\n // resolveHost failed — reconnect will use current host\n }\n }\n\n return { meta: entries, status: \"changed\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: \"system.reboot\",\n }\n },\n\n /**\n * Read the system uptime in seconds from `/proc/uptime`.\n * Always applies because it is informational and should always report.\n *\n * The uptime value is emitted as `system.uptime` meta.\n *\n * @returns A Module that reads the system uptime.\n */\n uptime(): Module {\n return {\n _dryRunMetaProducer: true,\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[system.uptime] SSH connection is required\")\n\n const seconds = await ssh.output(\"awk '{print int($1)}' /proc/uptime\")\n\n return { meta: [meta.env(\"system.uptime\", seconds)], status: \"ok\" }\n },\n // eslint-disable-next-line @typescript-eslint/require-await\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n return NEEDS_APPLY\n },\n name: \"system.uptime\",\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\nconst SYSTEMCTL = \"systemctl\"\nconst UNIT_NAME_PATTERN = /^[\\w@.\\-]+$/v\nconst SYSTEMD_UNIT_MODE = \"0644\"\n\n/**\n * Modules for managing systemd unit files and unit masking.\n *\n * The `unit` method writes unit files with idempotent checks and triggers a daemon-reload\n * on change. `masked` and `unmasked` control unit masking state.\n * `daemonReload` is a signal-style method that always applies.\n */\nexport const systemd = {\n /**\n * Reload the systemd manager configuration. Intended as a recipe signal --\n * always applies.\n * @returns A Module that runs `systemctl daemon-reload`.\n */\n daemonReload(): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[systemd.daemonReload] SSH connection is required\")\n const result = await ssh.exec(`${SYSTEMCTL} daemon-reload`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(\"[systemd.daemonReload] systemctl daemon-reload failed\", result)\n },\n // eslint-disable-next-line @typescript-eslint/require-await -- Interface requires async\n async check(): Promise<\"needs-apply\" | \"ok\"> {\n // Signal: always needs apply\n return NEEDS_APPLY\n },\n name: \"systemd.daemonReload\",\n }\n },\n\n /**\n * Ensure a systemd unit is masked and cannot be started.\n * @param name - The systemd unit name (e.g. `\"apt-daily.timer\"`).\n * @returns A Module that ensures the unit is masked.\n */\n masked(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[systemd.masked: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} mask ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[systemd.masked: ${name}] systemctl mask failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const result = await ssh.exec(`${SYSTEMCTL} is-enabled ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.stdout.trim().includes(\"masked\") ? \"ok\" : NEEDS_APPLY\n },\n name: `systemd.masked: ${name}`,\n }\n },\n\n /**\n * Write a systemd unit file to `/etc/systemd/system/` and reload the\n * daemon configuration when the content changes.\n *\n * The check phase compares the remote file content with the desired\n * content string. Apply writes the file and runs `systemctl daemon-reload`.\n *\n * @param name - Unit file name (e.g. `\"my-app.service\"` or `\"backup.timer\"`).\n * @param content - The full unit file content.\n * @returns A Module that ensures the unit file is present with the given content.\n */\n unit(name: string, content: string): Module {\n if (!UNIT_NAME_PATTERN.test(name)) {\n throw new Error(`systemd.unit: name must match ${String(UNIT_NAME_PATTERN)}, got: ${name}`)\n }\n const filePath = `/etc/systemd/system/${name}`\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[systemd.unit: ${name}] SSH connection is required`)\n await ssh.writeFile(filePath, content, { mode: SYSTEMD_UNIT_MODE })\n const result = await ssh.exec(`${SYSTEMCTL} daemon-reload`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[systemd.unit: ${name}] systemctl daemon-reload failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const exists = await ssh.exists(filePath)\n if (!exists) return NEEDS_APPLY\n const remoteContent = await ssh.readFile(filePath)\n return remoteContent.trim() === content.trim() ? \"ok\" : NEEDS_APPLY\n },\n name: `systemd.unit: ${name}`,\n }\n },\n\n /**\n * Ensure a systemd unit is unmasked and can be started normally.\n * @param name - The systemd unit name (e.g. `\"apt-daily.timer\"`).\n * @returns A Module that ensures the unit is unmasked.\n */\n unmasked(name: string): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[systemd.unmasked: ${name}] SSH connection is required`)\n const result = await ssh.exec(`${SYSTEMCTL} unmask ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[systemd.unmasked: ${name}] systemctl unmask failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const result = await ssh.exec(`${SYSTEMCTL} is-enabled ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.stdout.trim().includes(\"masked\") ? NEEDS_APPLY : \"ok\"\n },\n name: `systemd.unmasked: ${name}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\nimport { detectPackageManager, isPackageInstalled } from \"./package.js\"\n\nconst UFW = \"ufw\"\n\n/**\n * Modules for managing the UFW (Uncomplicated Firewall) on Debian/Ubuntu hosts.\n */\nexport const ufw = {\n /**\n * Ensure UFW is inactive. If UFW is not installed, this is treated as already satisfied.\n *\n * @returns A Module that ensures UFW is disabled.\n */\n disabled(): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[ufw.disabled] SSH connection is required\")\n const pm = await detectPackageManager(ssh)\n if (pm == null || !(await isPackageInstalled(ssh, pm, UFW))) {\n return { status: \"ok\" }\n }\n\n const result = await ssh.exec(`${UFW} --force disable`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(\"[ufw.disabled] ufw disable failed\", result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const pm = await detectPackageManager(ssh)\n if (pm == null || !(await isPackageInstalled(ssh, pm, UFW))) {\n return \"ok\"\n }\n\n const status = await ssh.output(`${UFW} status`)\n return status.includes(\"Status: inactive\") ? \"ok\" : NEEDS_APPLY\n },\n name: \"ufw.disabled\",\n }\n },\n\n /**\n * Ensure UFW is active. Enables the firewall non-interactively if not already running.\n *\n * @returns A Module that ensures UFW is enabled.\n */\n enabled(): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(\"[ufw.enabled] SSH connection is required\")\n const result = await ssh.exec(`echo 'y' | ${UFW} enable`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(\"[ufw.enabled] ufw enable failed\", result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n const status = await ssh.output(`${UFW} status`)\n return status.includes(\"Status: active\") ? \"ok\" : NEEDS_APPLY\n },\n name: \"ufw.enabled\",\n }\n },\n\n /**\n * Add an allow or deny rule for one or more ports.\n * The check phase reads `ufw status` and verifies the expected rule is present.\n *\n * @param action - Whether to `\"allow\"` or `\"deny\"` traffic on the given ports.\n * @param ports - A single port number or an array of port numbers.\n * @returns A Module that manages UFW rules.\n */\n rule(action: \"allow\" | \"deny\", ports: number | number[]): Module {\n const portList = Array.isArray(ports) ? ports : [ports]\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh)\n return failed(`[ufw.rule: ${action} ${portList.join(\",\")}] SSH connection is required`)\n\n for (const port of portList) {\n // eslint-disable-next-line no-await-in-loop\n const result = await ssh.exec(\n `${UFW} ${shellQuote(action)} ${shellQuote(String(port))}`,\n { ignoreExitCode: true, silent: true }\n )\n if (result.code !== 0) {\n return failedCommand(\n `[ufw.rule: ${action} ${portList.join(\",\")}] ufw ${action} failed for port ${String(port)}`,\n result\n )\n }\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n\n const status = await ssh.output(`${UFW} status`)\n for (const port of portList) {\n const expectedAction = action === \"allow\" ? \"ALLOW\" : \"DENY\"\n // eslint-disable-next-line security/detect-non-literal-regexp\n const pattern = new RegExp(`${port}\\\\s+${expectedAction}`, \"v\")\n if (!pattern.test(status)) {\n return NEEDS_APPLY\n }\n }\n return \"ok\"\n },\n name: `ufw.rule: ${action} ${portList.join(\",\")}`,\n }\n },\n}\n","import { failed, failedCommand } from \"../moduleFailure.js\"\nimport { shellQuote } from \"../ssh.js\"\nimport { type Module, type ModuleResult, NEEDS_APPLY, type SshConnection } from \"../types.js\"\n\ntype UserOptions = {\n groups?: string[]\n home?: string\n password?: string\n shell?: string\n uid?: number\n}\n\nconst ID_CMD = \"id\"\n\nfunction buildUserArguments(options?: UserOptions): string[] {\n const flags: string[] = []\n if (options?.uid != null) flags.push(`--uid ${String(options.uid)}`)\n if (options?.shell != null) flags.push(`--shell ${shellQuote(options.shell)}`)\n if (options?.home != null) flags.push(`--home ${shellQuote(options.home)}`)\n if (options?.groups != null) flags.push(`--groups ${shellQuote(options.groups.join(\",\"))}`)\n return flags\n}\n\nasync function setPassword(\n ssh: SshConnection,\n name: string,\n password: string\n): Promise<ModuleResult | null> {\n const credential = [name, password].join(\":\")\n const pwResult = await ssh.exec(`printf '%s\\\\n' ${shellQuote(credential)} | chpasswd -e`, {\n ignoreExitCode: true,\n silent: true,\n })\n if (pwResult.code !== 0) {\n return failedCommand(`[user.present: ${name}] chpasswd -e failed`, pwResult)\n }\n return null\n}\n\nfunction parsePasswdEntry(entry: string): { home: string; shell: string; uid: string } {\n const fields = entry.split(\":\")\n return { home: fields[5] ?? \"\", shell: fields[6] ?? \"\", uid: fields[2] ?? \"\" }\n}\n\nfunction groupsEqual(actual: Set<string>, desired: string[]): boolean {\n const expected = new Set(desired)\n return actual.size === expected.size && [...expected].every((g) => actual.has(g))\n}\n\nasync function supplementaryGroupsMatch(\n ssh: SshConnection,\n name: string,\n desiredGroups: string[]\n): Promise<boolean> {\n const groupOutput = await ssh.output(`${ID_CMD} -Gn ${shellQuote(name)}`)\n const primaryGroup = await ssh.output(`${ID_CMD} -gn ${shellQuote(name)}`)\n const actualSupplementaryGroups = new Set(\n groupOutput\n .split(/\\s+/v)\n .filter(Boolean)\n .filter((group) => group !== primaryGroup)\n )\n return groupsEqual(actualSupplementaryGroups, desiredGroups)\n}\n\nasync function passwdAttributesMatch(\n ssh: SshConnection,\n name: string,\n options: UserOptions\n): Promise<boolean> {\n const parsed = parsePasswdEntry(await ssh.output(`getent passwd ${shellQuote(name)}`))\n if (options.uid != null && parsed.uid !== String(options.uid)) return false\n if (options.home != null && parsed.home !== options.home) return false\n if (options.shell != null && parsed.shell !== options.shell) return false\n return true\n}\n\nasync function shadowHashMatches(\n ssh: SshConnection,\n name: string,\n password: string\n): Promise<boolean> {\n const shadowEntry = await ssh.output(`getent shadow ${shellQuote(name)}`)\n const currentHash = shadowEntry.split(\":\")[1] ?? \"\"\n return currentHash === password\n}\n\nasync function attributesMatch(\n ssh: SshConnection,\n name: string,\n options: UserOptions\n): Promise<boolean> {\n if (options.password != null && !(await shadowHashMatches(ssh, name, options.password))) {\n return false\n }\n\n const needsPasswdCheck = options.uid != null || options.shell != null || options.home != null\n if (needsPasswdCheck && !(await passwdAttributesMatch(ssh, name, options))) return false\n\n if (options.groups != null && !(await supplementaryGroupsMatch(ssh, name, options.groups))) {\n return false\n }\n\n return true\n}\n\n/**\n * Modules for managing Linux user accounts.\n */\nexport const user = {\n /**\n * Ensure a user account does not exist. Removes the account via `userdel`.\n *\n * @param name - The username to remove.\n * @param options - Optional removal configuration.\n * @param options.removeHome - When `true`, also delete the user's home directory.\n * @returns A Module that ensures the user account is absent.\n */\n absent(name: string, options?: { removeHome?: boolean }): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[user.absent: ${name}] SSH connection is required`)\n const removeFlag = options?.removeHome ? \"--remove\" : \"\"\n const result = await ssh.exec(`userdel ${removeFlag} ${shellQuote(name)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n return result.code === 0\n ? { status: \"changed\" }\n : failedCommand(`[user.absent: ${name}] userdel failed`, result)\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n return (await ssh.test(`${ID_CMD} ${shellQuote(name)}`)) ? NEEDS_APPLY : \"ok\"\n },\n name: `user.absent: ${name}`,\n }\n },\n\n /**\n * Ensure a user account exists. Creates the account if absent, or runs\n * `usermod` to update attributes if the user already exists.\n *\n * @param name - The username.\n * @param options - Optional user account configuration.\n * @param options.uid - Desired numeric UID.\n * @param options.shell - Login shell path (e.g. `\"/bin/bash\"`).\n * @param options.home - Home directory path.\n * @param options.groups - Supplementary groups to add the user to.\n * @param options.password - Pre-hashed password (e.g. SHA-512 `$6$...`) set via `chpasswd -e`.\n * @returns A Module that ensures the user account is present.\n */\n present(name: string, options?: UserOptions): Module {\n return {\n async apply(ssh: null | SshConnection): Promise<ModuleResult> {\n if (!ssh) return failed(`[user.present: ${name}] SSH connection is required`)\n\n const flags = buildUserArguments(options)\n const exists = await ssh.test(`${ID_CMD} ${shellQuote(name)}`)\n const cmd = exists\n ? `usermod ${flags.join(\" \")} ${shellQuote(name)}`\n : `useradd ${flags.join(\" \")} --create-home ${shellQuote(name)}`\n\n const result = await ssh.exec(cmd, { ignoreExitCode: true, silent: true })\n if (result.code !== 0) {\n return failedCommand(\n `[user.present: ${name}] ${exists ? \"usermod\" : \"useradd\"} failed`,\n result\n )\n }\n\n if (options?.password != null) {\n const failure = await setPassword(ssh, name, options.password)\n if (failure != null) return failure\n }\n\n return { status: \"changed\" }\n },\n async check(ssh: null | SshConnection): Promise<\"needs-apply\" | \"ok\"> {\n if (!ssh) return NEEDS_APPLY\n if (!(await ssh.test(`${ID_CMD} ${shellQuote(name)}`))) return NEEDS_APPLY\n if (options != null && !(await attributesMatch(ssh, name, options))) return NEEDS_APPLY\n return \"ok\"\n },\n name: `user.present: ${name}`,\n }\n },\n}\n"],"mappings":"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","names":["ssh","ssh","download","ssh","ssh","ssh","readFile","EXEC_OPTS","ssh","readFile","ssh","user","createHash","timingSafeEqual","createHash","group","timingSafeEqual","readFile","readFile","readFile","ssh","group","DEFAULT_FILE_WRITE_MODE","ssh","group","normalizeMode","resolveWriteMode","metadataMatches","readFile","EXEC_OPTS","SILENT","ssh","ssh","EXEC_OPTS","ssh","EXEC_OPTS","command","EXEC_OPTS","ssh","NONINTERACTIVE","ssh","command","firstNonEmptyLine","ssh","EXEC_OPTS","ssh","ssh","user","DEFAULT_SSH_PORT","user","randomUUID","DEFAULT_SSH_PORT","SYSTEMCTL","ssh","randomUUID","EXEC_OPTS","ssh","SYSTEMCTL","UNIT_NAME_PATTERN","SYSTEMD_UNIT_MODE","ssh","ssh","ssh","group"]}