Commit graph

7 commits

Author SHA1 Message Date
6a7c28b54c feat(bootstrap): caddy public ingress + DNS-01 TLS (T07)
foundation-caddy — the only public ingress (80/443 published), automatic TLS via
Let's Encrypt DNS-01 over Cloudflare. Standard caddy:2 lacks the DNS plugin, so
the egg builds a custom image on the VM (containers/caddy-cloudflare/Dockerfile:
xcaddy + caddy-dns/cloudflare@v0.2.4, base digests pinned) via a remote.Command
(ADR-007) whose stdout image id the container runs. The Caddyfile carries no
secrets — the CF token is read from the container env ({env.CF_API_TOKEN}) — and
is rendered + bind-mounted from the host. Routes forge -> Forgejo:3000 and
s3 -> RustFS:9000; Vault is intentionally not proxied publicly (CONTRACT_003
"restricted").

Live on cx33 Helsinki: certs obtained for forge + s3; https://forge.olsitec.net
= 502 (Forgejo lands in T08) and https://s3.olsitec.net = 403 (RustFS), both over
valid Let's Encrypt certs (DNS-01). Acceptance T07 met.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:54:12 +02:00
fa242e4e76 feat(bootstrap): write data-plane creds to Vault KV v2 (T06)
writeCredentialsToVault distributes the generated postgres + rustfs
service-credentials into the Vault `foundation` kv-v2 mount at the CONTRACT_002
paths, over docker-exec/SSH (ADR-007) since 8200 isn't reachable from the
operator. Secret values go in as a JSON object on the container's stdin (never
argv); the root token from the vault-init output authenticates. dependsOn
vault.init = GATE A. Idempotent: kv-v2 enable is guarded, `vault kv put`
overwrites. Forgejo crypto secrets, the runner token, registry tokens, and backup
creds are written by their own tasks (T08/T10/T12).

Live on cx33 Helsinki: foundation/{postgres,rustfs}/service-credentials present
with every CONTRACT_002 camelCase key non-empty; mount is kv v2. Acceptance T06
met for the data-plane slice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:37:26 +02:00
0e81635d88 feat(bootstrap): vault init/unseal + capture to encrypted config (T05)
foundation-vault (hashicorp/vault:1.18, digest-pinned) with integrated raft
storage in foundation-vault-data (-> /vault/file, which the entrypoint chowns to
the vault user), IPC_LOCK for mlock, internal only (8200 unpublished). Init +
unseal reuse the olsitec-core pattern but over docker-exec/SSH (ADR-007): the
foundation-vault-init command inits 1-of-1 Shamir, unseals, and emits keys + root
token on stdout — marked secret and NOT streamed (logging:Stderr) so they never
reach the terminal/logs (D2). run.sh captures them into vaultCredentials:* (the
one bootstrap secret that cannot live in Vault, CONTRACT_002 §2.4) with an
idempotent guard that avoids churning the config. vault-unseal.sh is the
passphrase-gated reboot helper (ADR-004): reads keys from config, unseals over an
SSH stdin pipe. run.sh also now pins the Pulumi backend per-process
(PULUMI_BACKEND_URL) instead of a global `pulumi login`.

Live on cx33 Helsinki: initialized + unsealed (raft 1.18.5), keys captured to
encrypted config, idempotent re-up reuses stored keys, container-restart reseal
recovered by vault-unseal.sh. Acceptance T05 met.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:32:52 +02:00
1792fd9f89 feat(bootstrap): rustfs S3 data-plane + buckets/service account (T04)
foundation-rustfs (rustfs/rustfs digest-pinned) on foundation-net, internal only
(9000/9001 unpublished); named volume foundation-rustfs-data with retainOnDelete.
The four buckets (forgejo-packages/-artifacts/-lfs, foundation-backups) and a
scoped service account with generated keys (CONTRACT_002 rustfs slice) are
provisioned post-boot by an idempotent, readiness-gated remote.Command using a
throwaway mc container (ADR-007). RustFS speaks enough MinIO admin API for
`svcacct add`; `mc ready` is unreliable so readiness gates on `mc ls`; the mc
image's busybox lacks grep so existence checks use a shell `case`. Pins the
IMAGE_MC tool image in VERSIONS.

Live on cx33 Helsinki: 4 buckets present, service key registered, put/get
roundtrip OK, no published ports. Acceptance T04 met.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:19:53 +02:00
6edba60612 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>
2026-06-30 21:10:34 +02:00
185be52763 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>
2026-06-30 20:47:30 +02:00
6a29db386f feat(bootstrap): shared docker provider + foundation-net precursor (ADR-006)
Composition substrate for Wave 2 (T03+):
- lib/context.ts: one Docker-over-SSH provider + DeployCtx threaded to component
  factories; FOUNDATION_DOCKER_HOST override for ephemeral validation.
- lib/versions.ts: resolve pinned images from VERSIONS; FOUNDATION_ALLOW_UNPINNED
  for local validation when digests are still PIN_DIGEST.
- components/network.ts: foundation-net (CONTRACT_003 §3.1).
- index.ts: phase-orchestration entrypoint with dependsOn gates; Wave-2 slots.
- ADR-006: shared-provider + per-component-factory model (egg does not route its
  phased bootstrap through the monolithic vendored DockerDeployments).

Validated: pulumi up over Docker-over-SSH created+verified+destroyed foundation-net
on crunchy01 (x86_64); ephemeral, nothing persisted. tsc + preview clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:18:40 +02:00