This commit is contained in:
Nicolas Chan 2021-03-03 18:39:57 -08:00
parent 904e6c3887
commit 11aa6bfe53
2 changed files with 136 additions and 11 deletions

View file

@ -1,5 +1,9 @@
# Minecraft Backup # Minecraft Backup
Backup script for Linux servers running a Minecraft server in a GNU Screen or tmux ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/nicolaschan/minecraft-backup/CI)
[![codecov](https://codecov.io/gh/nicolaschan/minecraft-backup/branch/master/graph/badge.svg?token=LCbVC4TbYJ)](https://codecov.io/gh/nicolaschan/minecraft-backup)
Backup script for Linux servers running a Minecraft server.
Supports servers running in [GNU screen](https://en.wikipedia.org/wiki/GNU_Screen), [tmux](https://en.wikipedia.org/wiki/Tmux), or with [rcon](https://wiki.vg/RCON) enabled.
### Disclaimer ### Disclaimer
Backups are essential to the integrity of your Minecraft world. You should automate regular backups and **check that your backups work**. While this script has been used in production for several years, it is up to you to make sure that your backups work and that you have a reliable backup policy. Backups are essential to the integrity of your Minecraft world. You should automate regular backups and **check that your backups work**. While this script has been used in production for several years, it is up to you to make sure that your backups work and that you have a reliable backup policy.
@ -12,17 +16,26 @@ Please refer to the LICENSE (MIT License) for the full legal disclaimer.
- "thin" - keep last 24 hourly, last 30 daily, and use remaining space for monthly backups - "thin" - keep last 24 hourly, last 30 daily, and use remaining space for monthly backups
- "sequential" - delete oldest backup - "sequential" - delete oldest backup
- Choose your own compression algorithm (tested with: `gzip`, `xz`, `zstd`) - Choose your own compression algorithm (tested with: `gzip`, `xz`, `zstd`)
- Works on vanilla -- no plugins required
- Able to print backup status and info to the Minecraft chat - Able to print backup status and info to the Minecraft chat
## Why not just use `tar` directly?
If the Minecraft server is currently running, you need to disable world autosaving, or you will likely get an error like this:
```
tar: /some/path/here/world/region/r.1.11.mca: file changed as we read it
```
This script will take care of disabling and then re-enabling autosaving for you, and also alert players in the chat of successful backups or errors. This script also manages deleting old backups.
## Requirements ## Requirements
- Linux computer (tested on Ubuntu) - Linux computer (tested on Ubuntu)
- GNU Screen (running your Minecraft server) - GNU Screen (running your Minecraft server)
- Minecraft server (tested with Vanilla 1.10.2 only) - Minecraft server
## Installation ## Quick Start
1. Download the script: `$ wget https://raw.githubusercontent.com/nicolaschan/minecraft-backup/master/backup.sh` ```bash
2. Mark as executable: `$ chmod +x backup.sh` wget https://raw.githubusercontent.com/nicolaschan/minecraft-backup/master/backup.sh`
3. Use the command line options or configure default values at the top of `backup.sh`: chmod +x backup.sh
```
Command line options: Command line options:
```text ```text
@ -47,11 +60,11 @@ Example usage of command line options:
```bash ```bash
./backup.sh -c -i /home/server/minecraft-server/world -o /mnt/external-storage/minecraft-backups -s minecraft ./backup.sh -c -i /home/server/minecraft-server/world -o /mnt/external-storage/minecraft-backups -s minecraft
``` ```
This will use show chat messages (`-c`) in the screen called "minecraft" and save a backup of `/home/server/minecraft-server/world` into `/mnt/external-storage/minecraft-backups` using the default thinning delete policy for old backups. This will show chat messages (`-c`) in the screen called "minecraft" and save a backup of `/home/server/minecraft-server/world` into `/mnt/external-storage/minecraft-backups` using the default thinning delete policy for old backups.
4. Create a cron job to automatically backup: ### Create a cron job to automatically backup:
- Edit the crontab: `$ crontab -e` - Edit the crontab with `crontab -e`
- Example for hourly backups: `00 * * * * /path/to/backup.sh` - Example for hourly backups: `00 * * * * /path/to/backup.sh ...`
## Retrieving Backups ## Retrieving Backups
Always test your backups! Backups are in the `tar` format and compressed depending on the option you choose. To restore, first decompress if necessary and then extract using tar. You may be able to do this in one command if `tar` supports your compression option, as is the case with `gzip`: Always test your backups! Backups are in the `tar` format and compressed depending on the option you choose. To restore, first decompress if necessary and then extract using tar. You may be able to do this in one command if `tar` supports your compression option, as is the case with `gzip`:
@ -70,6 +83,5 @@ Then you can move your restored world (`restored-world` in this case) to your Mi
- Make sure your compression algorithm is in the crontab's PATH - Make sure your compression algorithm is in the crontab's PATH
- Make sure cron has permissions for all the files involved and access to the Minecraft server's GNU Screen - Make sure cron has permissions for all the files involved and access to the Minecraft server's GNU Screen
- It's surprising how much space backups can take--make sure you have enough empty space - It's surprising how much space backups can take--make sure you have enough empty space
- `SERVER_DIRECTORY` should be the server directory, not the `world` directory
- Do not put trailing `/` in the `SERVER_DIRECTORY` or `BACKUP_DIRECTORY` - Do not put trailing `/` in the `SERVER_DIRECTORY` or `BACKUP_DIRECTORY`
- If "thin" delete method is behaving weirdly, try emptying your backup directory or switch to "sequential" - If "thin" delete method is behaving weirdly, try emptying your backup directory or switch to "sequential"

113
rcon.sh Executable file
View file

@ -0,0 +1,113 @@
#!/usr/bin/env bash
reverse-hex-endian () {
# Given a 4-byte hex integer, reverse endianness
while read -r -d '' -N 8 INTEGER; do
echo "$INTEGER" | sed -E 's/(..)(..)(..)(..)/\4\3\2\1/'
done
}
decode-hex-int () {
# decode little-endian hex integer
while read -r -d '' -N 8 INTEGER; do
BIG_ENDIAN_HEX=$(echo "$INTEGER" | reverse-hex-endian)
echo "$((16#$BIG_ENDIAN_HEX))"
done
}
encode-int () {
# Encode an integer as 4 bytes in little endian and return as hex
INT="$1"
# Source: https://stackoverflow.com/a/9955198
printf "%08x" "$INT" | sed -E 's/(..)(..)(..)(..)/\4\3\2\1/'
}
encode () {
# Encode a packet type and payload for the rcon protocol
TYPE="$1"
PAYLOAD="$2"
REQUEST_ID="$3"
PAYLOAD_LENGTH="${#PAYLOAD}"
TOTAL_LENGTH="$((4 + 4 + PAYLOAD_LENGTH + 1 + 1))"
OUTPUT=""
OUTPUT+=$(encode-int "$TOTAL_LENGTH")
OUTPUT+=$(encode-int "$REQUEST_ID")
OUTPUT+=$(encode-int "$TYPE")
OUTPUT+=$(echo -n "$PAYLOAD" | xxd -ps)
OUTPUT+="0000"
echo -n "$OUTPUT" | xxd -ps -r
}
read-response () {
# read next response packet and return the payload text
IN_PIPE="$1"
# HEX_LENGTH=$(head -c4 "$IN_PIPE" | xxd -ps | reverse-hex-endian)
HEX_LENGTH=$(head -c4 <&3 | xxd -ps | reverse-hex-endian)
LENGTH=$((16#$HEX_LENGTH))
RESPONSE_PAYLOAD=$(head -c $LENGTH <&3 | xxd -ps)
echo -n "$RESPONSE_PAYLOAD"
}
response-request-id () {
echo -n "${1:0:8}" | decode-hex-int
}
response-type () {
echo -n "${1:8:8}" | decode-hex-int
}
response-payload () {
echo -n "${1:16:-4}" | xxd -r -ps
}
login () {
PASSWORD="$1"
encode 3 "$PASSWORD" 12 >&3
RESPONSE=$(read-response "$IN_PIPE")
RESPONSE_REQUEST_ID=$(response-request-id "$RESPONSE")
if [ "$RESPONSE_REQUEST_ID" -eq -1 ] || [ "$RESPONSE_REQUEST_ID" -eq 4294967295 ]; then
echo "Authentication failed: Wrong RCON password" 1>&2
return 1
fi
}
run-command () {
COMMAND="$1"
# encode 2 "$COMMAND" 13 >> "$OUT_PIPE"
encode 2 "$COMMAND" 13 >&3
RESPONSE=$(read-response "$IN_PIPE")
response-payload "$RESPONSE"
}
rcon-command () {
HOST="$1"
PORT="$2"
PASSWORD="$3"
COMMAND="$4"
# Open a TCP socket
# Source: https://www.xmodulo.com/tcp-udp-socket-bash-shell.html
exec 3<>/dev/tcp/"$HOST"/"$PORT"
login "$PASSWORD" || return 1
run-command "$COMMAND"
# Close the socket
exec 3<&-
exec 3>&-
}
HOST="$1"
PORT="$2"
PASSWORD="$3"
COMMAND="$4"
rcon-command "$HOST" "$PORT" "$PASSWORD" "$COMMAND"