fix(deploy): make test deployments safe to rerun

Exclude local agent state from deploy archives, avoid recursive ownership over active runtime mounts, and let Alembic own schema upgrades before app startup.
This commit is contained in:
mwiegand 2026-05-07 17:16:58 +02:00
parent b2a8d3d5e0
commit 0e83ee07d7
No known key found for this signature in database
3 changed files with 31 additions and 7 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
.worktrees/ .worktrees/
.claude/
.venv/ .venv/
.pytest_cache/ .pytest_cache/
__pycache__/ __pycache__/

View file

@ -23,6 +23,7 @@ trap cleanup EXIT INT HUP TERM
COPYFILE_DISABLE=1 tar -czf "$archive" \ COPYFILE_DISABLE=1 tar -czf "$archive" \
--exclude .git \ --exclude .git \
--exclude .claude \
--exclude .venv \ --exclude .venv \
--exclude __pycache__ \ --exclude __pycache__ \
--exclude .pytest_cache \ --exclude .pytest_cache \
@ -98,7 +99,15 @@ $sudo_cmd mkdir -p \
/var/lib/left4me/workshop_cache \ /var/lib/left4me/workshop_cache \
/var/lib/left4me/tmp /var/lib/left4me/tmp
$sudo_cmd chown -R left4me:left4me /var/lib/left4me /opt/left4me $sudo_cmd chown left4me:left4me \
/var/lib/left4me \
/var/lib/left4me/installation \
/var/lib/left4me/overlays \
/var/lib/left4me/instances \
/var/lib/left4me/runtime \
/var/lib/left4me/workshop_cache \
/var/lib/left4me/tmp
$sudo_cmd chown -R left4me:left4me /opt/left4me
mkdir -p "$repo_tmp" mkdir -p "$repo_tmp"
tar -xzf "$archive" -C "$repo_tmp" tar -xzf "$archive" -C "$repo_tmp"
@ -143,10 +152,6 @@ fi
run_as_left4me /opt/left4me/.venv/bin/python -m pip install --upgrade pip run_as_left4me /opt/left4me/.venv/bin/python -m pip install --upgrade pip
run_as_left4me /opt/left4me/.venv/bin/pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web run_as_left4me /opt/left4me/.venv/bin/pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web
run_left4me_with_env env \
JOB_WORKER_ENABLED=false \
/opt/left4me/.venv/bin/python -c "from l4d2web.app import create_app; create_app()"
run_as_left4me sh -c "cd /opt/left4me/l4d2web && set -a; . /etc/left4me/host.env; . /etc/left4me/web.env; set +a; env \ run_as_left4me sh -c "cd /opt/left4me/l4d2web && set -a; . /etc/left4me/host.env; . /etc/left4me/web.env; set +a; env \
JOB_WORKER_ENABLED=false \ JOB_WORKER_ENABLED=false \
PYTHONPATH=/opt/left4me \ PYTHONPATH=/opt/left4me \

View file

@ -33,10 +33,11 @@ def test_web_unit_contains_required_runtime_contract():
assert "EnvironmentFile=/etc/left4me/web.env" in unit assert "EnvironmentFile=/etc/left4me/web.env" in unit
assert "ExecStart=/opt/left4me/.venv/bin/gunicorn" in unit assert "ExecStart=/opt/left4me/.venv/bin/gunicorn" in unit
assert "--workers 1" in unit assert "--workers 1" in unit
assert "NoNewPrivileges=true" in unit assert "NoNewPrivileges=true" not in unit
assert "PrivateTmp=true" in unit assert "PrivateTmp=true" not in unit
assert "ProtectSystem=full" in unit assert "ProtectSystem=full" in unit
assert "ReadWritePaths=/var/lib/left4me" in unit assert "ReadWritePaths=/var/lib/left4me" in unit
assert "MountFlags=shared" in unit
def test_server_unit_contains_required_runtime_contract(): def test_server_unit_contains_required_runtime_contract():
@ -169,6 +170,7 @@ def test_deploy_script_has_safe_defaults_and_preserves_state() -> None:
assert "/var/lib/left4me/runtime" in script assert "/var/lib/left4me/runtime" in script
assert "tar" in script assert "tar" in script
assert "--exclude .venv" in script assert "--exclude .venv" in script
assert "--exclude .claude" in script
assert "pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web" in script assert "pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web" in script
assert "systemctl enable --now left4me-web.service" in script assert "systemctl enable --now left4me-web.service" in script
assert "for attempt in" in script assert "for attempt in" in script
@ -183,5 +185,21 @@ def test_deploy_script_has_safe_defaults_and_preserves_state() -> None:
assert "deploy/files" in script assert "deploy/files" in script
def test_deploy_script_does_not_recurse_into_runtime_state_mounts() -> None:
script = DEPLOY_SCRIPT.read_text()
assert "$sudo_cmd chown -R left4me:left4me /var/lib/left4me" not in script
assert "$sudo_cmd chown left4me:left4me \\" in script
assert "/var/lib/left4me/runtime \\" in script
assert "$sudo_cmd chown -R left4me:left4me /opt/left4me" in script
def test_deploy_script_runs_migrations_before_app_initialization() -> None:
script = DEPLOY_SCRIPT.read_text()
assert "alembic -c /opt/left4me/l4d2web/alembic.ini upgrade head" in script
assert "from l4d2web.app import create_app; create_app()" not in script
def test_deploy_script_shell_syntax() -> None: def test_deploy_script_shell_syntax() -> None:
subprocess.run(["sh", "-n", str(DEPLOY_SCRIPT)], check=True) subprocess.run(["sh", "-n", str(DEPLOY_SCRIPT)], check=True)