radicle-reticulum/README.md

218 lines
6.1 KiB
Markdown

# 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: <hash> (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 <hash> # RTT probe to a peer
radicle-rns identity generate # create/show identity
radicle-rns sync <repo> # LXMF store-and-forward sync
radicle-rns bundle create <repo> # pack repo into a bundle file
radicle-rns bundle apply <bundle> <repo> # unpack a bundle into a repo
radicle-rns bundle info <bundle> # inspect bundle metadata
radicle-rns bundle qr-encode <bundle> # print ASCII QR (≤2953 bytes)
radicle-rns bundle qr-decode <image.png> # 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 <hash>` | — | Connect to a remote bridge by RNS hash |
| `--nid <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.