Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
0

Configure Feed

Select the types of activity you want to include in your feed.

erl-iroh / README.md
4.9 kB 147 lines
1# erl_iroh 2 3**Distributed Erlang over [iroh](https://iroh.computer) P2P**, with optional classic **TCP + TLS**, and a small **bot powers SDK**. 4 5Bots import the crate, get an iroh endpoint (public-key dialing, holepunch, QUIC), speak the real Erlang distribution handshake (`erl_dist`), and unlock capability-gated operations: RPC, send, introspect, cluster, eval, trace. 6 7```text 8 Bot SDK (powers: rpc | send | introspect | cluster | listen | eval | trace) 9 10 Session ── erl_dist handshake + message channel + net ticks + erpc 11 12 Transport 13 ├─ iroh public-key P2P (QUIC, TLS 1.3 built-in, relays + holepunch) 14 ├─ tcp classic BEAM / EPMD 15 └─ tls TCP + rustls (mTLS welcome, security pedants) 16``` 17 18## Why 19 20- **NAT-friendly mesh of BEAM-adjacent bots** without babysitting IPs — dial `EndpointId`s. 21- **Wire-compatible distribution** so a bot can also talk to real OTP nodes (TCP/TLS + cookie). 22- **Powers** keep each bot least-privileged: a scout does not need `TRACE` just to `RPC`. 23 24## Quick start 25 26### Nix (flake app) 27 28```bash 29# in-process two-bot iroh handshake demo 30nix run git+https://tangled.org/nandi.uk/erl-iroh 31# or from a checkout: 32nix run .#demo 33 34# long-running listener / dialer 35nix run .#bot -- --name alpha 36nix run .#peer -- --name beta --peer <endpoint-id> 37``` 38 39### Cargo 40 41```bash 42# two bots in one process (handshake over local iroh) 43cargo run --bin erl-iroh-demo 44# same as: cargo run --example two_bots 45 46# or two terminals 47cargo run --bin erl-iroh-bot -- --name alpha 48cargo run --bin erl-iroh-peer -- --name beta --peer <endpoint-id-from-alpha> 49``` 50 51### As a library 52 53```rust 54use erl_iroh::bot::Bot; 55use erl_iroh::powers::Powers; 56use erl_iroh::term; 57use erl_iroh::TlsConfig; 58 59#[tokio::main] 60async fn main() -> erl_iroh::Result<()> { 61 let bot = Bot::builder("scout") 62 .cookie("super-secret-cookie") 63 .powers(Powers::worker()) // rpc|send|introspect|cluster 64 .tls(TlsConfig::client_webpki()?) // optional classic path 65 .build() 66 .await?; 67 68 bot.listen_iroh().await?; 69 println!("dial me: {}", bot.endpoint_id()); 70 71 // P2P 72 // bot.connect_iroh("…endpoint id…").await?; 73 74 // Real BEAM via EPMD + TCP 75 // bot.connect_tcp("app@localhost", None).await?; 76 77 // Real BEAM via TLS 78 // bot.connect_tls("app@host", "host", 4370, "host", None).await?; 79 80 // Powers 81 // let procs = bot.processes("app@localhost").await?; 82 // bot.send("app@localhost", "logger", term::atom("hi").into()).await?; 83 // let v = bot.eval("app@localhost", "1 + 1").await?; 84 85 Ok(()) 86} 87``` 88 89## Powers 90 91| Power | Gates | 92|--------------|--------------------------------------------| 93| `RPC` | `rpc`, `ping` | 94| `SEND` | `send` to registered names | 95| `INTROSPECT` | `processes`, `applications`, `ets_tables`, … | 96| `CLUSTER` | `connect_iroh`, `connect_tcp`, `connect_tls` | 97| `LISTEN` | `listen_iroh`, `accept_tls` | 98| `EVAL` | `eval` (scan/parse/eval over RPC) | 99| `TRACE` | reserved for dbg/recon-style tracing | 100 101Presets: `Powers::all()`, `Powers::worker()`, `Powers::observer()`. 102 103## Security model 104 1051. **iroh path** — every connection is QUIC with TLS 1.3; peers authenticate by ed25519 `EndpointId`. The Erlang **cookie** still gates the dist handshake. 1062. **TLS path** — rustls (ring) over TCP; optional mutual TLS via `TlsConfig::server(..., Some(client_ca))` and `TlsConfig::client_with_ca(...)`. 1073. **TCP path** — plain sockets for local/dev; cookie is the only auth. Prefer TLS or iroh off-localhost. 1084. **Powers** — application-level capability checks before sensitive ops. 109 110Dev-only: `TlsConfig::client_insecure()` skips cert verification (self-signed labs). 111 112## Protocol notes 113 114- ALPN: `erl-iroh/1` 115- Handshake: OTP 23+ (`HANDSHAKE_23`, protocol v6), including `NAME_ME` dynamic names 116- RPC: `SPAWN_REQUEST``erpc:execute_call/4` (same approach as `erl_rpc`) 117- Ticks every 15s to keep links alive 118 119Real OTP nodes do not speak iroh natively. To bridge: 120 121- run a bot with `LISTEN` + `connect_tcp`/`connect_tls` as a gateway, or 122- put a TLS terminator / TCP proxy in front of `erl` distribution ports. 123 124## Layout 125 126``` 127src/ 128 lib.rs crate root, ALPN 129 bot.rs Bot + BotBuilder SDK 130 powers.rs capability bitflags 131 session.rs handshake, channel, ticks, RPC 132 stream.rs clonable futures-io duplex (iroh/tls/tcp → erl_dist) 133 transport/ 134 iroh.rs Endpoint bind / dial / accept 135 tcp.rs TCP + EPMD PORT_PLEASE2 136 tls.rs rustls client/server 137 term.rs ETF helpers 138 error.rs 139examples/ 140 bot.rs listen forever 141 peer.rs dial + send 142 two_bots.rs in-process mesh smoke test 143``` 144 145## License 146 147MIT OR Apache-2.0