diff --git a/servers/legion/k8s/vault/backup-cronjob.yaml b/servers/legion/k8s/vault/backup-cronjob.yaml new file mode 100644 index 0000000..951ae96 --- /dev/null +++ b/servers/legion/k8s/vault/backup-cronjob.yaml @@ -0,0 +1,94 @@ +--- +# Nightly Vault backup — raft snapshot → Cloudflare R2 via restic +# Schedule: 3am daily (offset from Gitea backup at 2am). +# Retains 30 daily / 12 weekly / 6 monthly snapshots. +apiVersion: batch/v1 +kind: CronJob +metadata: + name: vault-backup + namespace: vault +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 3 + successfulJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + template: + spec: + restartPolicy: OnFailure + serviceAccountName: default + initContainers: + - name: vault-snapshot + image: hashicorp/vault:latest + command: + - /bin/sh + - -c + - | + set -e + echo "Taking Vault raft snapshot..." + VAULT_TOKEN=$VAULT_SNAPSHOT_TOKEN \ + vault operator raft snapshot save /snapshot/vault.snap + echo "Snapshot complete, size: $(du -sh /snapshot/vault.snap)" + env: + - name: VAULT_ADDR + value: "http://vault.vault.svc.cluster.local:8200" + - name: VAULT_SNAPSHOT_TOKEN + valueFrom: + secretKeyRef: + name: vault-backup-credentials + key: VAULT_SNAPSHOT_TOKEN + volumeMounts: + - name: snapshot + mountPath: /snapshot + resources: + requests: + memory: 64Mi + cpu: 50m + limits: + memory: 256Mi + containers: + - name: backup + image: restic/restic:latest + command: + - /bin/sh + - -c + - | + set -e + + # Init repo if first run + restic snapshots 2>/dev/null || restic init + + echo "Running restic backup..." + restic backup \ + /snapshot/vault.snap \ + --tag vault \ + --tag legion \ + --host legion + + # Prune old snapshots + restic forget \ + --keep-daily 30 \ + --keep-weekly 12 \ + --keep-monthly 6 \ + --prune + + echo "Backup complete." + restic snapshots --latest 3 + envFrom: + - secretRef: + name: vault-backup-credentials + volumeMounts: + - name: snapshot + mountPath: /snapshot + readOnly: true + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 256Mi + volumes: + - name: snapshot + emptyDir: {} diff --git a/servers/legion/k8s/vault/backup-external-secrets.yaml b/servers/legion/k8s/vault/backup-external-secrets.yaml new file mode 100644 index 0000000..37db1c2 --- /dev/null +++ b/servers/legion/k8s/vault/backup-external-secrets.yaml @@ -0,0 +1,35 @@ +--- +# vault-backup-credentials — R2 keys + restic password + Vault snapshot token +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: vault-backup-credentials + namespace: vault +spec: + refreshInterval: 1h + secretStoreRef: + name: vault + kind: ClusterSecretStore + target: + name: vault-backup-credentials + creationPolicy: Owner + template: + data: + VAULT_SNAPSHOT_TOKEN: "{{ .snapshot_token }}" + RESTIC_PASSWORD: "vault-restic-2026" + AWS_ACCESS_KEY_ID: "{{ .r2_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ .r2_secret_access_key }}" + RESTIC_REPOSITORY: "s3:https://651161e0a3d321561b4c90b5bcd5f15b.r2.cloudflarestorage.com/legion-vault-backup" + data: + - secretKey: snapshot_token + remoteRef: + key: secret/data/vault/snapshot-token + property: token + - secretKey: r2_access_key_id + remoteRef: + key: secret/data/r2 + property: access_key_id + - secretKey: r2_secret_access_key + remoteRef: + key: secret/data/r2 + property: secret_access_key