Smoke testing on the test host revealed three issues with the helper as shipped: 1. bwrap 0.11+ rejects --uid without --unshare-user. Switching the UID drop from inside bwrap to systemd-run (--uid=l4d2-sandbox --gid=l4d2-sandbox) sidesteps the userns UID-mapping headaches and keeps file ownership on the bind-mounted /overlay matching l4d2-sandbox on the host (which the wipe path relies on). 2. bwrap running as an unprivileged uid still needs a user namespace to set up its mount-namespace bind-mounts. Adding --unshare-user-try gives it the userns context when needed and is a no-op otherwise. 3. /etc/alternatives wasn't bind-mounted, so symlinked tools like /usr/bin/awk -> /etc/alternatives/awk fell over inside the sandbox. Adds the ro-bind. Also: the helper now chowns the overlay dir to l4d2-sandbox before bwrap (idempotent — needed because the web app creates the dir as left4me), and the deploy script chmods /var/lib/left4me to 0711 so l4d2-sandbox can traverse to the bind-mount source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| files | ||
| templates/etc/left4me | ||
| tests | ||
| deploy-test-server.sh | ||
| README.md | ||
left4me Deployment
This directory contains the production-like test deployment for a Linux server. It installs the repository into a fixed host layout, configures a dedicated runtime user, installs systemd units, and wires the web app to host operations through privileged helper commands.
Target Layout
The deployment uses these paths:
/etc/left4me/host.env: host library environment configuration./etc/left4me/web.env: web app environment configuration./opt/left4me/.venv: Python virtual environment for deployed commands./opt/left4me: deployed repository contents./var/lib/left4me/left4me.db: SQLite database used by the web app./var/lib/left4me/installation: shared L4D2 installation./var/lib/left4me/overlays: overlay directories. Each overlay lives at${overlay_id}under here./var/lib/left4me/workshop_cache: deduplicated cache of.vpkfiles downloaded for workshop overlays. One file per Steam item, named{steam_id}.vpk. Workshop overlays symlink into this tree./var/lib/left4me/global_overlay_cache: cache of non-Steam map archives and extracted.vpkfiles used by managed global map overlays./var/lib/left4me/instances: rendered instance specifications and per-instance state./var/lib/left4me/runtime: per-instance runtime mount directories./var/lib/left4me/tmp: temporary files used by deployment/runtime operations./usr/local/lib/systemd/system: global systemd unit files, includingleft4me-server@.service./usr/local/libexec/left4me: privileged helper commands, includingleft4me-systemctl,left4me-journalctl, andleft4me-overlay(the latter mounts the per-instance kernel overlay in PID 1's mount namespace viansenter)./etc/sudoers.d/left4me: sudoers rules allowing the web/runtime commands to call the helpers non-interactively.
Static units are generated for /var/lib/left4me. If LEFT4ME_ROOT changes, regenerate and reinstall the unit files instead of reusing the existing static units.
Runtime User
The deployment creates and runs host operations as the dedicated runtime user:
- Username:
left4me - Home:
/var/lib/left4me - Shell:
/usr/sbin/nologin
Running A Test Deployment
Run the deployment from the repository root:
deploy/deploy-test-server.sh deploy-user@example-host
The SSH user must be able to run sudo on the target host. The deployment configures system packages, directories, environment files, helper scripts, sudoers rules, Python dependencies, and systemd units.
Admin Bootstrap
Set the bootstrap credentials in the environment when creating the first admin user:
LEFT4ME_ADMIN_USERNAME=admin \
LEFT4ME_ADMIN_PASSWORD='change-me' \
flask create-user "$LEFT4ME_ADMIN_USERNAME" --admin
Use a strong one-time password and rotate it after first login if needed.
Overlay References
Overlay references are relative paths below ${LEFT4ME_ROOT}/overlays. With the default deployment root, they resolve under /var/lib/left4me/overlays. New overlays use ${overlay_id} as their path; the digit-only form is the only one created by the web app.
Invalid references are rejected:
- Absolute paths such as
/srv/overlay. - Parent traversal such as
../otherorcompetitive/../../base. - Empty path components such as
competitive//base. - Symlink escapes that resolve outside
${LEFT4ME_ROOT}/overlays.
The web app currently supports two overlay surfaces:
workshopoverlays (user-owned) — populated by downloading.vpkfiles from the public Steam Web API into${LEFT4ME_ROOT}/workshop_cache/{steam_id}.vpkand creating absolute symlinks under${LEFT4ME_ROOT}/overlays/{overlay_id}/left4dead2/addons/{steam_id}.vpk.scriptoverlays — populated by an arbitrary user-authored bash script that runs insidebubblewrap+systemd-run --scopeas the unprivilegedl4d2-sandboxUID, with the overlay directory bind-mounted RW at/overlay. Resource caps: 1h walltime, 4 GB RAM, 512 tasks, 200% CPU, 20 GB post-build disk cap.
Both the caches and the overlay directories are owned by the left4me runtime user; if the web service ever runs as a different uid, ensure it shares a group with the host process and that both trees are group-readable.