From 11aa6bfe53bc88fa0899c834cafb400dcb610ed0 Mon Sep 17 00:00:00 2001 From: Nicolas Chan Date: Wed, 3 Mar 2021 18:39:57 -0800 Subject: [PATCH] Add rcon --- README.md | 34 ++++++++++------ rcon.sh | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 11 deletions(-) create mode 100755 rcon.sh diff --git a/README.md b/README.md index e3cf5d3..73a7208 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # 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 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 - "sequential" - delete oldest backup - 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 +## 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 - Linux computer (tested on Ubuntu) - GNU Screen (running your Minecraft server) -- Minecraft server (tested with Vanilla 1.10.2 only) +- Minecraft server -## Installation -1. Download the script: `$ wget https://raw.githubusercontent.com/nicolaschan/minecraft-backup/master/backup.sh` -2. Mark as executable: `$ chmod +x backup.sh` -3. Use the command line options or configure default values at the top of `backup.sh`: +## Quick Start +```bash +wget https://raw.githubusercontent.com/nicolaschan/minecraft-backup/master/backup.sh` +chmod +x backup.sh +``` Command line options: ```text @@ -47,11 +60,11 @@ Example usage of command line options: ```bash ./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: - - Edit the crontab: `$ crontab -e` - - Example for hourly backups: `00 * * * * /path/to/backup.sh` +### Create a cron job to automatically backup: +- Edit the crontab with `crontab -e` +- Example for hourly backups: `00 * * * * /path/to/backup.sh ...` ## 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`: @@ -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 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 -- `SERVER_DIRECTORY` should be the server directory, not the `world` 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" diff --git a/rcon.sh b/rcon.sh new file mode 100755 index 0000000..bf5e42e --- /dev/null +++ b/rcon.sh @@ -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"