Files
ql-lg-server/setup.sh
T
esefandClaude Opus 5.5 bc5bd09c15 ql-lg-server: Quake Live LG vampiric practice server in Docker
One-command setup (SteamCMD + minqlx build), .env settings with a
listed/unlisted switch, three factories (lgvamp, lgvamp_nolag, lgvamp100),
warmup-text and welcome minqlx plugins, watchdog/backup/daily-restart,
docs for admins and players.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 01:39:43 +03:00

123 lines
5.7 KiB
Bash
Executable File

#!/bin/sh
# ql-lg-server setup: run from the repo folder on a Linux machine with Docker.
# ./setup.sh first install (asks a few questions), or re-run safely any time
# ./setup.sh --yes no questions: use .env as it is (or the defaults)
# ./setup.sh --update also update the Quake Live game files from Steam
# Steps: check tools -> .env -> RCON password -> build image -> download QL (SteamCMD, ~1 GB)
# -> build minqlx -> start -> check the server answers.
set -e
cd "$(dirname "$0")"
YES=; UPDATE=
for a in "$@"; do
case "$a" in
--yes|-y) YES=1 ;;
--update) UPDATE=1 ;;
*) echo "unknown option: $a"; exit 1 ;;
esac
done
MINQLX_REPO=https://github.com/MinoMino/minqlx.git
MINQLX_COMMIT=fbdd915 # v0.5.3-15, the version this repo was tested with
say() { printf '\n\033[1m== %s\033[0m\n' "$*"; }
die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }
setenv() { # setenv KEY VALUE: replace the KEY= line in .env
v=$(printf '%s' "$2" | sed 's/[\\&|]/\\&/g')
sed -i "s|^$1=.*|$1=$v|" .env
}
getenv() { sed -n "s/^$1=//p" .env | tr -d '\r'; }
ask() { # ask "question" default -> answer (default when --yes or no terminal)
if [ -n "$YES" ] || [ ! -t 0 ]; then printf '%s' "$2"; return; fi
printf '%s [%s]: ' "$1" "$2" >&2; read -r r; printf '%s' "${r:-$2}"
}
say "Checking tools"
[ "$(id -u)" != 0 ] || die "don't run this as root: use a normal user in the 'docker' group"
command -v docker >/dev/null || die "docker is not installed (https://docs.docker.com/engine/install/)"
docker info >/dev/null 2>&1 || die "can't talk to docker: add yourself to the docker group (sudo usermod -aG docker \$USER, then log in again)"
docker compose version >/dev/null 2>&1 || die "'docker compose' (v2 plugin) is missing"
command -v python3 >/dev/null || die "python3 is missing (used by the health checks)"
echo "ok"
say "Settings (.env)"
if [ ! -f .env ]; then
cp .env.example .env
setenv QL_HOSTNAME "$(ask 'Server name' "$(getenv QL_HOSTNAME)")"
l=$(ask 'List it in the in-game server browser? (yes/no)' yes)
case "$l" in y|Y|yes|YES) setenv QL_LISTED yes ;; *) setenv QL_LISTED no ;; esac
setenv QL_PASSWORD "$(ask 'Join password (empty = open server)' '')"
setenv QL_PORT "$(ask 'Game port (UDP)' "$(getenv QL_PORT)")"
echo "created .env (edit it any time, then: docker compose up -d)"
else
echo "using existing .env"
fi
setenv QL_UID "$(id -u)"
setenv QL_GID "$(id -g)"
PORT=$(getenv QL_PORT); RPORT=$(getenv QL_RCON_PORT); NAME=$(getenv QL_NAME)
[ "$PORT" != "$RPORT" ] || die "QL_PORT and QL_RCON_PORT must differ"
RUNNING=$(docker inspect -f '{{.State.Status}}' "$NAME" 2>/dev/null || true)
if [ "$RUNNING" != running ] && command -v ss >/dev/null; then
! ss -lun | grep -q ":$PORT " || die "UDP port $PORT is already in use: set another QL_PORT in .env"
! ss -ltn | grep -q ":$RPORT " || die "TCP port $RPORT is already in use: set another QL_RCON_PORT in .env"
fi
echo "server '$(getenv QL_HOSTNAME)', container $NAME, UDP $PORT, listed: $(getenv QL_LISTED)"
say "RCON password"
mkdir -p secrets server steamcmd-home logs minqlx-build
chmod 700 secrets
if [ ! -s secrets/rcon-password.txt ]; then
(umask 077; head -c 64 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 24 > secrets/rcon-password.txt)
echo "generated secrets/rcon-password.txt (you never need to type it: ./rcon uses it)"
else
echo "keeping secrets/rcon-password.txt"
fi
chmod +x rcon tools/*.sh scripts/*.sh
say "Building the Docker image"
docker compose build -q
if [ ! -x server/qzeroded.x64 ] || [ -n "$UPDATE" ]; then
say "Downloading Quake Live dedicated server (SteamCMD app 349090, ~1 GB)"
docker compose run --rm --no-deps --entrypoint /opt/steamcmd/steamcmd.sh ql \
+@sSteamCmdForcePlatformType linux +force_install_dir /ql/server \
+login anonymous +app_update 349090 validate +quit
[ -x server/qzeroded.x64 ] || die "download failed (server/qzeroded.x64 missing): run ./setup.sh again"
else
echo "game files present (./setup.sh --update to update them)"
fi
if [ ! -f minqlx-build/minqlx.x64.so ]; then
say "Building minqlx ($MINQLX_COMMIT)"
docker run --rm -v "$PWD/minqlx-build:/out" debian:bookworm-slim sh -c "
apt-get update -qq && apt-get install -y -qq build-essential python3-dev git ca-certificates >/dev/null &&
git clone -q $MINQLX_REPO /src && cd /src && git checkout -q $MINQLX_COMMIT && make >/dev/null &&
cp bin/minqlx.x64.so bin/minqlx.zip bin/run_server_x64_minqlx.sh /out/ && chown -R $(id -u):$(id -g) /out"
[ -f minqlx-build/minqlx.x64.so ] || die "minqlx build failed"
fi
cp minqlx-build/minqlx.x64.so minqlx-build/minqlx.zip minqlx-build/run_server_x64_minqlx.sh server/
chmod +x server/run_server_x64_minqlx.sh
echo "minqlx installed into server/"
say "Starting"
docker compose up -d
printf 'waiting for the server to answer'
i=0
until python3 tools/qlq.py 127.0.0.1 "$PORT" >/dev/null 2>&1; do
i=$((i+1)); [ $i -lt 30 ] || { echo; die "no answer after 90 s: look at 'docker logs --tail 50 $NAME'"; }
printf '.'; sleep 3
done
echo " up"
python3 tools/qlq.py 127.0.0.1 "$PORT" --status || true
say "Done"
IPS=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -v ':' | grep -v '^172\.' | head -3 | tr '\n' ' ')
echo "Join from Quake Live (console, ~ key):"
for ip in $IPS; do echo " /connect $ip:$PORT"; done
if [ "$(getenv QL_LISTED)" = yes ]; then
echo "From the internet: forward UDP $PORT to this machine, then /connect <your public IP>:$PORT"
echo "(listed servers show up in the in-game browser after a few minutes)"
fi
echo
echo "Console: ./rcon status ./rcon addbot hunter 5 ./rcon kick allbots"
echo "Auto-restart: ./scripts/install-cron.sh (watchdog, nightly backup, daily restart)"
echo "Docs: README.md, docs/ADMIN.md, docs/PLAYERS.md"