Nodes should only carry node-specific metadata. Previously each node
running left4me had to declare git_url, git_branch, secret_key, plus
nginx vhost / letsencrypt / monitoring / nftables-input blocks for
every game port. All of those are derivable from one truly node-
specific value: the domain.
Move into the bundle:
- git_url + git_branch as defaults (override per-node only if needed).
- secret_key as a per-node vault-derived value
(random_bytes_as_base64_for f'{node.name} left4me secret_key',
same convention as postgresql/mosquitto/etc.).
- backup/paths defaults (set-merged with backup group / node paths).
Add a `derived_from_domain` reactor that reads left4me/domain and
emits:
- nginx/vhosts/<domain> proxying 127.0.0.1:8000
- letsencrypt/domains/<domain>
- monitoring/services/left4me-web (curl /health)
- nftables/input rules for the configured port range
(defaults 27015-27115, derived from left4me/port_range_*).
Net effect: a node opting into left4me declares only
metadata.left4me.domain = 'whatever.tld'
plus the universal node-level stuff (id, vm/cores, network, …).
|
||
|---|---|---|
| .. | ||
| files | ||
| items.py | ||
| metadata.py | ||
| README.md | ||
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) andl4d2-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 withvisudo -cfbefore install). git_deploys the left4me repo to/opt/left4me/src, builds a venv at/opt/left4me/.venv,pip install -es bothl4d2hostandl4d2web, runsalembic upgrade headandflask seed-script-overlays, then enablesleft4me-web.service.- Emits four systemd units via
systemd/unitsmetadata (consumed bybundles/systemd/):left4me-web.service— gunicorn on127.0.0.1:8000(TLS terminates upstream).left4me-server@.service— per-instance srcds template, started on demand by the web app via theleft4me-systemctlhelper.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(viadefaults).
Gotchas
- Requires
bundles/nftablesandbundles/systemdon the node. The bundle asserts membership atbw testtime. On Debian-13 these ride in via thedebian-13group, so attaching the bundle to a Debian-13 node is enough. left4me-web.servicedoes not haveNoNewPrivileges=true. This is intentional — workerssudothe privileged helpers;NoNewPrivilegeswould block setuid escalation. Per-instanceserver@.serviceunits do have it.- CAKE shaping is configured separately, via
network/<iface>/cakeon the node (consumed bybundles/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 onnproc --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/inputper 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.