8. Modo headless (servidor)

Nota

Esta sección describe un modo avanzado y opcional de blunderDB, destinado a los despliegues en servidor, al uso multiusuario y a la automatización. El uso normal y recomendado de blunderDB sigue siendo la aplicación de escritorio descrita en los capítulos anteriores. Si utiliza blunderDB en solitario, en su propio ordenador, no necesita este modo: puede ignorar este capítulo sin perder ninguna de las funcionalidades de análisis.

8.1. Visión general

El mismo binario blunderdb puede, además de la aplicación de escritorio y de los comandos en línea (véase Interfaz de línea de comandos (CLI)), funcionar en modo headless: sin interfaz gráfica, controlado por completo desde la línea de comandos o a través de la red. Este modo agrupa tres usos:

  • el demonio serve — expone el motor de blunderDB como un servicio HTTP + JSON, para ejecutar una base compartida en un servidor y acceder a ella entre varios usuarios;

  • el despachador genérico call — invoca cualquier operación de almacenamiento directamente, en local, para el scripting y las pruebas;

  • el comando migrate — transfiere una base SQLite de un solo usuario a un backend PostgreSQL multiusuario.

Estos tres usos se apoyan en una capa de almacenamiento común capaz de comunicarse con dos backends: SQLite (el formato de archivo .db habitual de la aplicación de escritorio) y PostgreSQL (para los despliegues en servidor multiusuario).

8.2. El demonio serve

blunderdb serve lanza el motor como un servicio HTTP que responde en JSON. Permite alojar una base de posiciones en una máquina y acceder a ella desde varios clientes.

# 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

Advertencia

El demonio no realiza ninguna autenticación. Confía en la cabecera de petición X-Tenant-ID y debe ejecutarse detrás de un reverse-proxy (nginx, Caddy…) encargado de la autenticación. Nunca lo exponga directamente en la Internet pública.

Opciones:

Opción

Por defecto

Significado

--db <chemin>

archivo SQLite (atajo de --backend sqlite --dsn <chemin>)

--backend <type>

sqlite

backend de almacenamiento: sqlite o postgres

--dsn <chaîne>

$BLUNDERDB_DSN

cadena de conexión del backend

--addr <hôte:port>

:8080

dirección de escucha

--log-level <niveau>

info

nivel de registro: debug|info|warn|error

--metrics

true

expone /metrics (formato Prometheus)

--cors-allow-origin <origine>

activa CORS para este origen (desactivado por defecto)

--rate-limit-rps <n>

0

límite de peticiones por segundo y por tenant (0 = desactivado)

--rate-limit-burst <n>

2×rps

tamaño del cubo de tokens para los picos de peticiones

--rls

false

PostgreSQL: activa la Row-Level Security por tenant (defensa en profundidad, opcional)

--bearoff-ts <fichier>

base de bearoff two-sided (.bd) opcional que amplía la base integrada TS-06-06 para el análisis de carrera del punto de acceso EPC; el demonio nunca descarga una base por sí mismo — monte el archivo como volumen y desígnelo aquí

La mayoría de las opciones también pueden indicarse mediante variables de entorno (BLUNDERDB_BACKEND, BLUNDERDB_DSN, BLUNDERDB_ADDR, BLUNDERDB_LOG_LEVEL, BLUNDERDB_RLS, BLUNDERDB_TS_PATH).

8.2.1. Puntos de acceso

El servicio expone puntos de acceso de explotación, siempre presentes:

  • GET /healthz — vivacidad (el proceso está en ejecución);

  • GET /readyz — disponibilidad (el almacenamiento responde);

  • GET /metrics — métricas Prometheus (si --metrics está activo).

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.

Dos métodos de la familia positions decodifican una posición sin guardarla: positions.fromXGID reconstruye una posición a partir de una cadena XGID, y positions.fromXGP a partir de un archivo de posición única .xgp.

La familia anki gana seis métodos que amplían el planificador de repetición espaciada (FSRS): anki.reviewLog (registro de cada revisión — calificación y resultado FSRS — para las estadísticas de retención y un historial fiel), anki.forecast (proyección del número de tarjetas que vencen en los próximos días, incluidas las atrasadas), anki.suspendCard / anki.buryCard / anki.removeCard (retirar una tarjeta de la cola de revisión temporal o definitivamente) y anki.optimizeParams (ajusta la tasa de retención objetivo de un mazo hacia la tasa de acierto observada en sus revisiones).

