Part II — Trunk and roots

04 · The roots: underground

Baobabs in the same region touch through their roots. A mucua doesn't need the internet to find another: it is enough that some path exists — a cable, an antenna, a Tor network, or someone walking with a USB stick.

Roots that touch: SSH, Tor, local network and sneakernet

Files to read

  1. infra/sankofa.py — the facade; infra/sync/__init__.py picks the backend.
  2. infra/sync/base.py and infra/sync/gitannex.py — the actual git/annex commands (_exec kills the whole process tree on timeout).
  3. services/links.py — each galaxy's remotes, the retry over Tor, the rediscovery of the .onion, authorization by SSH key.
  4. services/sincronizacao.py — sincronizar_galaxia, the heart of sync.
  5. infra/merge_bbx.py — the semantic merge driver for .bbx files.
  6. services/pedidos.py — store-and-forward (requests and routes).
  7. infra/farol_lan.py, infra/tor_service.py, infra/ssh_keys.py.
  8. services/propagacao.py — the local identity mirrored into every galaxy.

Git for text, git-annex for the rest

  • saber.bbx and small texts go to git (full history, merge).
  • Photos, videos, PDFs, .vtt subtitles, OCR texts go to the annex: git keeps only a link with the key (SHA256E-s…--hash.ext). Each mucua decides what it wants to have (wanted) and can request the rest later. The default (DEFAULT_WANTED) always brings event, mocambola and territory attachments, and article and collection ones up to 20 MB — the images in the text and the covers; a galaxy with the old default is updated by itself on sync (garantir_wanted).
  • The rule lives in GITATTRIBUTES (infra/sync/base.py): *.ocr.txt annex.largefiles=anything wins over *.txt annex.largefiles=nothing.
  • The same rules also go in the LOCAL .git/info/attributes (it beats the versioned one and is rewritten on every sync), with a limit: text only goes to git up to 2 MB (LIMITE_TEXTO_GIT) — a 300 MB .json goes to the annex. Without git-annex on the PATH, the heavy file stays out of the commit instead of going raw into git. What already went in wrong: bbx annex-reparar (or Care › Heavy files in git), in services/annex_reparo.py.

One sync cycle

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: galaxy X
    S->>S: TravaGalaxia (flock) — already running? leave
    S->>L: garantir_annex_uuids (remote ↔ mucua uuid)
    alt some link is .onion
        S->>S: _garantir_tor_local (starts our own Tor)
    end
    loop each enabled link
        S->>G: git annex sync --remote=link (with retry if Tor)
        G->>M: conflict in *.bbx → field-by-field merge
        alt failed and it is .onion
            S->>L: redescobrir_onion (asks for the new address)
            S->>G: tries again
        end
    end
    S->>G: resolver_conflitos (aborts a pending merge and reports)
    S->>R: reindex(incremental=True)
    opt data=true
        S->>R: refrescar_presenca()
    end
    S->>S: atualizar_estado (clears caches)

The merge that doesn't break

A textual conflict in a saber.bbx would leave invalid YAML, the saber would vanish from the index and the galaxy would stop synchronizing. The merge_bbx.py driver merges field by field: dictionaries by key, attachment lists by filename, scalars by the most recent updated, and the markdown body with git merge-file. It is symmetric: run on mucua A or on B, it produces the same bytes — that is what makes the network converge.

garantir_driver_merge writes the rule into .git/info/attributes (local, not versioned) before every sync: it heals itself when the Python path changes.

Requests: content travels from hand to hand

stateDiagram-v2
    [*] --> ativo: criar_pedido (fetch | send)
    ativo --> ativo: cycle without success (TTL unchanged)
    ativo --> ativo: got the copy and I am NOT the target → copy --to next hop
    ativo --> [*]: I am the target and received it → request REMOVED
    ativo --> expirado: ttl ≤ 0 (hops) or too old
    ativo --> cancelado: cancelar_pedido
    expirado --> [*]
    cancelado --> [*]
  • The request is a saber (nature: pedido) written in the branch of the mucua that asked — it travels through git like any other.
  • ttl counts hops, not time. The maximum age (_velho_demais) only applies to a request that never moved.
  • The cycle never deletes a copy on its own. Auto-drop once caused data loss; freeing space is an explicit action (POST …/dropar).
  • Routes (nature: rota) are ordered lists of mucuas — the path of whoever carries the USB stick. _proximo_salto decides whom to push to.

Network paths

PathWhere it isThe detail that matters
SSH over the internetthe mucua's uri_internetformat user@host[:port]; the remote path is always ~user/.baobaxia/galaxias/<folder> (util.montar_url_ssh_galaxia)
Toruri_tor + tor_service.pyown Tor on SOCKS port 19050; ProxyCommand with nc; diagnostics in infra/sync/tor_diagnostico.py
Local networkfarol_lan.pyUDP multicast announcement; the address comes from the datagram's source, not from what the packet says
On footrequests + routesstore-and-forward

Trust between mucuas is the SSH key: ssh_keys.py keeps the authorized_keys of the baobaxia user, whose shell is git-annex-shell — only git/annex commands get through.

Mirrored identity

The local mucua, its territory and its accounts are replicated with the same UUIDs in every galaxy of this instance (propagacao.sincronizar_identidade). That is how the same person exists in several collections.

Practice

  1. Run testes/test_merge_bbx.py and read test_as_duas_mucuas_convergem_para_os_mesmos_bytes. Build a conflict by hand (two edits of the same title) and see who wins.
  2. In test_pedidos_logica_pratica.py, find the case "a satisfied request is REMOVED". Why remove it, rather than mark it as satisfied?
  3. Draw a real rota for your region: which mucuas, in what order, and who carries the USB stick.
  4. In test_farol_rede_local.py, find the test showing that the address comes from the datagram's SOURCE, not from the packet content. Which attack does that prevent?

To talk about

  • What does "being in sync" mean in a network where never everyone is online at the same time?
  • Who deserves to be a hop on a route? What trust does that require?

ver este capítulo como saber