diff --git a/README.md b/README.md index 49c0dd4..1b88c6d 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,21 @@ deno task test # unit tests (sync, reconciliation, rules, reports) deno task check # svelte-check ``` +### Troubleshooting: "unable to open database file" + +The container runs as the non-root `deno` user (uid 1000), so `/data` must be +writable by it: + +- **Named volume** (`-v quantum-data:/data`) — works out of the box; Podman and + Docker copy the image's ownership into a fresh volume. +- **Bind mount** (`-v /srv/quantum:/data`) — the host directory keeps its host + ownership. With rootless Podman, either add the `:U` option + (`-v /srv/quantum:/data:U`, chowns to the container user) or run + `podman unshare chown -R 1000:1000 /srv/quantum` once. On SELinux hosts + (Fedora and friends) you also want `:Z`, i.e. `:U,Z`. +- A volume first created by some other image/user keeps its old ownership; + `podman volume inspect` shows its path if you need to fix it up. + ## Operational notes - **The database file is secret-grade.** It contains the SimpleFIN Access URL diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 7417ea5..d0d546b 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -1,6 +1,7 @@ import { DatabaseSync } from 'node:sqlite'; import { mkdirSync, readFileSync, readdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; +import process from 'node:process'; const MIGRATION_FILE = /^(\d+)_.*\.sql$/; @@ -18,8 +19,21 @@ export function getDb(): DatabaseSync { } export function openDatabase(dbPath: string, migrationsDir: string): DatabaseSync { - if (dbPath !== ':memory:') mkdirSync(dirname(dbPath), { recursive: true }); - const db = new DatabaseSync(dbPath); + let db: DatabaseSync; + try { + if (dbPath !== ':memory:') mkdirSync(dirname(dbPath), { recursive: true }); + db = new DatabaseSync(dbPath); + } catch (err) { + const reason = err instanceof Error ? err.message : String(err); + throw new Error( + `Cannot open database at ${dbPath}: ${reason}\n` + + `The server runs as uid ${process.getuid?.() ?? '?'} — the directory must be ` + + `writable by it. In a container, bind mounts keep host ownership: with rootless ` + + `Podman use "-v /host/path:/data:U" (chowns to the container user) or ` + + `"podman unshare chown -R 1000:1000 /host/path"; a named volume ` + + `(-v quantum-data:/data) inherits correct ownership from the image automatically.` + ); + } db.exec('PRAGMA journal_mode = WAL'); db.exec('PRAGMA foreign_keys = ON'); runMigrations(db, migrationsDir);