From c7c2da87d98960ee7bdf8247f0b58db9621bb201 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 16 Mar 2026 14:05:47 +0100 Subject: [PATCH] millores en setup-quake --- .gitignore | 5 +- README.md | 131 +++++++++++++++------ config.ini | 7 ++ setup-network/build.sh | 10 +- setup-quake/build.sh | 10 +- setup-quake/q3a/baseq3/autoexec.cfg | 8 ++ setup-quake/q3a/baseq3/autoexec_server.cfg | 6 + setup-quake/q3a/baseq3/levels_tdm.cfg | 5 + setup-quake/q3a/baseq3/nobots.cfg | 2 + setup-quake/q3a/baseq3/server_ctf.cfg | 20 ++++ setup-quake/q3a/baseq3/server_dm.cfg | 18 +++ setup-quake/q3a/baseq3/server_tdm.cfg | 21 ++++ setup-quake/q3a/scripts/activate-server.sh | 6 + setup-quake/q3a/scripts/client.sh | 2 + setup-quake/q3a/scripts/server-ctf-bots.sh | 3 + setup-quake/q3a/scripts/server-ctf.sh | 3 + setup-quake/q3a/scripts/server-dm-bots.sh | 3 + setup-quake/q3a/scripts/server-dm.sh | 3 + setup-quake/q3a/scripts/server-tdm-bots.sh | 3 + setup-quake/q3a/scripts/server-tdm.sh | 3 + setup-quake/setup_quake.py | 96 +++++++++++++-- 21 files changed, 308 insertions(+), 57 deletions(-) create mode 100644 config.ini create mode 100644 setup-quake/q3a/baseq3/autoexec.cfg create mode 100644 setup-quake/q3a/baseq3/autoexec_server.cfg create mode 100644 setup-quake/q3a/baseq3/levels_tdm.cfg create mode 100644 setup-quake/q3a/baseq3/nobots.cfg create mode 100644 setup-quake/q3a/baseq3/server_ctf.cfg create mode 100644 setup-quake/q3a/baseq3/server_dm.cfg create mode 100644 setup-quake/q3a/baseq3/server_tdm.cfg create mode 100755 setup-quake/q3a/scripts/activate-server.sh create mode 100755 setup-quake/q3a/scripts/client.sh create mode 100755 setup-quake/q3a/scripts/server-ctf-bots.sh create mode 100755 setup-quake/q3a/scripts/server-ctf.sh create mode 100755 setup-quake/q3a/scripts/server-dm-bots.sh create mode 100755 setup-quake/q3a/scripts/server-dm.sh create mode 100755 setup-quake/q3a/scripts/server-tdm-bots.sh create mode 100755 setup-quake/q3a/scripts/server-tdm.sh diff --git a/.gitignore b/.gitignore index 5c206bd..af34252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ +dist/ setup-network/build/ -setup-network/dist/ setup-network/*.spec setup-network/.venv/ setup-quake/build/ -setup-quake/dist/ setup-quake/*.spec setup-quake/.venv/ + +__pycache__/ diff --git a/README.md b/README.md index e5c36c7..158c74f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,41 @@ Tools for configuring PCs at a retro gaming fair. +## Deployment + +Build all utilities first (see each utility's Quick start below). All binaries +land in a shared `dist/` folder at the repo root: + +``` +dist/ + setup-network + setup-quake + config.ini ← single unified config for all utilities +``` + +Copy the entire `dist/` folder to a USB stick, then on each fair PC run the +relevant binary: + +```bash +sudo ./setup-network 4 +sudo ./setup-quake +``` + +### Configuration + +Edit `config.ini` at the repo root before building (it gets copied to `dist/` +automatically by each `build.sh`): + +```ini +[network] +subnet_root = 10.0.1 +hostname_prefix = retro-alcoi- + +[quake] +files_url = https://php.sustancia.synology.me/files/ioquake3-files.zip +install_dir = .q3a +``` + ## Utilities ### setup-network @@ -21,7 +56,7 @@ sudo ./setup-network 4 # → IP: 10.0.1.4/24, hostname: retro-alcoi-4 ``` -See [`setup-network/`](setup-network/) for source and build instructions. +See [`setup-network/`](setup-network/) for source. #### Quick start @@ -30,24 +65,7 @@ cd setup-network/ python3 -m venv .venv .venv/bin/pip install -r requirements.txt bash build.sh -# Binary is at dist/setup-network -``` - -Copy `dist/setup-network` and `dist/config.ini` to a USB stick, then on each -fair PC: - -```bash -sudo ./setup-network 4 -``` - -#### Configuration - -Edit `config.ini` (alongside the binary) before distributing: - -```ini -[network] -subnet_root = 10.0.1 -hostname_prefix = retro-alcoi- +# Binary is at ../dist/setup-network ``` #### How it works @@ -68,7 +86,7 @@ Quake 3 data files from a private server to the real user's home directory. sudo ./setup-quake ``` -See [`setup-quake/`](setup-quake/) for source and build instructions. +See [`setup-quake/`](setup-quake/) for source. #### Quick start @@ -77,24 +95,7 @@ cd setup-quake/ python3 -m venv .venv .venv/bin/pip install -r requirements.txt bash build.sh -# Binary is at dist/setup-quake -``` - -Copy `dist/setup-quake` and `dist/config.ini` to a USB stick, then on each -fair PC: - -```bash -sudo ./setup-quake -``` - -#### Configuration - -Edit `config.ini` (alongside the binary) before distributing: - -```ini -[quake] -files_url = https://php.sustancia.synology.me/files/ioquake3-files.zip -install_dir = .q3a +# Binary is at ../dist/setup-quake ``` #### How it works @@ -103,3 +104,57 @@ install_dir = .q3a 2. Downloads the Quake 3 data files zip from the configured URL. 3. Extracts the data files to `~/.q3a` (resolved via `$SUDO_USER` so the real user's home is used, not root's). +4. Fixes ownership of all extracted files to the real user (not root). +5. Locks `baseq3/autoexec.cfg` to read-only (444) — client mode. + +#### Client mode (default) + +After `setup-quake` all machines are clients. `autoexec.cfg` is read-only so +the game always starts at desktop resolution, fullscreen, with name "Jugador" +and auto-connects to the server at `10.0.1.200`. + +Use `~/.q3a/scripts/client.sh` to launch the game. + +#### Server mode + +On the server machine (10.0.1.200), run after `setup-quake`: + +```bash +~/.q3a/scripts/activate-server.sh +``` + +This restores write access to `autoexec.cfg`. Then use one of the server +launch scripts: + +``` +scripts/server-ctf.sh # CTF, no bots +scripts/server-ctf-bots.sh # CTF, bots fill server +scripts/server-dm.sh # Deathmatch, no bots +scripts/server-dm-bots.sh # Deathmatch, bots fill server +scripts/server-tdm.sh # Team Deathmatch, no bots +scripts/server-tdm-bots.sh # Team Deathmatch, bots fill server +``` + +#### Q3A data files zip + +The zip extracted by `setup-quake` is hosted at `files_url` in `config.ini`. +Its source lives in `setup-quake/q3a/` in this repo (pak files excluded — add +them separately before zipping). Structure after extraction: + +``` +~/.q3a/ + baseq3/ + pak0–pak8.pk3 + autoexec.cfg ← client config (read-only after setup-quake) + autoexec_server.cfg ← server base settings, exec'd by server scripts + server_ctf.cfg / server_dm.cfg / server_tdm.cfg + levels_ctf.cfg / levels_dm.cfg / levels_tdm.cfg + bots.cfg / bots_easy.cfg / nobots.cfg + missionpack/ + pak*.pk3 + scripts/ + client.sh + server-{ctf,dm,tdm}.sh + server-{ctf,dm,tdm}-bots.sh + activate-server.sh +``` diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..21531fe --- /dev/null +++ b/config.ini @@ -0,0 +1,7 @@ +[network] +subnet_root = 10.0.1 +hostname_prefix = retro-alcoi- + +[quake] +files_url = https://php.sustancia.synology.me/files/ioquake3-files.zip +install_dir = .q3a diff --git a/setup-network/build.sh b/setup-network/build.sh index 0803dfe..e43237e 100755 --- a/setup-network/build.sh +++ b/setup-network/build.sh @@ -15,13 +15,15 @@ elif ! command -v pyinstaller &>/dev/null; then exit 1 fi +DIST_DIR="${SCRIPT_DIR}/../dist" + echo "Building setup-network binary..." -pyinstaller --onefile --clean --name setup-network setup_network.py +pyinstaller --onefile --clean --name setup-network --distpath "$DIST_DIR" setup_network.py echo "Copying config.ini to dist/..." -cp config.ini dist/ +cp "${SCRIPT_DIR}/../config.ini" "$DIST_DIR/" echo "" echo "Build complete. Output:" -echo " ${SCRIPT_DIR}/dist/setup-network" -echo " ${SCRIPT_DIR}/dist/config.ini" +echo " ${DIST_DIR}/setup-network" +echo " ${DIST_DIR}/config.ini" diff --git a/setup-quake/build.sh b/setup-quake/build.sh index 11f4a69..e21eddd 100755 --- a/setup-quake/build.sh +++ b/setup-quake/build.sh @@ -15,13 +15,15 @@ elif ! command -v pyinstaller &>/dev/null; then exit 1 fi +DIST_DIR="${SCRIPT_DIR}/../dist" + echo "Building setup-quake binary..." -pyinstaller --onefile --clean --name setup-quake setup_quake.py +pyinstaller --onefile --clean --name setup-quake --distpath "$DIST_DIR" setup_quake.py echo "Copying config.ini to dist/..." -cp config.ini dist/ +cp "${SCRIPT_DIR}/../config.ini" "$DIST_DIR/" echo "" echo "Build complete. Output:" -echo " ${SCRIPT_DIR}/dist/setup-quake" -echo " ${SCRIPT_DIR}/dist/config.ini" +echo " ${DIST_DIR}/setup-quake" +echo " ${DIST_DIR}/config.ini" diff --git a/setup-quake/q3a/baseq3/autoexec.cfg b/setup-quake/q3a/baseq3/autoexec.cfg new file mode 100644 index 0000000..d8afaf5 --- /dev/null +++ b/setup-quake/q3a/baseq3/autoexec.cfg @@ -0,0 +1,8 @@ +set vm_game 2 +set vm_cgame 2 +set vm_ui 2 +set r_mode -2 // use desktop resolution +set r_fullscreen 1 +set name "Jugador" +set cl_allowDownload 0 +set cl_timeout 0 // never disconnect from server due to inactivity/timeout diff --git a/setup-quake/q3a/baseq3/autoexec_server.cfg b/setup-quake/q3a/baseq3/autoexec_server.cfg new file mode 100644 index 0000000..cd6a005 --- /dev/null +++ b/setup-quake/q3a/baseq3/autoexec_server.cfg @@ -0,0 +1,6 @@ +set vm_game 2 +set vm_cgame 2 +set vm_ui 2 +set dedicated 1 +set com_hunkmegs 128 +set net_port 27960 diff --git a/setup-quake/q3a/baseq3/levels_tdm.cfg b/setup-quake/q3a/baseq3/levels_tdm.cfg new file mode 100644 index 0000000..967e77c --- /dev/null +++ b/setup-quake/q3a/baseq3/levels_tdm.cfg @@ -0,0 +1,5 @@ +set tdm1 "map q3dm6; set nextmap vstr tdm2" +set tdm2 "map q3dm7; set nextmap vstr tdm3" +set tdm3 "map q3dm13; set nextmap vstr tdm4" +set tdm4 "map q3dm17; set nextmap vstr tdm1" +vstr tdm1 diff --git a/setup-quake/q3a/baseq3/nobots.cfg b/setup-quake/q3a/baseq3/nobots.cfg new file mode 100644 index 0000000..35ca902 --- /dev/null +++ b/setup-quake/q3a/baseq3/nobots.cfg @@ -0,0 +1,2 @@ +seta bot_enable 0 +seta bot_minplayers 0 diff --git a/setup-quake/q3a/baseq3/server_ctf.cfg b/setup-quake/q3a/baseq3/server_ctf.cfg new file mode 100644 index 0000000..e7785f9 --- /dev/null +++ b/setup-quake/q3a/baseq3/server_ctf.cfg @@ -0,0 +1,20 @@ +seta sv_hostname "RETRO-ALCOI" +seta g_motd "RETRO-ALCOI" +seta sv_maxclients 8 +seta sv_pure 1 +seta g_quadfactor 3 +seta g_gametype 4 +seta g_teamAutoJoin 0 +seta g_teamForceBalance 1 +seta timelimit 30 +seta capturelimit 8 +seta g_weaponrespawn 2 +seta g_inactivity 0 +seta g_forcerespawn 0 +seta g_log server.log +seta logfile 3 +seta rconpassword "secret" +seta rate "12400" +seta snaps "40" +seta cl_maxpackets "40" +seta cl_packetdup "1" diff --git a/setup-quake/q3a/baseq3/server_dm.cfg b/setup-quake/q3a/baseq3/server_dm.cfg new file mode 100644 index 0000000..d983cbc --- /dev/null +++ b/setup-quake/q3a/baseq3/server_dm.cfg @@ -0,0 +1,18 @@ +seta sv_hostname "RETRO-ALCOI" +seta g_motd "RETRO-ALCOI" +seta sv_maxclients 8 +seta sv_pure 1 +seta g_quadfactor 3 +seta g_gametype 0 +seta timelimit 10 +seta fraglimit 20 +seta g_weaponrespawn 2 +seta g_inactivity 0 +seta g_forcerespawn 0 +seta g_log server.log +seta logfile 3 +seta rconpassword "secret" +seta rate "12400" +seta snaps "40" +seta cl_maxpackets "40" +seta cl_packetdup "1" diff --git a/setup-quake/q3a/baseq3/server_tdm.cfg b/setup-quake/q3a/baseq3/server_tdm.cfg new file mode 100644 index 0000000..7dd12e1 --- /dev/null +++ b/setup-quake/q3a/baseq3/server_tdm.cfg @@ -0,0 +1,21 @@ +seta sv_hostname "RETRO-ALCOI" +seta g_motd "RETRO-ALCOI" +seta sv_maxclients 8 +seta sv_pure 1 +seta g_quadfactor 3 +seta g_friendlyFire 1 +seta g_gametype 3 +seta g_teamAutoJoin 0 +seta g_teamForceBalance 1 +seta timelimit 15 +seta fraglimit 25 +seta g_weaponrespawn 2 +seta g_inactivity 0 +seta g_forcerespawn 0 +seta g_log server.log +seta logfile 3 +seta rconpassword "secret" +seta rate "12400" +seta snaps "40" +seta cl_maxpackets "40" +seta cl_packetdup "1" diff --git a/setup-quake/q3a/scripts/activate-server.sh b/setup-quake/q3a/scripts/activate-server.sh new file mode 100755 index 0000000..7a75a1f --- /dev/null +++ b/setup-quake/q3a/scripts/activate-server.sh @@ -0,0 +1,6 @@ +#!/bin/bash +Q3A_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +chmod 644 "$Q3A_DIR/baseq3/autoexec.cfg" +echo "Server mode activated. Write access restored to autoexec.cfg." +echo "Available server scripts in: $Q3A_DIR/scripts/" +ls "$Q3A_DIR/scripts/server-"*.sh diff --git a/setup-quake/q3a/scripts/client.sh b/setup-quake/q3a/scripts/client.sh new file mode 100755 index 0000000..1240db1 --- /dev/null +++ b/setup-quake/q3a/scripts/client.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/lib/ioquake3/ioquake3 +connect 10.0.1.200 diff --git a/setup-quake/q3a/scripts/server-ctf-bots.sh b/setup-quake/q3a/scripts/server-ctf-bots.sh new file mode 100755 index 0000000..f44884a --- /dev/null +++ b/setup-quake/q3a/scripts/server-ctf-bots.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_ctf.cfg \ + +exec levels_ctf.cfg +exec bots.cfg diff --git a/setup-quake/q3a/scripts/server-ctf.sh b/setup-quake/q3a/scripts/server-ctf.sh new file mode 100755 index 0000000..baee260 --- /dev/null +++ b/setup-quake/q3a/scripts/server-ctf.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_ctf.cfg \ + +exec levels_ctf.cfg +exec nobots.cfg diff --git a/setup-quake/q3a/scripts/server-dm-bots.sh b/setup-quake/q3a/scripts/server-dm-bots.sh new file mode 100755 index 0000000..7088b93 --- /dev/null +++ b/setup-quake/q3a/scripts/server-dm-bots.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_dm.cfg \ + +exec levels_dm.cfg +exec bots.cfg diff --git a/setup-quake/q3a/scripts/server-dm.sh b/setup-quake/q3a/scripts/server-dm.sh new file mode 100755 index 0000000..52bd0b6 --- /dev/null +++ b/setup-quake/q3a/scripts/server-dm.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_dm.cfg \ + +exec levels_dm.cfg +exec nobots.cfg diff --git a/setup-quake/q3a/scripts/server-tdm-bots.sh b/setup-quake/q3a/scripts/server-tdm-bots.sh new file mode 100755 index 0000000..20963c4 --- /dev/null +++ b/setup-quake/q3a/scripts/server-tdm-bots.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_tdm.cfg \ + +exec levels_tdm.cfg +exec bots.cfg diff --git a/setup-quake/q3a/scripts/server-tdm.sh b/setup-quake/q3a/scripts/server-tdm.sh new file mode 100755 index 0000000..b6aacf7 --- /dev/null +++ b/setup-quake/q3a/scripts/server-tdm.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/lib/ioquake3/ioq3ded +exec autoexec_server.cfg +exec server_tdm.cfg \ + +exec levels_tdm.cfg +exec nobots.cfg diff --git a/setup-quake/setup_quake.py b/setup-quake/setup_quake.py index 4a7b2dd..eebbb7a 100644 --- a/setup-quake/setup_quake.py +++ b/setup-quake/setup_quake.py @@ -10,11 +10,22 @@ import argparse import configparser import os import pwd +import random import subprocess import sys import urllib.request import zipfile +PLAYER_NAMES = [ + "Ranger", "Keel", "Doom", "Slash", "Orbb", "Bones", "Hunter", "Major", + "Mynx", "Sorlag", "Xaero", "Anarki", "Bitterman", "Grunt", "Hossman", + "Klesk", "Lucy", "Patriot", "Phobos", "Razor", "Sarge", "Stripe", + "Tank Jr", "Uriel", "Visor", "Wrack", "Cadavre", "Daemia", "Gorre", + "Krusade", "Lynx", "Mouser", "Smear", "Tokay", "Twilight", "Warhero", + "Erebus", "Galena", "Gargoyle", "Kiltron", "Merman", "Moloch", "Nailgun", + "Peeker", "Rayne", "Skelebot", "Skrotch", "Swelt", "Thorn", "Whiskey", +] + def get_script_dir(): """Return the directory containing the script or binary.""" @@ -27,7 +38,12 @@ def parse_args(): parser = argparse.ArgumentParser( description="Install ioquake3 and download Quake 3 data files." ) - parser.parse_args() + parser.add_argument( + "--name", + default=None, + help="Player name to set in autoexec.cfg (default: random from built-in pool)", + ) + return parser.parse_args() def check_root(): @@ -87,21 +103,66 @@ def download_files(url): def extract_files(zip_path, home_dir, install_dir): - target = os.path.join(home_dir, install_dir) - print(f" Extracting to: {target}") - os.makedirs(target, exist_ok=True) + print(f" Extracting to: {home_dir}") with zipfile.ZipFile(zip_path) as zf: - zf.extractall(target) + zf.extractall(home_dir) os.remove(zip_path) print(f" Cleaned up: {zip_path}") +def chown_extracted(target_dir, username): + pw = pwd.getpwnam(username) + for dirpath, dirnames, filenames in os.walk(target_dir): + os.chown(dirpath, pw.pw_uid, pw.pw_gid) + for name in filenames: + os.chown(os.path.join(dirpath, name), pw.pw_uid, pw.pw_gid) + + +def chmod_scripts(home_dir, install_dir): + scripts_dir = os.path.join(home_dir, install_dir, "scripts") + if not os.path.isdir(scripts_dir): + print(f" Scripts dir not found: {scripts_dir}") + return + for name in os.listdir(scripts_dir): + if name.endswith(".sh"): + path = os.path.join(scripts_dir, name) + os.chmod(path, os.stat(path).st_mode | 0o111) + print(f" chmod +x: {path}") + + +def set_player_name(home_dir, install_dir, name): + autoexec = os.path.join(home_dir, install_dir, "baseq3", "autoexec.cfg") + if not os.path.exists(autoexec): + print(f" autoexec.cfg not found: {autoexec}") + return + # File may be read-only (set_client_mode runs after), ensure writable first + os.chmod(autoexec, 0o644) + with open(autoexec, "r") as f: + lines = f.readlines() + with open(autoexec, "w") as f: + for line in lines: + if line.startswith("set name "): + f.write(f'set name "{name}"\n') + else: + f.write(line) + print(f" Player name set to: {name}") + + +def set_client_mode(home_dir, install_dir): + autoexec = os.path.join(home_dir, install_dir, "baseq3", "autoexec.cfg") + if os.path.exists(autoexec): + os.chmod(autoexec, 0o444) + print(f" Locked (read-only): {autoexec}") + + def main(): - parse_args() + args = parse_args() check_root() + player_name = args.name if args.name else random.choice(PLAYER_NAMES) + files_url, install_dir = load_config() home_dir, username = get_real_home() @@ -109,20 +170,37 @@ def main(): print(f" User : {username}") print(f" Home directory : {home_dir}") print(f" Install dir : {os.path.join(home_dir, install_dir)}") + print(f" Player name : {player_name}") print() - print("[1/3] Installing ioquake3...") + print("[1/7] Installing ioquake3...") install_ioquake3() print() - print("[2/3] Downloading Quake 3 data files...") + print("[2/7] Downloading Quake 3 data files...") zip_path = download_files(files_url) print() - print("[3/3] Extracting data files...") + print("[3/7] Extracting data files...") extract_files(zip_path, home_dir, install_dir) print() + print("[4/7] Fixing ownership...") + chown_extracted(os.path.join(home_dir, install_dir), username) + print() + + print("[5/7] Making scripts executable...") + chmod_scripts(home_dir, install_dir) + print() + + print("[6/7] Setting player name...") + set_player_name(home_dir, install_dir, player_name) + print() + + print("[7/7] Setting client mode...") + set_client_mode(home_dir, install_dir) + print() + print("Done. Verify with:") print(f" dpkg -l ioquake3") print(f" ls {os.path.join(home_dir, install_dir)}")