diff --git a/deploy/README.md b/deploy/README.md index c0e9e76..d59e7fa 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -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: diff --git a/deploy/deploy-test-server.sh b/deploy/deploy-test-server.sh index 5bc48af..299cd41 100755 --- a/deploy/deploy-test-server.sh +++ b/deploy/deploy-test-server.sh @@ -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 diff --git a/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service b/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service deleted file mode 100644 index 78e9bd6..0000000 --- a/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service +++ /dev/null @@ -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 diff --git a/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer b/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer deleted file mode 100644 index 63249bb..0000000 --- a/deploy/files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer +++ /dev/null @@ -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 diff --git a/deploy/tests/test_deploy_artifacts.py b/deploy/tests/test_deploy_artifacts.py index d7c56f8..2fa9d73 100644 --- a/deploy/tests/test_deploy_artifacts.py +++ b/deploy/tests/test_deploy_artifacts.py @@ -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():