millores en setup-quake

This commit is contained in:
2026-03-16 14:05:47 +01:00
parent c13beff6b5
commit c7c2da87d9
21 changed files with 308 additions and 57 deletions
+3 -2
View File
@@ -1,8 +1,9 @@
dist/
setup-network/build/ setup-network/build/
setup-network/dist/
setup-network/*.spec setup-network/*.spec
setup-network/.venv/ setup-network/.venv/
setup-quake/build/ setup-quake/build/
setup-quake/dist/
setup-quake/*.spec setup-quake/*.spec
setup-quake/.venv/ setup-quake/.venv/
__pycache__/
+93 -38
View File
@@ -2,6 +2,41 @@
Tools for configuring PCs at a retro gaming fair. 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 ## Utilities
### setup-network ### setup-network
@@ -21,7 +56,7 @@ sudo ./setup-network 4
# → IP: 10.0.1.4/24, hostname: retro-alcoi-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 #### Quick start
@@ -30,24 +65,7 @@ cd setup-network/
python3 -m venv .venv python3 -m venv .venv
.venv/bin/pip install -r requirements.txt .venv/bin/pip install -r requirements.txt
bash build.sh bash build.sh
# Binary is at dist/setup-network # 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-
``` ```
#### How it works #### 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 sudo ./setup-quake
``` ```
See [`setup-quake/`](setup-quake/) for source and build instructions. See [`setup-quake/`](setup-quake/) for source.
#### Quick start #### Quick start
@@ -77,24 +95,7 @@ cd setup-quake/
python3 -m venv .venv python3 -m venv .venv
.venv/bin/pip install -r requirements.txt .venv/bin/pip install -r requirements.txt
bash build.sh bash build.sh
# Binary is at dist/setup-quake # 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
``` ```
#### How it works #### How it works
@@ -103,3 +104,57 @@ install_dir = .q3a
2. Downloads the Quake 3 data files zip from the configured URL. 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 3. Extracts the data files to `~/.q3a` (resolved via `$SUDO_USER` so the real
user's home is used, not root's). 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/
pak0pak8.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
```
+7
View File
@@ -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
+6 -4
View File
@@ -15,13 +15,15 @@ elif ! command -v pyinstaller &>/dev/null; then
exit 1 exit 1
fi fi
DIST_DIR="${SCRIPT_DIR}/../dist"
echo "Building setup-network binary..." 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/..." echo "Copying config.ini to dist/..."
cp config.ini dist/ cp "${SCRIPT_DIR}/../config.ini" "$DIST_DIR/"
echo "" echo ""
echo "Build complete. Output:" echo "Build complete. Output:"
echo " ${SCRIPT_DIR}/dist/setup-network" echo " ${DIST_DIR}/setup-network"
echo " ${SCRIPT_DIR}/dist/config.ini" echo " ${DIST_DIR}/config.ini"
+6 -4
View File
@@ -15,13 +15,15 @@ elif ! command -v pyinstaller &>/dev/null; then
exit 1 exit 1
fi fi
DIST_DIR="${SCRIPT_DIR}/../dist"
echo "Building setup-quake binary..." 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/..." echo "Copying config.ini to dist/..."
cp config.ini dist/ cp "${SCRIPT_DIR}/../config.ini" "$DIST_DIR/"
echo "" echo ""
echo "Build complete. Output:" echo "Build complete. Output:"
echo " ${SCRIPT_DIR}/dist/setup-quake" echo " ${DIST_DIR}/setup-quake"
echo " ${SCRIPT_DIR}/dist/config.ini" echo " ${DIST_DIR}/config.ini"
+8
View File
@@ -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
@@ -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
+5
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
seta bot_enable 0
seta bot_minplayers 0
+20
View File
@@ -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"
+18
View File
@@ -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"
+21
View File
@@ -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"
+6
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
/usr/lib/ioquake3/ioquake3 +connect 10.0.1.200
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+87 -9
View File
@@ -10,11 +10,22 @@ import argparse
import configparser import configparser
import os import os
import pwd import pwd
import random
import subprocess import subprocess
import sys import sys
import urllib.request import urllib.request
import zipfile 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(): def get_script_dir():
"""Return the directory containing the script or binary.""" """Return the directory containing the script or binary."""
@@ -27,7 +38,12 @@ def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Install ioquake3 and download Quake 3 data files." 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(): def check_root():
@@ -87,21 +103,66 @@ def download_files(url):
def extract_files(zip_path, home_dir, install_dir): def extract_files(zip_path, home_dir, install_dir):
target = os.path.join(home_dir, install_dir) print(f" Extracting to: {home_dir}")
print(f" Extracting to: {target}")
os.makedirs(target, exist_ok=True)
with zipfile.ZipFile(zip_path) as zf: with zipfile.ZipFile(zip_path) as zf:
zf.extractall(target) zf.extractall(home_dir)
os.remove(zip_path) os.remove(zip_path)
print(f" Cleaned up: {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(): def main():
parse_args() args = parse_args()
check_root() check_root()
player_name = args.name if args.name else random.choice(PLAYER_NAMES)
files_url, install_dir = load_config() files_url, install_dir = load_config()
home_dir, username = get_real_home() home_dir, username = get_real_home()
@@ -109,20 +170,37 @@ def main():
print(f" User : {username}") print(f" User : {username}")
print(f" Home directory : {home_dir}") print(f" Home directory : {home_dir}")
print(f" Install dir : {os.path.join(home_dir, install_dir)}") print(f" Install dir : {os.path.join(home_dir, install_dir)}")
print(f" Player name : {player_name}")
print() print()
print("[1/3] Installing ioquake3...") print("[1/7] Installing ioquake3...")
install_ioquake3() install_ioquake3()
print() print()
print("[2/3] Downloading Quake 3 data files...") print("[2/7] Downloading Quake 3 data files...")
zip_path = download_files(files_url) zip_path = download_files(files_url)
print() print()
print("[3/3] Extracting data files...") print("[3/7] Extracting data files...")
extract_files(zip_path, home_dir, install_dir) extract_files(zip_path, home_dir, install_dir)
print() 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("Done. Verify with:")
print(f" dpkg -l ioquake3") print(f" dpkg -l ioquake3")
print(f" ls {os.path.join(home_dir, install_dir)}") print(f" ls {os.path.join(home_dir, install_dir)}")