A go port of booklore/grimmory
  • Go 74.5%
  • JavaScript 21.1%
  • HTML 3%
  • CSS 1.2%
Find a file
Jeremy Bush 6c222b87e9
All checks were successful
/ JS Unit Tests (push) Successful in 53s
/ E2E API (push) Successful in 1m2s
/ Test Race (push) Successful in 1m36s
/ Lint (push) Successful in 1m50s
/ Integration (push) Successful in 1m57s
/ Coverage (push) Successful in 2m8s
/ Build and publish (amd64) (push) Successful in 2m11s
/ E2E Browser (push) Successful in 4m12s
Merge pull request 'fix(llm): feed filename to comic identify + harden year prompt (bookshelf-fg10w)' (#1487) from bd-bookshelf-fg10w into main
2026-10-09 01:35:49 +00:00
.beads bd init: initialize beads issue tracking 2026-05-20 11:11:12 -05:00
.claude docs(agents): use ${PR} not literal <PR> in mktemp example (review MINOR) 2026-08-20 08:35:45 -05:00
.forgejo/workflows ci: remove one-time MIGRATION_DDL_BOOTSTRAP flag now that allowlist.txt is on main 2026-09-06 19:57:17 -05:00
cmd/pergamum fix(scan): backfill audio chapters/duration for already-ingested audiobooks (bookshelf-4pndq) 2026-09-21 21:19:27 -05:00
data bd-bookshelf-qqz.1: scaffold repo structure 2026-05-20 11:46:44 -05:00
docker/mysql-ci docs(ci): fold review-minor comment corrections (bookshelf-7w1mn) 2026-08-05 09:46:45 -05:00
docs feat(bookdrop): auto-check row when fetched metadata is copied 2026-08-11 09:31:48 -05:00
e2e fix(e2e): match uppercase dt labels (CSS text-transform) in metadata header test 2026-10-06 20:26:21 -05:00
internal fix: rewrite guarded assertions as unconditional in LLM identify tests 2026-10-08 20:30:26 -05:00
loadtest fix(loadtest): report real p99 + drop broken bead-issue posting (bookshelf-22pag) 2026-07-10 13:41:00 -05:00
scripts fix(books): collapse multi-file metadata header to a Files/size summary 2026-10-06 20:26:21 -05:00
static feat(audiobook): folder-of-files chapters + multi-file playback (bookshelf-z25zw.4) 2026-09-28 21:21:46 -05:00
templates fix(books): collapse multi-file metadata header to a Files/size summary 2026-10-06 20:26:21 -05:00
.dockerignore fix(ci-image): add -cover/-race warming passes, bump to :5, minor cleanups (bookshelf-nwo0v.1) 2026-07-28 22:16:07 -05:00
.editorconfig bd-bookshelf-qqz.1: scaffold repo structure 2026-05-20 11:46:44 -05:00
.gitignore chore: gitignore scripts/test_policy_check/test_policy_check binary 2026-08-05 08:51:07 -05:00
.gitmodules feat(docs): stand up Hugo + Hextra docs site infrastructure (bookshelf-xpd.1) 2026-05-27 22:59:37 -05:00
.golangci.yml refactor(bookmeta): extract metadata persist/store spine from internal/books 2026-08-11 09:23:15 -05:00
AGENTS.md bd init: initialize beads issue tracking 2026-05-20 11:11:12 -05:00
CLAUDE.md feat(reviews): ui-reviewer agent + visual gate for template/CSS PRs (bookshelf-g3y8) 2026-06-18 19:32:52 -05:00
dev.env.example feat(config): rename default database name bookshelf → pergamum (bookshelf-50z.3) 2026-06-02 20:13:37 -05:00
docker-compose.worker.yml docs(worker): update docker-compose.worker.yml for go-workflows engine (bookshelf-3j83) 2026-06-29 06:25:07 -05:00
docker-compose.yml feat(dev): parameterize host ports to prevent worktree stack collisions 2026-06-24 12:33:06 -05:00
Dockerfile fix(docker): build static (CGO_ENABLED=0 + -tags nodynamic) to fix prod musl-loader crash (bookshelf-jxqf) 2026-06-30 07:08:10 -05:00
Dockerfile.ci fix(ci): clean stale /build layer before COPY in Dockerfile.ci 2026-08-10 20:25:30 -05:00
Dockerfile.mysql-ci docs(ci): fold review-minor comment corrections (bookshelf-7w1mn) 2026-08-05 09:46:45 -05:00
go.mod feat(tui): add pergamum tui subcommand with device-grant login + paginated book list (bookshelf-wtsp.7) 2026-07-25 21:39:40 -05:00
go.sum feat(tui): add pergamum tui subcommand with device-grant login + paginated book list (bookshelf-wtsp.7) 2026-07-25 21:39:40 -05:00
Makefile chore(db): enforce online DDL (ALGORITHM=INPLACE, LOCK=NONE) for heavy index migrations 2026-09-06 15:08:47 -05:00
package-lock.json ci(js): add Vitest coverage measurement (no gate yet) (bookshelf-y59n.5) 2026-06-17 08:35:58 -05:00
package.json ci(js): add Vitest coverage measurement (no gate yet) (bookshelf-y59n.5) 2026-06-17 08:35:58 -05:00
README.md docs: finish bookshelf→pergamum rename sweep (bookshelf-sw5a) 2026-06-11 21:49:38 -05:00
series_detail_xmen_2021.png chore: add series screenshot script and rendered screenshots for PR #932 2026-07-04 19:25:24 -05:00
series_list_two_xmen_volumes.png chore: add series screenshot script and rendered screenshots for PR #932 2026-07-04 19:25:24 -05:00
shot1153 chore: commit stray screenshot file left in worktree by previous agent 2026-07-16 08:17:31 -05:00
sqlc.yaml feat(sqlc): wire up sqlc code generation with smoke query (bookshelf-qqz.5) 2026-05-20 15:16:46 -05:00
vitest.config.js feat(js): consolidate CSRF-token helpers into window.CsrfHelper (bookshelf-t582g.3.1) 2026-07-27 21:11:36 -05:00

Pergamum

A self-hosted digital library server for eBooks, comics, PDFs, and audiobooks. Grimmory-compatible schema rewrite in Go.

Local development

Copy dev.env.example to ~/.config/pergamum/dev.env, fill in your values (API keys, admin credentials, books directory, host uid:gid), then run make up. The file lives outside the repo so it is never committed. Every worktree's make up / make dev / make down reads it automatically when present.

Commands

pergamum [FLAGS] [SUBCOMMAND [FLAGS] ...]

With no subcommand, the HTTP server starts (equivalent to pergamum serve).

serve (default)

Start the HTTP server.

pergamum [FLAGS]
pergamum serve [FLAGS]

Both forms start the HTTP server. The bare form is provided for backward compatibility with existing scripts and docker-compose files.

scan

Synchronously scan a configured library and populate the database with books. Useful for initial seeding and development without needing background workers.

pergamum scan <library_id>

A library must be created via the HTTP API before scanning. Example sequence:

docker compose up -d   # start server

# create the library
curl -X POST -H 'Content-Type: application/json' \
  -d '{"name":"My Books","organization_mode":"BOOK_PER_FILE","allowed_formats":"epub,pdf"}' \
  http://localhost:8080/libraries

# add a path to the library (use the library id returned above)
curl -X POST -H 'Content-Type: application/json' \
  -d '{"path":"/srv/books"}' \
  http://localhost:8080/libraries/1/paths

# scan the library
docker compose run --rm pergamum scan 1

All output is structured JSON (via log/slog). Progress logs are emitted every 100 files or 10 seconds, whichever comes first.

Example:

PERGAMUM_DSN="user:pass@tcp(localhost:3306)/bookshelf?parseTime=true" pergamum scan 1

worker

Run the background task pool as a standalone process (no HTTP server).

pergamum worker [FLAGS]

See Deployment — worker modes below for when to use this subcommand.

Configuration

Configuration is loaded from flags, PERGAMUM_* environment variables, and an optional config file (key=value lines). Precedence: flags > env > config file > built-in defaults.

Flags (shared by all subcommands):

  • --http-addr — HTTP server listen address (default :8080)
  • --metrics-addr — Prometheus metrics listen address (default: same as --http-addr; pergamum worker uses this for /metrics, /healthz, and /readyz)
  • --dsn — MySQL DSN
  • --data-dir — root directory for derived data (covers, thumbnails)
  • --log-level — debug, info, warn, error (default info)
  • --log-format — json or text (default json)
  • --config — path to optional config file
  • --slow-query-log — log queries slower than this threshold (default 100ms, 0 disables)
  • --worker-count — number of concurrent worker goroutines (default 4)
  • --worker-mode — embedded, external, or disabled (default embedded; see below)

Deployment — worker modes

Pergamum supports three worker pool topologies selected by --worker-mode / PERGAMUM_WORKER_MODE.

embedded (default)

The HTTP server starts and owns the worker pool in the same process.

pergamum           # or: PERGAMUM_WORKER_MODE=embedded pergamum

Suitable for single-process deployments and local development (docker compose up).

external

The HTTP server does not start a pool. A separate pergamum worker process handles task processing.

# Terminal 1 — HTTP server only
PERGAMUM_WORKER_MODE=external pergamum

# Terminal 2 — standalone worker pool
PERGAMUM_METRICS_ADDR=:9091 pergamum worker

Use the compose override to run this topology with Docker Compose:

docker compose -f docker-compose.yml -f docker-compose.worker.yml up

Note: After pulling changes that add database migrations, rebuild the Docker image to ensure the app's embedded migrations are up to date (docker compose ... up --build). Docker Compose reuses cached images by default; without a rebuild, the binary's embedded migrations may lag behind the database, resulting in "no migration found for version N" errors.

Scale worker replicas horizontally (each replica is a separate pool; they safely contend on the tasks table via the CAS-claim protocol):

docker compose -f docker-compose.yml -f docker-compose.worker.yml up --scale worker=N

disabled

Neither the HTTP server nor a separate pergamum worker process runs a pool. Use for read-only replicas or environments where task processing is handled elsewhere.

PERGAMUM_WORKER_MODE=disabled pergamum

Single-cron-owner rule

Cron always runs in the HTTP server process (embedded or external mode) — never in pergamum worker.

The cron scheduler is a singleton owned by the app process. In external mode, the app enqueues tasks on schedule and the worker process claims them. In disabled mode, no tasks are enqueued. Multiple pergamum worker replicas never race on cron because they do not run it.

Worker health endpoints

The standalone pergamum worker process exposes three endpoints on PERGAMUM_METRICS_ADDR:

Endpoint Purpose Success
/metrics Prometheus scrape target 200
/healthz Liveness — process is alive and serving 200
/readyz Readiness — DB is reachable; worker can claim tasks 200

/readyz pings the database with a 3-second timeout and returns 503 Service Unavailable when the ping fails, allowing orchestrators to hold traffic until the worker is ready.

Deployment

See docs/releases.md for Docker image streams, self-hoster setup, release procedures, and versioning policy.

Help

pergamum --help
pergamum serve --help
pergamum scan --help
pergamum worker --help