From 9cf5f9620c333c3b13bdb5e8101597527715eddb Mon Sep 17 00:00:00 2001 From: Shivam Patel Date: Mon, 9 Feb 2026 00:17:44 -0500 Subject: [PATCH] Fix SQLite init: lazy loading, writability check, and host permissions DB initialization was eager at module import time, crashing the entire analytics route when /server_storage wasn't writable. Now deferred to first logVisit() call with graceful fallback. Also fix deploy workflow to chown via a bind-mounted container so permissions apply on the host. Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/deploy.yaml | 12 +++----- lib/db.ts | 58 +++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index ca102d9..cb7c3c6 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -24,14 +24,10 @@ jobs: - name: Prepare Host Storage run: | - # 1. Create the host folder if it doesn't exist - sudo mkdir -p /server_storage - - # 2. Fix permissions for the 'nextjs' user (UID 1001) - # Since your Dockerfile runs as user 1001, it cannot write to - # a root-owned folder without this chown command. - sudo chown -R 1001:1001 /server_storage - sudo chmod -R 755 /server_storage + # The bind mount creates /server_storage on the host if missing. + # Run a throwaway container to fix ownership so UID 1001 (nextjs) can write. + docker run --rm -v /server_storage:/data alpine sh -c \ + "chown -R 1001:1001 /data && chmod -R 755 /data" - name: Run New Container run: | diff --git a/lib/db.ts b/lib/db.ts index 33569b8..6508cd5 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -1,28 +1,52 @@ import Database from 'better-sqlite3'; +import type BetterSqlite3 from 'better-sqlite3'; import path from 'path'; import fs from 'fs'; -const SERVER_STORAGE = '/server_storage'; -const dbPath = fs.existsSync(SERVER_STORAGE) - ? path.join(SERVER_STORAGE, 'visitors.db') - : path.join(process.cwd(), 'visitors.db'); +let db: BetterSqlite3.Database | null = null; +let insertStmt: BetterSqlite3.Statement | null = null; -const db = new Database(dbPath); -db.pragma('journal_mode = WAL'); +function getDb() { + if (db) return db; -db.exec(` - CREATE TABLE IF NOT EXISTS visits ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - ip_address TEXT NOT NULL, - path TEXT NOT NULL, - visited_at TEXT NOT NULL DEFAULT (datetime('now')) - ); -`); + try { + const SERVER_STORAGE = '/server_storage'; + let dbDir: string; + try { + fs.accessSync(SERVER_STORAGE, fs.constants.W_OK); + dbDir = SERVER_STORAGE; + } catch { + dbDir = process.cwd(); + } -const insertStmt = db.prepare( - 'INSERT INTO visits (ip_address, path) VALUES (?, ?)' -); + const dbPath = path.join(dbDir, 'visitors.db'); + db = new Database(dbPath); + db.pragma('journal_mode = WAL'); + + db.exec(` + CREATE TABLE IF NOT EXISTS visits ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL, + path TEXT NOT NULL, + visited_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + `); + + insertStmt = db.prepare( + 'INSERT INTO visits (ip_address, path) VALUES (?, ?)' + ); + + return db; + } catch (e) { + console.error('Failed to initialize SQLite database:', e); + db = null; + insertStmt = null; + return null; + } +} export function logVisit(ip: string, visitPath: string) { + const database = getDb(); + if (!database || !insertStmt) return; insertStmt.run(ip, visitPath); }