211 lines
6.3 KiB
Python
211 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
setup-quake: Install ioquake3 and download Quake 3 data files
|
|
to the real user's home directory for use at a retro gaming fair.
|
|
|
|
Usage: sudo ./setup-quake
|
|
"""
|
|
|
|
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."""
|
|
if getattr(sys, "frozen", False):
|
|
return os.path.dirname(sys.executable)
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Install ioquake3 and download Quake 3 data files."
|
|
)
|
|
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():
|
|
if os.geteuid() != 0:
|
|
print("Error: this script must be run as root (use sudo).", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def load_config():
|
|
config_path = os.path.join(get_script_dir(), "config.ini")
|
|
cfg = configparser.ConfigParser()
|
|
cfg.read(config_path)
|
|
files_url = cfg.get("quake", "files_url", fallback="https://php.sustancia.synology.me/files/ioquake3-files.zip")
|
|
install_dir = cfg.get("quake", "install_dir", fallback=".q3a")
|
|
return files_url, install_dir
|
|
|
|
|
|
def get_real_home():
|
|
"""Resolve the actual user's home when running under sudo."""
|
|
sudo_user = os.environ.get("SUDO_USER")
|
|
if sudo_user:
|
|
home_dir = pwd.getpwnam(sudo_user).pw_dir
|
|
return home_dir, sudo_user
|
|
home_dir = os.path.expanduser("~")
|
|
return home_dir, "root"
|
|
|
|
|
|
def install_ioquake3():
|
|
print(" Running: apt-get install -y ioquake3")
|
|
subprocess.run(["apt-get", "install", "-y", "ioquake3"], check=True)
|
|
|
|
|
|
def download_files(url):
|
|
"""Download the Quake 3 files zip to /tmp. Returns destination path."""
|
|
dest = "/tmp/ioquake3-files.zip"
|
|
print(f" Downloading: {url}")
|
|
print(f" Destination: {dest}")
|
|
|
|
with urllib.request.urlopen(url) as response:
|
|
total = response.headers.get("Content-Length")
|
|
total_mb = f"{int(total) / 1024 / 1024:.1f} MB" if total else "unknown size"
|
|
print(f" File size: {total_mb}")
|
|
|
|
downloaded = 0
|
|
chunk_size = 1024 * 1024 # 1 MB
|
|
with open(dest, "wb") as f:
|
|
while True:
|
|
chunk = response.read(chunk_size)
|
|
if not chunk:
|
|
break
|
|
f.write(chunk)
|
|
downloaded += len(chunk)
|
|
print(f" Downloaded: {downloaded / 1024 / 1024:.1f} MB", end="\r")
|
|
|
|
print() # newline after progress
|
|
return dest
|
|
|
|
|
|
def extract_files(zip_path, home_dir, install_dir):
|
|
print(f" Extracting to: {home_dir}")
|
|
|
|
with zipfile.ZipFile(zip_path) as zf:
|
|
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():
|
|
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()
|
|
|
|
print("=== setup-quake ===")
|
|
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/7] Installing ioquake3...")
|
|
install_ioquake3()
|
|
print()
|
|
|
|
print("[2/7] Downloading Quake 3 data files...")
|
|
zip_path = download_files(files_url)
|
|
print()
|
|
|
|
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)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|