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 serve daemon — 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 call dispatcher — calls any storage operation directly, locally, for scripting and testing;

  • the migrate command — 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

--db <path>

SQLite file (shorthand for --backend sqlite --dsn <path>)

--backend <type>

sqlite

storage backend: sqlite or postgres

--dsn <string>

$BLUNDERDB_DSN

backend connection string

--addr <host:port>

:8080

listen address

--log-level <level>

info

log level: debug|info|warn|error

--metrics

true

exposes /metrics (Prometheus format)

--cors-allow-origin <origin>

enables CORS for this origin (disabled by default)

--rate-limit-rps <n>

0

requests per second per tenant limit (0 = disabled)

--rate-limit-burst <n>

2×rps

token-bucket size for request bursts

--rls

false

PostgreSQL: enables per-tenant Row-Level Security (defence in depth, opt-in)

Most options can also be provided through environment variables (BLUNDERDB_BACKEND, BLUNDERDB_DSN, BLUNDERDB_ADDR, BLUNDERDB_LOG_LEVEL, BLUNDERDB_RLS).

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 --metrics is enabled).

The business surface follows the POST /v1/<family>.<method> scheme (for example /v1/positions.save, /v1/matches.get). The families cover positions, analyses, matches, comments, collections, tournaments, Anki cards, filters, sessions, search, metadata, statistics and import. Listing endpoints return an NDJSON stream (one JSON object per line). The server shuts down cleanly on 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.

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.

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

The migration copies the positions, their analyses and comments, the matches (games + moves), the tournaments (with their match links) and the collections (with their composition), reassigning primary and foreign keys, all within a single transaction on the destination side: the operation is atomic (a failure leaves the destination intact, just run it again). Progress and the final summary are emitted as NDJSON on standard output.

Option

Default

Meaning

--from <uri>

source SQLite database (sqlite:///path or a plain path)

--to <dsn>

destination PostgreSQL DSN (postgres://…)

--tenant-id <scope>

destination tenant scope (required except in --dry-run)

--dry-run

counts what would be copied without writing anything

--on-conflict <policy>

""

"" aborts if the tenant already has data; skip merges (deduplication of positions by Zobrist hash)

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

--db <path>

SQLite file (shorthand for --backend sqlite --dsn <path>)

--backend <type>

sqlite

sqlite or postgres

--dsn <string>

$BLUNDERDB_DSN

backend connection string

--scope <string>

default

tenant scope (sent as X-Tenant-ID)

--json <string>

{}

request body in JSON format

--json-file <path>

reads the request body from a file

--list

lists all <family>.<method> methods and exits

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).