# 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)`
