foundation/bootstrap/Pulumi.foundation.yaml
Andreas Niemann 8603177096
All checks were successful
CI / preflight (push) Successful in 4s
CI / typecheck (push) Successful in 15s
pulumi-preview / preview (push) Successful in 19s
feat(ci): state-dependent pulumi-preview + backup-verify pipelines (T14)
Completes T14: the two CI pipelines that need Pulumi stack state, which
bootstrap/state/ is gitignored from. Solves the blocker by publishing a
fresh `pulumi stack export` to RustFS after every `up`, then having CI
pull + import it.

- state-publish.sh: ships the stack export to rfs/foundation-ci-state/
  foundation-stack.json via a throwaway mc container on foundation-net
  (ADR-007), exactly like backup.sh. Secrets inside the export stay
  passphrase-encrypted; config travels in the committed (encrypted)
  Pulumi.foundation.yaml. run.sh invokes it best-effort after `up`.
- rustfs.ts + Pulumi.foundation.yaml: declare the foundation-ci-state
  bucket (created belt-and-suspenders by state-publish on first run).
- pulumi-preview.yml (push/PR): read-only drift/PR check. Pulls + imports
  state, materializes the operator key from the SSH_PRIVATE_KEY secret
  (the provider + index.ts read it), `pulumi preview` — never `up`. A diff
  is informational so the job fails only on a program/preview error.
- backup-verify.yml (weekly + dispatch): reuses backup.sh/restore.sh
  unchanged to produce a bundle and restore-verify it from offsite
  (CONTRACT_004 §4.6). Imports real state so the bundle's pulumi-state.json
  is real, not an empty deployment.

Repo-scoped Actions secrets set via the admin API: PULUMI_CONFIG_PASSPHRASE,
SSH_PRIVATE_KEY, RUSTFS_ACCESS_KEY, RUSTFS_SECRET_KEY. Both pipelines
validated end-to-end in a foundation-ci container on the VM (preview exit 0;
backup-verify RESTORE VERIFY PASS from offsite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 00:50:16 +02:00

70 lines
3.5 KiB
YAML

# Pulumi.foundation.yaml — stack config for the `foundation` stack (initial Hetzner home).
#
# NON-SECRET values only here (CONTRACT_001 §1.2) — safe to commit in plaintext.
# Secrets (CONTRACT_001 §1.3) are added by `pulumi config set --secret` as
# `secure: v1:…` (passphrase-encrypted): foundation:cloudflareApiToken,
# foundation:backup.offsiteAccessKey/SecretKey, and later vaultCredentials:*.
#
# Master passphrase: pass olsitec-foundation/PULUMI_CONFIG_PASSPHRASE (the ONE
# external secret). Image digests live in foundation/VERSIONS, not here (D5).
config:
# --- identity / networking (real: olsitec.net) ---
foundation:baseDomain: olsitec.net
foundation:hosts.forge: forge.olsitec.net
foundation:hosts.vault: vault.olsitec.net
foundation:hosts.s3: s3.olsitec.net
foundation:hosts.git: git.olsitec.net
foundation:forgeSshPort: "2222"
# --- deployment target: the Helsinki cx33 VM (Docker-over-SSH, port 222) ---
foundation:vm.host: 204.168.234.72
foundation:vm.user: root
foundation:vm.sshPort: "222"
# --- container plane (CONTRACT_003) ---
foundation:network.name: foundation-net
foundation:network.subnet: 172.30.0.0/24
foundation:dataRoot: /srv/foundation
# --- TLS: real Let's Encrypt via Cloudflare DNS-01 ---
foundation:tls.mode: letsencrypt-dns01
foundation:tls.acmeEmail: a.niemann@olsitec.de
# --- Cloudflare (DNS records + DNS-01); token is a SECRET set separately ---
foundation:cloudflare.zoneId: 27e587d5574d5fd6e2cf75b9e914a02c
# --- fixed names (derived, non-secret; creds generated → Vault) ---
foundation:postgres.db: foundation
foundation:postgres.forgejoDb: forgejo
foundation:rustfs.buckets:
- forgejo-packages
- forgejo-artifacts
- forgejo-lfs
- foundation-backups
- foundation-ci-state
foundation:forgejo.adminUser: platform-admin
foundation:forgejo.orgName: olsitec
foundation:runner.labels:
- docker:docker://node:20-bookworm
- dind:docker://-
# --- credential feature flags (ADR-002) ---
foundation:features.postgres: "true"
foundation:features.rustfs: "true"
foundation:features.forgejo: "true"
foundation:features.runner: "true"
foundation:features.backup: "true"
foundation:features.registry: "true"
# --- backup: offsite = home Synology MinIO (CONTRACT_004); creds are SECRET ---
foundation:backup.bucket: foundation-backups
foundation:backup.offsiteEndpoint: https://minio.wob.olsitec.de:19000
foundation:backup.retentionDaily: "7"
foundation:backup.retentionWeekly: "4"
foundation:cloudflareApiToken:
secure: v1:xDFqTVZxRm2nvIrQ:ddjNyqKi4C27Fppp9YA0B+gNZPtjWig/NBC6y9dR3cQ8xfNfwEsEHxvRgn8aUTH9UrmjXtLEoYk=
foundation:backup.offsiteAccessKey:
secure: v1:svEvJ5K9u+FMnpV/:RztjS8VMSxrdgpBtbNpBPA6gfPLVgnABp77diBC6nWGHZRnG
foundation:backup.offsiteSecretKey:
secure: v1:lkkGBjgmJqVziusc:gpmw5lkfFAjXzeFikhtQnvWObYpKD3Bq5XSmrBA/vlLaoqqxFGAAO4Cq7V8nOLZ926x3fXukPQI=
vaultCredentials:unsealKeys:
secure: v1:9YpTkFoQanMwxAQV:dJ4YmXS0aOTHPbuK1H6AJ0SAJ0CjYX0iIyLOQAUNfsOWLsSy5TXxPpGecieBWkzc4AALDkJNlQN9Xo6Q0ZcaSg==
vaultCredentials:rootToken:
secure: v1:OUpYMjnaftxMUKjv:2m+dydQopXGRleeX6ddhYSHgHP7HHZXYLAvQHXUvaA91qajoxU+VugDB/Rs=
foundation:backup.ageRecipient: age1x6dmgtt2eahpvyzkmy6j80rts28chw2lcam0rcxq3nhc8ld649sslzpsy4
foundation:backup.ageIdentity:
secure: v1:VCFVXswrmMrXyFbr:p4pfG/Kp2lreetYX4O86rZqpU1xQugRycF+PBBiNGZnaD0c15R+mJuLNrl0rBXY5vJwyZTbNSpFY1zPQ7TwuQcVp9h8oiGcgVEobsbb4BBp3lFhsObllgYM9
encryptionsalt: v1:5YhUt8BVfH0=:v1:DPCHl+7zwn4RaMPj:A19tZzBlZ1NmDtTWrHreEKk5e8idyw==