Python adapter bridging Radicle (decentralized Git) over Reticulum mesh networking (LoRa, packet radio, serial, I2P). Enables offline-first code collaboration without internet infrastructure or public seed nodes. - Identity mapping: Radicle Ed25519 DIDs ↔ RNS destinations, with persistence - TCP↔RNS bridge: tunnels radicle-node traffic over mesh, auto-discovers peers - LXMF sync: store-and-forward bundle delivery for offline peers, auto-push - Adaptive strategy: selects FULL/INCREMENTAL/MINIMAL/QR by RTT + throughput - Git bundles: full and incremental, delay-tolerant transfer - QR air-gap: encode/decode bundles as QR codes (≤2953 bytes) - CLI: radicle-rns bridge/node/sync/bundle/ping/peers/identity commands - 158 tests |
||
|---|---|---|
| src/radicle_reticulum | ||
| tests | ||
| .gitignore | ||
| README.md | ||
| pyproject.toml | ||
README.md
radicle-reticulum
Bridges Radicle (decentralized Git) over Reticulum 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.
Install
pip install uv # once
uv sync # install deps into .venv
QR encoding (optional):
uv sync --extra qr # ASCII QR output
pip install pillow pyzbar # image encode/decode
Quickstart: bridge two machines over mesh
Machine A (or any node on the mesh):
uv run radicle-rns bridge
# prints: RNS address: <HASH>
Machine B:
uv run radicle-rns bridge --connect <HASH-FROM-A>
Both machines — configure radicle-node to use the bridge as a seed:
# ~/.radicle/node/config.toml
[[seeds]]
address = "127.0.0.1:8777"
Then start radicle-node and use it normally:
rad node start
rad clone rad:z3xyz...
rad push / rad pull
The bridge auto-detects your local NID via rad self and auto-registers discovered remote NIDs with radicle-node (--no-auto-seed to disable).
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 a repo into a bundle
radicle-rns bundle apply <bundle> <repo> # unpack a bundle
radicle-rns bundle info <bundle> # inspect a bundle
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, --identity PATH (default ~/.radicle-rns/identity).
Air-gapped / QR transfer
For truly offline transfers (tiny incremental bundles ≤ 2953 bytes):
# Sender
radicle-rns bundle create myrepo --incremental --basis prev.refs.json
radicle-rns bundle qr-encode myrepo-*.radicle-bundle
# Receiver (photograph the QR, then:)
radicle-rns bundle qr-decode qr-photo.png -o received.radicle-bundle
radicle-rns bundle apply received.radicle-bundle ./myrepo
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> |
— | Manually connect to a remote bridge |
--nid <NID> |
auto | Override local radicle NID |
--no-auto-connect |
— | Disable auto-connect on discovery |
--no-auto-seed |
— | Disable auto-registering remote NIDs |
Architecture
radicle-node ──TCP:8777── RadicleBridge ──RNS Link── RadicleBridge ──TCP:8776── radicle-node
│ │
RNS announce RNS announce
(auto-discovery) (auto-discovery)
- Identity (
identity.py) — Ed25519 DID ↔ RNS destination mapping; persisted to~/.radicle-rns/identity - Adapter (
adapter.py) — peer discovery via RNS announces - Link (
link.py) — buffered RNS Link with state machine - SyncManager (
sync.py) — LXMF store-and-forward bundles; auto-push on refs announce - AdaptiveSyncManager (
adaptive.py) — picks FULL/INCREMENTAL/MINIMAL/QR by RTT + throughput - GitBundle (
git_bundle.py) — full and incremental Git bundles - QR (
qr.py) — visual air-gap transfer for tiny bundles
Development
uv run pytest # 158 tests
uv run pytest -x -q # stop on first failure
Reticulum interfaces
Reticulum auto-discovers local peers via UDP multicast. For LoRa / serial / I2P, configure ~/.reticulum/config:
[[lora_interface]]
type = RNodeInterface
port = /dev/ttyUSB0
frequency = 868000000
bandwidth = 125000
spreadingfactor = 7
codingrate = 5
See Reticulum docs for the full interface list.