/** * Shared helpers for the operator scripts. * This file validates env vars and runs SSH and SCP commands. * Keep workflow logic in the entrypoint files. */ import { $ } from "bun"; /** String-valued environment map. */ export type EnvSource = Readonly>; /** Remote SSH or SCP target. */ export type RemoteTarget = Readonly<{ host: string; port: number; user: string; }>; /** Shared SSH and SCP helpers. */ export type RemoteOperations = Readonly<{ scpToHost( target: RemoteTarget, sourcePath: string, destinationPath: string, ): Promise; ssh(target: RemoteTarget, command: string): Promise; }>; /** Returns a required env var. */ export function envFrom(envSource: EnvSource, name: string): string { const value = envSource[name]; if (value === undefined || value === "") { throw new Error(`Missing required env var: ${name}`); } return value; } /** Ensures every named env var is present. */ export function requireEnvVarsFrom( envSource: EnvSource, names: readonly string[], ): void { const missing = names.filter((name) => { const value = envSource[name]; return value === undefined || value === ""; }); if (missing.length > 0) { throw new Error(`Missing required env vars: ${missing.join(", ")}`); } } /** Quotes a value for a POSIX shell command. */ export function shellQuote(value: string): string { return `'${value.replaceAll("'", "'\\''")}'`; } /** Builds the `user@host` address. */ function buildRemoteAddress(target: RemoteTarget): string { return `${target.user}@${target.host}`; } /** Builds SSH arguments. */ function buildSshArgs( target: RemoteTarget, command: string, ): readonly string[] { return [ "ssh", "-p", String(target.port), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", buildRemoteAddress(target), command, ]; } /** Builds SCP arguments. */ function buildScpArgs( target: RemoteTarget, sourcePath: string, destinationPath: string, ): readonly string[] { return [ "scp", "-P", String(target.port), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", sourcePath, `${buildRemoteAddress(target)}:${destinationPath}`, ]; } /** Runs one command and throws on failure. */ async function runCommand(argv: readonly string[]): Promise { const command = await $`${argv}`.nothrow(); const exitCode = command.exitCode; if (exitCode !== 0) { throw new Error( `Command failed with exit code ${exitCode}: ${argv.join(" ")}`, ); } } /** Copies one file to the remote host. */ export async function scpToHost( target: RemoteTarget, sourcePath: string, destinationPath: string, ): Promise { await runCommand(buildScpArgs(target, sourcePath, destinationPath)); } /** Runs one shell command on the remote host. */ export async function ssh(target: RemoteTarget, command: string): Promise { await runCommand(buildSshArgs(target, command)); } /** Default SSH and SCP helpers. */ export const remoteOperations: RemoteOperations = { scpToHost, ssh, };