#!/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 subprocess import sys import urllib.request import zipfile 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.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): target = os.path.join(home_dir, install_dir) print(f" Extracting to: {target}") os.makedirs(target, exist_ok=True) with zipfile.ZipFile(zip_path) as zf: zf.extractall(target) os.remove(zip_path) print(f" Cleaned up: {zip_path}") def main(): parse_args() check_root() 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() print("[1/3] Installing ioquake3...") install_ioquake3() print() print("[2/3] Downloading Quake 3 data files...") zip_path = download_files(files_url) print() print("[3/3] Extracting data files...") extract_files(zip_path, 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()