7.8 KiB
Left4me Deployment Design
Goal
Provide a production-like test deployment for left4me on a real Linux server. The deployment should be quick to run from the local working tree, while keeping the runtime layout, service ownership, and hardening model close enough to the intended production setup that problems appear early.
Context
left4me has two components:
l4d2host, exposed throughl4d2ctl, manages L4D2 installation, per-server initialization, lifecycle, status, and logs.l4d2webis the Flask web UI and worker process. It calls host operations throughl4d2ctlrather than importing host internals.
The existing code uses hard-coded /opt/l4d2 paths and user systemd units. The deployment design replaces that with a product-level root, root-owned global units, and constrained helper commands so per-server hardening is owned by the host setup rather than by the runtime user.
Runtime User
The deployment uses one runtime user:
user: left4me
home: /var/lib/left4me
shell: /usr/sbin/nologin
The SSH deployment user is separate and must have sudo privileges. The left4me user owns runtime state and runs the web app and game servers, but does not own systemd unit definitions, sudoers rules, or helper installation paths.
Filesystem Layout
The deployed server uses these paths:
/etc/left4me/
host.env
web.env
/opt/left4me/
.venv/
<repository contents>
/var/lib/left4me/
left4me.db
installation/
overlays/
instances/
runtime/
tmp/
/usr/local/lib/systemd/system/
left4me-web.service
left4me-server@.service
/usr/local/libexec/left4me/
left4me-systemctl
left4me-journalctl
/etc/sudoers.d/
left4me
/var/lib/left4me is both the left4me home directory and the product state root. This keeps mutable state in one place and avoids mixing runtime data with deployed code.
Configuration
Configuration is split by component boundary:
/etc/left4me/host.env:
LEFT4ME_ROOT=/var/lib/left4me
/etc/left4me/web.env:
DATABASE_URL=sqlite:////var/lib/left4me/left4me.db
SECRET_KEY=<generated-or-managed-secret>
JOB_WORKER_THREADS=4
LEFT4ME_ROOT replaces the previous host-library hard-coded /opt/l4d2 root. Host paths derive from it:
${LEFT4ME_ROOT}/installation
${LEFT4ME_ROOT}/overlays
${LEFT4ME_ROOT}/instances
${LEFT4ME_ROOT}/runtime
${LEFT4ME_ROOT}/tmp
The test deployment script can generate web.env when missing. Production config management can own both env files directly.
Systemd Model
Both web and game servers use global root-owned systemd units under /usr/local/lib/systemd/system.
left4me-web.service:
- Runs as
User=left4me. - Loads
/etc/left4me/host.envand/etc/left4me/web.env. - Uses
/opt/left4meas its working directory. - Starts the web app from
/opt/left4me/.venv. - Uses one process worker because lifecycle jobs run in-process.
- Uses moderate systemd hardening, but must still allow the web worker to call
l4d2ctl, write SQLite state, and run host lifecycle commands.
left4me-server@.service:
- Runs each game server as
User=left4me. - Reads generated per-server environment from
${LEFT4ME_ROOT}/instances/%i/instance.env. - Uses
${LEFT4ME_ROOT}/runtime/%i/merged/left4dead2asWorkingDirectory. - Starts
${LEFT4ME_ROOT}/installation/srcds_run. - Applies stronger hardening than the web unit because each game server is the more important sandbox boundary.
The server unit template is root-owned. A compromised left4me process should not be able to edit the template to weaken future server sandboxing.
Privileged Helper Model
l4d2ctl should not call arbitrary sudo systemctl or sudo journalctl commands. Instead it calls constrained helpers:
sudo -n /usr/local/libexec/left4me/left4me-systemctl start <server-name>
sudo -n /usr/local/libexec/left4me/left4me-systemctl stop <server-name>
sudo -n /usr/local/libexec/left4me/left4me-systemctl show <server-name>
sudo -n /usr/local/libexec/left4me/left4me-journalctl <server-name> --lines <n> --follow|--no-follow
The helpers validate the action and server name, then map the server name to left4me-server@<server-name>.service. The sudoers rule only allows the left4me user to run these helpers as root.
Host Library Contract Changes
The host library changes from:
/opt/l4d2/installation
/opt/l4d2/overlays
/opt/l4d2/instances
/opt/l4d2/runtime
systemd --user units
to:
${LEFT4ME_ROOT}/installation
${LEFT4ME_ROOT}/overlays
${LEFT4ME_ROOT}/instances
${LEFT4ME_ROOT}/runtime
global left4me-server@.service managed through sudo helpers
The host library remains fail-fast and prerequisite-light. It assumes deployment or config management installed OS packages, created directories, installed units, and configured sudoers.
Overlay References
Overlay configuration should store safe relative refs under ${LEFT4ME_ROOT}/overlays, not absolute paths.
Valid examples:
standard
competitive/base
users/42/custom
Rejected examples:
/tmp/bad
../bad
bad/../evil
bad//evil
This supports a flat overlay catalog now and leaves room for user-managed overlays later through namespaced refs such as users/<user-id>/<overlay-name>.
Deployment Script
The test deployment script lives in deploy/deploy-test-server.sh. It runs locally and takes an SSH target for a sudo-capable deployment user.
The script should:
- Archive the current local working tree.
- Copy it to the remote host.
- Create the
left4mesystem user if missing. - Install OS prerequisites when a supported package manager is detected.
- Create
/etc/left4me,/opt/left4me,/var/lib/left4me,/usr/local/libexec/left4me, and/usr/local/lib/systemd/systemas needed. - Preserve
/var/lib/left4meacross redeployments. - Replace repository contents under
/opt/left4mewhile preserving/opt/left4me/.venv. - Install deployment units, helper scripts, sudoers rules, and env templates.
- Create or update
/opt/left4me/.venv. - Install
l4d2hostandl4d2webfrom the deployed local source. - Initialize the SQLite schema by importing/creating the Flask app.
- Optionally bootstrap an admin user from environment variables.
- Reload systemd and restart
left4me-web.service. - Verify
/healthlocally on the server.
The script is a test-server convenience, not a replacement for production config management. Production can implement the same layout with stronger package/version control.
Documentation Structure
Local deployment assets live under:
deploy/
README.md
deploy-test-server.sh
files/
templates/
tests/
deploy/files mirrors target filesystem paths for root-owned deployment artifacts. deploy/templates contains env templates that may need generated secrets. deploy/README.md is the operator-facing guide.
Component documentation should explain only component-specific deployment contracts:
l4d2host/README.md:LEFT4ME_ROOT, global service helpers, and overlay refs.l4d2web/README.md: env vars, admin bootstrap, and systemd service expectations.- Root
README.md: link todeploy/README.md.
Verification
Local verification before deploying:
pytest l4d2host/tests -q
pytest l4d2web/tests -q
pytest deploy/tests -q
Deployment verification on the target server should include:
systemctl status left4me-web.service --no-pagercurl http://127.0.0.1:8000/healthsudo -u left4me /opt/left4me/.venv/bin/l4d2ctl --help- helper validation for accepted and rejected server names
- a later gated smoke test for
install,initialize,start,status,logs,stop, anddelete
Out Of Scope
- Running deployment against a real server as part of this design.
- Replacing production config management.
- Managing overlay file contents through the web UI.
- Adding multi-host SSH transport to the web app.
- Supporting multiple game types beyond the existing L4D2 host workflow.