Files
home-network-k3s-impl/platform/backup/minio-credentials.sops.yaml.example
T
jakman85andClaude Opus 4.8 ac32f01f7b feat(backup): MinIO S3 backup target on Proxmox host for Velero + CNPG
Stand up a single-node/single-drive MinIO S3 endpoint on the Proxmox host as the
off-cluster backup target for Velero (R-STATE-03) and CloudNativePG (R-IDP-05).

Ansible (author + executed):
- roles/minio_backup_target + playbooks/backup-minio.yml — idempotent, additive only:
  ZFS dataset Storage/k3s-backups (zstd, quota=100G, /mnt/Storage/k3s-backups, no
  sharenfs); minio system user; pinned+checksummed MinIO/mc binaries (fetched on the
  controller since the host has no WAN, copied to host); hardened systemd unit bound to
  the host's cluster-reachable LAN IP (auto-discovered) on :9000; velero + cnpg buckets;
  a non-root access key scoped by IAM policy to only those two buckets.
- inventory: new [proxmox_hosts] group ([email protected].2.102); overrides the cluster-wide
  ansible_user. Does not disturb existing k3s node entries.
- group_vars/proxmox_hosts/vault.yml (ansible-vault): root + scoped MinIO creds.

GitOps hand-off:
- platform/backup/minio-credentials.sops.yaml (+ .example) — SOPS/age-encrypted endpoint +
  scoped key/secret for the Velero/CNPG wiring (WS-STORAGE/WS-SEC). Not yet wired into a
  kustomization (left for the consuming agents).

Verified live: dataset props correct; minio active+enabled bound to 192.168.1.9:9000;
S3 endpoint http://192.168.1.9:9000/minio/health/ready returns 200 from an in-cluster pod;
buckets velero+cnpg present; scoped key created+policy attached; re-run is idempotent.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-28 22:15:05 -05:00

52 lines
2.7 KiB
Plaintext

# =============================================================================
# TEMPLATE — NOT APPLIED, NOT ENCRYPTED (.yaml.example is ignored by kustomize and never
# auto-encrypted by SOPS). Reference scaffold + OPERATOR HAND-OFF.
#
# WHAT THIS IS: the scoped (non-root) MinIO S3 access key for the off-cluster backup target that
# roles/minio_backup_target stands up on the Proxmox host (playbooks/backup-minio.yml). It is the
# credential Velero (R-STATE-03) and CloudNativePG / Barman (R-IDP-05) use to write backups. The
# SAME key/secret live (ansible-vault) in infra/ansible/inventory/group_vars/proxmox_hosts/vault.yml
# as minio_backup_target_access_key/_secret_key — the two MUST stay in sync.
#
# ENDPOINT (not secret): http://192.168.1.9:9000 (path-style; region us-east-1 is a placeholder,
# MinIO ignores it but S3 clients require one). Buckets: velero, cnpg.
#
# HAND-OFF STEPS (operator):
# (a) Copy to: platform/backup/minio-credentials.sops.yaml
# (b) Fill the access key + secret (== the vaulted scoped key on the Proxmox host).
# (c) Encrypt: sops -e -i platform/backup/minio-credentials.sops.yaml
# (d) Wire per consumer (WS-STORAGE / WS-SEC):
# - Velero → credentials.existingSecret pointing at a Secret with the `cloud` INI key,
# backupStorageLocation.config.s3Url=S3_ENDPOINT, s3ForcePathStyle="true",
# bucket=velero (namespace: velero).
# - CNPG → Barman objectStore endpointURL=S3_ENDPOINT, destinationPath=s3://cnpg,
# s3Credentials.accessKeyId/secretAccessKey from ACCESS_KEY_ID/ACCESS_SECRET_KEY
# (namespace: cnpg-system).
# A Secret is namespace-scoped — replicate/rename this one into each consumer namespace as
# that wiring requires.
# =============================================================================
apiVersion: v1
kind: Secret
metadata:
name: minio-backup-credentials
namespace: backup
type: Opaque
stringData:
# ── endpoint + buckets (not secret; single source of truth) ──
S3_ENDPOINT: "http://192.168.1.9:9000"
S3_REGION: "us-east-1"
S3_FORCE_PATH_STYLE: "true"
VELERO_BUCKET: "velero"
CNPG_BUCKET: "cnpg"
# ── scoped access key (velero + cnpg buckets only) ──
AWS_ACCESS_KEY_ID: "REPLACE-ME"
AWS_SECRET_ACCESS_KEY: "REPLACE-ME"
# CNPG Barman object store consumes these discrete key names.
ACCESS_KEY_ID: "REPLACE-ME"
ACCESS_SECRET_KEY: "REPLACE-ME"
# Velero's aws plugin reads an INI credentials file from the `cloud` key of its existingSecret.
cloud: |
[default]
aws_access_key_id=REPLACE-ME
aws_secret_access_key=REPLACE-ME