# erl_iroh **Distributed Erlang over [iroh](https://iroh.computer) P2P**, with optional classic **TCP + TLS**, and a small **bot powers SDK**. Bots 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. ```text Bot SDK (powers: rpc | send | introspect | cluster | listen | eval | trace) │ Session ── erl_dist handshake + message channel + net ticks + erpc │ Transport ├─ iroh public-key P2P (QUIC, TLS 1.3 built-in, relays + holepunch) ├─ tcp classic BEAM / EPMD └─ tls TCP + rustls (mTLS welcome, security pedants) ``` ## Why - **NAT-friendly mesh of BEAM-adjacent bots** without babysitting IPs — dial `EndpointId`s. - **Wire-compatible distribution** so a bot can also talk to real OTP nodes (TCP/TLS + cookie). - **Powers** keep each bot least-privileged: a scout does not need `TRACE` just to `RPC`. ## Quick start ### Nix (flake app) ```bash # in-process two-bot iroh handshake demo nix run git+https://tangled.org/nandi.uk/erl-iroh # or from a checkout: nix run .#demo # long-running listener / dialer nix run .#bot -- --name alpha nix run .#peer -- --name beta --peer ``` ### Cargo ```bash # two bots in one process (handshake over local iroh) cargo run --bin erl-iroh-demo # same as: cargo run --example two_bots # or two terminals cargo run --bin erl-iroh-bot -- --name alpha cargo run --bin erl-iroh-peer -- --name beta --peer ``` ### As a library ```rust use erl_iroh::bot::Bot; use erl_iroh::powers::Powers; use erl_iroh::term; use erl_iroh::TlsConfig; #[tokio::main] async fn main() -> erl_iroh::Result<()> { let bot = Bot::builder("scout") .cookie("super-secret-cookie") .powers(Powers::worker()) // rpc|send|introspect|cluster .tls(TlsConfig::client_webpki()?) // optional classic path .build() .await?; bot.listen_iroh().await?; println!("dial me: {}", bot.endpoint_id()); // P2P // bot.connect_iroh("…endpoint id…").await?; // Real BEAM via EPMD + TCP // bot.connect_tcp("app@localhost", None).await?; // Real BEAM via TLS // bot.connect_tls("app@host", "host", 4370, "host", None).await?; // Powers // let procs = bot.processes("app@localhost").await?; // bot.send("app@localhost", "logger", term::atom("hi").into()).await?; // let v = bot.eval("app@localhost", "1 + 1").await?; Ok(()) } ``` ## Powers | Power | Gates | |--------------|--------------------------------------------| | `RPC` | `rpc`, `ping` | | `SEND` | `send` to registered names | | `INTROSPECT` | `processes`, `applications`, `ets_tables`, … | | `CLUSTER` | `connect_iroh`, `connect_tcp`, `connect_tls` | | `LISTEN` | `listen_iroh`, `accept_tls` | | `EVAL` | `eval` (scan/parse/eval over RPC) | | `TRACE` | reserved for dbg/recon-style tracing | Presets: `Powers::all()`, `Powers::worker()`, `Powers::observer()`. ## Security model 1. **iroh path** — every connection is QUIC with TLS 1.3; peers authenticate by ed25519 `EndpointId`. The Erlang **cookie** still gates the dist handshake. 2. **TLS path** — rustls (ring) over TCP; optional mutual TLS via `TlsConfig::server(..., Some(client_ca))` and `TlsConfig::client_with_ca(...)`. 3. **TCP path** — plain sockets for local/dev; cookie is the only auth. Prefer TLS or iroh off-localhost. 4. **Powers** — application-level capability checks before sensitive ops. Dev-only: `TlsConfig::client_insecure()` skips cert verification (self-signed labs). ## Protocol notes - ALPN: `erl-iroh/1` - Handshake: OTP 23+ (`HANDSHAKE_23`, protocol v6), including `NAME_ME` dynamic names - RPC: `SPAWN_REQUEST` → `erpc:execute_call/4` (same approach as `erl_rpc`) - Ticks every 15s to keep links alive Real OTP nodes do not speak iroh natively. To bridge: - run a bot with `LISTEN` + `connect_tcp`/`connect_tls` as a gateway, or - put a TLS terminator / TCP proxy in front of `erl` distribution ports. ## Layout ``` src/ lib.rs crate root, ALPN bot.rs Bot + BotBuilder SDK powers.rs capability bitflags session.rs handshake, channel, ticks, RPC stream.rs clonable futures-io duplex (iroh/tls/tcp → erl_dist) transport/ iroh.rs Endpoint bind / dial / accept tcp.rs TCP + EPMD PORT_PLEASE2 tls.rs rustls client/server term.rs ETF helpers error.rs examples/ bot.rs listen forever peer.rs dial + send two_bots.rs in-process mesh smoke test ``` ## License MIT OR Apache-2.0