Bootstraps Vellum as a single Go binary with one SQLite database. - core: env-driven config, slog logging, SQLite (WAL) with pooled pragmas, embedded migration runner keyed on PRAGMA user_version - migrations/0001_core.sql: identity, media, user state, infra. Media items are global; all per-user state (progress, favorites, grants) is a join. One polymorphic progress table covers every media kind. - httpapi: request-ID, structured request logging, panic recovery, JSON endpoints for /healthz and /api/v1/version - multi-arch Dockerfile (pure-Go SQLite, CGO_ENABLED=0) with an entrypoint that honours PUID/PGID like the rest of the self-hosted stack - tests for migration idempotency, FK enforcement, WAL, config, middleware Media does not exist yet. Nothing is exposed beyond health and version.
60 lines
1.8 KiB
Go
60 lines
1.8 KiB
Go
package core
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
// Pure-Go SQLite driver. No cgo means CGO_ENABLED=0 static binaries and
|
|
// trivial cross-compilation for arm64.
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
// pragmas applied to every pooled connection, via the DSN.
|
|
//
|
|
// These are passed in the DSN rather than run as statements after connecting
|
|
// because database/sql pools connections: a one-off PRAGMA would configure
|
|
// only whichever single connection happened to serve it.
|
|
var sqlitePragmas = []string{
|
|
"journal_mode(WAL)", // concurrent readers during a scan
|
|
"busy_timeout(5000)", // wait for the writer instead of failing
|
|
"foreign_keys(1)", // enforce the schema's referential integrity
|
|
"synchronous(NORMAL)", // safe under WAL, far faster than FULL
|
|
"cache_size(-32000)", // ~32 MB page cache
|
|
}
|
|
|
|
// OpenDB opens the SQLite database with the pragmas Vellum depends on.
|
|
func OpenDB(path string) (*sql.DB, error) {
|
|
db, err := sql.Open("sqlite", buildDSN(path, sqlitePragmas))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open sqlite %s: %w", path, err)
|
|
}
|
|
|
|
// SQLite is a single-writer database. A large pool just produces
|
|
// SQLITE_BUSY contention; a small pool with a busy_timeout is faster.
|
|
db.SetMaxOpenConns(8)
|
|
db.SetMaxIdleConns(8)
|
|
db.SetConnMaxLifetime(0)
|
|
db.SetConnMaxIdleTime(5 * time.Minute)
|
|
|
|
if err := db.Ping(); err != nil {
|
|
_ = db.Close()
|
|
return nil, fmt.Errorf("ping sqlite %s: %w", path, err)
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
// buildDSN assembles a modernc.org/sqlite DSN with repeated _pragma params.
|
|
func buildDSN(path string, pragmas []string) string {
|
|
var b strings.Builder
|
|
b.WriteString("file:")
|
|
b.WriteString(path)
|
|
b.WriteString("?_time_format=sqlite")
|
|
for _, p := range pragmas {
|
|
b.WriteString("&_pragma=")
|
|
b.WriteString(url.QueryEscape(p))
|
|
}
|
|
return b.String()
|
|
}
|