mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Restoring a settings backup ZIP appeared to succeed but the user found settings reverted to defaults, most printers/archive rows missing, and ~1 GB of archive files on disk with only 1 row in the database. Same shape as #668 (closed in March without an actual fix — that user happened to make it work by rolling back to a stable release, which masked the bug). Cause: the live DB runs in WAL mode. Anything the fresh container wrote between startup and the restore call (seed_default_groups, init_db migrations, heartbeat writes) sits in bambuddy.db-wal with valid checksums, and engine.dispose() doesn't checkpoint it. FastAPI's dependency injection keeps the route handler's own `db: Depends(get_db)` session checked out across engine.dispose() (per SQLAlchemy docs, dispose only closes pooled connections, not checked-out ones), so the WAL inode is held open through the whole restore. After shutil.copy2 rewrote the main DB inode in place, SQLite's WAL recovery on the next init_db() re-applied the stale frames on top of the restored content, partially clobbering it with fresh-install state. Initial fix attempt of deleting -wal/-shm/-journal sidecars before the copy was insufficient (verified experimentally) — the still-open request session reads the unlinked sidecars via held fds and bleeds the WAL state back into the new file when it eventually closes. Real fix: replace shutil.copy2 with SQLite's online backup API (src_conn.backup(dst_conn)). The page-by-page protocol opens both DBs as proper SQLite connections, acquires the right locks, and routes new pages through the destination's own WAL. Concurrent open sessions see their own transactional snapshot until they close (transaction isolation) but can't corrupt the restored state.