175 lines
5.0 KiB
Markdown
175 lines
5.0 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.
|
||
|
||
`rad push` and `rad fetch` work normally. The bridge is transparent: radicle-node sees ordinary TCP peers, unaware it is talking over a mesh.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
- **Radicle** — `rad` CLI + `radicle-node`: [radicle.xyz/install](https://radicle.xyz/install)
|
||
- **uv**: [docs.astral.sh/uv](https://docs.astral.sh/uv/getting-started/installation/) (manages Python and dependencies)
|
||
|
||
---
|
||
|
||
## Install
|
||
|
||
```sh
|
||
git clone rad:z4NMdcKbw2TETQ56fbQfbibFHtZqZ
|
||
cd radicle-reticulum
|
||
uv sync
|
||
```
|
||
|
||
Optional — faster push detection on Linux/macOS (inotify):
|
||
|
||
```sh
|
||
uv sync --extra watch
|
||
```
|
||
|
||
---
|
||
|
||
## Quick start
|
||
|
||
Do this on **every machine**.
|
||
|
||
### 1. Reticulum connectivity
|
||
|
||
On the same LAN, Reticulum discovers peers automatically via multicast — no configuration needed.
|
||
|
||
For other transports (internet, LoRa, packet radio, I2P) configure the appropriate Reticulum interface in `~/.reticulum/config` first. If this is your first time using Reticulum, generate the annotated example config:
|
||
|
||
```sh
|
||
rnsd --exampleconfig > ~/.reticulum/config
|
||
```
|
||
|
||
Then edit it to enable the interface for your transport. See [Reticulum interfaces](#reticulum-interfaces) below and the [Reticulum manual](https://reticulum.network/manual/) for details. Once Reticulum can reach between your machines the bridge works the same regardless of transport.
|
||
|
||
### 2. Configure radicle-node to listen on localhost
|
||
|
||
Edit `~/.radicle/config.json`:
|
||
|
||
```json
|
||
"node": {
|
||
"listen": ["127.0.0.1:8776"]
|
||
}
|
||
```
|
||
|
||
Restart:
|
||
|
||
```sh
|
||
rad node start
|
||
rad node status # should show "listening … 127.0.0.1:8776"
|
||
```
|
||
|
||
### 3. Start the bridge on both machines
|
||
|
||
```sh
|
||
uv run radicle-rns bridge
|
||
```
|
||
|
||
The bridges announce themselves over RNS and discover each other within about a minute. When a peer is found its radicle NID is registered automatically:
|
||
|
||
```
|
||
[+] Discovered bridge: <hash> (NID: z6Mk...)
|
||
```
|
||
|
||
Once radicle-node connects through the tunnel:
|
||
|
||
```
|
||
Tunnel 1 opened
|
||
[Status] Tunnels: 1, Remote bridges: 1, TX: 1551, RX: 1831
|
||
```
|
||
|
||
### 4. 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...
|
||
```
|
||
|
||
That's it. `rad push`, `rad fetch`, and `rad sync` all work as usual — they talk to the local daemon, which syncs through the bridge.
|
||
|
||
---
|
||
|
||
## Reticulum interfaces
|
||
|
||
For LoRa, serial, or 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.
|
||
|
||
Initial clones over LoRa are impractical (pack objects can be megabytes; LoRa is ~1–5 kbps). Clone over a fast link first, then sync incrementally over the mesh.
|
||
|
||
---
|
||
|
||
## Commands
|
||
|
||
```
|
||
radicle-rns bridge # TCP↔RNS bridge
|
||
radicle-rns gossip [RID ...] # ref-change relay (auto-detected from CWD)
|
||
radicle-rns setup # check prerequisites
|
||
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 for incoming radicle-node connections |
|
||
| `--radicle-port` | 8776 | Port radicle-node listens on |
|
||
| `-c, --connect <hash>` | — | Connect to a specific bridge by RNS hash |
|
||
| `--nid <NID>` | auto-detect | Local radicle NID to announce |
|
||
| `--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) |
|
||
|
||
### 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 |
|
||
|
||
---
|
||
|
||
## How it works
|
||
|
||
```
|
||
radicle-node ─TCP─ RadicleBridge ──RNS Link── RadicleBridge ─TCP─ radicle-node
|
||
(Machine A) (Machine A) (Machine B) (Machine B)
|
||
│ │
|
||
GossipRelay ──RNS Packet── GossipRelay
|
||
```
|
||
|
||
The bridge tunnels radicle-node's TCP stream over an `RNS.Buffer` — an ordered, reliable channel that works across all Reticulum interfaces including LoRa. Reticulum handles peer discovery, routing, encryption, and retransmission transparently.
|
||
|
||
The gossip relay is a lightweight side-channel (~300 bytes per event) that wakes peers when refs change, useful when the bridge TCP session is not yet live.
|
||
|
||
---
|
||
|
||
## Development
|
||
|
||
```sh
|
||
uv run pytest # 97 tests
|
||
uv run pytest -x -q # stop on first failure
|
||
mypy src/
|
||
```
|