bundlewrap/bundles/left4me
2026-05-10 18:07:58 +02:00
..
files left4me: wire LEFT4ME_PORT_RANGE_{START,END} into web.env 2026-05-10 17:19:02 +02:00
items.py left4me: fix bundle defects surfaced by real-node validation 2026-05-10 18:05:38 +02:00
metadata.py left4me: assert nftables + systemd bundle membership 2026-05-10 18:06:35 +02:00
README.md left4me: write bundle README 2026-05-10 18:07:58 +02:00

left4me

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

'metadata': {
    'left4me': {
        'git_url': 'git@git.sublimity.de:cronekorkn/left4me',  # required
        'git_branch': 'master',                                # required
        'secret_key': '!32_random_bytes_as_base64_for:<node>_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_deploys the left4me repo to /opt/left4me/src, builds a venv at /opt/left4me/.venv, pip install -es 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/<iface>/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=<picked> /opt/left4me/.venv/bin/flask --app l4d2web.app:create_app create-user <username> --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.