From 023cc5c9b0d389303a4c0a20e64087af0e9b3430 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Fri, 8 May 2026 17:11:42 +0200 Subject: [PATCH] fix(deploy): chown WAL+SHM sidecars too, not just left4me.db MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLite in WAL mode (the default for this app) maintains left4me.db-wal and left4me.db-shm sidecar files alongside the main DB. All three must be writable by the web service uid; if any one is root-owned, SQLite reports "attempt to write a readonly database" on the next INSERT — which surfaced as a 500 on POST /overlays/{id}/script after I'd done ad-hoc root-side sqlite3.connect() inspection earlier and the resulting root-owned WAL/SHM persisted. Loop over all three paths in the deploy chmod step so root-owned sidecars are corrected on every deploy. Idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) --- deploy/deploy-test-server.sh | 18 +++++++++++++----- deploy/tests/test_deploy_artifacts.py | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/deploy/deploy-test-server.sh b/deploy/deploy-test-server.sh index f905aa9..4cd11f6 100755 --- a/deploy/deploy-test-server.sh +++ b/deploy/deploy-test-server.sh @@ -182,11 +182,19 @@ run_as_left4me sh -c "cd /opt/left4me/l4d2web && set -a; . /etc/left4me/host.env # makes it world-readable on the host. The script-overlay sandbox runs as a # separate system uid (l4d2-sandbox) which is NOT in the left4me group — # 0640 blocks it via "other". The owner (left4me) keeps read+write so the -# web service can update the DB. Idempotent on rerun. -if [ -f /var/lib/left4me/left4me.db ]; then - $sudo_cmd chown left4me:left4me /var/lib/left4me/left4me.db - $sudo_cmd chmod 0640 /var/lib/left4me/left4me.db -fi +# web service can update the DB. +# +# SQLite in WAL mode (the default in this app) maintains -wal and -shm +# sidecar files; both must also be writable by the web service. If a previous +# operator opened the DB as root (e.g. for ad-hoc inspection), the sidecars +# may have ended up root-owned, which makes SQLite report "readonly database" +# on the next write. Re-chown them defensively. Idempotent on rerun. +for db_file in /var/lib/left4me/left4me.db /var/lib/left4me/left4me.db-wal /var/lib/left4me/left4me.db-shm; do + if [ -f "$db_file" ]; then + $sudo_cmd chown left4me:left4me "$db_file" + $sudo_cmd chmod 0640 "$db_file" + fi +done if [ -f "$remote_tmp/admin_username" ] && [ -f "$remote_tmp/admin_password" ]; then LEFT4ME_ADMIN_USERNAME=$(cat "$remote_tmp/admin_username") diff --git a/deploy/tests/test_deploy_artifacts.py b/deploy/tests/test_deploy_artifacts.py index dbcacf9..b4e068b 100644 --- a/deploy/tests/test_deploy_artifacts.py +++ b/deploy/tests/test_deploy_artifacts.py @@ -308,10 +308,19 @@ def test_deploy_script_does_not_install_bubblewrap(): def test_deploy_script_tightens_left4me_db_permissions(): script = DEPLOY_SCRIPT.read_text() - # Owner left4me (the web service runs as this user and writes the DB); - # group left4me; mode 0640 — `other` (incl. l4d2-sandbox) gets nothing. - assert "chown left4me:left4me /var/lib/left4me/left4me.db" in script - assert "chmod 0640 /var/lib/left4me/left4me.db" in script + # The DB and its WAL/SHM sidecars must be left4me:left4me 0640 — owner + # (web service) keeps rw, group is read-only, "other" (incl. l4d2-sandbox) + # gets nothing. The sidecars matter because SQLite in WAL mode requires + # write access to all three; if a sidecar ends up root-owned (e.g. from + # ad-hoc root-side inspection), the next write fails as "readonly db". + assert "chown left4me:left4me" in script + assert "chmod 0640" in script + for db_file in ( + "/var/lib/left4me/left4me.db", + "/var/lib/left4me/left4me.db-wal", + "/var/lib/left4me/left4me.db-shm", + ): + assert db_file in script, f"deploy script must touch {db_file}" def test_deploy_script_installs_script_sandbox_helper():