MinIO's open-source edition is de-prioritised (console removed, commercial
pivot), so swap the off-cluster S3 backup target on the Proxmox host to
versitygw v1.7.0 (github.com/versity/versitygw) — a maintained Go S3 gateway
serving a POSIX filesystem as S3.
Rename role minio_backup_target -> s3_backup_target and playbook backup-minio
-> backup-s3. The role now:
- decommissions any pre-existing MinIO (service/binary/env/mc-client/user/
object-layout), KEEPING the ZFS dataset Storage/k3s-backups and its mount
- installs the pinned, checksummed versitygw binary (fetched+extracted on the
controller, host has no internet), a dedicated versitygw system user, and a
Type=simple hardened systemd unit running the posix backend rooted at
/mnt/Storage/k3s-backups/data, bound to 192.168.1.9:9000 (same endpoint),
plain HTTP / path-style
- ensures the dataset has xattr=sa (versitygw stores object metadata in xattrs)
- pre-creates the velero + cnpg buckets (bucket == top-level subdir)
Credentials are REUSED: versitygw's ROOT key is set to the same scoped
access_key/secret the cluster already uses, so endpoint + key + secret + bucket
names are byte-identical to the old MinIO target. The GitOps SOPS secret
(platform/backup/minio-credentials.sops.yaml) and all cluster-side Velero/CNPG
config need ZERO change. The vaulted group_vars now expose
s3_backup_target_access_key/_secret_key (same values, MinIO root creds retired).
Verified live on the Proxmox host: versitygw active+enabled on 192.168.1.9:9000,
MinIO fully removed, dataset intact; SigV4 ListBuckets with the reused key
returns velero+cnpg; a throwaway in-cluster pod reaches http://192.168.1.9:9000
(versitygw S3 response). Re-run is idempotent (no host changes).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
roles/s3_backup_target
Stands up a single-node S3 endpoint on the Proxmox host to serve as the off-cluster backup target for the k3s platform's Velero (volume/resource DR, R-STATE-03) and CloudNativePG (Keycloak DB Barman object store, R-IDP-05). A backup target — not primary storage — so a single dataset is intentional.
Implemented with versitygw (Versity Gateway): a maintained Go S3 gateway that serves a POSIX filesystem as S3. It replaces the earlier MinIO implementation (MinIO's community edition was de-prioritised — console removed, commercial pivot). The role also decommissions any pre-existing MinIO install on the host, so a re-run is a clean, idempotent swap. Endpoint, access key, secret and bucket names are kept byte-identical to the previous MinIO target, so nothing downstream (the SOPS secret, Velero, CNPG) changes.
What it does (all additive + idempotent, surgical)
- Decommission MinIO (tolerant no-op on a fresh host): stop/disable/remove
minio.service, theminio+mcbinaries,/etc/default/minio, the IAM policy file, themcclient config, theminiouser/group, and MinIO's on-disk object layout (.minio,.minio.sys) inside the gateway-root dir. The ZFS dataset and mount are KEPT. - ZFS dataset
Storage/k3s-backups—compression=zstd,quota=100G,xattr=sa(required — versitygw stores object metadata in POSIX extended attributes and refuses to start without them),mountpoint=/mnt/Storage/k3s-backups, no sharenfs. Usescommunity.general.zfsso re-runs are no-ops and existing datasets are never touched. versitygwsystem user/group + the gateway-root dir (…/k3s-backups/data) it owns.- versitygw binary (pinned, checksummed). The Proxmox host has no outbound internet, so the release tarball is downloaded + verified + extracted on the Ansible controller and only the binary is copied to the host.
- systemd unit
versitygw.service(Type=simple, hardened),posixbackend rooted at the gateway root, bound to the host's cluster-reachable192.168.1.xaddress on port 9000 (same endpoint as the old MinIO) — auto-discovered from facts, assert-guarded. TLS off (plain HTTP, path-style). - Buckets
velero+cnpg— on the posix backend a bucket is simply a top-level subdirectory of the gateway root, so they are pre-created owned by the service user.
Credentials
versitygw authenticates every S3 request against a single ROOT access key/secret
(ROOT_ACCESS_KEY / ROOT_SECRET_KEY). This role sets it to the same scoped key/secret the
cluster already uses, supplied via the vaulted inventory/group_vars/proxmox_hosts/vault.yml
(ansible-vault). Nothing is defaulted — a missing vault fails the play loudly:
| var | purpose |
|---|---|
s3_backup_target_access_key / _secret_key |
versitygw ROOT key = the key handed to Velero + CNPG |
The same key/secret is staged for the GitOps side, SOPS-encrypted, at
platform/backup/minio-credentials.sops.yaml — the two MUST stay in sync.
Note: unlike MinIO, versitygw's single-binary posix backend has no separate "root admin" vs "scoped user" distinction without enabling an external IAM backend. For a dedicated backup target the scoped key is the root key — it only ever fronts the velero + cnpg buckets on this host.
Run
cd infra/ansible
ansible-galaxy collection install -r collections/requirements.yml # community.general (zfs)
ansible-playbook playbooks/backup-s3.yml
Endpoint
http://<host-192.168.1.x>:9000 — the S3 URL the cluster uses (path-style). An unauthenticated
GET / returns 403 AccessDenied once serving; a SigV4 ListBuckets with the root key returns
velero + cnpg. Verified reachable from an in-cluster pod at bring-up.