# atlibs-iroh The iroh transport for [atlibs](https://knotless.dev/johnandersen777.bsky.social/atlibs) (knotless://johnandersen777.bsky.social/atlibs) hosts: it serves `com.fedfork.exec.iroh.connect`, so a module can reach a VM over an iroh ticket. It dials with `dumbpipe`: an ssh command on the guest, or a raw ALPN exchange; with `subscribe: true` it is a session (output arrives as messages, `send` writes stdin). It also provides the `iroh:` scheme fetcher behind `com.fedfork.exec.http.fetch`: HTTP/1.1 to a unix socket on the guest (the guest agent's shell MCP service) over `ssh root@guest socialweb-guest-agent relay `. `iroh:///` (a market endpoint, e.g. a bidder's offering `endpoints`) is HTTP/1.1 over `dumbpipe connect ` itself: no ssh, no key; atlibs hosts send module calls with `endpoints` there with service auth. The host's ssh key never enters wasm (`ATLIBS_IROH_KEY`, else `/iroh_ed25519`). - `src/lib.rs`: the Rust transport (`atlibs_iroh::transport`), on atlibs-host's seam - `js/index.js`: the same transport for Node hosts; `js/fake-bin/`: stand-in `dumbpipe`, `ssh`, guest relay and HTTP service for tests - `modules/iroh-user-data`: an atlibs module, cloud-init for a guest that serves ssh over iroh (dumbpipe in front of sshd); `computer.socialweb.vm.irohUserData` - `examples/iroh.mjs`: reach socialweb.computer guests and atirc from a shell - `tests/`: both atlibs CLIs through this transport - `lexicons/com/fedfork/exec/iroh/connect.json`: the connect call's schema ## Set up Clone atlibs next to this repo; the JS side and the tests import it from `../atlibs`: ```bash git clone https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh git clone https://knotless.dev/johnandersen777.bsky.social/atlibs && git -C atlibs checkout -q 7df7c6a brew install dumbpipe # for real dials and tests/loopback.test.mjs ``` ## Build The crate depends on `atlibs-host` by git (Cargo.lock pins the commit): ```bash cargo build --release -p atlibs-iroh # the transport cargo wasm # modules/* -> target/wasm32-unknown-unknown/release/*.wasm ``` ## Link it into a Rust host atlibs-host at the rev this crate takes it at (the same full rev, so cargo builds one copy): ```toml [dependencies] atlibs-host = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs", rev = "7df7c6a4eab8b72a77ac5392d9be2fcb5141b4ad" } atlibs-iroh = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh", rev = "be11ce95acfaa0cf057cc059f955af6be5263596" } ``` Register it with the host's other transports, as the `atlibs` CLI does behind its `iroh` feature: ```rust transports.push(atlibs_iroh::transport(state_dir.join("iroh_ed25519"))); ``` A host that takes `atlibs-host` from a path (atlibs itself) must point this crate's copy at it too, or cargo builds two `atlibs-host`s: ```toml [patch."https://knotless.dev/johnandersen777.bsky.social/atlibs"] atlibs-host = { path = "host" } ``` ## Use it from Node `npm install @publicdomainrelay/atlibs-iroh @publicdomainrelay/atlibs` (Node >= 25, and `dumbpipe` on `PATH`), or use a checkout. The atlibs Node CLI loads transports named in `ATLIBS_TRANSPORTS`: ```bash cd ../atlibs && cargo wasm ATLIBS_TRANSPORTS=../atlibs-iroh/js/index.js node javascript/src/server/cli.mjs run \ target/wasm32-unknown-unknown/release/echo.wasm examples/rust/echo/module.json session '{"ticket":""}' ``` ## Fetch from a guest `iroh://///`, where `ref` is base64url of the JSON `{"ticket", "hostKeys": [...]}` and `socket` is `shell` (`/run/socialweb-guest-agent/shell.sock`, the guest agent's MCP service: `POST .../shell/mcp`) or a percent-encoded absolute unix path. Nothing in the URL is a secret. The request goes as HTTP/1.1 over `ssh root@guest socialweb-guest-agent relay ` through dumbpipe, with the host keys pinned as for `connect`. Each relay carries one request at a time and stays open (HTTP keep-alive) for the next request to the same `(ref, socket)`; concurrent requests open more relays (a long-poll `read` holds one). A request whose relay fails before any response byte arrives is retried once on a fresh relay. Responses come back whole, chunked bodies decoded, at most 16 MiB. - Node: `fetchers({ env, stateDir })` gives `{ "iroh:": async ({ method, url, headers, body, signal }) => ({ status, headers, body }) }`, ready to spread into runModule's `fetchers`. Idle relays never keep the process up; `fetchers()["iroh:"].close()` ends them. - Rust: the transport's call answers `Call::schemes()` (`["iroh"]`) and `Call::fetch()`, so `atlibs_host::transport::fetchers` puts it in `run::Http::schemes` (the atlibs CLI does, for `iroh://*` in `ATLIBS_HTTP`). Its relays end with the transport. A module reaches it through `com.fedfork.exec.http.fetch` once its `http` list and the host allowlist both accept `iroh://*`. ## Run the example ```bash atlibs run bobvmbuilder.bsky.social/iroh-user-data iroh_user_data "{\"ssh_key\":\"$(cat ~/.ssh/id_ed25519.pub)\"}" | jq -r .user_data # cloud-init node examples/iroh.mjs ssh guest.json ~/.ssh/id_ed25519 uname -a ``` ## Test ```bash (cd ../atlibs && cargo wasm && cargo host) # echo.wasm and the atlibs CLI (or set ATLIBS_BIN) cargo test --release -p atlibs-iroh && cargo wasm node --test js/*.test.mjs examples/*.test.mjs tests/*.test.mjs modules/*/test.mjs ``` `tests/conformance.test.mjs` runs both CLIs with `js/fake-bin` on PATH, and http.fetch to an `iroh:///` URL on both engines; `tests/loopback.test.mjs` dials a throwaway sshd through real `dumbpipe` (it skips, saying why, without `dumbpipe`, `sshd` or `ssh-keygen`). The Rust CLI in these tests is atlibs' `cargo host` build, which links this crate. ## Publish `js/` is `@publicdomainrelay/atlibs-iroh` on npm (`npm publish --access public`; the tests and `js/fake-bin/` stay out of the tarball). It shells out to `dumbpipe` (and `ssh`), which must be on the host's `PATH`. The crate is not on crates.io: hosts link it by git. `modules/*`: `atlibs publish --dir modules`.