Adds two managed system overlays (l4d2center-maps, cedapug-maps) that fetch curated map archives from upstream sources and reconcile addons symlinks for non-Steam maps. A daily systemd timer enqueues a coalesced refresh_global_overlays worker job; downloads, extraction, and rebuilds run in the existing job worker and surface in the job log UI. Schema: GlobalOverlaySource / GlobalOverlayItem / GlobalOverlayItemFile plus nullable Job.user_id so system jobs render as "system" in the UI. The new builder reconciles symlinks against the per-source vpk cache and leaves foreign symlinks untouched. Initialize-time guard refuses to mount a partial overlay if any expected vpk is missing from cache. Refresh service uses shutil.move to handle EXDEV when /tmp and the cache live on different filesystems. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
4.2 KiB
Markdown
80 lines
4.2 KiB
Markdown
# 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. External (admin-managed) overlays still live at any relative path under here; new overlays created through the web UI use `${overlay_id}` as their path.
|
|
- `/var/lib/left4me/workshop_cache`: deduplicated cache of `.vpk` files 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 `.vpk` files 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, including `left4me-server@.service`.
|
|
- `/usr/local/libexec/left4me`: privileged helper commands, including `left4me-systemctl` and `left4me-journalctl`.
|
|
- `/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:
|
|
|
|
```bash
|
|
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.
|
|
|
|
## Scheduled Jobs
|
|
|
|
`left4me-refresh-global-overlays.timer` runs daily with `Persistent=true`. It invokes `flask refresh-global-overlays`, which only enqueues a `refresh_global_overlays` job; downloads and rebuilds run in the web worker and are visible in the normal job log UI.
|
|
|
|
## Admin Bootstrap
|
|
|
|
Set the bootstrap credentials in the environment when creating the first admin user:
|
|
|
|
```bash
|
|
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`.
|
|
|
|
Valid examples:
|
|
|
|
- `standard`
|
|
- `competitive/base`
|
|
- `users/42/custom`
|
|
|
|
Invalid references are rejected:
|
|
|
|
- Absolute paths such as `/srv/overlay`.
|
|
- Parent traversal such as `../other` or `competitive/../../base`.
|
|
- Empty path components such as `competitive//base`.
|
|
- Symlink escapes that resolve outside `${LEFT4ME_ROOT}/overlays`.
|
|
|
|
Overlay content for `external` (admin-managed) overlays is populated outside the host library — typically via SFTP. The web app does not write into them.
|
|
|
|
`workshop` overlays are populated by the web app: it downloads `.vpk` files from the public Steam Web API into `${LEFT4ME_ROOT}/workshop_cache/{steam_id}.vpk` and creates absolute symlinks under `${LEFT4ME_ROOT}/overlays/{overlay_id}/left4dead2/addons/{steam_id}.vpk`. Both the cache and the overlay directory 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.
|