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>
93 lines
3.8 KiB
Markdown
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)`
|