8.2.2. Despliegue con Docker

El repositorio proporciona un Dockerfile.serve que construye una imagen de contenedor mínima del demonio: solo se compila el binario serve (Go puro, sin interfaz gráfica y sin CGO, por lo que está enlazado estáticamente), que luego se coloca en una imagen distroless.

# 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

La imagen escucha en el puerto 8080 y se configura mediante variables de entorno (BLUNDERDB_BACKEND, BLUNDERDB_DSN, BLUNDERDB_ADDR, BLUNDERDB_RLS).

Advertencia

Al igual que el propio demonio, el contenedor no realiza ninguna autenticación: debe colocarse detrás de un reverse-proxy encargado de la autenticación y no exponerse nunca directamente en la Internet pública.

8.3. Backend PostgreSQL y multiusuario

Para un despliegue compartido, blunderDB puede almacenar los datos en PostgreSQL en lugar de en un archivo SQLite. El backend se selecciona mediante --backend postgres y la cadena de conexión --dsn. El esquema se crea y se migra automáticamente al arrancar.

Los datos están compartimentados por tenant (inquilino): cada petición lleva un identificador de scope (cabecera X-Tenant-ID, por defecto default), lo que permite que varios usuarios compartan la misma instancia sin ver los datos de los demás. La opción --rls activa además la Row-Level Security de PostgreSQL: se instalan políticas de aislamiento por tenant y app.tenant_id se fija por conexión. Es una defensa en profundidad opcional, desactivada por defecto.

Cuando se da de baja un tenant, POST /v1/tenant.purge elimina definitivamente todos sus datos (posiciones, partidas, colecciones, historial, etc.) del tenant actual (el indicado por X-Tenant-ID), así como su estado de sesión (última búsqueda, última posición, pestañas abiertas — las pocas filas metadata con el prefijo de ese scope): la operación se ejecuta en una única transacción, es idempotente (no produce error al purgar un tenant ya vacío ni al repetir la llamada) y no afecta a ningún otro tenant ni a la fila global de versión de esquema. Solo está disponible con el backend PostgreSQL — devuelve un error invalid en un backend SQLite, que no tiene noción de tenant.

8.4. Migrar una base SQLite a PostgreSQL

blunderdb migrate copia una base SQLite de un solo usuario a un backend PostgreSQL, bajo un scope de tenant elegido: es la vía para « subir » una biblioteca de escritorio a un despliegue en servidor.

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.

Opción

Por defecto

Significado

--from <uri>

base SQLite de origen (sqlite:///chemin o una simple ruta)

--to <dsn>

DSN PostgreSQL de destino (postgres://…)

--tenant-id <scope>

scope de tenant de destino (obligatorio salvo en --dry-run)

--dry-run

cuenta lo que se copiaría sin escribir nada

--on-conflict <politique>

""

"" se interrumpe si el tenant ya tiene datos; skip fusiona (deduplicación de las posiciones por hash Zobrist)

Nota

No se migran (todavía) los estados de la aplicación: mazos/tarjetas Anki, biblioteca de filtros, historial de búsqueda y de comandos, y metadatos de sesión. La prioridad es la migración de la biblioteca de posiciones y del historial de partidas.

8.5. El despachador genérico call

Como complemento de los subcomandos históricos (Interfaz de línea de comandos (CLI)), blunderdb call expone todas las operaciones de almacenamiento directamente, en local. Pasa por los mismos gestores que el demonio serve: el comportamiento es, por tanto, idéntico al de POST /v1/<famille>.<méthode>. Resulta útil para el scripting y las pruebas de integración.

# 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}'

Opciones:

Opción

Por defecto

Significado

--db <chemin>

archivo SQLite (atajo de --backend sqlite --dsn <chemin>)

--backend <type>

sqlite

sqlite o postgres

--dsn <chaîne>

$BLUNDERDB_DSN

cadena de conexión del backend

--scope <chaîne>

default

scope de tenant (enviado como X-Tenant-ID)

--json <chaîne>

{}

cuerpo de la petición en formato JSON

--json-file <chemin>

lee el cuerpo de la petición desde un archivo

--list

muestra todos los métodos <famille>.<méthode> y sale

La respuesta JSON (o el flujo NDJSON para los endpoints *.list) se escribe en la salida estándar. En caso de error, el proceso termina con un código distinto de cero y la envoltura {"error":{…}} se imprime en la salida estándar para seguir siendo analizable (por ejemplo con jq).