From 1aa6b4234ad9bcc2adc7fe8077a5f8f2501e127a Mon Sep 17 00:00:00 2001 From: Hermes Agent service account Date: Sun, 19 Jul 2026 17:48:53 -0500 Subject: [PATCH] minecraft: add hourly world backup CronJob with 3-day local retention --- .../minecraft/backup-cronjob.yaml | 117 ++++++++++++++++++ .../applications/minecraft/backup-pvc.yaml | 19 +++ 2 files changed, 136 insertions(+) create mode 100644 cluster/applications/minecraft/backup-cronjob.yaml create mode 100644 cluster/applications/minecraft/backup-pvc.yaml diff --git a/cluster/applications/minecraft/backup-cronjob.yaml b/cluster/applications/minecraft/backup-cronjob.yaml new file mode 100644 index 0000000..5c04c23 --- /dev/null +++ b/cluster/applications/minecraft/backup-cronjob.yaml @@ -0,0 +1,117 @@ +--- +# Minecraft world backup — hourly CronJob, 3-day local retention +# +# Strategy (two-tier): +# Short-term : this CronJob — hourly tarballs on papermc-backups PVC, 72-hour retention +# Long-term : Pure FlashArray protection group snapshots on utilidor (managed separately) +# +# Backup sequence: +# 1. RCON save-all — flush all dirty chunks to disk +# 2. RCON save-off — pause auto-save to keep the world consistent during tar +# 3. tar world directories to /backups/world-YYYY-MM-DDTHH-MM.tar.gz +# 4. RCON save-on — re-enable auto-save +# 5. Prune backups older than 3 days +# +# RCON password sourced from the papermc-rcon Secret (ESO-managed, same as server). +# The backup pod mounts both PVCs read-write; world-data is safe because the +# server has already quiesced saves via RCON before the tar runs. +# +# Note: both PVCs are RWO. The backup job runs only while the main server pod is +# running (RCON is reachable), so there is no volume attach conflict — they are +# mounted on the same node by the scheduler. If the server pod is down, the backup +# job will fail at the RCON step, which is correct behavior (nothing to back up). +apiVersion: batch/v1 +kind: CronJob +metadata: + name: minecraft-backup + namespace: minecraft + labels: + app.kubernetes.io/name: minecraft + app.kubernetes.io/component: backup +spec: + schedule: "0 * * * *" # every hour on the hour + concurrencyPolicy: Forbid # skip if a previous backup is still running + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 0 # don't retry — a partial backup is worse than no backup + template: + metadata: + labels: + app.kubernetes.io/name: minecraft + app.kubernetes.io/component: backup + spec: + restartPolicy: Never + securityContext: + runAsNonRoot: false + fsGroup: 1000 + containers: + - name: backup + image: itzg/rcon-cli:latest + imagePullPolicy: IfNotPresent + env: + - name: RCON_HOST + value: "journey-into-imagination.minecraft.svc.cluster.local" + - name: RCON_PORT + value: "25575" + - name: RCON_PASSWORD + valueFrom: + secretKeyRef: + name: papermc-rcon + key: rcon-password + command: + - /bin/sh + - -c + - | + set -e + + TIMESTAMP=$(date -u +%Y-%m-%dT%H-%M) + BACKUP_FILE="/backups/world-${TIMESTAMP}.tar.gz" + RETAIN_DAYS=3 + + echo "[backup] Starting backup at ${TIMESTAMP}" + + # Step 1: quiesce the world + echo "[backup] Flushing chunks (save-all)..." + rcon-cli save-all + + echo "[backup] Pausing auto-save (save-off)..." + rcon-cli save-off + + # Step 2: archive world directories + echo "[backup] Archiving world to ${BACKUP_FILE}..." + tar -czf "${BACKUP_FILE}" \ + -C /data \ + world \ + world_nether \ + world_the_end \ + whitelist.json \ + ops.json \ + banned-players.json \ + banned-ips.json + + echo "[backup] Archive complete: $(du -sh ${BACKUP_FILE} | cut -f1)" + + # Step 3: resume auto-save + echo "[backup] Resuming auto-save (save-on)..." + rcon-cli save-on + + # Step 4: prune old backups + echo "[backup] Pruning backups older than ${RETAIN_DAYS} days..." + find /backups -name "world-*.tar.gz" -mtime +${RETAIN_DAYS} -delete + REMAINING=$(find /backups -name "world-*.tar.gz" | wc -l) + echo "[backup] Done. ${REMAINING} backup(s) retained." + volumeMounts: + - name: world-data + mountPath: /data + readOnly: true + - name: backups + mountPath: /backups + volumes: + - name: world-data + persistentVolumeClaim: + claimName: papermc-world-data + - name: backups + persistentVolumeClaim: + claimName: papermc-backups diff --git a/cluster/applications/minecraft/backup-pvc.yaml b/cluster/applications/minecraft/backup-pvc.yaml new file mode 100644 index 0000000..58949f8 --- /dev/null +++ b/cluster/applications/minecraft/backup-pvc.yaml @@ -0,0 +1,19 @@ +--- +# Minecraft backup storage — local hourly backups, 3-day retention +# Sized for ~72 backup tarballs; world data compresses well (~90% reduction typical) +# Long-term retention handled by Pure FlashArray protection group snapshots (utilidor) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: papermc-backups + namespace: minecraft + labels: + app.kubernetes.io/name: minecraft + app.kubernetes.io/component: backup +spec: + accessModes: + - ReadWriteOnce + storageClassName: px-fa-direct-access + resources: + requests: + storage: 10Gi