From aff4719910297c7cb61aacb1ea0233a8d6a5a7b2 Mon Sep 17 00:00:00 2001 From: "Maciek \"mab122\" Bator" Date: Thu, 23 Apr 2026 14:59:08 +0200 Subject: [PATCH] docs: rewrite README to match current implementation - Remove "Why:" line and air-gapped/QR section (those modules were deleted) - Remove stale commands: sync, bundle create/apply/info, qr-encode/decode - Add seed mode quick-start and setup instructions - Add gossip relay section with delta broadcast note - Add --lora flag to all command flag tables - Update architecture diagram: per-bridge port routing, gossip relay, 383 B MTU note - Update module list: bridge/gossip/seed/adapter (remove sync/adaptive/git_bundle/qr) - Update test count: 149 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 187 +++++++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 32671a1..33ec4a6 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,18 @@ Bridges [Radicle](https://radicle.xyz) (decentralized Git) over [Reticulum](https://reticulum.network) mesh networking — LoRa, packet radio, serial, I2P, and more. Enables offline-first code collaboration without internet infrastructure. -**Why:** Radicle requires publicly reachable seed nodes; Reticulum routes over any physical medium. Both use Ed25519 keys — a natural fit. - --- ## Prerequisites -Both machines need: - [Radicle](https://radicle.xyz/install) (`rad` CLI + `radicle-node`) -- [uv](https://docs.astral.sh/uv/getting-started/installation/) (Python package manager) +- [uv](https://docs.astral.sh/uv/getting-started/installation/) - Git --- ## Install -On **each machine**, clone this repo and install: - ```sh git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ # via radicle # or: git clone https://github.com/youruser/radicle-reticulum @@ -26,176 +21,194 @@ cd radicle-reticulum uv sync ``` +Optional: faster push detection (inotify-based, Linux/macOS): + +```sh +uv sync --extra watch +``` + --- -## Setup: connect two machines over mesh +## Two modes -Do this once on each machine. +### Bridge mode — peer-to-peer + +Each machine runs a bridge. They discover each other over RNS, then radicle-node syncs through the tunnel as if both nodes were on the same network. + +### Seed mode — always-on relay + +One machine runs a dedicated seed radicle-node plus a bridge. Other machines' bridges discover and register the seed automatically. The seed self-populates: it calls `rad seed ` for any repo announced by gossip peers. + +--- + +## Quick start: bridge mode ### Step 1 — Configure radicle-node to listen on localhost -Edit `~/.radicle/config.json`, find the `"node"` section, set `"listen"`: +Edit `~/.radicle/config.json`: ```json "node": { - "listen": ["127.0.0.1:8776"], - ... + "listen": ["127.0.0.1:8776"] } ``` -Then (re)start radicle-node: +Restart radicle-node: ```sh rad node start -rad node status # confirm: "listening for inbound connections on 127.0.0.1:8776" +rad node status # "listening for inbound connections on 127.0.0.1:8776" ``` ### Step 2 — Start the bridge on both machines -Run on each machine: - ```sh uv run radicle-rns bridge ``` -Within ~30 seconds the bridges discover each other via RNS announce, connect automatically, and register each other's NIDs with radicle-node. You'll see: +Within ~30 s the bridges discover each other via RNS announce, connect automatically, and register each other's NIDs with radicle-node: ``` [+] Discovered bridge: (NID: z6Mk...) [Status] Tunnels: 0, Remote bridges: 1, TX: 0, RX: 0 ``` -Once radicle-node connects through the bridge, tunnels open automatically: +Once radicle-node syncs through the bridge: ``` -Tunnel 1 opened / Incoming tunnel 1 opened +Tunnel 1 opened [Status] Tunnels: 1, Remote bridges: 1, TX: 1551, RX: 1831 ``` -Bytes in TX/RX confirm radicle gossip is flowing over the mesh. +> **LoRa:** Pass `--lora` to apply duty-cycle-safe announce delays (60 s, 300 s, 900 s) instead of the WiFi defaults. -> **LoRa note:** The bridge re-announces at t+5s, t+15s, t+30s after startup. On LoRa, -> use `--announce-retry-delays 60,300,900` to respect duty cycle limits. +### Step 3 — Use radicle normally + +```sh +# Machine A +rad init --name myproject --description "" --default-branch main +rad push # prints RID: rad:z3... + +# Machine B +rad clone rad:z3... +``` --- -## Share a repository +## Quick start: seed mode -### Machine A — init and push a repo +Run this once to initialise the seed identity: ```sh -mkdir myproject && cd myproject -git init -git commit --allow-empty -m "init" -rad init --name myproject --description "my project" --default-branch main +uv run radicle-rns setup # checks prerequisites and prints instructions +RAD_HOME=~/.radicle-seed rad auth ``` -`rad init` prints the repository ID (`rad:z3...`). Share it with Machine B. +Then start the seed (keeps running, restarts cleanly): ```sh -# make a commit and push -echo "hello" > hello.txt -git add . && git commit -m "hello" -rad push +uv run radicle-rns seed ``` -### Machine B — clone it +Other machines running `radicle-rns bridge` (or `seed`) discover the seed automatically over RNS. The seed begins tracking any repo announced by gossip peers. + +Register the seed in your local radicle-node (one-time, per machine): ```sh -rad clone rad:z3... # use the RID from Machine A -cd myproject -cat hello.txt # hello +rad node connect @127.0.0.1:8777 ``` -### Machine A — fetch updates from Machine B +The seed NID is printed by `radicle-rns seed` on startup. + +--- + +## Gossip relay + +The gossip relay watches local Radicle storage for ref changes and sends small notifications (~100–200 B) to peer relays over RNS. On receipt, the peer calls `rad sync --fetch` against the announcing node. Seed mode enables gossip automatically; for bridge mode run it separately: ```sh -# Machine B: edit, commit, push -echo "world" >> hello.txt -git add . && git commit -m "world" -rad push - -# Machine A: -rad fetch -git pull +uv run radicle-rns gossip rad:z3... # auto-detected from CWD if omitted ``` +Gossip broadcasts only the changed refs (delta mode), so a one-commit push sends ~120 B instead of the full ref list. + --- ## Commands ``` -radicle-rns bridge # TCP↔RNS bridge (main command) -radicle-rns node # lightweight peer-announce node -radicle-rns peers # discover peers on the mesh -radicle-rns ping # RTT probe to a peer -radicle-rns identity generate # create/show identity -radicle-rns sync # LXMF store-and-forward sync -radicle-rns bundle create # pack repo into a bundle file -radicle-rns bundle apply # unpack a bundle into a repo -radicle-rns bundle info # inspect bundle metadata -radicle-rns bundle qr-encode # print ASCII QR (≤2953 bytes) -radicle-rns bundle qr-decode # decode QR back to bundle +radicle-rns bridge # TCP↔RNS bridge +radicle-rns seed # dedicated seed node + bridge + gossip +radicle-rns gossip [RID ...] # standalone gossip relay +radicle-rns setup # check prerequisites, print fix instructions +radicle-rns node # lightweight peer-announce node +radicle-rns peers # discover peers on the mesh +radicle-rns ping # RTT probe to a peer +radicle-rns identity generate # create identity +radicle-rns identity info # show DID and RNS hash ``` Global flags: `-v` verbose logging, `--identity PATH` (default `~/.radicle-rns/identity`). -### Bridge flags +### bridge flags | Flag | Default | Description | |------|---------|-------------| -| `-l, --listen-port` | 8777 | TCP port radicle-node connects to | +| `-l, --listen-port` | 8777 | Base TCP port (first discovered bridge gets this) | | `--radicle-port` | 8776 | Port radicle-node listens on | -| `-c, --connect ` | — | Connect to a remote bridge by RNS hash | +| `-c, --connect ` | — | Connect to a specific remote bridge by RNS hash | | `--nid ` | auto-detect | Override local radicle NID | | `--no-auto-connect` | — | Disable auto-connect on discovery | | `--no-auto-seed` | — | Disable auto-registering remote NIDs | +| `--lora` | — | LoRa-safe defaults: announce delays 60,300,900 s | +| `--announce-retry-delays` | 5,15,30 | Startup re-announce delays (seconds, comma-separated) | ---- +### seed flags -## Air-gapped / QR transfer +| Flag | Default | Description | +|------|---------|-------------| +| `--seed-home` | `~/.radicle-seed` | RAD_HOME for the seed radicle-node | +| `--seed-port` | 8776 | TCP port for the seed radicle-node | +| `--bridge-port` | 8778 | TCP listen port for the seed bridge | +| `--poll-interval` | 30 | Seconds between gossip ref polls | +| `--lora` | — | LoRa-safe defaults | -For links too slow even for Reticulum, transfer tiny incremental bundles via QR code (max 2953 bytes): +### gossip flags -```sh -# Sender — create a small incremental bundle and encode it -uv run radicle-rns bundle create ./myrepo --incremental --basis myrepo.refs.json -uv run radicle-rns bundle qr-encode myrepo-*.radicle-bundle # prints ASCII QR to terminal - -# Receiver — photograph the QR, then decode and apply -uv run radicle-rns bundle qr-decode qr-photo.png -o received.radicle-bundle -uv run radicle-rns bundle apply received.radicle-bundle ./myrepo -``` - -QR image output (PNG) and image decoding require optional deps: -```sh -uv sync --extra qr # qrcode for PNG output -pip install pillow pyzbar # for qr-decode from image file -``` +| Flag | Default | Description | +|------|---------|-------------| +| `--nid` | auto-detect | Local radicle NID to advertise | +| `--bridge-port` | 8777 | TCP port of the local bridge | +| `--poll-interval` | 30 | Seconds between ref polls | +| `--lora` | — | LoRa-safe defaults (delays 60,300,900 s; poll 120 s) | --- ## Architecture ``` -radicle-node ──TCP:8777── RadicleBridge ──RNS Link── RadicleBridge ──TCP:8776── radicle-node - (Machine A) (Machine A) (Machine B) (Machine B) +radicle-node ─TCP─ RadicleBridge ──RNS Link── RadicleBridge ─TCP─ radicle-node + (Machine A) (Machine A) (Machine B) (Machine B) + │ │ + GossipRelay ──RNS Packet── GossipRelay ``` -- **Identity** (`identity.py`) — Ed25519 DID ↔ RNS destination; saved to `~/.radicle-rns/identity` -- **Bridge** (`bridge.py`) — TCP↔RNS tunnel, announces itself, discovers peers -- **SyncManager** (`sync.py`) — LXMF store-and-forward bundles; auto-pushes on refs announce -- **AdaptiveSyncManager** (`adaptive.py`) — selects FULL/INCREMENTAL/MINIMAL/QR by RTT + throughput -- **GitBundle** (`git_bundle.py`) — full and incremental Git bundles for delay-tolerant transfer -- **QR** (`qr.py`) — visual air-gap transfer for tiny bundles +Each discovered remote bridge gets its own OS-assigned TCP listen port, so radicle-node connections always route to the correct peer. All RNS packets are chunked to ≤383 B (LoRa encrypted MTU). + +- **`identity.py`** — Ed25519 DID ↔ RNS identity; saved to `~/.radicle-rns/identity` +- **`bridge.py`** — TCP↔RNS tunnel, per-bridge port allocation, path maintenance, reconnect +- **`gossip.py`** — ref-change notifications, delta broadcasts, auto-seed for unknown repos +- **`seed.py`** — dedicated radicle-node process lifecycle (separate RAD_HOME) +- **`adapter.py`** — RNS peer discovery and announce filtering --- ## Development ```sh -uv run pytest # 158 tests +uv run pytest # 149 tests uv run pytest -x -q # stop on first failure +mypy src/ # type check ``` --- @@ -214,4 +227,4 @@ On the same LAN, Reticulum auto-discovers peers via UDP multicast — no config codingrate = 5 ``` -See [Reticulum docs](https://reticulum.network/manual/) for the full interface list. +See the [Reticulum manual](https://reticulum.network/manual/) for the full interface list.