radicle-reticulum/README.md

231 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
---
## Prerequisites
- [Radicle](https://radicle.xyz/install) (`rad` CLI + `radicle-node`)
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- Git
---
## Install
```sh
git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ # via radicle
# or: git clone https://github.com/youruser/radicle-reticulum
cd radicle-reticulum
uv sync
```
Optional: faster push detection (inotify-based, Linux/macOS):
```sh
uv sync --extra watch
```
---
## Two modes
### 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
Edit `~/.radicle/config.json`:
```json
"node": {
"listen": ["127.0.0.1:8776"]
}
```
Restart radicle-node:
```sh
rad node start
rad node status # "listening for inbound connections on 127.0.0.1:8776"
```
### Step 2 — Start the bridge on both machines
```sh
uv run radicle-rns bridge
```
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...)
[Status] Tunnels: 0, Remote bridges: 1, TX: 0, RX: 0
```
Once radicle-node syncs through the bridge:
```
Tunnel 1 opened
[Status] Tunnels: 1, Remote bridges: 1, TX: 1551, RX: 1831
```
> **LoRa:** Pass `--lora` to apply duty-cycle-safe announce delays (60 s, 300 s, 900 s) instead of the WiFi defaults.
### 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...
```
---
## Quick start: seed mode
Run this once to initialise the seed identity:
```sh
uv run radicle-rns setup # checks prerequisites and prints instructions
RAD_HOME=~/.radicle-seed rad auth
```
Then start the seed (keeps running, restarts cleanly):
```sh
uv run radicle-rns seed
```
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 node connect <SEED_NID>@127.0.0.1:8777
```
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
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
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 <hash> # 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
| Flag | Default | Description |
|------|---------|-------------|
| `-l, --listen-port` | 8777 | Base TCP port (first discovered bridge gets this) |
| `--radicle-port` | 8776 | Port radicle-node listens on |
| `-c, --connect <hash>` | — | Connect to a specific 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 |
| `--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
| 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 |
### gossip flags
| 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─ RadicleBridge ──RNS Link── RadicleBridge ─TCP─ radicle-node
(Machine A) (Machine A) (Machine B) (Machine B)
│ │
GossipRelay ──RNS Packet── GossipRelay
```
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 # 149 tests
uv run pytest -x -q # stop on first failure
mypy src/ # type check
```
---
## 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 the [Reticulum manual](https://reticulum.network/manual/) for the full interface list.