chore(deploy): provision l4d2-sandbox + bubblewrap; drop globals refresh timer

deploy-test-server.sh: provisions the l4d2-sandbox system user (no home,
nologin shell) and installs the bubblewrap apt/dnf package; copies the
left4me-script-sandbox helper into /usr/local/libexec/left4me with mode
0755. Drops the global_overlay_cache directory provisioning, the
refresh-global-overlays unit installation, and the timer enable.

Deletes the orphaned left4me-refresh-global-overlays.{service,timer}
files. Trims the matching paragraph from deploy/README.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-08 15:54:57 +02:00
parent 75e703e1a4
commit e51a4d58a4
No known key found for this signature in database
5 changed files with 39 additions and 55 deletions

View file

@ -42,10 +42,6 @@ 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:

View file

@ -77,11 +77,17 @@ if ! id left4me >/dev/null 2>&1; then
$sudo_cmd useradd --system --home-dir /var/lib/left4me --create-home --shell /usr/sbin/nologin left4me
fi
# Sandbox uid for script-overlay builds. No home, no login shell — the bwrap
# invocation uses --uid/--gid to drop to it.
if ! id l4d2-sandbox >/dev/null 2>&1; then
$sudo_cmd useradd --system --no-create-home --shell /usr/sbin/nologin l4d2-sandbox
fi
if command -v apt-get >/dev/null 2>&1; then
$sudo_cmd apt-get update
$sudo_cmd apt-get install -y python3 python3-venv python3-pip curl ca-certificates tar gzip util-linux sudo
$sudo_cmd apt-get install -y python3 python3-venv python3-pip curl ca-certificates tar gzip util-linux sudo bubblewrap
elif command -v dnf >/dev/null 2>&1; then
$sudo_cmd dnf install -y python3 python3-pip curl ca-certificates tar gzip util-linux sudo
$sudo_cmd dnf install -y python3 python3-pip curl ca-certificates tar gzip util-linux sudo bubblewrap
else
printf 'Unsupported package manager: expected apt-get or dnf\n' >&2
exit 1
@ -97,7 +103,6 @@ $sudo_cmd mkdir -p \
/var/lib/left4me/instances \
/var/lib/left4me/runtime \
/var/lib/left4me/workshop_cache \
/var/lib/left4me/global_overlay_cache \
/var/lib/left4me/tmp
$sudo_cmd chown left4me:left4me \
@ -107,7 +112,6 @@ $sudo_cmd chown left4me:left4me \
/var/lib/left4me/instances \
/var/lib/left4me/runtime \
/var/lib/left4me/workshop_cache \
/var/lib/left4me/global_overlay_cache \
/var/lib/left4me/tmp
$sudo_cmd chown -R left4me:left4me /opt/left4me
@ -126,12 +130,11 @@ $sudo_cmd chown -R left4me:left4me /opt/left4me
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-web.service /usr/local/lib/systemd/system/left4me-web.service
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-server@.service /usr/local/lib/systemd/system/left4me-server@.service
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service /usr/local/lib/systemd/system/left4me-refresh-global-overlays.service
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer /usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-systemctl
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-journalctl /usr/local/libexec/left4me/left4me-journalctl
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-overlay /usr/local/libexec/left4me/left4me-overlay
$sudo_cmd chmod 0755 /usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-journalctl /usr/local/libexec/left4me/left4me-overlay
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-script-sandbox /usr/local/libexec/left4me/left4me-script-sandbox
$sudo_cmd chmod 0755 /usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-journalctl /usr/local/libexec/left4me/left4me-overlay /usr/local/libexec/left4me/left4me-script-sandbox
$sudo_cmd cp /opt/left4me/deploy/files/etc/sudoers.d/left4me /etc/sudoers.d/left4me
$sudo_cmd chmod 0440 /etc/sudoers.d/left4me
$sudo_cmd visudo -cf /etc/sudoers.d/left4me
@ -197,7 +200,6 @@ fi
$sudo_cmd systemctl daemon-reload
$sudo_cmd systemctl enable --now left4me-web.service
$sudo_cmd systemctl restart left4me-web.service
$sudo_cmd systemctl enable --now left4me-refresh-global-overlays.timer
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if curl -fsS http://127.0.0.1:8000/health; then
exit 0

