Files
homelab/cluster/applications/minecraft/backup-cronjob.yaml

120 lines
4.6 KiB
YAML

---
# 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
# Same image as the server — has both /bin/sh and rcon-cli built in.
# itzg/rcon-cli is distroless (no shell); don't use it for scripted jobs.
image: itzg/minecraft-server:2026.7.0
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