feat(bootstrap): real olsitec.net config + DNS records (steps 1+2)

CONTRACT_001 amendments: hosts.git, vm.sshPort (default 22; VM uses 222),
cloudflare.zoneId. config.ts + lib/context.ts (provider host uses sshPort).
- components/dns.ts: forge/vault/s3/git.olsitec.net A -> VM (DNS-only, own CF
  provider from encrypted token). Deployed + verified authoritative = 204.168.234.72.
- Pulumi.foundation.yaml: real config (olsitec.net, vm 204.168.234.72:222,
  letsencrypt-dns01) + encrypted secrets (cloudflare token, offsite creds).
  Master passphrase: pass olsitec-foundation/PULUMI_CONFIG_PASSPHRASE.
- run.sh: reproducible deploy (passphrase + ssh key from pass/home).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 20:47:30 +02:00
parent db47037bdc
commit 185be52763
10 changed files with 141 additions and 60 deletions

View file

@ -32,9 +32,10 @@ export interface FoundationConfig {
baseDomain: string; // "olsitec.de"
hosts: {
// public FQDNs terminated by Caddy
forge: string; // "forge.olsitec.de" (Forgejo web/API/registry)
vault: string; // "vault.olsitec.de" (Vault UI/API — internal-restricted)
s3: string; // "s3.olsitec.de" (RustFS API, optional public)
forge: string; // "forge.olsitec.net" (Forgejo web/API/registry)
vault: string; // "vault.olsitec.net" (Vault UI/API — internal-restricted)
s3: string; // "s3.olsitec.net" (RustFS API, optional public)
git: string; // "git.olsitec.net" (Git-over-SSH host; CONTRACT_001 amendment 2026-06-30)
};
forgeSshPort: number; // 2222 (git-over-ssh, published directly, not via Caddy)
@ -42,9 +43,15 @@ export interface FoundationConfig {
vm: {
host: string; // IP or DNS of the foundation VM
user: string; // ssh user (e.g. "root" or "deploy")
sshPort: number; // SSH port for the Docker-over-SSH provider (22 default; test VM uses 222)
// private key path comes from ENV SSH_PRIVATE_KEY_PATH, never config
};
// --- Cloudflare (DNS records + ACME DNS-01); token is a SECRET (§1.3) ---
cloudflare: {
zoneId: string; // olsitec.net zone id (non-secret)
};
// --- container plane (see CONTRACT_003 for names/ports) ---
network: { name: string; subnet: string }; // "foundation-net", "172.30.0.0/24"
dataRoot: string; // host path for bind mounts / named-volume root (e.g. "/srv/foundation")
@ -165,9 +172,12 @@ export function loadConfig(): FoundationConfig {
const hostsForge = reqStr("hosts.forge");
const hostsVault = reqStr("hosts.vault");
const hostsS3 = reqStr("hosts.s3");
const hostsGit = reqStr("hosts.git");
const forgeSshPort = reqNum("forgeSshPort");
const vmHost = reqStr("vm.host");
const vmUser = reqStr("vm.user");
const vmSshPort = c.getNumber("vm.sshPort") ?? 22; // optional; defaults to standard SSH
const cloudflareZoneId = reqStr("cloudflare.zoneId");
const networkName = reqStr("network.name");
const networkSubnet = reqStr("network.subnet");
const dataRoot = reqStr("dataRoot");
@ -221,9 +231,10 @@ export function loadConfig(): FoundationConfig {
// All keys present + well-typed past this point (the `!` are now sound).
return {
baseDomain: baseDomain!,
hosts: { forge: hostsForge!, vault: hostsVault!, s3: hostsS3! },
hosts: { forge: hostsForge!, vault: hostsVault!, s3: hostsS3!, git: hostsGit! },
forgeSshPort: forgeSshPort!,
vm: { host: vmHost!, user: vmUser! },
vm: { host: vmHost!, user: vmUser!, sshPort: vmSshPort },
cloudflare: { zoneId: cloudflareZoneId! },
network: { name: networkName!, subnet: networkSubnet! },
dataRoot: dataRoot!,
tls: {