222 lines
6.4 KiB
Markdown
222 lines
6.4 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.
|
||
|
||
---
|
||
|
||
## 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
|
||
```
|
||
|
||
### 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 (~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
|
||
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 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 |
|
||
| `--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 |
|
||
|
||
### 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 |
|
||
|
||
---
|
||
|
||
## 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. The tunnel uses `RNS.Buffer` over `RNS.Channel` for ordered, reliable delivery — Reticulum handles retransmission transparently across all interface types including LoRa.
|
||
|
||
- **`identity.py`** — Ed25519 DID ↔ RNS identity; saved to `~/.radicle-rns/identity`
|
||
- **`bridge.py`** — TCP↔RNS tunnel via `RNS.Buffer`, per-bridge port allocation, path maintenance
|
||
- **`gossip.py`** — ref-change notifications, delta broadcasts, auto-seed for unknown repos
|
||
- **`seed.py`** — dedicated radicle-node process lifecycle (separate RAD_HOME)
|
||
|
||
---
|
||
|
||
## Development
|
||
|
||
```sh
|
||
uv run pytest # 97 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.
|