server-config/banutama-mc-backup.sh

125 lines
3.2 KiB
Bash

#!/bin/bash
set -euo pipefail
# We have to retry rcon quite often, so we have a function to encapsulate repeatedly attempting a command a given
# number of files with a given interval.
retry() {
local retries="${1}"
local interval="${2}"
readonly retries interval
shift 2
local i=-1
while ((retries >= ++i)); do
if output="$(timeout --signal=SIGINT --kill-after=30s 5m "${@}" 2>&1 | tr '\n' '\t')"; then
echo "Command executed successfully ${*}"
return 0
else
echo "ERROR: unable to execute ${*} (attempt ${i}/${retries}. Retrying in ${interval}"
if [ -n "${output}" ]; then
echo "ERROR: Failure reason: ${output}"
fi
fi
sleep ${interval}
done
return 2
}
init() {
echo "Ensuring backup root directory: ${BACKUP_ROOT}"
mkdir -p "${BACKUP_ROOT}"
}
BACKUP_SRC="/var/lib/docker/volumes/minecraft_data/_data"
BACKUP_ROOT="$HOME/banutama-mc-backups"
backup() {
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
OUTFILE="${BACKUP_ROOT}/banutama-mc-backup-$1-${TIMESTAMP}.tar.gz"
echo "Performing $1 backup: ${OUTFILE}"
(cd "${BACKUP_SRC}" && {
find . -type f -name "*.dat" -o -name "*.dat_old"
find . -type f -not -name "*.dat" -not -name "*.dat_old"
}) | command tar --gzip -cf "${OUTFILE}" -C "${BACKUP_SRC}" -T - || EXIT_CODE=$?
if [ ${EXIT_CODE:-0} -eq 0 ]; then
true
elif [ ${EXIT_CODE:-0} -eq 1 ]; then
echo "WARNING: dat files were changed during backup"
elif [ ${EXIT_CODE:-0} -gt 1 ]; then
echo "ERROR: tar exited with code ${EXIT_CODE}"
exit 1
fi
echo "Creating link to latest backup: ${BACKUP_ROOT}/banutama-mc-backup-$1-latest.tar.gz"
ln -sf "${OUTFILE}" "${BACKUP_ROOT}/banutama-mc-backup-$1-latest.tar.gz"
}
prune() {
local interval="${1}"
local limit=""
case "${interval}" in
hourly)
limit=$(date -d '6 hours ago' '+%Y-%m-%d %H:00:00')
;;
daily)
limit=$(date -d '7 days ago' '+%Y-%m-%d 00:00:00')
;;
*)
echo "ERROR: invalid interval '${interval}' passed to prune()"
;;
esac
echo "Deleting ${interval} backups older than ${limit} ..."
find "${BACKUP_ROOT}" -name "banutama-mc-backup-${interval}-"'*' ! -newermt "${limit}" | while read file; do
echo "Deleting old ${interval} backup: ${file}"
rm "${file}"
done
}
# -----------------------------------------------------------------------------------------------------------------
# Main entry point
INTERVAL="${1:-hourly}"
case "${INTERVAL}" in
hourly)
echo "Performing hourly backup"
;;
daily)
echo "Performing daily backup"
;;
*)
echo "Unknown backup interval '${INTERVAL}'; falling back to hourly"
INTERVAL="hourly"
;;
esac
RCON_CLI="docker exec minecraft rcon-cli"
RCON_CLI_RETRIES=5
RCON_CLI_INTERVAL=10s
init
echo "Waiting for RCON ..."
retry ${RCON_CLI_RETRIES} ${RCON_CLI_INTERVAL} ${RCON_CLI} save-on
if retry ${RCON_CLI_RETRIES} ${RCON_CLI_INTERVAL} ${RCON_CLI} save-off; then
# Make sure that when this script explodes, we turn saving on
trap 'retry 5 5s ${RCON_CLI} save-on' EXIT
retry ${RCON_CLI_RETRIES} ${RCON_CLI_INTERVAL} ${RCON_CLI} save-all flush
retry ${RCON_CLI_RETRIES} ${RCON_CLI_INTERVAL} sync
backup ${INTERVAL}
# Turn saving back on, and clear our trap
retry ${RCON_CLI_RETRIES} ${RCON_CLI_INTERVAL} ${RCON_CLI} save-on
trap EXIT
else
echo "ERROR: Unable to turn saving off. Is the server running?"
exit 1
fi
prune ${INTERVAL}