No description
Find a file
mithral 5283e073f5 Web UI: Svelte app, embedded into the binary
The frontend the app needs to be usable at all.

- web/: Svelte 5 + Vite + TypeScript SPA. Login, app shell with per-type nav,
  library list/create/scan/delete, item grid with covers, admin user
  management, and an epub.js reader that syncs progress.
- Reader resumes by spine index — an integer — which is why the polymorphic
  progress table needs no text column.
- Session state uses classic stores in a plain .ts module rather than runes in
  a .svelte.ts module, avoiding special module resolution.
- Embedding: web/embed.go embeds web/dist. A committed .gitkeep keeps the
  directive valid before the frontend is built, so `go build ./...` never
  fails. Unknown non-API paths serve index.html for client routes; /api/* and
  /healthz always return JSON and never fall back to HTML.
- Dockerfile builds the UI in a node stage and overlays dist into the Go stage.
- pnpm 12 needs allowBuilds in pnpm-workspace.yaml; esbuild approved, the two
  unused transitive scripts explicitly denied so install exits 0.

svelte-check: 0 errors, 0 warnings. Verified served: SPA shell at /, route
fallback, hashed asset, and JSON 404 on /api/*.
2026-10-09 16:40:58 -04:00
cmd/vellum Phase 3: EPUB module, file streaming, per-user progress 2026-10-09 16:36:27 -04:00
internal Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
migrations Phase 2: libraries and the file scanner 2026-10-09 16:33:56 -04:00
web Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
.dockerignore Phase 0: skeleton, config, migrations, health check, container 2026-10-09 16:26:08 -04:00
.gitignore Phase 0: skeleton, config, migrations, health check, container 2026-10-09 16:26:08 -04:00
compose.yml Phase 0: skeleton, config, migrations, health check, container 2026-10-09 16:26:08 -04:00
Dockerfile Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
entrypoint.sh Phase 0: skeleton, config, migrations, health check, container 2026-10-09 16:26:08 -04:00
go.mod Phase 1: accounts, sessions, per-library grants 2026-10-09 16:31:24 -04:00
go.sum Phase 1: accounts, sessions, per-library grants 2026-10-09 16:31:24 -04:00
Makefile Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
README.md Phase 0: skeleton, config, migrations, health check, container 2026-10-09 16:26:08 -04:00

Vellum

One application, one process, one database for every media type — ebooks, comics, audiobooks, music, photos, games, and video — behind a single account system with real per-user progress and per-library permissions.

A modular monolith. One Go binary, one SQLite file, one web frontend.

The full plan lives in ~/.opencode/plan/vellum.md.

Status

Phase Scope State
0 Repo, config, migrations, health, Docker done
1 Users, sessions, library grants not started
2 Scanner, items, assets, FTS5 search not started
3 EPUB reader + progress not started
4 Comic (CBZ/PDF) viewers not started
5 Metadata providers not started
6 Audiobooks, music, photos not started
7 Games not started
— Video (stays on Jellyfin for now) deferred

Phase 0 delivers a binary that boots, migrates its schema, serves a health check, and shuts down cleanly. No media features yet.

Quick start

sudo dnf install golang        # once; Go is not in the base image here
make build
make run
curl localhost:7788/healthz    # {"database":"ok","status":"ok",...}

Run with media roots configured:

VELLUM_MEDIA_DIRS=/mnt/storage/media/ebooks:/mnt/storage/media/comics make run

Configuration

All configuration is environment variables. Every one has a default, so the binary runs with no configuration at all.

Variable Default Meaning
VELLUM_LISTEN :7788 Bind address
VELLUM_DATA_DIR ./data SQLite database lives here
VELLUM_CONFIG_DIR ./config Regenerable cache (thumbnails, pages)
VELLUM_MEDIA_DIRS (none) Read-only library roots, colon-separated
VELLUM_LOG_LEVEL info debug, info, warn, error
VELLUM_LOG_FORMAT text text or json
VELLUM_SESSION_TTL 720h Login session lifetime
VELLUM_SHUTDOWN_GRACE 15s Graceful shutdown timeout

Layout

cmd/vellum/          main — wiring and lifecycle only
internal/
  core/              config, logging, database, migration runner
  httpapi/           router, middleware, handlers
migrations/          embedded SQL, applied in order at boot

Module rule: the core never imports a feature module; modules import the core and mount their own routes, tables, and scanner rules. Adding a media type is a new package plus a migration, never a core edit.

Schema rule: media items are global; everything user-specific is a join. Nothing may hold per-user state on a media row.

Database

SQLite in WAL mode, opened through modernc.org/sqlite — a pure-Go driver, so the binary builds with CGO_ENABLED=0 and cross-compiles without a toolchain per target.

Migrations are embedded and applied in filename order; applied files are recorded only by number in PRAGMA user_version, so never edit an applied migration — add a new one.

Back up by copying the database (or its directory); VACUUM INTO 'backup.db' takes a consistent snapshot while running.

Deploy

make docker                                  # podman build
podman compose up -d                         # see compose.yml

The container mounts /data (database), /config (cache), and /media (read-only). On SELinux hosts every bind mount needs :z — compose.yml already has it.

The application never writes to /media.

Development

make check      # gofmt + vet + test
make test