125 lines
5.2 KiB
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
|