#!/bin/bash # ClayGarth.com - Sequential Factorio server manager using INI config # Fancy terminal output version CONFIG_FILE="/home/factorio/servers-config.ini" # Color and style codes RED='\033[1;31m' GREEN='\033[1;32m' YELLOW='\033[1;33m' BLUE='\033[1;34m' CYAN='\033[1;36m' BOLD='\033[1m' UNDERLINE='\033[4m' RESET='\033[0m' timestamp() { date +"%Y-%m-%d %H:%M:%S" } section() { echo -e "${BOLD}${CYAN}\n==============================" echo -e "$1" echo -e "==============================${RESET}" } status() { # $1 = message, $2 = color echo -e "${2}${BOLD}[$(timestamp)]${RESET} $1" } # Monitor a server's log for "Opening socket for broadcast" and show a ready message monitor_server_ready() { local server="$1" local log_file="/home/factorio/servers/${server}/factorio-current.log" local status_file="/tmp/factorio_server_ready_${server}" rm -f "$status_file" if timeout 120 grep -m 1 "Opening socket for broadcast" <(tail -n 0 -F "$log_file") > /dev/null; then touch "$status_file" fi } # Stop a single server, monitor for clean shutdown, and cleanup stop_server() { local server="$1" local log_file="/home/factorio/servers/${server}/factorio-current.log" local monitor_string="Goodbye" section "๐Ÿ›‘ Stopping ${server} server" screen -p 0 -S "$server" -X stuff $'/quit\n' >> /dev/null 2>&1 status "Monitoring $server log for shutdown confirmation: $log_file" "$YELLOW" if [ -f "$log_file" ] && grep -q "$monitor_string" "$log_file"; then status "$server was already stopped." "$GREEN" else if [ -f "$log_file" ] && timeout 30 tail -n 0 -F "$log_file" | grep -q "$monitor_string"; then status "$server has saved and stopped cleanly." "$GREEN" else status "Timeout: $server did not shut down cleanly in 30 seconds." "$RED" screen -S "$server" -p 0 -X stuff "^C" >> /dev/null 2>&1 sleep 2 screen -X -S "$server" quit >> /dev/null 2>&1 sleep 1 fi fi # Cleanup status "Cleaning up $server server files..." "$BLUE" cd "/home/factorio/servers/$server/saves" 2>/dev/null && rm ./*.tmp.zip >> /dev/null 2>&1 cd "/home/factorio/servers/$server/" 2>/dev/null && rm -f .lock >> /dev/null 2>&1 cp /home/factorio/mods/mod-settings.dat /home/factorio/mods/default/ > /dev/null 2>&1 cp /home/factorio/mods/mod-list.json /home/factorio/mods/default/ > /dev/null 2>&1 status "Cleanup completed for $server." "$GREEN" } # Start a single server with its config start_server() { local server="$1" local port="$2" local settings="$3" local config="$4" local mod_directory="$5" local use_whitelist="$6" local factorio_executable="/home/factorio/factorio/bin/x64/factorio" if [ ! -x "$factorio_executable" ]; then status "Error: Factorio executable not found or not executable at $factorio_executable" "$RED" exit 1 fi local start_command="$factorio_executable --start-server-load-latest --server-settings $settings --config $config --executable-path /home/factorio/factorio/bin/x64/ --mod-directory $mod_directory --port $port" if [ "$use_whitelist" = "true" ]; then start_command="$start_command --use-server-whitelist true" fi section "๐Ÿš€ Starting ${server} server" status "Command: $start_command" "$CYAN" /usr/bin/screen -dmS "$server" $start_command sleep 2 if screen -list | grep -q "\.${server}[[:space:]]"; then status "$server started successfully." "$GREEN" # Start log monitor in the background monitor_server_ready "$server" & else status "Error: Failed to start $server." "$RED" exit 1 fi } # Update Factorio and mods update_factorio() { section "โฌ†๏ธ Updating Factorio and Mods" local FACTORIO_API="https://factorio.com/api/latest-releases" local FACTORIO_RESPONSE FACTORIO_VERSION FACTORIO_VERSION_NO_PERIODS VERSION_FILE CURRENT_VERSION FACTORIO_RESPONSE=$(curl -s "$FACTORIO_API") FACTORIO_VERSION=$(echo "$FACTORIO_RESPONSE" | jq -r '.stable.headless') FACTORIO_VERSION_NO_PERIODS=$(echo "$FACTORIO_VERSION" | tr -d '.') VERSION_FILE="/home/factorio/factorio_version.txt" if [ -f "$VERSION_FILE" ]; then CURRENT_VERSION=$(cat "$VERSION_FILE") if [ "$FACTORIO_VERSION_NO_PERIODS" -gt "$CURRENT_VERSION" ]; then status "New Factorio version available. Updating..." "$YELLOW" echo "$FACTORIO_VERSION_NO_PERIODS" > "$VERSION_FILE" cd /home/factorio || exit 1 wget https://www.factorio.com/get-download/stable/headless/linux64 -O factorio.tar.xz tar -xf factorio.tar.xz chmod +x /home/factorio/factorio/bin/x64/factorio > /dev/null 2>&1 rm -f factorio.tar.xz status "Factorio updated to $FACTORIO_VERSION." "$GREEN" else status "Factorio is already up-to-date." "$GREEN" fi else echo "$FACTORIO_VERSION_NO_PERIODS" > "$VERSION_FILE" status "Factorio version file created." "$GREEN" fi #status "Updating mods..." "$BLUE" cp /home/factorio/mods/mod-list.json /home/factorio/mods/default/ > /dev/null 2>&1 #status "Mods updated." "$GREEN" } # Sequentially stop and/or start servers as specified in the INI file process_servers() { local action="$1" # "stop", "start", or "restart" local server port settings config mod_directory use_whitelist while IFS= read -r line || [ -n "$line" ]; do if [[ $line =~ ^\[([^\]]+)\]$ ]]; then server="${BASH_REMATCH[1]}" elif [[ $line =~ ^port=([0-9]+)$ ]]; then port="${BASH_REMATCH[1]}" elif [[ $line =~ ^settings=(.+)$ ]]; then settings="${BASH_REMATCH[1]}" elif [[ $line =~ ^config=(.+)$ ]]; then config="${BASH_REMATCH[1]}" elif [[ $line =~ ^mod_directory=(.+)$ ]]; then mod_directory="${BASH_REMATCH[1]}" elif [[ $line =~ ^use_whitelist=(.+)$ ]]; then use_whitelist="${BASH_REMATCH[1]}" if [ "$action" = "stop" ]; then stop_server "$server" elif [ "$action" = "start" ]; then start_server "$server" "$port" "$settings" "$config" "$mod_directory" "$use_whitelist" elif [ "$action" = "restart" ]; then stop_server "$server" start_server "$server" "$port" "$settings" "$config" "$mod_directory" "$use_whitelist" fi fi done < "$CONFIG_FILE" } # Argument handling if [ "$1" = "--help" ]; then echo -e "${BOLD}Usage:${RESET}" echo -e " ${YELLOW}--update${RESET} : Stop all servers, update, then start each server sequentially" echo -e " ${YELLOW}--stop${RESET} : Stop all servers sequentially" exit 0 fi if [ "$1" = "--stop" ]; then section "๐Ÿ›‘ Stopping All Servers" process_servers "stop" section "๐Ÿงน Cleaning up Screens" screen -wipe > /dev/null 2>&1 status "Below is the list of active screen sessions. Each running server should appear here. If you don't see a server, it may not be running." "$YELLOW" screen -ls exit 0 fi if [ "$1" = "--update" ]; then section "๐Ÿ›‘ Stopping All Servers" process_servers "stop" update_factorio section "๐Ÿš€ Starting All Servers" process_servers "start" section "๐Ÿงน Cleaning up Screens" screen -wipe > /dev/null 2>&1 status "Below is the list of active screen sessions. Each running server should appear here. If you don't see a server, it may not be running." "$YELLOW" screen -ls exit 0 fi # Default: restart each server sequentially (stop, then start) section "๐Ÿ”„ Restarting All Servers Sequentially" process_servers "restart" section "๐Ÿงน Cleaning up Screens" screen -wipe > /dev/null 2>&1 status "Below is the list of active screen sessions. Each running server should appear here. If you don't see a server, it may not be running." "$YELLOW" screen -ls # After showing screens, check and display each server's ready status while IFS= read -r line || [ -n "$line" ]; do if [[ $line =~ ^\[([^\]]+)\]$ ]]; then server="${BASH_REMATCH[1]}" enabled=1 elif [[ $line =~ ^use_whitelist=(.+)$ ]]; then use_whitelist="${BASH_REMATCH[1]}" if [ "$enabled" = "1" ]; then status_file="/tmp/factorio_server_ready_${server}" if [ -f "$status_file" ]; then status "$server is ONLINE and ready for connections!" "$GREEN" else status "Waiting for $server to be ready for connections..." "$YELLOW" # Wait up to 2 minutes for the status file to appear if timeout 120 bash -c "while [ ! -f '$status_file' ]; do sleep 1; done"; then status "$server is ONLINE and ready for connections!" "$GREEN" else status "Timeout: $server did not report as online within 2 minutes." "$RED" fi fi rm -f "$status_file" fi enabled=0 fi done < "$CONFIG_FILE" exit 0