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

125 lines
5.2 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 (nether/end may not exist yet)
echo "[backup] Archiving world to ${BACKUP_FILE}..."
DIRS_TO_BACKUP="world"
[ -d /data/world_nether ] && DIRS_TO_BACKUP="$DIRS_TO_BACKUP world_nether"
[ -d /data/world_the_end ] && DIRS_TO_BACKUP="$DIRS_TO_BACKUP world_the_end"
FILES_TO_BACKUP=""
[ -f /data/whitelist.json ] && FILES_TO_BACKUP="$FILES_TO_BACKUP whitelist.json"
[ -f /data/ops.json ] && FILES_TO_BACKUP="$FILES_TO_BACKUP ops.json"
[ -f /data/banned-players.json ] && FILES_TO_BACKUP="$FILES_TO_BACKUP banned-players.json"
[ -f /data/banned-ips.json ] && FILES_TO_BACKUP="$FILES_TO_BACKUP banned-ips.json"
tar -czf "${BACKUP_FILE}" -C /data $DIRS_TO_BACKUP $FILES_TO_BACKUP || {
echo "[backup] ERROR: tar failed — re-enabling saves and exiting"
rcon-cli save-on
exit 1
}
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