Parte II — Tronco y raíces

04 · Las raíces: bajo tierra

Los baobabs de una misma región se tocan por las raíces. Una mucua no necesita internet para encontrar a otra: basta con que exista un camino — un cable, una antena, una red Tor, o alguien caminando con un pendrive.

Raíces que se tocan: SSH, Tor, red local y sneakernet

Archivos para leer

  1. infra/sankofa.py — la fachada; infra/sync/__init__.py elige el backend.
  2. infra/sync/base.py e infra/sync/gitannex.py — los comandos git/annex de verdad (_exec mata el árbol de procesos en el timeout).
  3. services/links.py — los remotes de cada galaxia, el reintento por Tor, el redescubrimiento del .onion, la autorización por clave SSH.
  4. services/sincronizacao.py — sincronizar_galaxia, el corazón del sync.
  5. infra/merge_bbx.py — el driver de merge semántico de los .bbx.
  6. services/pedidos.py — el store-and-forward (pedidos y rutas).
  7. infra/farol_lan.py, infra/tor_service.py, infra/ssh_keys.py.
  8. services/propagacao.py — la identidad local reflejada en toda galaxia.

Git para el texto, git-annex para el resto

  • saber.bbx y los textos pequeños van a git (historial completo, merge).
  • Fotos, videos, PDF, subtítulos .vtt, textos de OCR van al annex: en git queda solo un enlace con la clave (SHA256E-s…--hash.ext). Cada mucua decide qué quiere tener (wanted) y puede pedir el resto después. El predeterminado (DEFAULT_WANTED) trae siempre los anexos de evento, mocambola y territorio, y los de artículo y colección hasta 20 MB — las imágenes del texto y las portadas; una galaxia con el predeterminado antiguo se actualiza sola al sincronizar (garantir_wanted).
  • La regla está en GITATTRIBUTES (infra/sync/base.py): *.ocr.txt annex.largefiles=anything gana sobre *.txt annex.largefiles=nothing.
  • Las mismas reglas van también en el .git/info/attributes LOCAL (gana al versionado y se rehace en cada sync), con un límite: el texto solo va a git hasta 2 MB (LIMITE_TEXTO_GIT) — un .json de 300 MB va al annex. Sin git-annex en el PATH, el archivo pesado queda fuera del commit en lugar de entrar crudo en git. Lo que ya entró mal: bbx annex-reparar (o Cuidar › Archivos pesados en git), en services/annex_reparo.py.

Un ciclo de sincronización

sequenceDiagram
    autonumber
    participant T as timer / POST /sync
    participant S as sincronizar_galaxia
    participant L as links
    participant G as Sankofa (git-annex)
    participant M as merge_bbx (driver)
    participant R as SaberRepository
    T->>S: galaxia X
    S->>S: TravaGalaxia (flock) — ¿ya en curso? sale
    S->>L: garantir_annex_uuids (remote ↔ uuid de la mucua)
    alt algún enlace es .onion
        S->>S: _garantir_tor_local (levanta el Tor propio)
    end
    loop cada enlace habilitado
        S->>G: git annex sync --remote=link (con reintento si es Tor)
        G->>M: conflicto en *.bbx → merge campo a campo
        alt falló y es .onion
            S->>L: redescobrir_onion (pregunta la dirección nueva)
            S->>G: lo intenta de nuevo
        end
    end
    S->>G: resolver_conflitos (aborta un merge pendiente y lo informa)
    S->>R: reindex(incremental=True)
    opt data=true
        S->>R: refrescar_presenca()
    end
    S->>S: atualizar_estado (limpia cachés)

El merge que no rompe

Un conflicto textual en un saber.bbx dejaría el YAML inválido, el saber desaparecería del índice y la galaxia dejaría de sincronizarse. El driver merge_bbx.py mezcla campo a campo: diccionarios por clave, listas de anexos por filename, escalares por el updated más reciente, y el cuerpo markdown con git merge-file. Es simétrico: ejecutado en la mucua A o en la B, genera los mismos bytes — eso es lo que hace converger a la red.

garantir_driver_merge graba la regla en .git/info/attributes (local, no versionado) antes de cada sync: se cura sola cuando cambia la ruta de Python.

Pedidos: el contenido camina de mano en mano

stateDiagram-v2
    [*] --> ativo: criar_pedido (buscar | enviar)
    ativo --> ativo: ciclo sin lograrlo (el TTL no cambia)
    ativo --> ativo: obtuvo la copia y NO soy el destino → copy --to próximo salto
    ativo --> [*]: soy el destino y la recibí → pedido ELIMINADO
    ativo --> expirado: ttl ≤ 0 (saltos) o demasiado viejo
    ativo --> cancelado: cancelar_pedido
    expirado --> [*]
    cancelado --> [*]
  • El pedido es un saber (nature: pedido) grabado en la rama de la mucua que lo hizo — viaja por git como cualquier otro.
  • ttl cuenta saltos, no tiempo. La edad máxima (_velho_demais) solo vale para un pedido que nunca se movió.
  • El ciclo nunca borra una copia por su cuenta. El auto-drop ya causó pérdida de datos; liberar espacio es una acción explícita (POST …/dropar).
  • Las rutas (nature: rota) son listas ordenadas de mucuas — el camino de quien lleva el pendrive. _proximo_salto decide a quién empujar.

Caminos de red

CaminoDónde estáEl detalle que importa
SSH por interneturi_internet de la mucuaformato usuario@host[:puerto]; la ruta remota es siempre ~usuario/.baobaxia/galaxias/<carpeta> (util.montar_url_ssh_galaxia)
Toruri_tor + tor_service.pyTor propio en el puerto SOCKS 19050; ProxyCommand con nc; diagnóstico en infra/sync/tor_diagnostico.py
Red localfarol_lan.pyanuncio UDP multicast; la dirección viene del origen del datagrama, no de lo que dice el paquete
A piepedidos + rutasstore-and-forward

La confianza entre mucuas es la clave SSH: ssh_keys.py mantiene el authorized_keys del usuario baobaxia, cuyo shell es git-annex-shell — solo pasan comandos git/annex.

Identidad reflejada

La mucua local, su territorio y sus cuentas se replican con los mismos UUID en toda galaxia de esta instancia (propagacao.sincronizar_identidade). Así la misma persona existe en varios acervos.

Prácticas

  1. Corre testes/test_merge_bbx.py y lee test_as_duas_mucuas_convergem_para_os_mesmos_bytes. Arma a mano un conflicto (dos ediciones del mismo título) y mira quién gana.
  2. En test_pedidos_logica_pratica.py, busca el caso "pedido satisfecho es ELIMINADO". ¿Por qué eliminarlo, y no marcarlo como satisfecho?
  3. Dibuja, para tu región, una rota real: qué mucuas, en qué orden, y quién lleva el pendrive.
  4. En test_farol_rede_local.py, busca el test de que la dirección viene del ORIGEN del datagrama, y no del contenido del paquete. ¿Qué ataque evita?

Para conversar

  • ¿Qué es "estar sincronizado" en una red donde nunca todo el mundo está en línea al mismo tiempo?
  • ¿Quién merece ser un salto en una ruta? ¿Qué confianza pide eso?

ver este capítulo como saber