left4me/l4d2host/README.md
mwiegand 9985ecc56c
chore(deploy): cleanup left4me-web hardening + docs for kernel overlayfs
Drop MountFlags=shared (the assumption that it propagated fuse mounts
to host was incorrect on systemd 257 with ProtectSystem+ReadWritePaths).
Restore PrivateTmp=true (was dropped in 593611e for fuse propagation
that did not work). Rewrite the comment block to describe the new
model: mounts go through the left4me-overlay helper which nsenters
into PID 1's mount namespace, so the unit's mount-ns layout is no
longer load-bearing.

Update the three user-facing READMEs (root, l4d2host, deploy) to drop
fuse-overlayfs / fusermount3 prereqs and call out the kernel overlayfs
mount path through the privileged helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:29:49 +02:00

93 lines
3.8 KiB
Markdown

# l4d2-host-lib
Python host library and CLI for managing L4D2 instances.
## CLI
`l4d2ctl` exposes these write commands in v1:
- `install`
- `initialize <name> -f <spec.yaml>`
- `start <name>`
- `stop <name>`
- `delete <name>`
It also exposes read commands used by the web app host boundary:
- `status <name> --json`
- `logs <name> --lines <n> --follow/--no-follow`
Subprocess failures are fail-fast. Raw stderr is written to stderr and the command exits with the same subprocess return code.
## Runtime Paths
The host library reads `LEFT4ME_ROOT` from the environment. It defaults to `/var/lib/left4me`:
- `${LEFT4ME_ROOT}/installation`
- `${LEFT4ME_ROOT}/overlays/<overlay-ref>`
- `${LEFT4ME_ROOT}/instances/<name>`
- `${LEFT4ME_ROOT}/runtime/<name>/{upper,work,merged}`
- `${LEFT4ME_ROOT}/tmp`
Overlay specs use relative refs below `${LEFT4ME_ROOT}/overlays`, for example `standard`, `competitive/base`, or `users/42/custom`. Absolute refs, `..`, empty path components, and symlink escapes outside the overlays root are rejected.
## systemd Integration
`l4d2ctl start`, `stop`, `status`, and `logs` use non-interactive sudo helper commands:
- `sudo -n /usr/local/libexec/left4me/left4me-systemctl ...`
- `sudo -n /usr/local/libexec/left4me/left4me-journalctl ...`
Deployment/config management owns the global `left4me-server@.service` unit under `/usr/local/lib/systemd/system`. The host library does not install or manage the unit file directly.
## Host Prerequisites
The host library intentionally does not install or preflight runtime dependencies. The target host must provide them before running `l4d2ctl`.
Validated on Debian 13 during the `ckn@10.0.4.128` smoke test:
- Python 3.12+ with virtualenv/pip tooling for installing `l4d2host`.
- `steamcmd` available on `PATH` and able to self-update as the runtime user.
- 32-bit compatibility libraries for SteamCMD on amd64 Debian: `libc6-i386`, `lib32gcc-s1`, `lib32stdc++6`.
- Kernel overlayfs (`mount -t overlay`); mount/umount go through the `left4me-overlay` privileged helper, which `nsenter`s into PID 1's mount namespace.
- `systemctl --user` and `journalctl --user` available for the runtime user.
- User lingering enabled when services must survive SSH sessions: `sudo loginctl enable-linger <user>`.
- `/var/lib/left4me` created and writable by the runtime user, unless `LEFT4ME_ROOT` is set to another deployment-managed root.
Example Debian setup:
```bash
sudo apt-get update
sudo apt-get install -y \
python3 python3-venv python3-pip \
curl ca-certificates tar gzip \
util-linux \
libc6-i386 lib32gcc-s1 lib32stdc++6
sudo mkdir -p /opt/steamcmd /var/lib/left4me/{installation,overlays,instances,runtime,tmp}
sudo chown -R "$USER:$USER" /opt/steamcmd /var/lib/left4me
sudo loginctl enable-linger "$USER"
```
SteamCMD should be installed so the runtime user can update it. If installing from Valve's tarball, avoid symlinking `steamcmd.sh` directly because it derives its install root from `$0`. Use a wrapper instead:
```bash
curl -fsSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz -o /tmp/steamcmd_linux.tar.gz
tar -xzf /tmp/steamcmd_linux.tar.gz -C /opt/steamcmd
sudo tee /usr/local/bin/steamcmd >/dev/null <<'EOF'
#!/bin/sh
exec /opt/steamcmd/steamcmd.sh "$@"
EOF
sudo chmod 755 /usr/local/bin/steamcmd
chmod 755 /opt/steamcmd/steamcmd.sh /opt/steamcmd/linux32/steamcmd
steamcmd +quit
```
`uv` is optional deployment tooling. Debian 13 did not provide an `uv` package during the smoke test, so install it explicitly if you want to use it for faster virtualenv/dependency setup. `l4d2ctl` does not require `uv` at runtime.
## Host-Local Read APIs
These Python read APIs back the CLI read commands and remain available for host-local callers:
- `get_instance_status(name)`
- `stream_instance_logs(name, lines=200, follow=True)`