Skip to content

Instantly share code, notes, and snippets.

@glektarssza
Last active May 11, 2025 20:46
Show Gist options
  • Save glektarssza/c39b4d34ca893108fa1f29742c3e3e27 to your computer and use it in GitHub Desktop.
Save glektarssza/c39b4d34ca893108fa1f29742c3e3e27 to your computer and use it in GitHub Desktop.
Factorio Systemd Service
#-- Put your environment variable overrides here!
[Unit]
#-- Unit metadata
Description=Factorio Server (%i)
#-- Dependencies
After=network-online.target
Wants=network-online.target
[Service]
#-- Service type
Type=simple
#-- Restart on failure
Restart=on-failure
#-- Wait 3 seconds before restarting
RestartSec=3s
#-- Limit to a maximum of 3 restarts per day
StartLimitInterval=1d
StartLimitBurst=3
#-- User and group
User=factorio
Group=factorio
#-- Define I/O
[email protected]
StandardInput=socket
StandardOutput=journal
StandardError=journal
#-- Directories
RuntimeDirectory=factorio
RuntimeDirectoryMode=0775
RuntimeDirectoryPreserve=no
WorkingDirectory=/<YOUR_FACTORIO_ROOT>/
#-- Protect various system parameters
ProtectSystem=full
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
#-- Timeout settings
TimeoutStopSec=30
#-- Default environment variables
Environment=FACTORIO_STOP_BEFORE_SAVE_DELAY=10
Environment=FACTORIO_STOP_AFTER_SAVE_DELAY=5
Environment=FACTORIO_FINAL_STOP_DELAY=3
#-- Environment file
EnvironmentFile=-/<YOUR_FACTORIO_ROOT>/config/%i/.envrc
#-- Startup command(s)
ExecStart=/<YOUR_FACTORIO_ROOT>/startFactorioServer.sh "%i"
#-- Stop command(s)
ExecStop=/bin/sh -c 'echo "Server is preparing to shut down!" > /run/factorio/%i.stdin'
ExecStop=/bin/sh -c 'echo "Saving the world in ${FACTORIO_STOP_BEFORE_SAVE_DELAY} seconds..." > /run/factorio/%i.stdin'
ExecStop=sleep ${FACTORIO_STOP_BEFORE_SAVE_DELAY}
ExecStop=/bin/sh -c 'echo "Saving the world!" > /run/factorio/%i.stdin'
ExecStop=/bin/sh -c 'echo "/save" > /run/factorio/%i.stdin'
ExecStop=/bin/sh -c 'echo "World saved! Shutting down in ${FACTORIO_STOP_AFTER_SAVE_DELAY} seconds..." > /run/factorio/%i.stdin'
ExecStop=sleep ${FACTORIO_STOP_AFTER_SAVE_DELAY}
ExecStop=/bin/sh -c 'echo "Shutting down! Good bye!" > /run/factorio/%i.stdin'
ExecStop=sleep ${FACTORIO_FINAL_STOP_DELAY}
ExecStop=/bin/sh -c 'echo "/quit" > /run/factorio/%i.stdin'
[Install]
WantedBy=multi-user.target
[Unit]
[email protected]
[Socket]
#-- User and group
SocketUser=factorio
SocketGroup=factorio
#-- The mode to create the FIFO node in
SocketMode=0665
#-- Define standard input socket
ListenFIFO=%t/factorio/%i.stdin
#-- Remove when the parent service stops
RemoveOnStop=true
[Unit]
#-- Unit metadata
Description=Restart the %i Factorio server daily.
#-- Dependencies
Requisite=factorio@%i.service
[Service]
#-- Service type
Type=oneshot
#-- Timeout settings
TimeoutStartSec=30
#-- Startup command(s)
ExecStart=/usr/bin/systemctl try-restart factorio@%i.service
[Unit]
#-- Unit metadata
Description=Restart the %i Factorio server daily.
[Timer]
#-- The calendar time to restart at (optionally add a time zone)
OnCalendar=*-*-* 00:00:00
#-- Persist next restart time to disk
Persistent=true
[Install]
WantedBy=timers.target
#!/usr/bin/env bash
#-- Resolve script directory
SCRIPT_SOURCE="${BASH_SOURCE[0]}";
while [[ -L "${SCRIPT_SOURCE}" ]]; do
SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_SOURCE}")" > /dev/null 2>&1 && pwd)";
SCRIPT_SOURCE="$(readlink "${SCRIPT_SOURCE}")";
[[ ${SCRIPT_SOURCE} != /* ]] && SOURCE="${SCRIPT_DIR}/${SCRIPT_SOURCE}";
done
SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_SOURCE}")" > /dev/null 2>&1 && pwd)";
if [[ -z "$1" ]]; then
printf "[\x1b[91mFATAL\x1b[0m] A save name/configuration to start is required!\n";
exit 1;
fi
#-- Start server
exec "${SCRIPT_DIR}/bin/x64/factorio" --start-server "$1" --server-adminlist "${SCRIPT_DIR}/config/$1/server-adminlist.json" --server-settings "${SCRIPT_DIR}/config/$1/server-settings.json" $([[ -n "${FACTORIO_USE_WHITELIST}" ]] && echo --use-server-whitelist --server-whitelist "${SCRIPT_DIR}/config/$1/server-whitelist.json" || echo "")
#!/usr/bin/env bash
#-- Resolve script directory
SCRIPT_SOURCE="${BASH_SOURCE[0]}";
while [[ -L "${SCRIPT_SOURCE}" ]]; do
SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_SOURCE}")" > /dev/null 2>&1 && pwd)";
SCRIPT_SOURCE="$(readlink "${SCRIPT_SOURCE}")";
[[ ${SCRIPT_SOURCE} != /* ]] && SOURCE="${SCRIPT_DIR}/${SCRIPT_SOURCE}";
done
SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_SOURCE}")" > /dev/null 2>&1 && pwd)";
#-- Parse CLI arguments
while [[ -n "$1" ]]; do
case "$1" in
--help|-h)
printf "updateFactorioServer.sh [--help|-h] [stable|experimental]\n";
exit 0;
;;
*)
if [[ -z "${CHANNEL}" ]]; then
CHANNEL="$1"
else
printf "[\x1b[33mWARN\x1b[0m] Factorio channel already set to '\x1b[96m${CHANNEL}\x1b[0m', ignoring CLI argument '\x1b[95m$1\x1b[0m'!\n";
fi
;;
esac
shift 1;
done
#-- Default to stable update channel
if [[ -z "${CHANNEL}" ]]; then
printf "[\x1b[33mWARN\x1b[0m] No Factorio channel selected, assuming '\x1b[96mstable\x1b[0m'!\n";
CHANNEL="stable"
else
printf "[\x1b[94mINFO\x1b[0m] Factorio channel '\x1b[96m${CHANNEL}\x1b[0m' selected!\n";
fi
#-- Check update channel is valid
if [[ "${CHANNEL}" != "stable" && "${CHANNEL}" != "experimental" ]]; then
printf "[\x1b[91mFATAL\x1b[0m] Unknown Factorio channel '\x1b[96m${CHANNEL}\x1b[0m' selected!\n";
exit 1;
fi
#-- Make sure we're in Factorio root directory
pushd "${SCRIPT_DIR}";
#-- Create temporary directory
mkdir -p "${SCRIPT_DIR}/tmp/";
#-- Enter temporary directory
pushd "${SCRIPT_DIR}/tmp/";
#-- Get latest update
wget -O "${SCRIPT_DIR}/tmp/factorio-headless-latest.tar.xz" "https://factorio.com/get-download/${CHANNEL}/headless/linux64";
#-- Unpack latest
tar xvf "${SCRIPT_DIR}/tmp/factorio-headless-latest.tar.xz";
#-- Return to Factorio root
popd;
#-- Remove executable and data directories
rm -r "${SCRIPT_DIR}/bin/" "${SCRIPT_DIR}/data/";
#-- Apply update
mv "${SCRIPT_DIR}/tmp/factorio/bin/" "${SCRIPT_DIR}/tmp/factorio/data/" "${SCRIPT_DIR}";
#-- Remove temporary directory
rm -r "${SCRIPT_DIR}/tmp/";
#-- Return to original directory
popd;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment