From bfae47f58469aa1fed035ef62a319e577d1178ef Mon Sep 17 00:00:00 2001 From: Tronax Date: Thu, 27 Aug 2026 19:07:07 +0200 Subject: [PATCH] fix(db): auto-create DB directory and improve open error messages Open() now creates the parent directory of the database path itself, so e.g. DB_PATH=/data/app.db works on first start without a prior mkdir. Open, ping, and migration errors are wrapped with context and hints about writable directories and Docker bind-mount ownership (chown 1000:1000 or use a named volume). Also add a README troubleshooting section for the "database could not be opened" error. --- README.md | 8 ++++++++ backend/internal/db/db.go | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1faa6c6..6e0575c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,14 @@ Hinter einem Reverse-Proxy / mit Domain zusätzlich setzen: Wichtig: `JWT_SECRET` und `ENCRYPTION_KEY` dauerhaft setzen (Container-Neustarts sonst neue Secrets → Logins/Token ungültig), und die Google-Redirect-URI auf `{APP_URL}/api/calendars/google/callback` konfigurieren. Das SQLite-File liegt im Volume `/data`. +**Troubleshooting „Datenbank konnte nicht geöffnet werden“:** Der Server legt Verzeichnis und Datei selbst an, braucht dafür aber Schreibrechte. Der Container läuft als User 1000 (`app`). Bei einem **Bind-Mount** (`-v /pfad/auf/host:/data`) muss das Host-Verzeichnis diesem User gehören: + +```bash +sudo chown -R 1000:1000 /pfad/auf/host +``` + +Docker erstellt nicht-existierende Bind-Mount-Pfade automatisch als root – deshalb entweder den Pfad vorher anlegen und `chown`en, oder den **Named Volume** nutzen (`-v wannpassts-data:/data` bzw. die docker-compose.yml), wo das automatisch passt. + ### PWA-Hinweise - Der Service Worker wird nur im Production-Build registriert (`npm run build` + Auslieferung über `STATIC_DIR`), nicht im Vite-Dev-Modus. diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index ddb6d02..77c12a3 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -3,6 +3,9 @@ package db import ( "database/sql" "errors" + "fmt" + "os" + "path/filepath" "time" _ "modernc.org/sqlite" @@ -63,18 +66,28 @@ type Booking struct { } func Open(path string) (*DB, error) { + // Übergeordnetes Verzeichnis selbst anlegen, damit z. B. DB_PATH=/data/app.db + // beim ersten Start funktioniert, ohne dass jemand vorab mkdir ausführt. + if dir := filepath.Dir(path); dir != "" && dir != "." { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, fmt.Errorf("Datenbank-Verzeichnis %s konnte nicht angelegt werden (Berechtigungen prüfen): %w", dir, err) + } + } d, err := sql.Open("sqlite", "file:"+path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)") if err != nil { - return nil, err + return nil, fmt.Errorf("Datenbank %s konnte nicht geöffnet werden: %w", path, err) } // SQLite verträgt parallele Writer nicht gut – ein Writer reicht hier. d.SetMaxOpenConns(1) if err := d.Ping(); err != nil { - return nil, err + return nil, fmt.Errorf( + "Datenbank %s konnte nicht geöffnet oder angelegt werden: %w – "+ + "ist das Verzeichnis beschreibbar? (Docker: Bind-Mounts müssen dem Container-User gehören, z. B. `sudo chown -R 1000:1000 `; bei Named Volumes passiert das automatisch)", + path, err) } dbase := &DB{d} if err := dbase.migrate(); err != nil { - return nil, err + return nil, fmt.Errorf("Datenbank-Schema konnte nicht initialisiert werden: %w", err) } return dbase, nil }