fix(db): keep SQLite temp files in memory so trips can be deleted (#2518)

Deleting a trip failed with SQLITE_IOERR_GETTEMPPATH when the container
runs with a read-only root filesystem and no tmpfs on /tmp. The cascade
behind DELETE FROM trips keeps a statement journal, SQLite moves it to a
temp file once it passes 64 KiB, and it looks for a directory for that
file in SQLITE_TMPDIR, TMPDIR, /var/tmp, /usr/tmp, /tmp and the working
directory. None of them is writable there. Installs on the shipped
compose file have a tmpfs on /tmp and never hit it.

Every connection the server opens now comes from openDatabase() in
src/db/connection.ts, which sets temp_store = MEMORY: the main database
(also when it is reopened after a restore), each plugin's own database
and the uploaded backup checked before a restore. reset-admin.js and the
key rotation script cannot import src/ in the image, so they set the
pragma themselves. A test pins that nothing else in src/ constructs a
handle, and one deletes a full trip on the real schema and watches for
temp files.
This commit is contained in:
Maurice
2026-09-25 22:57:17 +02:00
parent a5745fd856
commit 83d591dc6c
8 changed files with 260 additions and 7 deletions
+3 -2
View File
@@ -1,7 +1,8 @@
import Database from 'better-sqlite3';
import type Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
import { readEnv } from '../app-config';
import { openDatabase } from './connection';
import { applyDurabilityPragmas } from './durability';
import { createTables } from './schema';
import { runMigrations } from './migrations';
@@ -39,7 +40,7 @@ function initDb(): void {
_db = null;
}
_db = new Database(dbPath);
_db = openDatabase(dbPath);
// Ahead of the journal switch now: changing journal_mode needs an exclusive
// lock, which a sibling process (reset-admin, the rotation script) may hold.
_db.exec('PRAGMA busy_timeout = 5000');