feat(backup): prod shares dev's MinIO — plus the path tenancy that makes it survivable

Operator ruling: "the S3 backup location is the same as Dev." Prod's
S3_BACKUP_ENDPOINT is now http://192.168.1.9:9000 and the TBD-PROD-* placeholder
is retired. Set EXPLICITLY rather than by omitting the op — an omitted op would
inherit the same value silently, and a shared backup target must be visible in
the overlay that owns it.

SHARING THE ENDPOINT WAS NOT SAFE AS AUTHORED. Nothing about the backup PATHS
was per-environment, and enabling prod against dev's MinIO would have been the
2026-09-04 external-dns incident again, in a worse place:

  VELERO — bucket `velero`, NO prefix, and both clusters run schedules named
    `daily` / `daily-resources-only`. Velero SYNCS backups it finds in its BSL
    into itself as Backup CRs and GARBAGE-COLLECTS them at TTL, so prod would
    have adopted dev's backups and then deleted them on schedule.
  CNPG — the worse one. destinationPath `s3://cnpg` with CNPG appending
    serverName, which defaults to the CLUSTER NAME. Both environments have
    keycloak-db, nextcloud-db and semaphore-db, so prod's Postgres would archive
    WAL into the EXACT prefix dev's does. Two servers interleaving WAL into one
    Barman stream CORRUPTS THE CHAIN and destroys PITR — and dev's restore is
    PROVEN (R-STATE-03). Sharing an endpoint is fine; sharing a path is not.

Two knobs, authored once, environments differing only by scoped overrides
(R-ENV-02):
  VELERO_BACKUP_PREFIX  dev ""          | prod "prod"
  CNPG_BACKUP_PATH      dev "s3://cnpg" | prod "s3://cnpg/prod"

THE BASE VALUES ARE DEV'S CURRENT LAYOUT ON PURPOSE. Dev's existing backups live
at the bucket root and at s3://cnpg; giving dev a prefix would ORPHAN the very
backups its proven restore depends on — the same reasoning that keeps dev's
EXTERNALDNS_TXT_OWNER_ID at `default`. Prod has no history to preserve.

*** PROD STILL HAS NO BACKUP COVERAGE — the endpoint is not reachable. ***
Measured from a throwaway pod INSIDE the prod cluster (the workstation proves
nothing about pod egress, and a workstation-side reading has already misled this
project once): curl http://192.168.1.9:9000/minio/health/live → connection timed
out after 15s. pfSense does not route prod's pod network to the dev LAN's MinIO.
OPERATOR ACTION REQUIRED. velero stays suspended; note apps-wave-0 applies GREEN
regardless — the token IS defined so strict substitution succeeds — while every
write fails at RUNTIME. A green apps-wave-0 does not mean backups work.

New `standup.sh s3check` step probes the endpoint from a throwaway in-cluster
pod and fails closed, with `--s3-endpoint <url>` to test a CANDIDATE before
authoring it into cluster-vars. That is how the unreachability above was
established rather than assumed.

VERIFIED: full CI gate PASS. flux-postbuild-substitution 254 -> 260 assertions,
and neither new knob appears in the dead-knob advisory list, so both are really
consumed. Rendered both overlays: all three CNPG destinationPaths and Velero's
prefix carry the tokens; dev resolves to its existing layout, prod to the nested
paths.

DELIVERY: prod reconciles the bootstrap Gitea's main at c8c1d29. This branch also
carries the DNS inversion, which must NOT reach prod. Delivering needs a
c8c1d29-based branch with only these commits — no urgency while the routing is
still closed.

Refs R-STATE-03, R-ENV-02, R-IDP-05.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-05 10:59:50 -05:00
1 parent e6bcecc78c
commit b80e8eef82
8 files changed
+172 -19

No files matched your search

+32 -4
View File
@@ -383,10 +383,38 @@ patches:
- op: replace
path: /data/CRAFTY_L4_VIP
value: "192.168.2.22"
# Off-cluster DR target. Dev's versitygw/MinIO (192.168.1.9:9000) sits on the DEV side of the
# network — a prod backup written there is not off-cluster DR and would mix two environments'
# PITR streams in one bucket. Operator supplies the prod endpoint (full URL incl. scheme+port).
# ------------------------------------------------------------------------------------------
# OFF-CLUSTER DR TARGET — operator ruling 2026-09-05: "the S3 backup location is the same as
# Dev." Prod therefore points at the on-Proxmox MinIO, and the TBD placeholder is retired.
# Stated EXPLICITLY rather than omitting the op: an omitted op would inherit the same value
# silently, and a shared backup target is exactly the kind of decision that must be visible
# in the overlay that owns it.
# ------------------------------------------------------------------------------------------
# *** NOT YET REACHABLE — velero and the CNPG backups CANNOT be enabled on prod today. ***
# Measured 2026-09-05 from a throwaway pod INSIDE the prod cluster (not from the workstation,
# which proves nothing about pod egress): `curl http://192.168.1.9:9000/minio/health/live`
# → connection timed out after 15s. pfSense does not route prod's pod network to the dev LAN's
# MinIO. Until that routing exists, Velero and CNPG would install cleanly and fail every
# backup at RUNTIME — a tier that reports healthy while no backup exists.
# Re-test with: bootstrap/standup.sh ... --s3-endpoint http://192.168.1.9:9000 s3check
#
# SHARING THE ENDPOINT IS SAFE ONLY BECAUSE OF THE TWO PATH KNOBS BELOW. Nothing about the
# paths was per-environment before this change: Velero used one unprefixed bucket, and every
# CNPG Cluster used `s3://cnpg` with serverName defaulting to the CLUSTER NAME — and both
# environments have keycloak-db / nextcloud-db / semaphore-db. Enabling prod against dev's
# MinIO without these knobs would (a) let prod adopt and TTL-delete dev's Velero backups and
# (b) interleave two Postgres servers' WAL into one Barman stream, CORRUPTING dev's chain and
# destroying the PITR that R-STATE-03 proved. See clusters/base/cluster-vars.yaml.
- op: replace
path: /data/S3_BACKUP_ENDPOINT
value: "TBD-PROD-S3-BACKUP-ENDPOINT"
value: "http://192.168.1.9:9000"
# Prod's Velero objects nest under `prod/`; dev keeps the bucket root where its backups already
# are (renaming dev's path would orphan the backups its proven restore depends on).
- op: replace
path: /data/VELERO_BACKUP_PREFIX
value: "prod"
# Prod's Barman objects nest under s3://cnpg/prod/<cluster>/; dev keeps s3://cnpg/<cluster>/.
- op: replace
path: /data/CNPG_BACKUP_PATH
value: "s3://cnpg/prod"
# TODO(WS-INFRA, Phase 7): pin prod node placement once the bare-metal role labels exist.