Parte I — Preparar el suelo

02 · Arquitectura: el árbol entero

El baobab de Baobáxia: raíces, tronco, ramas y copa

Principios

  1. El sistema de archivos es la fuente de verdad. Cada saber es un archivo saber.bbx (YAML + markdown) versionado en git; los binarios van por git-annex. El índice SQLite es una caché: bórralo y corre bbx reindex.
  2. Primero sin conexión. Cada mucua funciona sola. La red es un extra.
  3. Federación de iguales. No hay mucua central. Las mucuas corren el mismo código; solo cambia la configuración.
  4. Modular por variable de entorno. BBX_AUTH_MODE activa la criptografía; BBX_FED_XMPP/BBX_FED_AP activan la federación; BBX_ENRIQUECER activa OCR y Whisper.

Capas del backend

flowchart TB
    subgraph Entradas["Entradas (ramas)"]
        API["api/v3/ — FastAPI<br/>~150 rutas"]
        CLI["cli.py — bbx"]
        BOT["federacao/xmpp_bot.py"]
        TIMERS["timers systemd<br/>bbx sincronizar · bbx enriquecer"]
    end
    subgraph Servicos["services/ — reglas del negocio"]
        SS["SaberService<br/>(saber_service.py)"]
        PERM["permissions · acl · grupos<br/>acesso_galaxia"]
        SYNC["sincronizacao · links<br/>pedidos · propagacao"]
        OUT["copias · restauro · historico<br/>recovery · challenge · jobs"]
    end
    subgraph Pub["publica/ — operaciones comunes de la CLI y el bot"]
        OPS["operacoes.py · processamento.py"]
    end
    subgraph Nucleo["core/ — el tronco"]
        SAB["saber.py — modelo"]
        REPO["repository.py — SaberRepository"]
        IDX["index.py — GalaxyIndex (SQLite)"]
        REG["registry.py · relation_graph.py"]
    end
    subgraph Infra["infra/ — las raíces"]
        STO["storage.py — .bbx"]
        SANK["sankofa.py + sync/ — git-annex"]
        IDT["identity · envelope · jwt"]
        NET["tor_service · farol_lan · ssh_keys"]
    end
    FED["federacao/ — XMPP · ActivityPub"]
    ENR["enriquecimento/ — texto · OCR · Whisper"]
    FS[("disco: ~/.baobaxia/galaxias/*<br/>git + git-annex")]

    API --> SS
    CLI --> OPS --> SS
    BOT --> OPS
    TIMERS --> SYNC
    TIMERS --> ENR
    SS --> PERM
    SS --> SYNC
    SS --> OUT
    SS --> REPO
    FED --> SS
    ENR --> REPO
    REPO --> SAB
    REPO --> IDX
    REPO --> STO
    REPO --> SANK
    SYNC --> SANK
    SYNC --> NET
    PERM --> IDT
    STO --> FS
    SANK --> FS

La regla de dependencia: de arriba hacia abajo. core/ no importa services/; infra/ no conoce FastAPI. Cuando veas un import tardío (dentro de una función), casi siempre es para mantener esa regla sin ciclos.

Despliegue de una mucua

flowchart LR
    pessoa(["navegador"]) -->|":80"| NGINX
    subgraph Maquina["máquina de la mucua (paquete baobaxia.deb)"]
        NGINX["nginx<br/>sites-available/baobaxia"]
        FRONT["baobaxia-front<br/>node build/index.js :3000"]
        APIS["baobaxia-api<br/>bbx api :8000"]
        T1["baobaxia-sincronizar.timer<br/>cada 30 min"]
        T2["baobaxia-enriquecer.timer<br/>20 min después de cada ronda"]
        T3["baobaxia-healthcheck.timer<br/>cada 2 min"]
        TOR["tor propio<br/>SOCKS 19050 + servicio .onion"]
        SSHD["sshd<br/>usuario baobaxia → git-annex-shell"]
        DADOS[("/var/lib/baobaxia/.baobaxia")]
    end
    NGINX -->|"/"| FRONT
    NGINX -->|"/api/"| APIS
    FRONT -->|"SSR: 127.0.0.1:8000"| APIS
    APIS --> DADOS
    T1 --> DADOS
    T2 --> DADOS
    TOR -->|".onion → :8000"| APIS
    outra(["otra mucua"]) -->|"git annex sync por SSH"| SSHD
    SSHD --> DADOS
    APIS -.->|"ssh / tor"| outra

Fíjate: tres procesos escriben en la misma galaxia (la API, el timer de sync, el timer de enriquecimiento). Por eso existe services/trava_galaxia.py — un flock entre procesos — y el índice abre SQLite con timeout.

Una petición, del clic al disco

sequenceDiagram
    autonumber
    actor P as Persona
    participant F as Front (SSR)
    participant A as API /api/v3
    participant D as deps.py
    participant S as SaberService
    participant R as SaberRepository
    participant I as GalaxyIndex
    P->>F: GET /es-ES/saber/ab12cd34
    F->>A: GET /saberes/ab12… (Bearer de la cookie)
    A->>D: current_mocambola(token)
    D-->>A: {uuid, role, galaxias, mucua, grupos}
    A->>S: get_saber(uuid, actor)
    S->>R: _find_saber → repo.get(uuid) en cada galaxia
    R->>I: get(uuid) — encuentra rel_path
    R-->>S: Saber (leído de saber.bbx + presencia en disco)
    S->>S: permissions.can(actor, READ, _resource_of(saber))
    S-->>A: to_public_dict(lang) + contexto
    A-->>F: JSON
    F-->>P: HTML

Prácticas

  1. Elige una ruta de api/v3/saberes.py y síguela hasta el disco, anotando cada archivo y función. Compara con el diagrama de secuencia.
  2. Busca en services/ un import dentro de una función. ¿Qué ciclo evita?
  3. En api/v3/app.py, ¿qué middlewares hay? ¿Qué vigila infra/vigia_api.py y por qué?

Para conversar

  • ¿Cuáles son las ventajas y los costos de que el disco sea la verdad, y no la base de datos?
  • Si una mucua pasa un año sin red, ¿qué ocurre cuando vuelve?

vedi questo capitolo come sapere