Each site's iroh now runs in its own process: its endpoint, run by the site process's main runtime with the node key the front server sends it, its git sync with peers, its rooms served to peers directly, and its collaborators. What the endpoint needs of the hub it asks the admin thread, which alone holds the hub; the front server relays those asks and runs no iroh of its own. The separate lichen-iroh crate is gone: it was folded into lichen-site, its transport moved to lichen-server for SITE-FRONT-BOUNDARY, and FULL-SITE-PROCESS brought it back into lichen-site.
AST-verified changes
crates/lichen-iroh/src/**folded into lichen-site assrc/iroh.rsandsrc/iroh/(24 files; PER-SITE-PROCESS P2c-1, 20e9fa4c). Checked by notify-9a29b94c: "ast-checker rn6 at 8eef92b4..20e9fa4c ok (3538 items, 24 renamed, 217 carried, 0 visibility changes, 252 module declarations)"; adversary rerun "20e9fa4c equal=true 24-renamed 217-carried 0-vis" (lichen-adversary.md 11324). Allowed:lichen_irohrewritten tolichen_site::iroh, andcrate::tocrate::iroh::inside the folded files.- The transport from lichen-site to lichen-server, verbatim (SITE-FRONT-BOUNDARY 10b1, 43e47f1a):
AclGatewith itsProtocolHandlerimpl andIrohSyncServicewith its impl from the site'siroh.rsto a newlichen-server/src/iroh.rs;iroh/{endpoint,git_transport,runtime,site_network}.rstolichen-server/src/iroh/. Checked by notify-7dab54a2 on a rebuilt pure move (b754f831 → 71639fc0, which differs from the landed 43e47f1a by one#[cfg]line's placement): "AST-equal (4 moved, 6 files renamed carrying 67, 5140 stayed, plants caught)"; adversary rerun notify-ee68ddd8 "my checker equal; 4 items, 6 files, 67 carried". 10b1's changes follow in later commits. ensure_iroh_identity,site_iroh_keyandown_iroh_handlefrom the front server'sfeatures.rstoiroh_keys.rs(P2c-2a seam 5, e6415c2a). Run id not recorded: "move e6415c2a ok (1458 items, 3 moved, 0 visibility; plant on own_iroh_handle refused)" (lichen-builder.md 2347); adversary "equal=true 1458 items 3 moved 0 visibility". The three functions were written after 09-23, so thefeatures.rshalf is not in this diff (#31).- The path rewrites of SECURITY-FOLDER-AND-PRINCIPALS units 1 and 2 in this PR's files (
crate::site_area→crate::admin_area,crate::access→crate::security::access, and the othersecurity::paths), which those checks cover (unit 1 notify-eebdd32e, unit 2 adversary notify-020eb199, both equal, errors 0), with the rustfmt rewraps after them (d6c25912, bdaa795b), proven bycargo fmt. - Not AST-checked: FULL-SITE-PROCESS 6f3b moved the transport back from lichen-server into lichen-site (
iroh.rs→iroh/service.rs,iroh/git_transport.rs→iroh/git_sync.rs,iroh/runtime.rsrewritten as the site's runtime) with changes in the same reviewed diffs; no move record covers it.
Roughly 450 of this PR's 4,688 changed lines are code these moves carried verbatim. The rest is new: the 1,650-line git_transport.rs and 321-line runtime.rs of the old crate are deleted, and the site's iroh/git_sync.rs (627 lines) and iroh/runtime.rs (479) were rewritten for the hub and the admin thread, so they read as new code here.
On this forge the 5 renamed files here show as a whole deletion and a whole addition, so its diff reads 6,956 changed lines; with rename detection (git diff -M full/11-ssg full/12-iroh) it is 4,688.
At 09-23 each site's runtime ran its own iroh endpoint from the lichen-iroh crate, with its own copy of the site's git. Over the range:
- The crate is folded, split, and brought back (PER-SITE-PROCESS P2c-1; SITE-FRONT-BOUNDARY unit 10b1; FULL-SITE-PROCESS 6f): SITE-FRONT-BOUNDARY gave the transport to the front server, with rooms bridged to the site's process; FULL-SITE-PROCESS gave it to each site's process. The front server's
iroh.rsandiroh/git_transport.rsbecame the site'siroh/service.rsandiroh/git_sync.rs; itsiroh/runtime.rs,iroh/bridge.rs,iroh/endpoint.rsandiroh/site_network.rsare deleted. All of them existed only inside this range, so this diff shows only the site's files. - The runtime (
iroh/runtime.rs, 6f3b): started by the site process's main runtime where the server runs iroh, and ended with the process: an idle site's process stops, and its node with it. The endpoint uses iroh's N0 preset; its UDP, netlink route socket and name lookups are the site process's own, by its filter's iroh variant, and its TCP goes through the front server's egress. - The node key (
site_process/node_key.rs, 6f2;lichen-server/src/iroh_keys.rs): the owner's, minted into the user record by the front server, which sends it once, right after the launch. The admin thread moves it to the main runtime's side and keeps no copy; the main runtime checks its public half against the node id the launch carried. Its bytes are zeroed on drop and never printed. - Git sync with a peer (
iroh/git_sync.rs): a ref exchange, then packs over iroh-blobs. It runs when the hub'smainmoves, every 30 s with one peer in turn, and at start; each answer of a peer's is bounded to 60 s, and a whole exchange to 10 min. The main runtime runs no git: what it needs of the hub it asks the admin thread (site_process/iroh_hub.rs, 6f3a), through the front server, which relays those asks as an iroh-only identity admitted to nothing else. Only the hub'smainis packed for a peer (git/peers.rs), and a peer's pack is taken only from a peer the hub'smainlists, under every rule a fetch meets (git::inbound::take_a_peers_pack, #23). - Peers and collaborators come from the hub's
main([iroh].peers,collaborators.toml), read by the admin thread; a change restarts the endpoint. Collaborators' git remotes are fetched into the hub by the admin thread at start and whencollaborators.tomlchanges. - Rooms are served from the site's own process (6f3b): the endpoint takes a peer's room stream, checks the room name against its grammar (
peer_room_name, wasbridged_room_name), and serves it from the site's rooms. The bounds the front server's bridge held are the site's now (protocol.rs): 16 streams per peer, 64 per site, 30 s for a stream's header. The bridge and the site's room-upgrade route are gone. - The site is told its peers' state, for the editor's peers panel (
peers-changed,sync-sent).
Files
Site (crates/lichen-site/src/):
iroh.rs— the module list: runtime, service, git sync, rooms' protocol, collaborators, settings. Read this first.iroh/service.rs(renamed fromlichen-iroh/src/lib.rs) — the endpoint: the ACL gate and the three protocols on it.iroh/runtime.rs— new: the site's runtime: peers, sync triggers, collaborators' remotes, rooms.iroh/git_sync.rs— new: git sync between the hub and a peer, through the admin thread.iroh/protocol.rs,iroh/stream.rs(renamed fromlichen-iroh) — the room exchange, now with the per-peer and per-site bounds.iroh/collaborators.rs,iroh/settings.rs(renamed fromlichen-iroh) — parse text read from the hub.git/peers.rs— new: the hub as its iroh peers see it.site_process/iroh_hub.rs— new: the admin thread's hub routes for the endpoint.site_process/node_key.rs— new: the node key in the process.ipc/iroh.rs— the site's iroh peers and collaborators, as the front server changes them (the room upgrade and room feed are gone).
Front server (crates/lichen-server/src/):
iroh_keys.rs— new: the node key minted into the owner's user record, and the site's identity at setup.
The old crate: crates/lichen-iroh/Cargo.toml, src/endpoint.rs, src/git_transport.rs, src/runtime.rs — deleted.
Moved in: git/peers.rs, site_process/iroh_hub.rs and site_process/node_key.rs (new this round, placed with their subject, iroh).
Lives elsewhere
- The hub, and
git/hub_files.rs, which the peers are read with: #21. Taking a peer's pack and fetching collaborators' remotes (git/inbound.rs): #23. - The rooms themselves and their sync with files: #29.
- The site process's start, which starts the endpoint on the main runtime and the admin thread beside it: #38. The control channel's
NodeKeyframe and the launch: #19. - The iroh routes' permission groups (
lichen-site/src/acts.rs,access.rs'sIROH): #19, #18. lichen-server/Cargo.toml: #31.lichen-site/Cargo.toml: #37.- Tests (
iroh/tests/*.rs, lichen-server'stests/iroh/, e2e) are in the base commit.
Category: iroh: lichen-iroh folded in, each site process runs its own iroh
Size: 17 files, +2,410/−2,278
Rounds: round 0 at stack ae42e3c0; round 1 at 6b650b2a (SECURITY-FOLDER-AND-PRINCIPALS): path rewrites and rustfmt in iroh/git_transport.rs and iroh/runtime.rs; round 2 at b66397c0 (FULL-SITE-PROCESS 6f): the site's iroh moved from the front server into its own process: the endpoint and git sync are lichen-site's again (iroh/service.rs, iroh/git_sync.rs, iroh/runtime.rs; not AST-checked), the node key is sent at launch, the hub asks go to the admin thread (site_process/iroh_hub.rs, git/peers.rs), rooms are served without the bridge, and the front server's iroh files are deleted. Read this round in its interdiff: https://tangled.org/notplants-bot.bsky.social/lichen.page.october/pulls/28/round/2/interdiff
Plans and ledger: FULL-SITE-PROCESS-2026-10-25.md (parts 4, 5, 7; units 6f1–6f3b); SITE-FRONT-BOUNDARY-2026-10-17.md (unit 1 item 2, unit 10: 10a, 10b1, 10b2, 10c; 11a1, 11e11), PER-SITE-PROCESS-2026-10-09.md (P2c-1 fold, P2c-2a seam 5, P3-5), OPERATOR-REVIEW-FIXES-2026-10-07.md U4 (N3: the identity made locally); lichen-builder.md 2326, 2347, 3477.
Inline #[cfg(test)] modules: lichen-site/src/iroh/protocol.rs (tests, wire_property_tests, generation_tests). Seven more files point to a test file with #[path].
Bucket moves: none against round 1. As in round 1, lichen-site/src/ipc/iroh.rs is here rather than with the site process. The transport kept this PR through both of its moves.
Round 1: #9 (iroh, sync and rooms) covered these files from 0b92cb61; the deleted lichen-iroh files and the files new this round are new to the set.