left4me/deploy
mwiegand 92d6ebbe82
feat(l4d2-web): managed global map overlays with daily refresh
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>
2026-05-08 08:05:14 +02:00
..
files feat(l4d2-web): managed global map overlays with daily refresh 2026-05-08 08:05:14 +02:00
templates/etc/left4me feat(deploy): add production-like test deployment 2026-05-06 19:30:10 +02:00
tests feat(l4d2-web): managed global map overlays with daily refresh 2026-05-08 08:05:14 +02:00
deploy-test-server.sh feat(l4d2-web): managed global map overlays with daily refresh 2026-05-08 08:05:14 +02:00
README.md feat(l4d2-web): managed global map overlays with daily refresh 2026-05-08 08:05:14 +02:00

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:

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:

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.