left4me/deploy
mwiegand 1968684c03
fix(deploy): MountFlags=shared on web service for fuse mount propagation
ProtectSystem=full + ReadWritePaths implicitly give the unit a private
mount namespace (systemd needs to remount /usr read-only). The default
namespace propagation is slave, so mounts the worker creates inside
never reach the host. The gameserver units (started via systemctl,
each with their own namespace) then inherit a host that lacks the
overlay, and their CHDIR into /var/lib/left4me/runtime/<name>/merged
fails.

Set MountFlags=shared so mount events propagate from the worker's
namespace back to the host, then onward to gameserver units at their
unshare time.

Verified on test box: nsenter -t <gunicorn-pid> -m mount showed the
fuse-overlayfs mount inside the worker but plain mount on the host
did not, while web unit had ProtectSystem=full + ReadWritePaths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:01:24 +02:00
..
files fix(deploy): MountFlags=shared on web service for fuse mount propagation 2026-05-07 02:01:24 +02:00
templates/etc/left4me feat(deploy): add production-like test deployment 2026-05-06 19:30:10 +02:00
tests fix(deploy): add venv to PATH in left4me-web systemd service 2026-05-06 20:45:37 +02:00
deploy-test-server.sh fix(deploy): exclude macOS AppleDouble files from deploy archive 2026-05-07 00:58:29 +02:00
README.md feat(deploy): add production-like test deployment 2026-05-06 19:30:10 +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: externally managed overlay directories.
  • /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.

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 is external to the host library and deployment contract. Populate overlay directories separately before referencing them from blueprints or instance specs.