2021-12-25 21:23:27 -06:00
#!/bin/bash
echo_rgb( ) {
# Echo a colored string to the terminal based on rgb values
#
# Positional Arguments:
#
# message <type: string> <position: 1> <required: true>
# - The message to be printed to stdout
# red <type: int> <position: 2> <required: true>
# - The red value from 0 to 255
# green <type: int> <position: 3> <required: true>
# - The green value from 0 to 255
# blue <type: int> <position: 4> <required: true>
# - The blue value from 0 to 255
#
# Usage:
# echo_rgb "Yep" 10 8 30
#
# POSIX Compliant:
# N/A
#
local red
local green
local blue
local input
input = " ${ 1 } "
red = " ${ 2 } "
green = " ${ 3 } "
blue = " ${ 4 } "
printf "\e[0;38;2;%s;%s;%sm%s\e[m\n" " ${ red } " " ${ green } " " ${ blue } " " ${ input } "
}
log( ) {
# Print a message and send it to stdout or stderr depending upon log level, also configurable with debug etc.
#
# Arguments:
# level <type: string> <position: 1> <required: true>
# - The log level, defined within a case check in this function
# message <type: string> <position: 2> <required: true>
# - The info message
# line_number <type: int> <position: 3> <required: false>
# - The line number of the calling function (${LINNO})
#
# Usage:
# log "info" "Could not find that directory"
#
# POSIX Compliant:
# Yes
#
# Set debug status depending if a global debug variable has been set to either 1 or 0
local debug
if [ ${ DEBUG } ] ; then
debug = ${ DEBUG }
else
debug = 0
fi
local FORMAT
FORMAT = " [ $( echo_rgb " $( date +%Y-%m-%dT%H:%M:%S) " 180 140 255) ] "
# Convert the level to uppercase
local level
level = $( echo " ${ 1 } " | tr '[:lower:]' '[:upper:]' )
local message
message = " ${ 2 } "
case " ${ level } " in
INFO)
# Output all info log levels to stdout
printf " ${ FORMAT } [ $( echo_rgb "INFO" 0 140 255) ] %s\n " " ${ message } " >& 1
return 0
; ;
WARN | WARNING)
# Output all warning log levels to stdout
printf " ${ FORMAT } [ $( echo_rgb "WARNING" 255 255 0) ] %s\n " " ${ message } " >& 1
return 0
; ;
DEBUG)
# Output all debug log levels to stdout
if [ " ${ DEBUG } " ] ; then
printf " ${ FORMAT } [ $( echo_rgb "DEBUG" 0 160 110) ] %s\n " " ${ message } " >& 1
fi
return 0
; ;
ERROR)
# Output all error log levels to stderr
printf " ${ FORMAT } [ $( echo_rgb "ERROR" 255 0 0) ] %s\n " " ${ message } " >& 2
return 0
; ;
# Further log levels can be added by extending this switch statement with more comparisons
*) # Default case, no matches
# Returns non-zero code as an improper log option was passed, this helps with using `set -e`
printf " ${ FORMAT } [ERROR] %s\n " " Invalid log level passed, received level \" ${ level } \" with message \" ${ message } \" " >& 2
return 1
; ;
esac
}
confirmation( ) {
# Receive confirmation from user as y, Y, n, or N
# returns 0 when answer is yes and 1 when answer is no
#
# Arguments:
# message <type: string> <position: 1> <required: true>
# - The confirmation prompt sent to the user, for example:
# Would you like to overwrite foobar.txt (y/N)?
#
# Usage:
# confirmation "Some prompt"
# - Sends "Some prompt" to the user and gets their input
#
# POSIX Compliant:
# Yes
#
local message
message = " ${ 1 } "
local choice
while true; do
read -p " ${ message } " -n 1 -r choice
case " $choice " in
y | Y)
echo ""
return 0
; ;
n | N)
echo ""
return 1
; ;
*) echo -e "\nInput must be either y, Y, n, or N" ; ;
esac
done
}
2021-11-15 14:33:21 -06:00
### IMPORTS ###
### CONSTANTS ###
START_PORT_RANGE = 50000
BASE_DIR = " ${ HOME } /7-Days-To-Die "
### CONSTANTS ###
important( ) {
echo_rgb " ${ 1 } " 135 195 255
}
start_server( ) {
local server_id
local can_kill
server_id = ""
can_kill = 1
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" start [options]
--server <server id: int> | -s <server id: int>
Starts the given server id
Example:
--server 3
--can-kill | -c
Automatically kills the server if it is running without prompting
Example:
--can-kill"
exit
; ;
--) # End of all options.
break
; ;
--server | -s)
shift
server_id = " ${ 1 } "
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
; ;
--can-kill | -c)
can_kill = 0
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
local server_directory
local server_config
2021-12-25 21:23:27 -06:00
local server_save_dir
2021-11-15 14:33:21 -06:00
local server_name
local prefix
local server_session_name
server_name = " Server- ${ server_id } "
server_directory = " ${ BASE_DIR } / ${ server_name } "
server_config = " ${ server_directory } /serverconfig.xml "
2021-12-25 21:23:27 -06:00
server_save_dir = " ${ server_directory } /Saves/ "
server_userdata_dir = " ${ server_directory } /UserData "
2021-11-15 14:33:21 -06:00
prefix = "7D2D"
server_session_name = " ${ prefix } - ${ server_name } "
[ [ ! -d " ${ server_directory } " ] ] &&
2021-11-16 21:49:59 -06:00
log "error" " Unable to find the server directory for $( important " ${ server_name } " ) " &&
return 1
2021-11-15 14:33:21 -06:00
2021-12-25 21:23:27 -06:00
backup_configs " ${ server_directory } "
2021-11-15 14:33:21 -06:00
local server_port
server_port = " $(( START_PORT_RANGE + server_id )) "
2021-12-25 21:23:27 -06:00
log "info" " Generating a few required directories in $( important " ${ server_directory } " ) "
mkdir -p " ${ server_save_dir } "
mkdir -p " ${ server_userdata_dir } "
log "info" "Configuring serverconfig.xml"
2021-11-15 14:33:21 -06:00
# Overwrite values that we want to control, e.g. server port
while IFS = '' read -r; do
2021-12-25 21:23:27 -06:00
if [ [ " ${ REPLY } " = *"property name=\"ServerPort\"" * ] ] ; then
2021-11-15 14:33:21 -06:00
printf "\t%s\n" " <property name=\"ServerPort\" value=\" ${ server_port } \" /> "
2021-12-25 21:23:27 -06:00
# Override SaveGameFolder, opiniated in that it should exist in the Server Directory
elif [ [ " ${ REPLY } " = *"property name=\"SaveGameFolder\"" * ] ] ; then
printf "\t%s\n" " <property name=\"SaveGameFolder\" value=\" ${ server_save_dir } \" /> "
# Override UserDataFolder, opiniated in that it should exist in the Server Directory
elif [ [ " ${ REPLY } " = *"property name=\"UserDataFolder\"" * ] ] ; then
printf "\t%s\n" " <property name=\"UserDataFolder\" value=\" ${ server_userdata_dir } \" "
# This is a path RELATIVE to the save game folder, hence we overwrite ../ so it's in the
# base of the server directory
elif [ [ " ${ REPLY } " = *"property name=\"AdminFileName\"" * ] ] ; then
printf "\t%s\n" "<property name=\"AdminFileName\" value=\"../serveradmin.xml\""
2021-11-15 14:33:21 -06:00
else
printf "%s\n" " ${ REPLY } "
fi
done < " ${ server_config } " > "temp-serverconfig.xml"
2021-12-25 21:23:27 -06:00
# Occasionally the closing tag in serverconfig.xml will be missing, this ensures it is there in that scenario
2021-11-15 14:33:21 -06:00
if [ [ ! " $( tail "temp-serverconfig.xml" -n 1 ) " = *"ServerSettings" * ] ] ; then
printf "</ServerSettings>\n" >> "temp-serverconfig.xml"
fi
local backups_dir
local backup_file
backups_dir = " ${ server_directory } /Config-Backups "
backup_file = " ${ backups_dir } /serverconfig.xml- $( date +%s) "
mkdir -p " ${ backups_dir } "
cp " ${ server_config } " " ${ backup_file } "
mv "temp-serverconfig.xml" " ${ server_config } "
2021-12-25 21:23:27 -06:00
log "info" " Starting $( important " ${ server_name } " ) located at $( important " ${ server_directory } " ) on port $( important " ${ server_port } " ) "
2021-11-15 14:33:21 -06:00
if tmux has-session -t " ${ server_session_name } " >/dev/null 2>& 1; then
log "warning" " $( important " ${ server_name } " ) is currently running "
if ( ( can_kill = = 0 ) ) ; then
log "info" "Been explicitly permitted to kill, bypassing confirmation"
kill_server -s " ${ server_id } "
else
if confirmation " Would you like to kill $( important " ${ server_name } " ) ? (Y/n) " ; then
log "info" " Given answer $( important "yes" ) to kill the server, killing $( important " ${ server_name } " ) "
kill_server -s " ${ server_id } "
else
log "info" " Given answer $( important "no" ) to kill the server, exiting "
exit 0
fi
fi
fi
log "info" " Creating new session for $( important " ${ server_name } " ) as session $( important " ${ server_session_name } " ) "
2021-12-24 22:00:22 -06:00
tmux new-session -d -s " ${ server_session_name } "
tmux send-keys \
" until ${ server_directory } /startserver.sh -configfile= ${ server_directory } /serverconfig.xml; do
echo 'Server died with code $?, restarting in 60 seconds...' >& 2
sleep 60;
done " C-m
2021-11-15 14:33:21 -06:00
log "info" " Finished starting $( important " ${ server_name } " ) on port $( important " ${ server_port } " ) as tmux session $( important " ${ server_session_name } " ) "
}
kill_server( ) {
local prefix
local server_id
server_id = ""
prefix = "7D2D"
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" kill [options]
--server <server id: int> | -s <server id: int>
Forcefully kills the server with the given id
Example:
--server 3"
exit
; ;
--) # End of all options.
break
; ;
--server | -s)
shift
server_id = " ${ 1 } "
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
if tmux kill-session -t " ${ prefix } -Server- ${ server_id } " >/dev/null 2>& 1; then
log "info" " Stopped $( important " ${ prefix } -Server- ${ server_id } " ) "
return 0
else
2021-11-16 21:49:59 -06:00
log "error" " $( important " ${ prefix } -Server- ${ server_id } " ) is not running "
2021-11-15 14:33:21 -06:00
return 1
fi
}
install( ) {
local server_id
server_id = ""
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" install [OPTIONS]
--server <server id: int> | -s <server id: int>
Installs the server to the given id if it doesn' t exist
Example:
--server 3"
exit
; ;
--) # End of all options.
break
; ;
--server | -s)
shift
server_id = " ${ 1 } "
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
local prefix
prefix = "7D2D"
local server_name
local server_directory
2021-12-25 21:23:27 -06:00
local server_admin_xml
2021-11-15 14:33:21 -06:00
server_name = " Server- ${ server_id } "
server_directory = " ${ BASE_DIR } / ${ server_name } "
2021-12-25 21:23:27 -06:00
server_admin_xml = " ${ HOME } /.local/share/7DaysToDie/Saves/serveradmin.xml "
2021-11-15 14:33:21 -06:00
# Ensure that the global mods directory exists
[ [ -d " ${ server_directory } " ] ] &&
log "error" " A server already exists at $( important " ${ server_directory } " ) , delete it and try again " &&
exit 1
log "info" " Installing $( important " ${ server_name } " ) to $( important " ${ server_directory } " ) "
2021-12-24 09:42:56 -06:00
steamcmd +force_install_dir " ${ server_directory } " +login anonymous +app_update 294420 validate +quit
2021-11-15 14:33:21 -06:00
mkdir -p " ${ server_directory } /Mods "
2021-12-25 21:23:27 -06:00
log "info" " Running $( important " ${ server_name } " ) to get a valid $( important "serveradmin.xml" ) "
start_server -s " ${ server_id } " -c
log "info" " Waiting to kill $( important " ${ server_name } " ) "
sleep 5
kill_server -s " ${ server_id } "
cp " ${ server_admin_xml } " " ${ server_directory } / "
2021-11-15 14:33:21 -06:00
log "info" " Successfully installed $( important " ${ server_name } " ) to $( important " ${ server_directory } " ) "
}
update( ) {
local server_id
server_id = ""
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" update [OPTIONS]
--server <server id: int> | -s <server id: int>
2021-12-24 22:00:22 -06:00
Updates the given server id
2021-11-15 14:33:21 -06:00
Example:
--server 3"
exit
; ;
--) # End of all options.
break
; ;
--server | -s)
shift
server_id = " ${ 1 } "
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
# Kill the server to ensure a smooth update
kill_server -s " ${ server_id } " >/dev/null 2>& 1
local server_directory
local server_name
2021-12-25 21:23:27 -06:00
local wait_time
2021-11-15 14:33:21 -06:00
server_name = " Server- ${ server_id } "
server_directory = " ${ BASE_DIR } / ${ server_name } "
2021-12-25 21:23:27 -06:00
wait_time = "5"
log "info" " Recommend running a $( important "backup" ) operation before running this, waiting $( important " ${ wait_time } " ) seconds before continuing... "
sleep " ${ wait_time } "
backup_configs " ${ server_directory } "
2021-11-15 14:33:21 -06:00
log "info" " Updating server $( important " ${ server_name } " ) located at $( important " ${ server_directory } " ) ... "
2021-12-24 09:42:56 -06:00
steamcmd +force_install_dir " ${ server_directory } " +login anonymous +app_update 294420 validate +quit
2021-11-15 14:33:21 -06:00
log "info" " Finished updating $( important " ${ server_name } " ) located at $( important " ${ server_directory } " ) "
}
2021-12-25 21:23:27 -06:00
backup_configs( ) {
local server_directory
local config_backup_directory
server_directory = " ${ 1 } "
config_backup_directory = " ${ server_directory } /config-backups/ $( date +s) "
log "info" " Backing up $( important ".xml configurations" ) in $( important " ${ server_directory } " ) to $( important " ${ config_backup_directory } " ) "
mkdir -p " ${ config_backup_directory } "
cp " ${ server_directory } / " *.xml " ${ config_backup_directory } / "
}
2021-12-24 22:00:22 -06:00
backup( ) {
local server_id
server_id = ""
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" backup [OPTIONS]
--server <server id: int> | -s <server id: int>
Backups the given server id
Example:
--server 3"
exit
; ;
--) # End of all options.
break
; ;
--server | -s)
shift
server_id = " ${ 1 } "
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ server_id } " ] ] && log "error" "No server id passed" && exit 1
# Kill the server to ensure a smooth backup
kill_server -s " ${ server_id } " >/dev/null 2>& 1
local backup_dir
local backup_full_path
local server_directory
local server_name
server_name = " Server- ${ server_id } "
server_directory = " ${ BASE_DIR } / ${ server_name } "
2021-12-25 21:23:27 -06:00
backup_dir = " ${ HOME } /7D2D-Server-Backups/ ${ server_name } "
2021-12-24 22:00:22 -06:00
backup_full_path = " ${ backup_dir } / $( date +%s) .tar.gz "
[ [ ! -d " ${ server_directory } " ] ] && log "info" " The server $( important " ${ server_name } " ) had no directory located at $( important " ${ server_directory } " ) " && exit 1
log "info" " Backing up server $( important " ${ server_name } " ) to $( important " ${ backup_full_path } " ) , this may take a while "
mkdir -p " ${ backup_dir } "
2021-12-25 21:23:27 -06:00
# Do a check if pv is there, pv is used for showing progress
if which pv > /dev/null 2>& 1; then
tar cf - " ${ server_directory } " -P 2> /dev/null | pv -s " $( du -sb " ${ server_directory } " | awk '{print $1}' ) " | gzip > " ${ backup_full_path } "
else
log "info" " $( important "pv" ) not installed, not showing progress... "
tar czf " ${ server_directory } " " ${ backup_full_path } " 2> /dev/null
fi
2021-12-24 22:00:22 -06:00
}
2021-11-16 21:49:59 -06:00
list_servers( ) {
local picked_option
picked_option = ""
while :; do
case ${ 1 } in
-h | -\? | --help)
printf "Usage: %s\n" \
" list [OPTIONS]
2021-12-19 15:25:16 -06:00
--running | -r
2021-11-16 21:49:59 -06:00
Lists the currently running 7 Days To Die Servers
Example:
--running
--installed | -i
Lists the currently installed 7 Days To Die Servers
Example:
--installed"
exit
; ;
--) # End of all options.
break
; ;
--running | -r)
picked_option = 0
; ;
--installed | -i)
picked_option = 1
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
return 1
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
2021-12-19 15:25:16 -06:00
2021-11-16 21:49:59 -06:00
[ [ -z " ${ picked_option } " ] ] &&
log "error" "An option must be passed for list, check list -h" &&
return 1
2021-12-24 23:07:00 -06:00
if ( ( picked_option = = 0 ) ) ; then
log "debug" "Listing running servers"
local tmux_sessions
tmux_sessions = " $( tmux list-sessions) " >/dev/null 2>& 1
if [ [ ! " ${ ? } " -eq "0" ] ] ; then
important "No servers currently running."
fi
while read -r; do
if [ [ " ${ REPLY } " = *"-Server-" * ] ] ; then
local running_server
running_server = " $( echo " ${ REPLY } " | cut -d ":" -f1) "
important " ${ running_server } "
2021-11-16 21:49:59 -06:00
fi
2021-12-24 23:07:00 -06:00
done <<< " ${ tmux_sessions } "
elif ( ( picked_option = = 1 ) ) ; then
log "debug" "Listing installed servers"
while read -r; do
important " ${ BASE_DIR } / ${ REPLY } "
done <<< " $( find " ${ BASE_DIR } " -name "startserver.sh" | cut -d "/" -f5) "
fi
2021-11-16 21:49:59 -06:00
}
2021-11-15 14:33:21 -06:00
usage( ) {
# Print out usage instructions for the local script
#
# Arguments:
# None
#
# Usage:
# usage
#
# POSIX Compliant:
# Yes
#
printf "Usage: %s\n" \
2021-12-24 22:00:22 -06:00
" $( basename " ${ 0 } " ) -h
2021-11-15 14:33:21 -06:00
start
Exposes options to start 7 Days To Die Servers, pass -h to it for details
kill
Exposes options to kill 7 Days To Die Servers, pass -h to it for details
install
Exposes options to install 7 Days To Die Servers, pass -h to it for details
update
2021-11-16 21:49:59 -06:00
Exposes options to update 7 Days To Die Servers, pass -h to it for details
list
2021-12-24 22:00:22 -06:00
Exposes options to list 7 Days To Die Servers, pass -h to it for details
backup
Exposes options to backup 7 Days To Die Servers, pass -h to it for details"
2021-11-15 14:33:21 -06:00
}
main( ) {
# Parse input arguments
#
# Arguments:
# Consult the `usage` function
#
# Usage:
# parse_args "$@"
# - All arguments should be ingested by parse_args first for variable setting
#
# POSIX Compliant:
# Yes
#
while :; do
case ${ 1 } in
-h | -\? | --help)
usage # Display a usage synopsis.
exit
; ;
--) # End of all options.
break
; ;
start | s)
shift
start_server " $@ "
break
; ;
kill | k)
shift
kill_server " $@ "
break
; ;
install | i)
shift
install " $@ "
break
; ;
update | u)
shift
update " $@ "
break
; ;
2021-11-16 21:49:59 -06:00
list | l)
shift
list_servers " $@ "
break
; ;
2021-12-24 22:00:22 -06:00
backup | b)
shift
backup " $@ "
break
; ;
2021-11-15 14:33:21 -06:00
-?*)
printf "Unknown option: %s\n" " $1 " >& 2
usage
exit 1
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
}
main " $@ "