8. Headless mode (server)
Note
This section describes an advanced, optional mode of blunderDB, intended for server deployments, multi-user setups and automation. The normal and recommended use of blunderDB remains the desktop application described in the previous chapters. If you use blunderDB on your own, on your computer, you do not need this mode: you can skip this chapter without losing any of the analysis features.
8.1. Overview
In addition to the desktop application and the command-line commands (see Command Line Interface (CLI)), the same blunderdb binary can run in headless mode: without a graphical interface, driven entirely from the command line or over the network. This mode covers three use cases:
the
servedaemon — exposes the blunderDB engine as an HTTP + JSON service, to run a shared database on a server and access it from several clients;the generic
calldispatcher — calls any storage operation directly, locally, for scripting and testing;the
migratecommand — transfers a single-user SQLite database to a multi-user PostgreSQL backend.
These three use cases rely on a shared storage layer that can talk to two backends: SQLite (the usual .db file format of the desktop application) and PostgreSQL (for multi-user server deployments).
8.2. The serve daemon
blunderdb serve runs the engine as an HTTP service that responds in JSON. It lets you host a position database on one machine and access it from several clients.
# Servir une base SQLite locale sur le port 8080
blunderdb serve --db ma_base.db --addr :8080
# Servir un backend PostgreSQL
blunderdb serve --backend postgres \
--dsn "postgres://user:pass@host:5432/blunderdb?sslmode=disable" \
--addr :8080
Warning
The daemon performs no authentication. It trusts the X-Tenant-ID request header and must run behind a reverse proxy (nginx, Caddy…) responsible for authentication. Never expose it directly to the public Internet.
Options:
Option |
Default |
Meaning |
|---|---|---|
|
– |
SQLite file (shorthand for |
|
|
storage backend: |
|
|
backend connection string |
|
|
listen address |
|
|
log level: |
|
|
exposes |
|
– |
enables CORS for this origin (disabled by default) |
|
|
requests per second per tenant limit (0 = disabled) |
|
|
token-bucket size for request bursts |
|
|
PostgreSQL: enables per-tenant Row-Level Security (defence in depth, opt-in) |
|
– |
optional two-sided bearoff database ( |
Most options can also be provided through environment variables (BLUNDERDB_BACKEND, BLUNDERDB_DSN, BLUNDERDB_ADDR, BLUNDERDB_LOG_LEVEL, BLUNDERDB_RLS, BLUNDERDB_TS_PATH).
8.2.1. Endpoints
The service exposes operational endpoints, always present:
GET /healthz— liveness (the process is running);GET /readyz— readiness (storage responds);GET /metrics— Prometheus metrics (if--metricsis enabled).
La surface métier suit le schéma POST /v1/<famille>.<méthode> (par exemple
/v1/positions.save, /v1/matches.get). Les familles couvrent les
positions, analyses, matchs, commentaires, collections, tournois, cartes Anki,
filtres, sessions, historique (recherche et commandes), recherche,
métadonnées, statistiques, import et export, ainsi que le cycle de vie des
tenants (tenant.purge, réservé au backend PostgreSQL). Les endpoints de
listing renvoient un flux NDJSON (un objet JSON par ligne). Le serveur
s’arrête proprement sur SIGINT / SIGTERM.
Two methods in the positions family decode a position without storing it: positions.fromXGID rebuilds a position from an XGID string, and positions.fromXGP from a single-position .xgp file.
The anki family gains six methods that extend the spaced-repetition scheduler (FSRS): anki.reviewLog (a log of every review — rating and FSRS outcome — powering retention statistics and a faithful history), anki.forecast (a projection of the number of cards due over the coming days, including overdue cards), anki.suspendCard / anki.buryCard / anki.removeCard (remove a card from the review queue temporarily or permanently) and anki.optimizeParams (nudges a deck’s target retention rate toward the pass rate observed on its reviews).
8.2.2. Deployment with Docker
The repository provides a Dockerfile.serve that builds a minimal container image of the daemon: only the serve binary is compiled (pure Go, with no graphical interface and no CGO, therefore statically linked), then placed into a distroless image.
# Construire l'image (depuis la racine du dépôt)
docker build -f Dockerfile.serve -t blunderdb-serve .
# Lancer le démon (le backend par défaut de l'image est postgres)
docker run --rm -p 8080:8080 \
-e BLUNDERDB_DSN="postgres://user:pass@hôte:5432/blunderdb?sslmode=disable" \
blunderdb-serve
The image listens on port 8080 and is configured through environment variables (BLUNDERDB_BACKEND, BLUNDERDB_DSN, BLUNDERDB_ADDR, BLUNDERDB_RLS).
Warning
Like the daemon itself, the container performs no authentication: it must be placed behind a reverse proxy responsible for authentication and never be exposed directly to the public Internet.
8.3. PostgreSQL backend and multi-user
For a shared deployment, blunderDB can store data in PostgreSQL rather than in a SQLite file. The backend is selected with --backend postgres and the --dsn connection string. The schema is created and migrated automatically at startup.
Data is partitioned per tenant: each request carries a scope identifier (the X-Tenant-ID header, default by default), which lets several users share the same instance without seeing each other’s data. The --rls option additionally enables PostgreSQL’s Row-Level Security: per-tenant isolation policies are installed and app.tenant_id is set per connection. This is an optional defence in depth, disabled by default.
When a tenant is decommissioned, POST /v1/tenant.purge permanently deletes all its data (positions, matches, collections, history, etc.) for the current tenant (the one carried by X-Tenant-ID), as well as its session state (last search, last position, open tabs — the handful of metadata rows prefixed with that scope): the operation runs in a single transaction, is idempotent (no error purging an already-empty tenant or repeating the call), and does not affect any other tenant or the global schema-version row. It is only available with the PostgreSQL backend — it returns an invalid error on a SQLite backend, which has no notion of tenant.
8.4. Migrating a SQLite database to PostgreSQL
blunderdb migrate copies a single-user SQLite database to a PostgreSQL backend, under a chosen tenant scope — this is the path to “upload” a desktop library to a server deployment.
blunderdb migrate \
--from sqlite:///chemin/vers/base.db \
--to "postgres://user:pass@host:5432/db?sslmode=disable" \
--tenant-id mon-tenant
# Prévisualiser sans rien écrire
blunderdb migrate --from sqlite:///chemin/vers/base.db \
--tenant-id mon-tenant --dry-run
La migration copie les positions, leurs analyses et commentaires, les matchs
(parties + coups), les tournois (avec leurs liens de match) et les collections
(avec leur composition), en réattribuant les clés primaires et étrangères, le
tout dans une seule transaction côté destination : l’opération est atomique
(un échec laisse la destination intacte, il suffit de relancer). La progression
et le bilan final sont émis en NDJSON sur la sortie standard. Si la base source
est assez ancienne pour nécessiter sa propre mise à niveau de schéma sur place,
celle-ci s’exécute d’abord et émet ses propres événements
"schema-migration" (phase/effectué/total) avant que la copie ligne à ligne
ne commence.
Option |
Default |
Meaning |
|---|---|---|
|
– |
source SQLite database ( |
|
– |
destination PostgreSQL DSN ( |
|
– |
destination tenant scope (required except in |
|
– |
counts what would be copied without writing anything |
|
|
|
Note
Application state is not (yet) migrated: Anki decks/cards, the filter library, search and command history, and session metadata. The priority is migrating the position library and the match history.
8.5. The generic call dispatcher
In addition to the historical subcommands (Command Line Interface (CLI)), blunderdb call exposes all storage operations directly, locally. It goes through the same handlers as the serve daemon: the behaviour is therefore identical to POST /v1/<family>.<method>. This is useful for scripting and integration testing.
# Lister toutes les méthodes disponibles
blunderdb call --list
# Lectures
blunderdb call metadata.counts --db ma_base.db
blunderdb call positions.list --db ma_base.db --json '{"limit":10}'
blunderdb call matches.get --db ma_base.db --json '{"id":1}'
# Écritures
blunderdb call positions.save --db ma_base.db --json '{"position":{...}}'
blunderdb call matches.delete --db ma_base.db --json '{"id":42}'
Options:
Option |
Default |
Meaning |
|---|---|---|
|
– |
SQLite file (shorthand for |
|
|
|
|
|
backend connection string |
|
|
tenant scope (sent as |
|
|
request body in JSON format |
|
– |
reads the request body from a file |
|
– |
lists all |
The JSON response (or the NDJSON stream for *.list endpoints) is written to standard output. On error, the process exits with a non-zero code and the {"error":{…}} envelope is printed to standard output so it stays parseable (for example with jq).