View file

@ -1,17 +0,0 @@
[Unit]
Description=left4me refresh global map overlays
After=network-online.target left4me-web.service
Wants=network-online.target
[Service]
Type=oneshot
User=left4me
Group=left4me
WorkingDirectory=/opt/left4me
Environment=HOME=/var/lib/left4me
Environment=PATH=/opt/left4me/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
EnvironmentFile=/etc/left4me/host.env
EnvironmentFile=/etc/left4me/web.env
ExecStart=/opt/left4me/.venv/bin/flask --app l4d2web.app:create_app refresh-global-overlays
ProtectSystem=full
ReadWritePaths=/var/lib/left4me

View file

@ -1,10 +0,0 @@
[Unit]
Description=Daily left4me global map overlay refresh
[Timer]
OnCalendar=daily
Persistent=true
Unit=left4me-refresh-global-overlays.service
[Install]
WantedBy=timers.target

View file

@ -11,6 +11,7 @@ WEB_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-web.service"
SERVER_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-server@.service"
GLOBAL_REFRESH_SERVICE = DEPLOY / "files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service"
GLOBAL_REFRESH_TIMER = DEPLOY / "files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer"
SANDBOX_UNIT_DIR = DEPLOY / "files/usr/local/lib/systemd/system"
SYSTEMCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-systemctl"
JOURNALCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-journalctl"
OVERLAY_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-overlay"
@ -270,26 +271,38 @@ def test_deploy_script_shell_syntax() -> None:
subprocess.run(["sh", "-n", str(DEPLOY_SCRIPT)], check=True)
def test_global_refresh_timer_units_exist_and_enqueue_only():
service = GLOBAL_REFRESH_SERVICE.read_text()
timer = GLOBAL_REFRESH_TIMER.read_text()
assert "User=left4me" in service
assert "EnvironmentFile=/etc/left4me/host.env" in service
assert "EnvironmentFile=/etc/left4me/web.env" in service
assert "flask --app l4d2web.app:create_app refresh-global-overlays" in service
assert "OnCalendar=daily" in timer
assert "Persistent=true" in timer
assert "WantedBy=timers.target" in timer
def test_globals_refresh_units_removed():
"""Global-overlays subsystem deleted in favor of script overlays."""
assert not GLOBAL_REFRESH_SERVICE.exists()
assert not GLOBAL_REFRESH_TIMER.exists()
def test_deploy_script_installs_and_enables_global_refresh_timer():
def test_deploy_script_does_not_reference_globals_subsystem():
script = DEPLOY_SCRIPT.read_text()
assert "/var/lib/left4me/global_overlay_cache" in script
assert "left4me-refresh-global-overlays.service" in script
assert "left4me-refresh-global-overlays.timer" in script
assert "systemctl enable --now left4me-refresh-global-overlays.timer" in script
assert "/var/lib/left4me/global_overlay_cache" not in script
assert "left4me-refresh-global-overlays" not in script
def test_deploy_script_provisions_sandbox_user():
script = DEPLOY_SCRIPT.read_text()
assert "useradd --system --no-create-home --shell /usr/sbin/nologin l4d2-sandbox" in script
def test_deploy_script_installs_bubblewrap():
install_lines = [
line for line in DEPLOY_SCRIPT.read_text().splitlines()
if ("apt-get install" in line or "dnf install" in line)
]
assert install_lines, "expected at least one apt/dnf install line"
for line in install_lines:
assert "bubblewrap" in line, f"missing bubblewrap in install line: {line}"
def test_deploy_script_installs_script_sandbox_helper():
script = DEPLOY_SCRIPT.read_text()
assert "/usr/local/libexec/left4me/left4me-script-sandbox" in script
assert "chmod 0755" in script and "left4me-script-sandbox" in script
def test_script_sandbox_helper_present():