A go port of booklore/grimmory
  • Go 74%
  • JavaScript 21.5%
  • HTML 3.1%
  • CSS 1.2%
Find a file
Jeremy Bush 79a578ab8f
All checks were successful
/ E2E API (push) Successful in 1m20s
/ Test Race (push) Successful in 1m51s
/ JS Unit Tests (push) Successful in 54s
/ Integration (push) Successful in 2m21s
/ Lint (push) Successful in 2m28s
/ Coverage (push) Successful in 2m56s
/ Build and publish (amd64) (push) Successful in 2m25s
/ E2E Browser (push) Successful in 5m10s
Merge pull request 'fix(attach): only reassign DB after a successful physical move (bookshelf-8l09k)' (#1461) from bd-bookshelf-8l09k into main
2026-08-31 01:34:31 +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 fix(e2e): de-flake E2E Browser job — root-cause CDP freeze under load 2026-08-10 21:17:46 -05:00
cmd/pergamum fix(db): add context to MigrationStatus + exit non-zero on dirty schema (bookshelf-jjcgz) 2026-08-10 19:34: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 flake(e2e): re-query series-merge typeahead element to fix detached-node race (bookshelf-duy4m) 2026-08-30 20:13:11 -05:00
internal fix(attach): move-then-commit so a failed move never orphans the DB (bookshelf-8l09k) 2026-08-30 20:25:51 -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(review): thread decode seam into public llm factories, drop coverage exclusion 2026-08-20 07:21:55 -05:00
static flake(js): raise timeout for sibling heavy-fixture comic editor tests (bookshelf-lwfh6) 2026-08-28 14:07:49 -05:00
templates BookDrop: auto-collapse metadata editor on library select (bookshelf-ar5x7) 2026-08-27 11:10:35 -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 nnb9: black-box internal/seeder + internal/metadata/llm (nnb9.25, nnb9.20) 2026-08-20 07:21:55 -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-leveldebug, info, warn, error (default info)
  • --log-formatjson 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-modeembedded, 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