# radicle-reticulum 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) - 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 cd radicle-reticulum uv sync ``` --- ## Setup: connect two machines over mesh Do this once on each machine. ### Step 1 — Configure radicle-node to listen on localhost Edit `~/.radicle/config.json`, find the `"node"` section, set `"listen"`: ```json "node": { "listen": ["127.0.0.1:8776"], ... } ``` Then (re)start radicle-node: ```sh rad node start rad node status # confirm: "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: ``` [+] Discovered bridge: (NID: z6Mk...) [Status] Tunnels: 0, Remote bridges: 1, TX: 0, RX: 0 ``` Once radicle-node connects through the bridge, tunnels open automatically: ``` Tunnel 1 opened / Incoming 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 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. --- ## Share a repository ### Machine A — init and push a repo ```sh mkdir myproject && cd myproject git init git commit --allow-empty -m "init" rad init --name myproject --description "my project" --default-branch main ``` `rad init` prints the repository ID (`rad:z3...`). Share it with Machine B. ```sh # make a commit and push echo "hello" > hello.txt git add . && git commit -m "hello" rad push ``` ### Machine B — clone it ```sh rad clone rad:z3... # use the RID from Machine A cd myproject cat hello.txt # hello ``` ### Machine A — fetch updates from Machine B ```sh # Machine B: edit, commit, push echo "world" >> hello.txt git add . && git commit -m "world" rad push # Machine A: rad fetch git pull ``` --- ## 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 ``` Global flags: `-v` verbose logging, `--identity PATH` (default `~/.radicle-rns/identity`). ### Bridge flags | Flag | Default | Description | |------|---------|-------------| | `-l, --listen-port` | 8777 | TCP port radicle-node connects to | | `--radicle-port` | 8776 | Port radicle-node listens on | | `-c, --connect ` | — | Connect to a 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 | --- ## Air-gapped / QR transfer For links too slow even for Reticulum, transfer tiny incremental bundles via QR code (max 2953 bytes): ```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 ``` --- ## Architecture ``` radicle-node ──TCP:8777── RadicleBridge ──RNS Link── RadicleBridge ──TCP:8776── radicle-node (Machine A) (Machine A) (Machine B) (Machine B) ``` - **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 --- ## Development ```sh uv run pytest # 158 tests uv run pytest -x -q # stop on first failure ``` --- ## Reticulum interfaces On the same LAN, Reticulum auto-discovers peers via UDP multicast — no config needed. For LoRa / serial / I2P, edit `~/.reticulum/config`: ```ini [[lora_interface]] type = RNodeInterface port = /dev/ttyUSB0 frequency = 868000000 bandwidth = 125000 spreadingfactor = 7 codingrate = 5 ``` See [Reticulum docs](https://reticulum.network/manual/) for the full interface list.