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 <noreply@anthropic.com>
This commit is contained in:
Maciek "mab122" Bator 2026-04-23 14:59:08 +02:00
parent d7b124e830
commit aff4719910
1 changed files with 100 additions and 87 deletions

187
README.md
View File

@ -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. 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 ## Prerequisites
Both machines need:
- [Radicle](https://radicle.xyz/install) (`rad` CLI + `radicle-node`) - [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 - Git
--- ---
## Install ## Install
On **each machine**, clone this repo and install:
```sh ```sh
git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ # via radicle git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ # via radicle
# or: git clone https://github.com/youruser/radicle-reticulum # or: git clone https://github.com/youruser/radicle-reticulum
@ -26,176 +21,194 @@ cd radicle-reticulum
uv sync 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 <RID>` for any repo announced by gossip peers.
---
## Quick start: bridge mode
### Step 1 — Configure radicle-node to listen on localhost ### Step 1 — Configure radicle-node to listen on localhost
Edit `~/.radicle/config.json`, find the `"node"` section, set `"listen"`: Edit `~/.radicle/config.json`:
```json ```json
"node": { "node": {
"listen": ["127.0.0.1:8776"], "listen": ["127.0.0.1:8776"]
...
} }
``` ```
Then (re)start radicle-node: Restart radicle-node:
```sh ```sh
rad node start 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 ### Step 2 — Start the bridge on both machines
Run on each machine:
```sh ```sh
uv run radicle-rns bridge 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: <hash> (NID: z6Mk...) [+] Discovered bridge: <hash> (NID: z6Mk...)
[Status] Tunnels: 0, Remote bridges: 1, TX: 0, RX: 0 [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 [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, ### Step 3 — Use radicle normally
> use `--announce-retry-delays 60,300,900` to respect duty cycle limits.
```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 ```sh
mkdir myproject && cd myproject uv run radicle-rns setup # checks prerequisites and prints instructions
git init RAD_HOME=~/.radicle-seed rad auth
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. Then start the seed (keeps running, restarts cleanly):
```sh ```sh
# make a commit and push uv run radicle-rns seed
echo "hello" > hello.txt
git add . && git commit -m "hello"
rad push
``` ```
### 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 ```sh
rad clone rad:z3... # use the RID from Machine A rad node connect <SEED_NID>@127.0.0.1:8777
cd myproject
cat hello.txt # hello
``` ```
### 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 (~100200 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 ```sh
# Machine B: edit, commit, push uv run radicle-rns gossip rad:z3... # auto-detected from CWD if omitted
echo "world" >> hello.txt
git add . && git commit -m "world"
rad push
# Machine A:
rad fetch
git pull
``` ```
Gossip broadcasts only the changed refs (delta mode), so a one-commit push sends ~120 B instead of the full ref list.
--- ---
## Commands ## Commands
``` ```
radicle-rns bridge # TCP↔RNS bridge (main command) radicle-rns bridge # TCP↔RNS bridge
radicle-rns node # lightweight peer-announce node radicle-rns seed # dedicated seed node + bridge + gossip
radicle-rns peers # discover peers on the mesh radicle-rns gossip [RID ...] # standalone gossip relay
radicle-rns ping <hash> # RTT probe to a peer radicle-rns setup # check prerequisites, print fix instructions
radicle-rns identity generate # create/show identity radicle-rns node # lightweight peer-announce node
radicle-rns sync <repo> # LXMF store-and-forward sync radicle-rns peers # discover peers on the mesh
radicle-rns bundle create <repo> # pack repo into a bundle file radicle-rns ping <hash> # RTT probe to a peer
radicle-rns bundle apply <bundle> <repo> # unpack a bundle into a repo radicle-rns identity generate # create identity
radicle-rns bundle info <bundle> # inspect bundle metadata radicle-rns identity info # show DID and RNS hash
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`). Global flags: `-v` verbose logging, `--identity PATH` (default `~/.radicle-rns/identity`).
### Bridge flags ### bridge flags
| Flag | Default | Description | | 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 | | `--radicle-port` | 8776 | Port radicle-node listens on |
| `-c, --connect <hash>` | — | Connect to a remote bridge by RNS hash | | `-c, --connect <hash>` | — | Connect to a specific remote bridge by RNS hash |
| `--nid <NID>` | auto-detect | Override local radicle NID | | `--nid <NID>` | auto-detect | Override local radicle NID |
| `--no-auto-connect` | — | Disable auto-connect on discovery | | `--no-auto-connect` | — | Disable auto-connect on discovery |
| `--no-auto-seed` | — | Disable auto-registering remote NIDs | | `--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 | Flag | Default | Description |
# Sender — create a small incremental bundle and encode it |------|---------|-------------|
uv run radicle-rns bundle create ./myrepo --incremental --basis myrepo.refs.json | `--nid` | auto-detect | Local radicle NID to advertise |
uv run radicle-rns bundle qr-encode myrepo-*.radicle-bundle # prints ASCII QR to terminal | `--bridge-port` | 8777 | TCP port of the local bridge |
| `--poll-interval` | 30 | Seconds between ref polls |
# Receiver — photograph the QR, then decode and apply | `--lora` | — | LoRa-safe defaults (delays 60,300,900 s; poll 120 s) |
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 ## Architecture
``` ```
radicle-node ──TCP:8777── RadicleBridge ──RNS Link── RadicleBridge ──TCP:8776── radicle-node radicle-node ─TCP─ RadicleBridge ──RNS Link── RadicleBridge ─TCP─ radicle-node
(Machine A) (Machine A) (Machine B) (Machine B) (Machine A) (Machine A) (Machine B) (Machine B)
│ │
GossipRelay ──RNS Packet── GossipRelay
``` ```
- **Identity** (`identity.py`) — Ed25519 DID ↔ RNS destination; saved to `~/.radicle-rns/identity` 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).
- **Bridge** (`bridge.py`) — TCP↔RNS tunnel, announces itself, discovers peers
- **SyncManager** (`sync.py`) — LXMF store-and-forward bundles; auto-pushes on refs announce - **`identity.py`** — Ed25519 DID ↔ RNS identity; saved to `~/.radicle-rns/identity`
- **AdaptiveSyncManager** (`adaptive.py`) — selects FULL/INCREMENTAL/MINIMAL/QR by RTT + throughput - **`bridge.py`** — TCP↔RNS tunnel, per-bridge port allocation, path maintenance, reconnect
- **GitBundle** (`git_bundle.py`) — full and incremental Git bundles for delay-tolerant transfer - **`gossip.py`** — ref-change notifications, delta broadcasts, auto-seed for unknown repos
- **QR** (`qr.py`) — visual air-gap transfer for tiny bundles - **`seed.py`** — dedicated radicle-node process lifecycle (separate RAD_HOME)
- **`adapter.py`** — RNS peer discovery and announce filtering
--- ---
## Development ## Development
```sh ```sh
uv run pytest # 158 tests uv run pytest # 149 tests
uv run pytest -x -q # stop on first failure 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 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.