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.
This commit is contained in:
Tronax 2026-08-27 19:07:07 +02:00
parent d096295997
commit bfae47f584
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o
2 changed files with 24 additions and 3 deletions

View file

@ -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`. 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 ### PWA-Hinweise
- Der Service Worker wird nur im Production-Build registriert (`npm run build` + Auslieferung über `STATIC_DIR`), nicht im Vite-Dev-Modus. - Der Service Worker wird nur im Production-Build registriert (`npm run build` + Auslieferung über `STATIC_DIR`), nicht im Vite-Dev-Modus.

View file

@ -3,6 +3,9 @@ package db
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"os"
"path/filepath"
"time" "time"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
@ -63,18 +66,28 @@ type Booking struct {
} }
func Open(path string) (*DB, error) { 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)") d, err := sql.Open("sqlite", "file:"+path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)")
if err != nil { 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. // SQLite verträgt parallele Writer nicht gut ein Writer reicht hier.
d.SetMaxOpenConns(1) d.SetMaxOpenConns(1)
if err := d.Ping(); err != nil { 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 <host-pfad>`; bei Named Volumes passiert das automatisch)",
path, err)
} }
dbase := &DB{d} dbase := &DB{d}
if err := dbase.migrate(); err != nil { if err := dbase.migrate(); err != nil {
return nil, err return nil, fmt.Errorf("Datenbank-Schema konnte nicht initialisiert werden: %w", err)
} }
return dbase, nil return dbase, nil
} }