--- # Syncs Nextcloud's file PVC content (nextcloud-nextcloud, 200Gi) to the VPS # MinIO backup receiver via restic. The one piece of the Nextcloud backup story # no operator covers - actual file blobs, not database rows (DB is handled by # CNPG's barmanObjectStore in cnpg-cluster.yaml). # # Caveat: this backs up the live-mounted volume with no application-level # quiesce (no Nextcloud maintenance-mode pause around the snapshot). Acceptable # for a no-real-data test environment; for anything with real user data, pair # this with `occ maintenance:mode --on` before / `--off` after. # # Uses podAffinity to land on the same node as a running Nextcloud pod, since # the PVC is RWO (Ceph RBD) - RWO allows multiple pods to mount it concurrently # only when co-located on the same node. Verify the label selector below # matches the actual Nextcloud chart's pod labels before relying on this. apiVersion: generators.external-secrets.io/v1alpha1 kind: Password metadata: name: nextcloud-restic-password namespace: nextcloud spec: length: 48 digits: 5 symbols: 5 symbolCharacters: "-_$@" noUpper: false allowRepeat: true --- apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: nextcloud-restic-password namespace: nextcloud spec: refreshInterval: "0" # generate once - changing this after the repo is initialized would lock you out of existing backups target: name: nextcloud-restic-password creationPolicy: Owner data: [] dataFrom: - sourceRef: generatorRef: apiVersion: generators.external-secrets.io/v1alpha1 kind: Password name: nextcloud-restic-password --- apiVersion: batch/v1 kind: CronJob metadata: name: nextcloud-pvc-sync namespace: nextcloud spec: schedule: "0 2 * * *" # daily at 02:00 - bulky, don't run it as often as the DB backups concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: backoffLimit: 2 template: spec: restartPolicy: Never securityContext: runAsNonRoot: true runAsUser: 65534 seccompProfile: type: RuntimeDefault affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app.kubernetes.io/name: nextcloud topologyKey: kubernetes.io/hostname containers: - name: restic-backup image: restic/restic:0.16.4 securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] command: - /bin/sh - -c - | set -eu export RESTIC_REPOSITORY="s3:http://${VPS_MINIO_ENDPOINT}/nextcloud-files/restic-repo" export RESTIC_PASSWORD_FILE=/restic-secret/password export AWS_ACCESS_KEY_ID="${MINIO_ACCESS_KEY}" export AWS_SECRET_ACCESS_KEY="${MINIO_SECRET_KEY}" restic snapshots >/dev/null 2>&1 || restic init echo "==> Backing up /data..." restic backup /data --tag nextcloud --host nextcloud-k8s echo "==> Pruning: keep 7 daily, 4 weekly, 6 monthly..." restic forget --tag nextcloud --host nextcloud-k8s \ --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune echo "==> Done." env: # Locally-terminated socat forward on the VPS host to # MinIO's ClusterIP, not the NodePort - see # infrastructure/vault/manifests/raft-snapshot-cronjob.yaml # for why (Netbird has no DNS, and blocks new forwarded # connections through a peer by default). - name: VPS_MINIO_ENDPOINT value: "100.108.113.41:9000" - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: name: vps-minio-secret key: accesskey - name: MINIO_SECRET_KEY valueFrom: secretKeyRef: name: vps-minio-secret key: secretkey volumeMounts: - name: nextcloud-data mountPath: /data readOnly: true - name: restic-secret mountPath: /restic-secret readOnly: true volumes: - name: nextcloud-data persistentVolumeClaim: claimName: nextcloud-nextcloud - name: restic-secret secret: secretName: nextcloud-restic-password items: - key: password path: password