feat(bootstrap): postgres data-plane + remote helper (T03)

foundation-postgres (postgres:17, digest-pinned in VERSIONS) on foundation-net,
internal only (5432 unpublished); named volume foundation-postgres-data with
retainOnDelete. The forgejo login role + database are created post-boot by an
idempotent, readiness-gated remote.Command (ADR-007), since 5432 isn't reachable
from the operator. Adds the generator half of credentials.ts (@pulumi/random →
CONTRACT_002 postgres keys) and lib/remote.ts (vmConnection over the VM SSH path).

Live on cx33 Helsinki: container healthy, role 'forgejo' + db 'forgejo' present,
no published ports. Acceptance T03 met.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 21:10:34 +02:00
parent 2e11fd2448
commit 6edba60612
8 changed files with 252 additions and 13 deletions

39
bootstrap/lib/remote.ts Normal file
View file

@ -0,0 +1,39 @@
// lib/remote.ts
//
// Builds the SSH connection that @pulumi/command's remote.Command uses to run
// in-VM control-plane operations (docker exec over SSH) — ADR-007. It targets
// the SAME host the Docker provider does: the config VM coordinates, overridable
// by FOUNDATION_DOCKER_HOST (ssh://user@host[:port]) for local validation, with
// the private key read from SSH_PRIVATE_KEY_PATH (CONTRACT_001 §1) — never config.
import * as fs from "fs";
import * as pulumi from "@pulumi/pulumi";
import * as command from "@pulumi/command";
import { BaseCtx } from "./context";
/** Parsed `ssh://user@host:port` override, if FOUNDATION_DOCKER_HOST is set. */
function parseOverride():
| { host: string; user?: string; port?: number }
| undefined {
const o = process.env.FOUNDATION_DOCKER_HOST;
if (!o) return undefined;
const m = o.match(/^ssh:\/\/(?:([^@]+)@)?([^:/]+)(?::(\d+))?/);
if (!m) return undefined;
return { user: m[1], host: m[2], port: m[3] ? Number(m[3]) : undefined };
}
/**
* The remote.Command connection to the foundation VM. The private key is read
* from disk and marked secret so it is redacted in logs and encrypted in state.
*/
export function vmConnection(
ctx: BaseCtx,
): command.types.input.remote.ConnectionArgs {
const ov = parseOverride();
const privateKey = pulumi.secret(fs.readFileSync(ctx.sshKeyPath, "utf8"));
return {
host: ov?.host ?? ctx.cfg.vm.host,
port: ov?.port ?? ctx.cfg.vm.sshPort,
user: ov?.user ?? ctx.cfg.vm.user,
privateKey,
};
}