From d425afad027246d91f52aadf52462fd7945a2dde Mon Sep 17 00:00:00 2001 From: CroneKorkN Date: Sun, 10 May 2026 18:07:58 +0200 Subject: [PATCH] left4me: write bundle README --- bundles/left4me/README.md | 88 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/bundles/left4me/README.md b/bundles/left4me/README.md index eba2d99..b11d3f0 100644 --- a/bundles/left4me/README.md +++ b/bundles/left4me/README.md @@ -1,3 +1,89 @@ # left4me -L4D2 game-server management platform. See Task B12 for the full README. +L4D2 game-server management platform: a Flask web UI on gunicorn that +provisions per-instance srcds servers via templated systemd units, with +kernel-overlayfs layering for shared installations + per-overlay maps, +and uid-based DSCP/priority marking on the egress path so CAKE on the +external interface prioritizes srcds UDP over bulk traffic. + +## Metadata + +```python +'metadata': { + 'left4me': { + 'git_url': 'git@git.sublimity.de:cronekorkn/left4me', # required + 'git_branch': 'master', # required + 'secret_key': '!32_random_bytes_as_base64_for:_left4me_secret_key', + # optional, defaults shown: + # 'gunicorn_workers': 1, + # 'gunicorn_threads': 32, + # 'job_worker_threads': 4, + # 'port_range_start': 27015, + # 'port_range_end': 27115, + }, +}, +``` + +## What this bundle does + +- Creates system users `left4me` (uid/gid 980, home `/var/lib/left4me`, + mode 0711) and `l4d2-sandbox` (uid/gid 981, no home, used by bwrap + script-overlay builds). +- Drops privileged helpers under `/usr/local/libexec/left4me/` + (`left4me-systemctl`, `left4me-journalctl`, `left4me-overlay`, + `left4me-script-sandbox`) plus a tight sudoers file (validated with + `visudo -cf` before install). +- `git_deploy`s the left4me repo to `/opt/left4me/src`, builds a venv at + `/opt/left4me/.venv`, `pip install -e`s both `l4d2host` and `l4d2web`, + runs `alembic upgrade head` and `flask seed-script-overlays`, then + enables `left4me-web.service`. +- Emits four systemd units via `systemd/units` metadata (consumed by + `bundles/systemd/`): + - `left4me-web.service` — gunicorn on `127.0.0.1:8000` (TLS terminates upstream). + - `left4me-server@.service` — per-instance srcds template, started on + demand by the web app via the `left4me-systemctl` helper. + - `l4d2-game.slice` / `l4d2-build.slice` — cgroup slices for the + perf-baseline (CPU/IO weights, memory caps). +- Contributes uid-based DSCP/priority marks for srcds UDP egress to + `nftables/output` (via `defaults`). + +## Gotchas + +- **Requires `bundles/nftables` and `bundles/systemd` on the node.** The + bundle asserts membership at `bw test` time. On Debian-13 these ride + in via the `debian-13` group, so attaching the bundle to a Debian-13 + node is enough. +- **`left4me-web.service` does not have `NoNewPrivileges=true`.** This is + intentional — workers `sudo` the privileged helpers; `NoNewPrivileges` + would block setuid escalation. Per-instance `server@.service` units + *do* have it. +- **CAKE shaping is configured separately**, via + `network//cake` on the node (consumed by `bundles/network/`), + not by this bundle. +- **First-run admin user is manual.** After `bw apply`, run on the host: + `sudo -u left4me sh -c '. /etc/left4me/host.env && . /etc/left4me/web.env && + LEFT4ME_ADMIN_PASSWORD= /opt/left4me/.venv/bin/flask + --app l4d2web.app:create_app create-user --admin'`. + The bundle deliberately doesn't seed an admin to keep credentials out + of the metadata pipeline. +- **CPU isolation drop-ins are not managed by this bundle.** The + upstream shell deploy generated `/etc/systemd/system/system.slice.d/ + 99-left4me-cpuset.conf` (and siblings for user/build/game slices) + dynamically based on `nproc --all`. That logic is incompatible with + static bundle metadata and is out of scope here. Apply CPU isolation + manually post-deploy if needed. +- **Kernel feature requirement:** kernel-overlayfs (`CONFIG_OVERLAY_FS`). + Standard on debian-13. +- **Game ports** open by the web app on demand in the range 27015-27115 + (UDP+TCP). Add corresponding accept rules to `nftables/input` per + node if the host's policy is default-drop on input. +- **Pinned UIDs/GIDs (980/981).** Chosen for deterministic ownership + across rebuilds and backup restores. If you add another bundle that + pins UIDs in this repo, make sure it doesn't collide. + +## Slice support requires `bundles/systemd` ≥ commit cc1c6a5 + +This bundle's `l4d2-game.slice` and `l4d2-build.slice` units rely on +`bundles/systemd/items.py` accepting the `.slice` extension. Older +revisions raised `Exception(f'unknown type slice')` at apply time. +The repo-wide `bw test` will catch this if it regresses.