diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e92cc84 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM openjdk:16-slim + +RUN apt update +RUN apt install -y xxd cron + +COPY cron-backup /etc/cron.d/minecraft-backup + +RUN chmod 0644 /etc/cron.d/minecraft-backup +RUN crontab /etc/cron.d/minecraft-backup + +RUN touch /var/log/cron.log + +CMD cron diff --git a/cron-backup b/cron-backup new file mode 100644 index 0000000..ce76533 --- /dev/null +++ b/cron-backup @@ -0,0 +1 @@ +0 */6 * * * /mc-server/scripts/backup_task.sh diff --git a/docker-compose.yml b/docker-compose.yml index 9395b7e..7b3e502 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,11 +3,13 @@ version: '3.7' services: mc_srv: restart: unless-stopped - image: openjdk:16-alpine + build: + context: . + dockerfile: Dockerfile ports: - 25565:25565 volumes: - ${PWD}:/mc-server working_dir: "/mc-server" - entrypoint: ["sh", "/mc-server/scripts/start.sh"] + entrypoint: ["sh", "/mc-server/scripts/init.sh"] diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..d6d3f3d --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,564 @@ +#!/usr/bin/env bash + +# Minecraft server automatic backup management script +# https://github.com/nicolaschan/minecraft-backup +# MIT License +# +# For Minecraft servers running in a GNU screen, tmux, or RCON. +# For most convenience, run automatically with cron. + +# Default Configuration +SCREEN_NAME="" # Name of the GNU Screen, tmux session, or hostname:port:password for RCON +SERVER_WORLDS=() # Server world directory +BACKUP_DIRECTORY="" # Directory to save backups in +MAX_BACKUPS=128 # -1 indicates unlimited +DELETE_METHOD="thin" # Choices: thin, sequential, none; sequential: delete oldest; thin: keep last 24 hourly, last 30 daily, and monthly (use with 1 hr cron interval) +COMPRESSION_ALGORITHM="gzip" # Leave empty for no compression +COMPRESSION_FILE_EXTENSION=".gz" # Leave empty for no compression; Precede with a . (for example: ".gz") +COMPRESSION_LEVEL=3 # Passed to the compression algorithm +ENABLE_CHAT_MESSAGES=false # Tell players in Minecraft chat about backup status +PREFIX="Backup" # Shows in the chat message +DEBUG=false # Enable debug messages +SUPPRESS_WARNINGS=false # Suppress warnings +RESTIC_HOSTNAME="" # Leave empty to use system hostname +LOCK_FILE="" # Optional lock file to acquire to ensure two backups don't run at once +LOCK_FILE_TIMEOUT="" # Optional lock file wait timeout (in seconds) +WINDOW_MANAGER="screen" # Choices: screen, tmux, RCON + +# Other Variables (do not modify) +DATE_FORMAT="%F_%H-%M-%S" +TIMESTAMP=$(date +$DATE_FORMAT) + +log-fatal () { + echo -e "\033[0;31mFATAL:\033[0m $*" +} +log-warning () { + echo -e "\033[0;33mWARNING:\033[0m $*" +} +debug-log () { + if "$DEBUG"; then + echo "$1" + fi +} + +while getopts 'a:cd:e:f:hH:i:l:m:o:p:qr:s:t:u:vw:x' FLAG; do + case $FLAG in + a) COMPRESSION_ALGORITHM=$OPTARG ;; + c) ENABLE_CHAT_MESSAGES=true ;; + d) DELETE_METHOD=$OPTARG ;; + e) COMPRESSION_FILE_EXTENSION=".$OPTARG" ;; + f) TIMESTAMP=$OPTARG ;; + h) echo "Minecraft Backup" + echo "Repository: https://github.com/nicolaschan/minecraft-backup" + echo "-a Compression algorithm (default: gzip)" + echo "-c Enable chat messages" + echo "-d Delete method: thin (default), sequential, none" + echo "-e Compression file extension, exclude leading \".\" (default: gz)" + echo "-f Output file name (default is the timestamp)" + echo "-h Shows this help text" + echo "-H Set hostname for restic backup (restic only)" + echo "-i Input directory (path to world folder, use -i once for each world)" + echo "-l Compression level (default: 3)" + echo "-m Maximum backups to keep, use -1 for unlimited (default: 128)" + echo "-o Output directory" + echo "-p Prefix that shows in Minecraft chat (default: Backup)" + echo "-q Suppress warnings" + echo "-r Restic repo name (if using restic)" + echo "-s Screen name, tmux session name, or hostname:port:password for RCON" + echo "-t Enable lock file (lock file not used by default)" + echo "-u Lock file timeout seconds (empty = unlimited)" + echo "-v Verbose mode" + echo "-w Window manager: screen (default), tmux, RCON" + exit 0 + ;; + H) RESTIC_HOSTNAME=$OPTARG ;; + i) SERVER_WORLDS+=("$OPTARG") ;; + l) COMPRESSION_LEVEL=$OPTARG ;; + m) MAX_BACKUPS=$OPTARG ;; + o) BACKUP_DIRECTORY=$OPTARG ;; + p) PREFIX=$OPTARG ;; + q) SUPPRESS_WARNINGS=true ;; + r) RESTIC_REPO=$OPTARG ;; + s) SCREEN_NAME=$OPTARG ;; + t) LOCK_FILE=$OPTARG ;; + u) LOCK_FILE_TIMEOUT=$OPTARG ;; + v) DEBUG=true ;; + w) WINDOW_MANAGER=$OPTARG ;; + *) log-fatal "Invalid option -$FLAG"; exit 1 ;; + esac +done + +rcon-command () { + HOST="$(echo "$1" | cut -d: -f1)" + PORT="$(echo "$1" | cut -d: -f2)" + PASSWORD="$(echo "$1" | cut -d: -f3-)" + COMMAND="$2" + + 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 + } + + stream-to-hex () { + xxd -ps + } + + hex-to-stream () { + xxd -ps -r + } + + 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" | stream-to-hex) + OUTPUT+="0000" + + echo -n "$OUTPUT" | hex-to-stream + } + + read-response () { + # read next response packet and return the payload text + HEX_LENGTH=$(head -c4 <&3 | stream-to-hex | reverse-hex-endian) + LENGTH=$((16#$HEX_LENGTH)) + + RESPONSE_PAYLOAD=$(head -c $LENGTH <&3 | stream-to-hex) + 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}" | hex-to-stream + } + + login () { + PASSWORD="$1" + encode 3 "$PASSWORD" 12 >&3 + RESPONSE=$(read-response "$IN_PIPE") + + RESPONSE_REQUEST_ID=$(response-request-id "$RESPONSE") + if [[ "$RESPONSE_REQUEST_ID" == "-1" ]] || [[ "$RESPONSE_REQUEST_ID" == "4294967295" ]]; then + log-warning "RCON connection 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" + } + + # Open a TCP socket + # Source: https://www.xmodulo.com/tcp-udp-socket-bash-shell.html + if ! exec 3<>/dev/tcp/"$HOST"/"$PORT"; then + log-warning "RCON connection failed: Could not connect to $HOST:$PORT" + return 1 + fi + + login "$PASSWORD" || return 1 + debug-log "$(run-command "$COMMAND")" + + # Close the socket + exec 3<&- + exec 3>&- +} + +if ! "$DEBUG"; then + QUIET="-q" +else + QUIET="" +fi + +if [[ "$COMPRESSION_FILE_EXTENSION" == "." ]]; then + COMPRESSION_FILE_EXTENSION="" +fi + +# Check for missing encouraged arguments +if ! $SUPPRESS_WARNINGS; then + if [[ "$SCREEN_NAME" == "" ]]; then + log-warning "Minecraft screen/tmux/rcon location not specified (use -s)" + fi +fi +# Check for required arguments +MISSING_CONFIGURATION=false +if [[ "${#SERVER_WORLDS[@]}" == "0" ]]; then + log-fatal "Server world not specified (use -i)" + MISSING_CONFIGURATION=true +fi +if [[ "$BACKUP_DIRECTORY" == "" ]] && [[ "$RESTIC_REPO" == "" ]]; then + log-fatal "Backup location not specified (use -o or -r)" + MISSING_CONFIGURATION=true +fi +if [[ "$RESTIC_REPO" != "" ]]; then + if [[ "$BACKUP_DIRECTORY" != "" ]]; then + log-fatal "Both output directory (-o) and restic repo (-r) specified but only one may be used at a time" + MISSING_CONFIGURATION=true + fi + if [[ $MAX_BACKUPS -ge 0 ]] && [[ $MAX_BACKUPS -lt 70 ]] && [[ $DELETE_METHOD == "thin" ]]; then + log-fatal "Thinning delete with restic requires at least 70 snapshots to be kept. If you need to keep fewer than 70, use sequential delete." + MISSING_CONFIGURATION=true + fi +fi + +if $MISSING_CONFIGURATION; then + exit 1 +fi + +if [[ "$BACKUP_DIRECTORY" != "" ]]; then + ARCHIVE_FILE_NAME="$TIMESTAMP.tar$COMPRESSION_FILE_EXTENSION" + ARCHIVE_PATH="$BACKUP_DIRECTORY/$ARCHIVE_FILE_NAME" +fi +if [[ "$RESTIC_REPO" != "" ]]; then + ARCHIVE_PATH="$RESTIC_REPO $TIMESTAMP" +fi + +# Minecraft server screen interface functions +message-players () { + local MESSAGE=$1 + local HOVER_MESSAGE=$2 + message-players-color "$MESSAGE" "$HOVER_MESSAGE" "gray" +} +execute-command () { + local COMMAND=$1 + if [[ $SCREEN_NAME != "" ]]; then + case $WINDOW_MANAGER in + "screen") screen -S "$SCREEN_NAME" -p 0 -X stuff "$COMMAND$(printf \\r)" + ;; + "tmux") tmux send-keys -t "$SCREEN_NAME" "$COMMAND" ENTER + ;; + "RCON"|"rcon") rcon-command "$SCREEN_NAME" "$COMMAND" + ;; + esac + fi +} +message-players-error () { + local MESSAGE=$1 + local HOVER_MESSAGE=$2 + message-players-color "$MESSAGE" "$HOVER_MESSAGE" "red" +} +message-players-success () { + local MESSAGE=$1 + local HOVER_MESSAGE=$2 + message-players-color "$MESSAGE" "$HOVER_MESSAGE" "green" +} +message-players-color () { + local MESSAGE=$1 + local HOVER_MESSAGE=$2 + local COLOR=$3 + debug-log "$MESSAGE ($HOVER_MESSAGE)" + if $ENABLE_CHAT_MESSAGES; then + execute-command "tellraw @a [\"\",{\"text\":\"[$PREFIX] \",\"color\":\"gray\",\"italic\":true},{\"text\":\"$MESSAGE\",\"color\":\"$COLOR\",\"italic\":true,\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"\",\"extra\":[{\"text\":\"$HOVER_MESSAGE\"}]}}}]" + fi +} + +# Parse file timestamp to one readable by "date" +parse-file-timestamp () { + local DATE_STRING + DATE_STRING="$(echo "$1" | awk -F_ '{gsub(/-/,":",$2); print $1" "$2}')" + echo "$DATE_STRING" +} + +# Delete a backup +delete-backup () { + local BACKUP=$1 + rm "$BACKUP_DIRECTORY"/"$BACKUP" + message-players "Deleted old backup" "$BACKUP" +} + +# Sequential delete method +delete-sequentially () { + local BACKUPS=("$BACKUP_DIRECTORY"/*) # List oldest first + while [[ $MAX_BACKUPS -ge 0 && ${#BACKUPS[@]} -gt $MAX_BACKUPS ]]; do + delete-backup "$(basename "${BACKUPS[0]}")" + BACKUPS=("$BACKUP_DIRECTORY"/*) + done +} + +# Functions to sort backups into correct categories based on timestamps +is-hourly-backup () { + local TIMESTAMP=$* + local MINUTE + MINUTE=$(date -d "$TIMESTAMP" +%M) + return "$MINUTE" +} +is-daily-backup () { + local TIMESTAMP=$* + local HOUR + HOUR=$(date -d "$TIMESTAMP" +%H) + return "$HOUR" +} +is-weekly-backup () { + local TIMESTAMP=$* + local DAY + DAY=$(date -d "$TIMESTAMP" +%u) + return "$((DAY - 1))" +} + +# Helper function to sum an array +array-sum () { + SUM=0 + for NUMBER in "$@"; do + (( SUM += NUMBER )) + done + echo "$SUM" +} + +# Given two exit codes, print a nonzero one if there is one +exit-code () { + if [[ "$1" != "0" ]]; then + echo "$1" + else + if [[ "$2" == "" ]]; then + echo 0 + else + echo "$2" + fi + fi +} + +# Thinning delete method +delete-thinning () { + # sub-hourly, hourly, daily, weekly is everything else + local BLOCK_SIZES=(16 24 30) + # First block is unconditional + # The next blocks will only accept files whose names cause these functions to return true (0) + local BLOCK_FUNCTIONS=("is-hourly-backup" "is-daily-backup" "is-weekly-backup") + + # Warn if $MAX_BACKUPS does not have enough room for all the blocks + TOTAL_BLOCK_SIZE=$(array-sum "${BLOCK_SIZES[@]}") + if [[ $MAX_BACKUPS != -1 ]] && [[ $TOTAL_BLOCK_SIZE -gt $MAX_BACKUPS ]]; then + if ! $SUPPRESS_WARNINGS; then + log-warning "MAX_BACKUPS ($MAX_BACKUPS) is smaller than TOTAL_BLOCK_SIZE ($TOTAL_BLOCK_SIZE)" + fi + fi + + local CURRENT_INDEX=0 + local BACKUPS=("$BACKUP_DIRECTORY"/*) # Oldest first + local NUM_BACKUPS="${#BACKUPS[@]}" + + for BLOCK_INDEX in "${!BLOCK_SIZES[@]}"; do + local BLOCK_SIZE=${BLOCK_SIZES[BLOCK_INDEX]} + local BLOCK_FUNCTION=${BLOCK_FUNCTIONS[BLOCK_INDEX]} + local OLDEST_BACKUP_IN_BLOCK_INDEX=$((NUM_BACKUPS - 1 - (BLOCK_SIZE + CURRENT_INDEX))) # Not an off-by-one error because a new backup was already saved + if [ "$OLDEST_BACKUP_IN_BLOCK_INDEX" -lt 0 ]; then + break; + fi + local OLDEST_BACKUP_IN_BLOCK + OLDEST_BACKUP_IN_BLOCK="$(basename "${BACKUPS[OLDEST_BACKUP_IN_BLOCK_INDEX]}")" + + local OLDEST_BACKUP_TIMESTAMP + OLDEST_BACKUP_TIMESTAMP=$(parse-file-timestamp "${OLDEST_BACKUP_IN_BLOCK:0:19}") + local BLOCK_COMMAND="$BLOCK_FUNCTION $OLDEST_BACKUP_TIMESTAMP" + + if $BLOCK_COMMAND; then + # Oldest backup in this block satisfies the condition for placement in the next block + debug-log "$OLDEST_BACKUP_IN_BLOCK promoted to next block" + else + # Oldest backup in this block does not satisfy the condition for placement in next block + delete-backup "$OLDEST_BACKUP_IN_BLOCK" + break + fi + + ((CURRENT_INDEX += BLOCK_SIZE)) + done + + delete-sequentially +} + +delete-restic-sequential () { + if [ "$MAX_BACKUPS" -ge 0 ]; then + restic forget -r "$RESTIC_REPO" --keep-last "$MAX_BACKUPS" "$QUIET" + fi +} + +delete-restic-thinning () { + if [ "$MAX_BACKUPS" -ge 70 ]; then + # MAX_BACKUPS >= 70 + restic forget -r "$RESTIC_REPO" --keep-last 16 --keep-hourly 24 --keep-daily 30 --keep-weekly $((MAX_BACKUPS - 70)) "$QUIET" + else + # We have a check that MAX_BACKUPS is not 70 > MAX_BACKUPS >= 0, so we can assume here it is negative + # Negative means don't delete old snapshots + restic forget -r "$RESTIC_REPO" --keep-last 16 --keep-hourly 24 --keep-daily 30 --keep-weekly 9999999 "$QUIET" + fi +} + +# Delete old backups +delete-old-backups () { + if [[ "$BACKUP_DIRECTORY" != "" ]]; then + case $DELETE_METHOD in + "sequential") delete-sequentially + ;; + "thin") delete-thinning + ;; + esac + fi + if [[ "$RESTIC_REPO" != "" ]]; then + case $DELETE_METHOD in + "sequential") delete-restic-sequential + ;; + "thin") delete-restic-thinning + ;; + esac + fi +} + +clean-up () { + # Re-enable world autosaving + execute-command "save-on" + + # Save the world + execute-command "save-all" + + TIME_DELTA=$((END_TIME - START_TIME)) + + if [[ "$BACKUP_DIRECTORY" != "" ]]; then + WORLD_SIZE_BYTES=$(du --bytes --total --max-depth=0 "${SERVER_WORLDS[@]}" | tail -n 1 | awk '{print $1}') + ARCHIVE_SIZE_BYTES=$(du -b "$ARCHIVE_PATH" | awk '{print $1}') + ARCHIVE_SIZE=$(du -h "$ARCHIVE_PATH" | awk '{print $1}') + BACKUP_DIRECTORY_SIZE=$(du -h --max-depth=0 "$BACKUP_DIRECTORY" | awk '{print $1}') + + # Check that archive size is not null and at least 200 Bytes + if [[ "$ARCHIVE_EXIT_CODE" == "0" && "$WORLD_SIZE_BYTES" -gt 0 && "$ARCHIVE_SIZE" != "" && "$ARCHIVE_SIZE_BYTES" -gt 200 ]]; then + # Notify players of completion + COMPRESSION_PERCENT=$((ARCHIVE_SIZE_BYTES * 100 / WORLD_SIZE_BYTES)) + message-players-success "Backup complete!" "$TIME_DELTA s, $ARCHIVE_SIZE/$BACKUP_DIRECTORY_SIZE, $COMPRESSION_PERCENT%" + delete-old-backups + exit 0 + else + rm "$ARCHIVE_PATH" # Delete bad archive so we can't fill up with bad archives + message-players-error "Backup was not saved!" "Please notify an administrator" + exit 1 + fi + fi + + if [[ "$RESTIC_REPO" != "" ]]; then + if [[ "$ARCHIVE_EXIT_CODE" == "0" ]]; then + message-players-success "Backup complete!" "$TIME_DELTA s" + delete-old-backups + exit 0 + else + message-players-error "Backup was not saved!" "Please notify an administrator" + exit 1 + fi + fi +} + +trap "clean-up" 2 + +do-backup () { + # Notify players of start + message-players "Starting backup..." "$ARCHIVE_PATH" + + # Disable world autosaving + execute-command "save-off" + + # Backup world + START_TIME=$(date +"%s") + + if [[ "$BACKUP_DIRECTORY" != "" ]]; then + # Ensure backup directory exists + mkdir -p "$(dirname "$ARCHIVE_PATH")" + + case $COMPRESSION_ALGORITHM in + # No compression + "") tar -cf "$ARCHIVE_PATH" "${SERVER_WORLDS[@]}" + ;; + # With compression + *) tar -cf - "${SERVER_WORLDS[@]}" | $COMPRESSION_ALGORITHM -cv -"$COMPRESSION_LEVEL" - > "$ARCHIVE_PATH" 2>> /dev/null + ;; + esac + EXIT_CODES=("${PIPESTATUS[@]}") + + # tar exit codes: http://www.gnu.org/software/tar/manual/html_section/Synopsis.html + # 0 = successful, 1 = some files differ, 2 = fatal + if [ "${EXIT_CODES[0]}" == "1" ]; then + log-warning "Some files may differ in the backup archive (file changed as read)" + TAR_EXIT_CODE="0" + else + TAR_EXIT_CODE="${EXIT_CODES[0]}" + fi + + ARCHIVE_EXIT_CODE="$(exit-code "$TAR_EXIT_CODE" "${EXIT_CODES[1]}")" + if [ "$ARCHIVE_EXIT_CODE" -ne 0 ]; then + log-fatal "Archive command exited with nonzero exit code $ARCHIVE_EXIT_CODE" + fi + fi + + if [[ "$RESTIC_REPO" != "" ]]; then + RESTIC_TIMESTAMP="${TIMESTAMP:0:10} ${TIMESTAMP:11:2}:${TIMESTAMP:14:2}:${TIMESTAMP:17:2}" + if [[ "$RESTIC_HOSTNAME" == "" ]]; then + RESTIC_HOSTNAME_OPTION=() + else + RESTIC_HOSTNAME_OPTION=("--host" "$RESTIC_HOSTNAME") + fi + restic backup -r "$RESTIC_REPO" "${SERVER_WORLDS[@]}" --time "$RESTIC_TIMESTAMP" "$QUIET" "${RESTIC_HOSTNAME_OPTION[@]}" + ARCHIVE_EXIT_CODE=$? + if [ "$ARCHIVE_EXIT_CODE" -eq 3 ]; then + log-warning "Incomplete snapshot taken (some files could not be read)" + ARCHIVE_EXIT_CODE="0" + else + if [ "$ARCHIVE_EXIT_CODE" -ne 0 ]; then + # According to the restic docs, exit code is either 0, 1, or 3 + # Exit code 1 means fatal + # See: https://restic.readthedocs.io/en/latest/040_backup.html + log-fatal "No restic snapshot created (exit code $ARCHIVE_EXIT_CODE)" + fi + fi + fi + + sync + END_TIME=$(date +"%s") + + clean-up +} + +if [[ "$LOCK_FILE" != "" ]]; then + TIMEOUT_OPTION=() + if [[ "$LOCK_FILE_TIMEOUT" != "" ]]; then + TIMEOUT_OPTION=("-w" "$LOCK_FILE_TIMEOUT") + fi + (if ! flock "${TIMEOUT_OPTION[@]}" --no-fork 200; then + log-fatal "Could not acquire lock on lock file: $LOCK_FILE" + exit 1 + fi + do-backup) 200>"$LOCK_FILE" +else + do-backup +fi diff --git a/scripts/backup_task.sh b/scripts/backup_task.sh new file mode 100755 index 0000000..3fa54b6 --- /dev/null +++ b/scripts/backup_task.sh @@ -0,0 +1 @@ +/mc-server/scripts/backup.sh -c -i /mc-server/world -o /mc-server/backups -s localhost:25575:secret -w rcon diff --git a/scripts/init.sh b/scripts/init.sh new file mode 100644 index 0000000..9476ded --- /dev/null +++ b/scripts/init.sh @@ -0,0 +1,2 @@ +sh -c "sleep 60 && /mc-server/scripts/backup_task.sh" & +/mc-server/scripts/start.sh diff --git a/scripts/start.sh b/scripts/start.sh old mode 100644 new mode 100755 index e0fb94c..c4ff9a2 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -1 +1 @@ -java -XX:+UseG1GC -Xmx10G -Xms10G -Dsun.rmi.dgc.server.gcInterval=2147483646 -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M -jar /mc-server/server.jar nogui +java -XX:+UseG1GC -Xmx2G -Xms2G -Dsun.rmi.dgc.server.gcInterval=2147483646 -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M -jar /mc-server/server.jar nogui diff --git a/server.properties b/server.properties index 550bd6a..4898824 100644 --- a/server.properties +++ b/server.properties @@ -1,5 +1,5 @@ #Minecraft server properties -#Sun Aug 23 18:38:11 CEST 2020 +#Sun Jul 18 17:40:53 UTC 2021 enable-jmx-monitoring=false rcon.port=25575 level-seed= @@ -14,9 +14,10 @@ pvp=true generate-structures=true difficulty=hard network-compression-threshold=256 +require-resource-pack=false max-tick-time=60000 -use-native-transport=true max-players=20 +use-native-transport=true online-mode=true enable-status=true allow-flight=false @@ -24,17 +25,19 @@ broadcast-rcon-to-ops=true view-distance=10 max-build-height=256 server-ip= +resource-pack-prompt= allow-nether=true server-port=25565 -enable-rcon=false +enable-rcon=true sync-chunk-writes=true op-permission-level=4 prevent-proxy-connections=false resource-pack= entity-broadcast-range-percentage=100 -rcon.password= +rcon.password=secret player-idle-timeout=0 force-gamemode=false +rate-limit=0 hardcore=false white-list=true broadcast-console-to-ops=true @@ -43,6 +46,7 @@ spawn-animals=true snooper-enabled=true function-permission-level=2 level-type=default +text-filtering-config= spawn-monsters=true enforce-whitelist=false resource-pack-sha1=