282 lines
8.3 KiB
Bash
Executable File
282 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Adjustable parameters
|
|
## The number of backups to keep
|
|
BACKUPS_KEEP=24
|
|
|
|
# Global variables (Do NOT change)
|
|
CLEANUP_FABRIC=false
|
|
CLEANUP_LUCKPERMS=false
|
|
CLEANUP_PAPER=false
|
|
|
|
main() {
|
|
log_info "=== Backup run started at $(date -u '+%Y-%m-%dT%H:%M:%SZ') ==="
|
|
log_info "Keeping last $BACKUPS_KEEP backups"
|
|
|
|
check_root_permissions
|
|
init_backup
|
|
|
|
broadcast_status started
|
|
|
|
backup_bluemap || cleanup_failure
|
|
backup_fabric || cleanup_failure
|
|
backup_luckperms || cleanup_failure
|
|
backup_paper || cleanup_failure
|
|
backup_schematics || cleanup_failure
|
|
backup_velocity || cleanup_failure
|
|
|
|
finalize_backup || cleanup_failure
|
|
prune_backups
|
|
cleanup_success
|
|
|
|
broadcast_status finished
|
|
|
|
log_info "=== Backup run completed successfully at $(date -u '+%Y-%m-%dT%H:%M:%SZ') ==="
|
|
}
|
|
|
|
check_root_permissions() {
|
|
if [ "$EUID" -ne 0 ]; then
|
|
log_error "This script must be run by the root user"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Running as root (EUID=$EUID)"
|
|
}
|
|
|
|
init_backup() {
|
|
BACKUP_ID="$(date -u +%Y-%m-%dT%H-%M-%SZ)"
|
|
BACKUP_DIR="/backups/$BACKUP_ID"
|
|
|
|
log_info "Creating backup directory: $BACKUP_DIR"
|
|
|
|
docker compose run --rm init sh -c '
|
|
mkdir -p "$1" &&
|
|
chown minecraft_server:minecraft_server "$1"
|
|
' -- "$BACKUP_DIR" || {
|
|
log_error "Failed to create backup directory $BACKUP_DIR"
|
|
exit 1
|
|
}
|
|
|
|
log_info "Backup directory created and ownership set"
|
|
}
|
|
|
|
broadcast_status() {
|
|
STATUS="$1"
|
|
log_info "Broadcasting backup status: $STATUS"
|
|
|
|
if docker compose exec -T fabric true > /dev/null 2>&1; then
|
|
docker compose exec -T fabric rcon-cli "tellraw @a [{\"text\":\"Server\",\"color\":\"light_purple\"},{\"text\":\": Backup $STATUS.\",\"color\":\"white\"}]" > /dev/null 2>&1
|
|
fi
|
|
if docker compose exec -T paper true > /dev/null 2>&1; then
|
|
docker compose exec -T paper rcon-cli "tellraw @a [{\"text\":\"Server\",\"color\":\"light_purple\"},{\"text\":\": Backup $STATUS.\",\"color\":\"white\"}]" > /dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
backup_bluemap() {
|
|
log_info "Starting Bluemap backup..."
|
|
|
|
docker compose run --rm init sh -c '
|
|
cp -a --reflink=auto /bluemap_data "$1" &&
|
|
cp -a --reflink=auto /bluemap_maps "$1" &&
|
|
cp -a --reflink=auto /bluemap_web "$1"
|
|
' -- "$BACKUP_DIR" || return 1
|
|
|
|
log_info "Finished Bluemap backup"
|
|
}
|
|
|
|
backup_fabric() {
|
|
log_info "Starting Fabric backup..."
|
|
|
|
CLEANUP_FABRIC=false
|
|
if docker compose exec -T fabric true > /dev/null 2>&1; then
|
|
CLEANUP_FABRIC=true
|
|
log_info "Fabric server detected, disabling saves"
|
|
|
|
docker compose exec -T fabric rcon-cli save-off > /dev/null 2>&1
|
|
docker compose exec -T fabric rcon-cli save-all > /dev/null 2>&1
|
|
fi
|
|
|
|
docker compose run --rm init sh -c '
|
|
mkdir -p "$1"/fabric_data &&
|
|
cp -a --reflink=auto /fabric_data/g4mespeed "$1"/fabric_data &&
|
|
cp -a --reflink=auto /fabric_data/fabric-essentials.json "$1"/fabric_data &&
|
|
cp -a --reflink=auto /fabric_data/world "$1"/fabric_data
|
|
' -- "$BACKUP_DIR" || return 1
|
|
|
|
log_info "Finished Fabric backup"
|
|
}
|
|
|
|
backup_luckperms() {
|
|
log_info "Starting LuckPerms backup..."
|
|
|
|
CLEANUP_LUCKPERMS=false
|
|
if ! docker compose exec -T luckperms true > /dev/null 2>&1; then
|
|
CLEANUP_LUCKPERMS=true
|
|
log_info "LuckPerms not running, starting temporary container"
|
|
|
|
docker compose up --wait luckperms > /dev/null 2>&1
|
|
fi
|
|
|
|
docker compose exec luckperms sh -c '
|
|
pg_dump -U luckperms luckperms > "$1"/luckperms.sql
|
|
' -- "$BACKUP_DIR" || return 1
|
|
|
|
log_info "Finished LuckPerms backup"
|
|
}
|
|
|
|
backup_paper() {
|
|
log_info "Starting Paper backup..."
|
|
|
|
CLEANUP_PAPER=false
|
|
if docker compose exec -T paper true > /dev/null 2>&1; then
|
|
CLEANUP_PAPER=true
|
|
log_info "Paper server detected, disabling saves"
|
|
|
|
docker compose exec -T paper rcon-cli save-off > /dev/null 2>&1
|
|
docker compose exec -T paper rcon-cli save-all > /dev/null 2>&1
|
|
fi
|
|
|
|
docker compose run --rm init sh -c '
|
|
mkdir -p "$1"/paper_data/plugins/PlotSquared &&
|
|
cp -a --reflink=auto /paper_data/plugins/Multiverse-Inventories "$1"/paper_data/plugins &&
|
|
cp -a --reflink=auto /paper_data/plugins/Essentials "$1"/paper_data/plugins &&
|
|
cp -a --reflink=auto /paper_data/plugins/PlotSquared/backups "$1"/paper_data/plugins/PlotSquared &&
|
|
cp -a --reflink=auto /paper_data/creative "$1"/paper_data &&
|
|
cp -a --reflink=auto /paper_data/creative_nether "$1"/paper_data &&
|
|
cp -a --reflink=auto /paper_data/survival "$1"/paper_data &&
|
|
cp -a --reflink=auto /paper_data/survival_nether "$1"/paper_data &&
|
|
cp -a --reflink=auto /paper_data/survival_the_end "$1"/paper_data
|
|
' -- "$BACKUP_DIR" || return 1
|
|
|
|
docker compose run --rm sqlite_helper \
|
|
/paper_data/plugins/PlotSquared/storage.db \
|
|
".backup $BACKUP_DIR/paper_data/plugins/PlotSquared/storage.db" || return 1
|
|
|
|
docker compose run --rm sqlite_helper \
|
|
/paper_data/plugins/PlotSquared/user_cache.db \
|
|
".backup $BACKUP_DIR/paper_data/plugins/PlotSquared/user_cache.db" || return 1
|
|
|
|
log_info "Finished Paper backup"
|
|
}
|
|
|
|
backup_schematics() {
|
|
log_info "Starting schematics backup..."
|
|
|
|
docker compose run --rm init sh -c '
|
|
cp -a --reflink=auto /schematics "$1"
|
|
' -- "$BACKUP_DIR" || return 1
|
|
|
|
log_info "Finished schematics backup"
|
|
}
|
|
|
|
backup_velocity() {
|
|
log_info "Starting Velocity backup..."
|
|
|
|
docker compose run --rm init sh -c 'mkdir -p "$1"/velocity_data/plugins/dclink-velocity' -- "$BACKUP_DIR" || return 1
|
|
|
|
docker compose run --rm sqlite_helper \
|
|
/velocity_data/plugins/dclink-velocity/dclink.db \
|
|
".backup $BACKUP_DIR/velocity_data/plugins/dclink-velocity/dclink.db" || return 1
|
|
|
|
log_info "Finished Velocity backup"
|
|
}
|
|
|
|
finalize_backup() {
|
|
log_info "Finalizing backup $BACKUP_ID"
|
|
|
|
docker compose run --rm init chown -R minecraft_server:minecraft_server "$BACKUP_DIR" || {
|
|
log_error "Failed to update ownership of backup files"
|
|
return 1
|
|
}
|
|
|
|
log_info "Backup ownership updated"
|
|
|
|
docker compose run --rm init sh -c '
|
|
ln -sfn "$1" /backups/.latest_tmp &&
|
|
mv -Tf /backups/.latest_tmp /backups/latest
|
|
' -- "$BACKUP_DIR" || {
|
|
log_error "Failed to update /backups/latest symlink"
|
|
return 1
|
|
}
|
|
|
|
log_info "Updated /backups/latest symlink"
|
|
}
|
|
|
|
prune_backups() {
|
|
BACKUPS_ALL=$(docker compose run --rm init sh -c '
|
|
find /backups -mindepth 1 -maxdepth 1 -type d \
|
|
-name "????-??-??T??-??-??Z" | sort
|
|
')
|
|
|
|
BACKUPS_TOTAL=$(echo "$BACKUPS_ALL" | wc -l)
|
|
BACKUPS_PRUNE=$((BACKUPS_TOTAL - BACKUPS_KEEP))
|
|
|
|
[ "$BACKUPS_PRUNE" -le 0 ] && BACKUPS_PRUNE=0
|
|
|
|
log_info "Total backups found: $BACKUPS_TOTAL"
|
|
log_info "Backups to prune: $BACKUPS_PRUNE"
|
|
|
|
echo "$BACKUPS_ALL" | head -n "$BACKUPS_PRUNE" | while read -r OLD_BACKUP_DIR; do
|
|
log_info "Removing old backup: $OLD_BACKUP_DIR"
|
|
docker compose run --rm init rm -rf "$OLD_BACKUP_DIR"
|
|
done
|
|
}
|
|
|
|
trap cleanup_trap HUP INT QUIT ABRT TERM
|
|
cleanup_trap() {
|
|
log_info "Backup cancelled by signal"
|
|
broadcast_status cancelled
|
|
cleanup
|
|
delete_backup
|
|
exit 0
|
|
}
|
|
|
|
cleanup_failure() {
|
|
log_info "Running cleanup due to failure"
|
|
broadcast_status failed
|
|
cleanup
|
|
delete_backup
|
|
exit 1
|
|
}
|
|
|
|
cleanup_success() {
|
|
log_info "Running cleanup"
|
|
cleanup
|
|
}
|
|
|
|
cleanup() {
|
|
# Ignore specified conditions to ensure cleanup runs to completion uninterrupted
|
|
trap '' HUP INT QUIT ABRT TERM
|
|
|
|
if [ "$CLEANUP_FABRIC" = true ]; then
|
|
log_info "Re-enabling Fabric saves"
|
|
docker compose exec -T fabric rcon-cli save-on > /dev/null 2>&1
|
|
fi
|
|
|
|
if [ "$CLEANUP_LUCKPERMS" = true ]; then
|
|
log_info "Bringing down temporary LuckPerms container"
|
|
docker compose stop luckperms > /dev/null 2>&1
|
|
fi
|
|
|
|
if [ "$CLEANUP_PAPER" = true ]; then
|
|
log_info "Re-enabling Paper saves"
|
|
docker compose exec -T paper rcon-cli save-on > /dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
delete_backup() {
|
|
if ! docker compose run --rm init rm -rf "$BACKUP_DIR"; then
|
|
log_error "Failed to remove $BACKUP_DIR"
|
|
fi
|
|
}
|
|
|
|
log_info() {
|
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] INFO: $*"
|
|
}
|
|
|
|
log_error() {
|
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] ERROR: $*" >&2
|
|
}
|
|
|
|
main
|