Kesto
Orquestador de copias de seguridad de bases de datos y volúmenes a S3, dentro de un contenedor.
Qué es
Kesto —en esperanto, arca, caja, cesto o cajón— es un contenedor único que:
- ejecuta
mariadb-dump/pg_dumpalldentro del contenedor de tu base de datos (víaexecpor la API del daemon) y transmite la salida en streaming, sin pasar por disco; - guarda el resultado en un repositorio rustic sobre S3 (MinIO, RusticFS, AWS);
- aplica retención tras cada copia (
keep-daily,keep-weekly,keep-monthly); - lo programa con cron y lo muestra en un panel web con el progreso en vivo por SSE — las dos últimas piezas, todavía sin construir (ver Estado).
Estado
Proyecto en construcción, pero el código ya ha empezado y los cinco primeros bloques están cerrados. La base está lista —especificación, gitflow, CI y .justfile— y sobre ella ya están implementados bootstrap-core (arranque, configuración desde el entorno, redacción de credenciales, envoltura JSON única y apagado ordenado), auth-capability (sesión en cookie HttpOnly, flujo OIDC contra un proveedor externo y puerta sobre toda la superficie de negocio salvo la salud y el propio flujo de entrada), db-schema (esquema del §3 aplicado por migraciones embebidas, pool de conexiones, capa de acceso de las cuatro tablas y cifrado en reposo de los tres secretos), api-endpoints (el estado de base dentro del estado de la aplicación, los tres recursos del §5 con cinco rutas cada uno y el listado de ejecuciones con filtros y paginación acotada) y backup-engine (el motor sin planificador: extracción del volcado sin que un byte pase por el disco, repositorios Rustic sobre S3 con retención, cola con un trabajador por repositorio y SSE con el progreso en vivo), con 405 tests en verde. Lo siguiente son el planificador y el panel. La especificación técnica (docs/DEFINITION.md) manda sobre todo lo demás.
| Pieza | Estado |
|---|---|
| Arranque y API | Cerrado: configuración desde el entorno, envoltura JSON única, salud, 404/405 y apagado ordenado (25 tests) |
| Autenticación | Cerrado: sesión firmada con KESTO_SECRET en cookie HttpOnly, flujo OIDC contra un proveedor externo y todo /api/v1/* bajo puerta salvo la salud y el flujo de entrada (93 tests acumulados) |
| Base de datos | Cerrado: esquema del §3 por migraciones embebidas, pool de conexiones, capa de acceso de las cuatro tablas y cifrado en reposo de los tres secretos |
| Superficie del §5 | Cerrado: los diez endpoints del §5 —los tres recursos con cinco rutas cada uno, el listado de ejecuciones, el disparo manual POST /jobs/:id/run, el explorador GET /snapshots y el stream GET /events— (214 tests acumulados) |
| Motor de copias | Cerrado: extracción por exec con la salida en streaming sin pasar por disco, repositorios Rustic sobre S3 con retención, un trabajador secuencial por repositorio, hub de eventos con SSE y las tres rutas nuevas del §5 (POST /jobs/:id/run, GET /snapshots, GET /events) (405 tests acumulados —401 en el binario, 20 de ellos ignored por exigir un motor de contenedores, más 4 de integración—) |
CI (.github/workflows/ci.yml) |
Verde: cargo fmt --check, cargo clippy -- -D warnings y cargo test --locked |
| Imagen | Pendiente de Dockerfile; el job del pipeline se salta solo hasta que exista |
| Crate | publish = false a propósito, para no dejar un placeholder permanente en crates.io |
| Panel | ui/ todavía no existe; el job de frontend del CI se salta solo |
Arquitectura
flowchart LR
subgraph K["Contenedor Kesto"]
UI["Panel React + Ant Design"]
API["Axum: REST + SSE"]
DB[("SQLite")]
CRON["Scheduler tokio-cron"]
ENG["Motor de ejecucion"]
end
SOCK["Socket del daemon"]
PG[("MariaDB / PostgreSQL")]
S3[("S3")]
UI -.->|"SSE"| API
API --> DB
API --> CRON
CRON --> ENG
ENG -->|"bollard: exec"| SOCK
SOCK --> PG
PG -->|"stdout en streaming"| ENG
ENG -->|"rustic_core"| S3
| Capa | Tecnología |
|---|---|
| API | Rust + Axum (src/) |
| Estado | SQLite con SQLx (WAL, migraciones embebidas) |
| Docker | bollard sobre el socket del daemon |
| Repositorios | rustic_core + rustic_backend hacia S3 |
| Planificador y motor | tokio-cron-scheduler para el cron, que todavía no existe; el trabajador secuencial por repositorio está construido y vive en el motor (src/engine/), porque Rustic toma un candado exclusivo del repositorio |
| Panel | React + TypeScript + Ant Design (ui/), progreso por SSE |
Requisitos
- Rust 1.85+ (
edition = "2024"; desarrollado con 1.97). - Podman en desarrollo y Docker en producción: el motor habla con la API del daemon a través del socket UNIX.
- Node 24 + pnpm 12 solo si vas a tocar el panel.
Instalación
La imagen multi-arquitectura (amd64 + arm64) se publica en ghcr.io/atareao/kesto en cuanto exista un Dockerfile:
El despliegue de referencia —socket del daemon, directorios del host a respaldar y volumen para la SQLite— está en docs/DEFINITION.md §8.
Desarrollo
Todo pasa por el .justfile:
just dev necesita KESTO_MASTER_KEY en el entorno o el proceso no arranca, y necesita su propio DATABASE_URL: el valor por defecto (sqlite:///app/data/kesto.db) es el del volumen del compose del §8, así que en una máquina sin /app/data el arranque falla nombrando la ruta —Kesto no crea el directorio a propósito—; exporta uno tuyo (export DATABASE_URL=sqlite:///tmp/kesto/kesto.db, con el directorio ya creado) y las migraciones embebidas se aplican solas al abrir. Sin variables OIDC_* arranca en modo desarrollo: sin capa de autenticación, con todo /api/v1/* abierto y /api/v1/auth/* contestando 404; con las tres OIDC_* presentes exige además KESTO_SECRET y pone la puerta sobre toda la superficie de negocio. Usa RUST_LOG=debug para ver el detalle.
Las recetas cubren además el ciclo gitflow (gf-*), los planes y las features (plan-*, feature-new), la imagen local (build, run) y OpenSpec (check-spec, spec-*).
Pipeline
Un merge a main es una release, sin pasos manuales: release-prepare.yml deduce el bump de los conventional commits, sube la versión con vampus, genera el CHANGELOG con git-cliff y etiqueta vX.Y.Z. El tag dispara release.yml, que publica la imagen multi-arquitectura en GHCR y el crate en crates.io. Detalles en GIT_FLOW.md.
Estructura
src/ backend Rust (Axum)
api/ los recursos del §5, la salud, el flujo de entrada, la sesión y las rutas del motor
auth/ el flujo OIDC, la sesión y el gate (módulo propio, no del §7)
db/ pool, migraciones embebidas, cifrado en reposo y entidades del §3
docker/ el transporte de la API de Docker: exec y salida en streaming
engine/ el motor: extracción, cola por repositorio, trabajador y hub de eventos
rustic/ repositorios Rustic sobre S3, puente de lectura, progreso y retención
config.rs configuración desde el entorno
secret.rs redacción de credenciales
error.rs errores tipados + IntoResponse
state.rs estado compartido
# solo scheduler llega con el change siguiente (el planificador)
migrations/ esquema del §3 e índices (0001_init.sql, 0002_indexes.sql)
ui/ panel React + TypeScript + Ant Design (pendiente)
docs/ SRS y un documento por feature (FEATURE-NNN-<slug>.md)
plans/ planes de implementación (PLAN-NNN.md)
Documentación
- docs/DEFINITION.md — SRS: esquema SQLite, endpoints, flujos y despliegue
- AGENTS.md — instrucciones para agentes: layout, podman-first y las trampas del SRS
- GIT_FLOW.md — ramas, commits convencionales y releases
Licencia
MIT — ver LICENSE. También declarada en Cargo.toml (license = "MIT"), que es lo que exige crates.io para publicar.