Sfoglia il codice sorgente

Add 'backup.sh'

initial commit after relocation
Michael Honkoop 8 mesi fa
parent
commit
a968edadb0
1 ha cambiato i file con 423 aggiunte e 0 eliminazioni
  1. 423 0
      backup.sh

+ 423 - 0
backup.sh

@@ -0,0 +1,423 @@
+#!/bin/bash
+
+##############################################
+# The original backup script was found on the internet, but i forgot where i originally got it from, and i no longer have the source,
+# nor was i able to retrace my steps.
+# If the original script looks familiar and you know where it came from please contact me so i can give credits to the original author.
+############################################### 
+
+# changelog v1.1.1
+# - adapted for use with tmux instead of screen
+
+# Minecraft Backup Script v1.1.1 - adapted for use with tmux instead of screen
+
+
+# The script assumes tmux was launched with a named session called 'minecraft'
+
+# File and directory configuration
+# Ensure these directories have correct permissions
+# Do not add trailing slashes
+MCDIR="/opt/minecraft"
+BACKUPDIR="${MCDIR}/backups"
+
+# Log location - set to false to disable logging (not recommended)
+LOGFILE="${MCDIR}/backup.log"
+
+# Revision directories
+# These directories MUST all be on the same filesystem as BACKUPDIR
+ONDEMANDDIR=${BACKUPDIR}
+HOURLYDIR=${BACKUPDIR}/hourly
+DAILYDIR=${BACKUPDIR}/daily
+WEEKLYDIR=${BACKUPDIR}/weekly
+MONTHLYDIR=${BACKUPDIR}/monthly
+
+# Increments of time on which to back up
+HOURLY=true
+DAILY=true
+WEEKLY=true
+MONTHLY=false
+
+# How many revisions to retain (0 for infinite)
+RETAINHOURS=24
+RETAINDAYS=7
+RETAINWEEKS=5
+RETAINMONTHS=12
+
+# Name of Minecraft tmux session
+MCSCREENNAME="minecraft"
+
+### Do not modify below this line unless you know what you are doing! ###
+
+mcsend() {
+	# $1 - Command to send
+	if mcrunning; then
+		tmux send-keys -t $MCSCREENNAME "$1" ENTER
+	fi
+}
+
+mcsay() {
+	# $1 - Message to send
+	mcsend "say [§3Backup§r] $1"
+}
+
+logmsg() {
+	# $1 - Message to log
+	if [ "$LOGFILE" = false ]; then
+		return
+	fi
+
+	# Accept argument or stream from STDIN
+	if [ -n "$1" ]; then
+		IN="$1"
+	else
+		read IN
+	fi
+
+	if [ -n "$IN" ]; then
+		echo "`date +"%Y-%m-%d %H:%M:%S"` mcbackup[$$]: $IN" >> $LOGFILE
+	fi
+}
+
+enablesave() {
+	# FIXME: Remove this when save-off works correctly
+	chmod -R u+w $MCDIR/world/playerdata
+	chmod -R u+w $MCDIR/world/stats
+	mcsend "save-on"
+}
+
+err() {
+	# $1 - Message to log
+	logmsg "[ERROR] $1"
+	mcsay "§cBackup §cfailure"
+	exit 1
+}
+
+fileage() {
+	# $1 - Directory to search
+	# $2 - Formatting string
+	find $1 -maxdepth 1 -name $(ls -t $1 | grep -G "World_.*\.tar\.gz" | head -1) -printf $2
+}
+
+hasfile() {
+	# $1 - Directory to check
+	if [ $(numfiles $1) != 0 ]; then
+		true
+	else
+		false
+	fi
+}
+
+numfiles() {
+	# $1 - Directory to check
+	ls $1 | grep -G "World_.*\.tar\.gz" | wc -l
+}
+
+PURGEFAIL=false
+purgefiles() {
+	# $1 - Directory in which to purge
+	# $2 - Number of files to preserve (0 disables purging)
+
+	# Only purge if retention is 0 or files exceed maximum number
+	FILESINDIR=$(numfiles $1)
+	if [ $2 -gt 0 ] && [ $FILESINDIR -gt $2 ]; then
+		NUMTOPURGE=$(($FILESINDIR - $2))
+		logmsg "Purging ${NUMTOPURGE} backup(s) from ${1}."
+
+		# DANGER ZONE - Delete files matching above script
+		FILENUM=0
+		while [ $FILENUM -lt $NUMTOPURGE ]
+		do
+			FILENUM=$(($FILENUM + 1))
+			FILE= read -rd $'\0' line < <(
+				find $1 -maxdepth 1 -type f -printf '%T@ %p\0' 2>/dev/null |
+				grep -ZzG "World_.*\.tar\.gz" |
+				sort -zn
+			)
+			TOPURGE="${line#* }"
+			logmsg "Purging backup file ${TOPURGE}"
+			if ! $(rm ${TOPURGE} 2>&1 | logmsg ; test ${PIPESTATUS[0]} -eq 0); then
+				PURGEFAIL=true
+				logmsg "[WARNING] Failed to purge a backup; stopping purge."
+				break;
+			fi
+		done
+	fi
+}
+
+mcrunning() {
+	$(pidof minecraft &>/dev/null)
+	ismcrunning=$?
+
+	if $(tmux ls | grep -q "$MCSCREENNAME") && [ ismcrunning = 0 ]; then
+		return 1
+	else
+		return 0
+	fi
+}
+
+# Do not send console commands if MC or screen are not running
+# NOTE: This relies on the Minecraft server process being named "minecraft"
+mcrunning
+if mcrunning; then
+	: # noop
+else
+	logmsg "WARN: Minecraft is not running or is inaccessible; not sending commands to console."
+fi
+
+logmsg "Backup started"
+
+# Determine whether to run on a schedule
+if [ "$1" == "-s" ]; then
+	SCHEDULE=true
+else
+	SCHEDULE=false
+fi
+
+# Generate a filename
+STARTDATE=$(date +"%Y-%m-%d %H:%M:%S")
+FILEPREFIX="World_$(date +"%Y-%m-%d_%H.%M.%S" --date="$STARTDATE")"
+
+# If running on a schedule, check if backups are necessary
+if [ "$SCHEDULE" = true ]; then
+	DOBACKUP=false
+
+	# Hourly backups can run if directory is missing, or if the year, day of the year, or hour
+	# of the day do not match the current time.
+	if ([ "$HOURLY" = true ] && (
+		[ ! -d $HOURLYDIR ] ||
+		((! $(hasfile $HOURLYDIR)) ||
+		[ $(fileage $HOURLYDIR "%TY") != $(date +"%Y" --date="$STARTDATE") ] ||
+		[ $(fileage $HOURLYDIR "%Tj") != $(date +"%j" --date="$STARTDATE") ] ||
+		[ $(fileage $HOURLYDIR "%TH") != $(date +"%H" --date="$STARTDATE") ])
+	)); then
+		DOBACKUP=true
+	else
+		HOURLY=false
+	fi
+
+	# Daily backups can run if directory is missing, or if the year or day of the year
+	# do not match the current time.
+	if ([ "$DAILY" = true ] && (
+		[ ! -d $DAILYDIR ] ||
+		((! $(hasfile $DAILYDIR)) ||
+		[ $(fileage $DAILYDIR "%TY") != $(date +"%Y" --date="$STARTDATE") ] ||
+		[ $(fileage $DAILYDIR "%Tj") != $(date +"%j" --date="$STARTDATE") ])
+	)); then
+		DOBACKUP=true
+	else
+		DAILY=false
+	fi
+
+	# Weekly backups can run if directory is missing, or if the year or week of the year do
+	# not match the current time.
+	if ([ "$WEEKLY" = true ] && (
+		[ ! -d $WEEKLYDIR ] ||
+		((! $(hasfile $WEEKLYDIR)) ||
+		[ $(fileage $WEEKLYDIR "%TY") != $(date +"%Y" --date="$STARTDATE") ] ||
+		[ $(fileage $WEEKLYDIR "%TW") != $(date +"%W" --date="$STARTDATE") ])
+	)); then
+		DOBACKUP=true
+	else
+		WEEKLY=false
+	fi
+
+	# Monthly backups can run if directory is missing, of if the year or month do not match
+	# the current time.
+	if ([ "$MONTHLY" = true ] && (
+		[ ! -d $MONTHLYDIR ] ||
+		((! $(hasfile $MONTHLYDIR)) ||
+		[ $(fileage $MONTHLYDIR "%TY") != $(date +"%Y" --date="$STARTDATE") ] ||
+		[ $(fileage $MONTHLYDIR "%Tm") != $(date +"%m" --date="$STARTDATE") ])
+	)); then
+		DOBACKUP=true
+	else
+		MONTHLY=false
+	fi
+
+
+	# If no scheduled backups are needed, exit
+	if [ "$DOBACKUP" = false ]; then
+		logmsg "Scheduled backups already up to date; aborting."
+		exit 0
+	fi
+fi
+
+# Make backup directory if needed
+if [ ! -d "$BACKUPDIR" ]; then
+	mkdir "$BACKUPDIR"
+fi
+
+# Send a warning message to the server and disable saving
+mcsay "Backup started."
+mcsend "save-off"
+
+# Workaround to lock playerdata and stats while saving is turned off
+# See https://bugs.mojang.com/browse/MC-3208
+# FIXME: Remove this when save-off works correctly
+chmod -R u-w $MCDIR/world/playerdata
+chmod -R u-w $MCDIR/world/stats
+
+# Back up the world to a temorary location
+# NOTE: This must be on the same filesystem as the backup target directory
+TEMPFILE=$BACKUPDIR/.mcbackup.tar
+# FIXME: Remove permissions override when above mentioned bug is resolved
+if ! $(tar --mode="a+rw" -cf $TEMPFILE -C $MCDIR world 2>&1 | logmsg ; test ${PIPESTATUS[0]} -eq 0); then
+	enablesave
+	rm $TEMPFILE 2>/dev/null
+	err "Unable to generate tar file. Aborting."
+fi
+
+# Allow server to begin saving again
+enablesave
+
+# Check if anything has changed since the last backup
+SUMFILE=$MCDIR/backup.md5
+if md5sum --status -c $SUMFILE 2>/dev/null; then
+	NOCHANGE=true
+else
+	NOCHANGE=false
+	md5sum $TEMPFILE > $SUMFILE
+	if ! $(gzip -fq $TEMPFILE 2>&1 | logmsg ; test ${PIPESTATUS[0]} -eq 0); then
+		rm $TEMPFILE 2>/dev/null
+		err "Unable to generate gzip file. Aborting."
+	fi
+	TEMPFILE=${TEMPFILE}.gz
+fi
+
+# Only perform backups if something has changed
+BACKUPRUN=false
+BACKUPFAIL=false
+if [ "$NOCHANGE" = false ]; then
+	# Create scheduled files if the schedule is enabled
+	if [ "$SCHEDULE" = true ]; then
+		# Perform the hourly backup if enabled
+		if [ "$HOURLY" = true ]; then
+			# Create the hourly backup directory if it does not already exist
+			if [ ! -d $HOURLYDIR ]; then
+				mkdir -p $HOURLYDIR
+			fi
+
+			# Hard link the hourly backup to the temporary file
+			if $(
+				ln $TEMPFILE $HOURLYDIR/${FILEPREFIX}.tar.gz 2>&1 |
+				logmsg;
+				test ${PIPESTATUS[0]} -eq 0
+			); then
+				logmsg "Performed hourly backup"
+				BACKUPRUN=true
+
+				# Purge outdated files
+				purgefiles $HOURLYDIR $RETAINHOURS
+			else
+				logmsg "[WARNING] Failed to complete hourly backup"
+				BACKUPFAIL=true
+			fi
+		fi
+
+		# Perform the daily backup if enabled
+		if [ "$DAILY" = true ]; then
+			# Create the daily backup directory if it does not already exist
+			if [ ! -d $DAILYDIR ]; then
+				mkdir -p $DAILYDIR
+			fi
+
+			# Hard link the daily backup to the temporary file
+			if $(
+				ln $TEMPFILE $DAILYDIR/${FILEPREFIX}.tar.gz 2>&1 |
+				logmsg;
+				test ${PIPESTATUS[0]} -eq 0
+			); then
+				logmsg "Performed daily backup"
+				BACKUPRUN=true
+
+				# Purge outdated files
+				purgefiles $DAILYDIR $RETAINDAYS
+			else
+				logmsg "[WARNING] Failed to complete daily backup"
+				BACKUPFAIL=true
+			fi
+		fi
+
+		# Perform the weekly backup if enabled
+		if [ "$WEEKLY" = true ]; then
+			# Create the weekly backup directory if it does not already exist
+			if [ ! -d $WEEKLYDIR ]; then
+				mkdir -p $WEEKLYDIR
+			fi
+
+			# Hard link the weekly backup to the temporary file
+			if $(
+				ln $TEMPFILE $WEEKLYDIR/${FILEPREFIX}.tar.gz 2>&1 |
+				logmsg;
+				test ${PIPESTATUS[0]} -eq 0
+			); then
+				logmsg "Performed weekly backup"
+				BACKUPRUN=true
+
+				# Purge outdated files
+				purgefiles $WEEKLYDIR $RETAINWEEKS
+			else
+				logmsg "[WARNING] Failed to complete weekly backup"
+				BACKUPFAIL=true
+			fi
+		fi
+
+		# Perform the monthly backup if enabled
+		if [ "$MONTHLY" = true ]; then
+			# Create the monthly backup directory if it does not already exist
+			if [ ! -d $MONTHLYDIR ]; then
+				mkdir -p $MONTHLYDIR
+			fi
+
+			# Hard link the monthly backup to the temporary file
+			if $(
+				ln $TEMPFILE $MONTHLYDIR/${FILEPREFIX}.tar.gz 2>&1 |
+				logmsg;
+				test ${PIPESTATUS[0]} -eq 0
+			); then
+				logmsg "Performed monthly backup"
+				BACKUPRUN=true
+
+				# Purge outdated files
+				purgefiles $MONTHLYDIR $RETAINWEEKS
+			else
+				logmsg "[WARNING] Failed to complete monthly backup"
+				BACKUPFAIL=true
+			fi
+		fi
+
+	else
+		# Create on-demand backup if this is not a schedule run
+		logmsg "Performed backup on demand"
+		ln $TEMPFILE $BACKUPDIR/${FILEPREFIX}.tar.gz
+		BACKUPRUN=true
+	fi
+
+	# Always link the last backup to latest if a backup ran and did not fail
+	if [ "$BACKUPFAIL" = false ]; then
+		if [ "$BACKUPRUN" = true ]; then
+			rm $BACKUPDIR/latest.tar.gz 2>/dev/null
+			ln $TEMPFILE $BACKUPDIR/latest.tar.gz
+			logmsg "Backup completed successfully"
+		else
+			logmsg "Scheduled backups are already up to date"
+		fi
+	fi
+else
+	logmsg "No change was detected in the world file; backup stopped"
+fi
+
+# Remove the temporary file
+rm $TEMPFILE
+
+# If there was a purge failure, notify the server
+if [ "$PURGEFAIL" = true ]; then
+	mcsay "§cPurge §cfailure §c- §ccheck §clog §cfile"
+fi
+
+# Display appropriate message, depending on backup status
+if [ "$BACKUPFAIL" = false ]; then
+	mcsay "Backup complete."
+else
+	err "Unable to complete all backups."
+fi