2026-05-15 15:08:20 +02:00
|
|
|
# Architecture
|
|
|
|
|
|
|
|
|
|
`geth` is a single-binary local-first mesh runtime. One executable provides both
|
|
|
|
|
daemon mode and control mode. The daemon owns local identity, metadata storage,
|
2026-05-16 03:17:45 +02:00
|
|
|
the shared Iroh endpoint, resource registry, module routing, and local control
|
|
|
|
|
socket. Control commands connect to the Unix socket and send typed JSONL
|
2026-05-15 15:08:20 +02:00
|
|
|
requests.
|
|
|
|
|
|
2026-05-16 00:17:08 +02:00
|
|
|
Service management is also exposed through the single binary. `geth daemon
|
|
|
|
|
service ...` installs and controls a user-level service definition for the local
|
|
|
|
|
daemon. The initial backends are systemd user units on Linux, launchd user agents
|
|
|
|
|
on macOS, and per-user scheduled tasks on Windows. Geth does not install itself
|
|
|
|
|
as a privileged system service.
|
|
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
## Iroh-Only Remote Communication
|
|
|
|
|
|
|
|
|
|
Remote geth node-to-node communication is Iroh-only. The daemon will own one
|
|
|
|
|
shared Iroh endpoint and register module protocols on ALPNs such as
|
|
|
|
|
`/geth/cas/1`, `/geth/kv/1`, `/geth/pipe/1`, and `/geth/ssh-proxy/1`.
|
|
|
|
|
|
2026-05-16 01:54:00 +02:00
|
|
|
The first pinned Iroh integration uses `iroh = 0.90.0`, because newer
|
|
|
|
|
Rust-1.85-compatible candidates in the 0.93-0.95 range failed to compile through
|
|
|
|
|
a transitive `ed25519-dalek` prerelease dependency. `geth-iroh` wraps
|
|
|
|
|
`iroh::Endpoint::builder()`, configures geth ALPNs with `Builder::alpns`, uses
|
|
|
|
|
`Builder::relay_mode`, persists an `iroh::SecretKey` as hex-encoded 32-byte key
|
2026-05-16 03:17:45 +02:00
|
|
|
material, and shuts down through `Endpoint::close().await`. The default config
|
|
|
|
|
uses Iroh's default relay policy; local-only/offline development can set
|
2026-05-16 14:28:38 +02:00
|
|
|
`[iroh].relay_mode = "disabled"`. Named custom relay maps are configured under
|
|
|
|
|
`[iroh.relay_maps.<name>]`, selected with `relay_mode = "custom"` plus
|
|
|
|
|
`relay_map = "<name>"`, validated at config load, and reported in status as
|
|
|
|
|
`custom:<name>` without exposing relay URLs.
|
2026-05-16 01:54:00 +02:00
|
|
|
|
2026-05-16 03:37:51 +02:00
|
|
|
Module ALPNs are registered through `geth-iroh`'s protocol router scaffold. The
|
|
|
|
|
router owns the default protocol descriptors, rejects duplicate ALPN
|
|
|
|
|
registrations, and returns explicit unknown-ALPN errors. It does not yet accept
|
|
|
|
|
or dispatch remote streams; peer authentication and module handlers are later
|
|
|
|
|
Phase 1 work.
|
|
|
|
|
|
2026-05-16 02:06:28 +02:00
|
|
|
The target product should use Iroh relay support for practical internet
|
|
|
|
|
connectivity and mDNS/LAN discovery for local networks. These are connectivity
|
|
|
|
|
and candidate-discovery mechanisms only. They do not grant trust, mutate
|
|
|
|
|
authorization state, or make EndpointID knowledge sufficient for access.
|
2026-05-16 14:33:45 +02:00
|
|
|
The current daemon can enable Iroh's local-network discovery service through
|
|
|
|
|
`[iroh].local_discovery = true`, which is the default. This publishes and
|
2026-05-18 04:03:52 +02:00
|
|
|
discovers Iroh node addressing. `geth peer export/import/list` supports manual
|
2026-05-18 12:09:50 +02:00
|
|
|
exchange of signed peer cards as untrusted candidates. Peer cards include the
|
|
|
|
|
Iroh EndpointID plus relay/direct address candidates when the daemon can observe
|
|
|
|
|
them. `geth peer ping <node-id>` dials an imported peer card over Iroh and
|
2026-05-18 17:01:50 +02:00
|
|
|
exchanges signed peer-card metadata. `geth peer auth-check <node-id>
|
|
|
|
|
<resource> <capability>` sends a protected Iroh control request that validates
|
|
|
|
|
the caller's signed peer card against the actual Iroh EndpointID before
|
2026-05-18 17:05:43 +02:00
|
|
|
evaluating resource-local capabilities. When local discovery is enabled, the
|
|
|
|
|
daemon also advertises and discovers signed peer cards through a geth-specific
|
|
|
|
|
mDNS service. The LAN payload is TXT-encoded signed metadata only; remote geth
|
|
|
|
|
traffic still uses Iroh.
|
2026-05-16 02:06:28 +02:00
|
|
|
|
2026-05-16 14:24:21 +02:00
|
|
|
Peer cards are the discovery payload. A peer card carries node ID, agent ID,
|
2026-05-18 04:03:52 +02:00
|
|
|
endpoint candidates, timestamp, signing public key, and an Ed25519 signature
|
2026-05-18 12:09:50 +02:00
|
|
|
over a canonical payload. Imported and ping-discovered peer cards are stored as
|
|
|
|
|
untrusted metadata in `peer_cards`; trust reduction is future work. `auth
|
|
|
|
|
explain` reports when a subject is only a discovered peer candidate and denies
|
|
|
|
|
access. The peer ping path authenticates the Iroh endpoint and peer-card
|
2026-05-18 17:01:50 +02:00
|
|
|
signature, but it does not authorize any resource module. Protected peer
|
|
|
|
|
control requests must also prove that the signed peer card binds the observed
|
|
|
|
|
Iroh EndpointID, then reduce resource auth ops; an EndpointID alone is not
|
|
|
|
|
accepted as a resource principal.
|
2026-05-16 14:24:21 +02:00
|
|
|
|
2026-05-16 01:54:00 +02:00
|
|
|
The daemon starts this endpoint during `geth daemon run` and keeps it alive for
|
|
|
|
|
the daemon lifetime. When endpoint startup succeeds, the Iroh EndpointID is
|
|
|
|
|
recorded as a transport binding for the stable geth node identity. If local UDP
|
|
|
|
|
binding is unavailable, the daemon keeps local control running and reports the
|
|
|
|
|
Iroh startup error through status output.
|
|
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
SSH keys are not transport keys. They are admin trust anchors and signing
|
|
|
|
|
identities for keychain and authorization operations. SSH proxying, when added,
|
|
|
|
|
will carry SSH bytes over an authorized Iroh stream and will not make SSH a geth
|
|
|
|
|
transport backend.
|
|
|
|
|
|
2026-05-16 00:17:08 +02:00
|
|
|
SSH certificate flows use the same split. Nodes can request new OpenSSH
|
|
|
|
|
certificates or renewals through geth metadata. A machine with the CA key or
|
|
|
|
|
YubiKey can approve the request and run an explicit `ssh-keygen -s ...` command,
|
|
|
|
|
then import the resulting certificate for distribution. Certificate and key
|
2026-05-18 17:24:10 +02:00
|
|
|
revocations are stored as signed-list-ready records. The bootstrap can pull
|
|
|
|
|
certificate-flow metadata over Iroh with `geth ssh cert sync <node-id>` when the
|
|
|
|
|
peer grants `ssh_cert.sync` on `resource:ssh:certs`, and revocation metadata with
|
|
|
|
|
`geth ssh revocation sync <node-id>` when the peer grants `ssh_revocation.sync`
|
2026-05-18 18:29:45 +02:00
|
|
|
on `resource:ssh:revocations`. The daemon also runs a 30-second background
|
|
|
|
|
live-sync tick for known peers and records per-peer high-water cursors in
|
|
|
|
|
`module_state`, so repeated ticks request only records at or beyond the last
|
|
|
|
|
remote cursor. Boundary duplicates are harmless because records are keyed by
|
|
|
|
|
stable IDs and inserted with replace semantics.
|
2026-05-18 22:17:27 +02:00
|
|
|
Before issuing per-module pulls, the daemon can request an authorized sync
|
|
|
|
|
status summary over the same protected Iroh control ALPN. The serving peer
|
|
|
|
|
validates endpoint/card binding and returns only watermarks for streams where
|
|
|
|
|
the caller already has the required resource capability, which reduces blind
|
|
|
|
|
polling without letting discovery reveal private resource names.
|
2026-05-16 00:17:08 +02:00
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
## Resource Model
|
|
|
|
|
|
|
|
|
|
Everything meaningful is modeled as a resource. Resources have a kind, name,
|
|
|
|
|
authority reference, local role, replication policy, retention policy, and
|
|
|
|
|
status. Authorization is resource-scoped and capability-based.
|
|
|
|
|
|
|
|
|
|
Resource kinds:
|
|
|
|
|
|
|
|
|
|
- `db`: cr-sqlite-backed SQLite synchronization
|
|
|
|
|
- `kv`: Iroh Documents backed key-value data
|
|
|
|
|
- `pipe`: authorized byte streams and forwarding
|
|
|
|
|
- `document`: Automerge CRDT documents
|
|
|
|
|
- `pubsub`: lossy notifications and presence
|
|
|
|
|
- `cas`: content-addressed blobs
|
|
|
|
|
- `ssh-proxy`: SSH/admin proxying over Iroh
|
|
|
|
|
|
|
|
|
|
## Module Overview
|
|
|
|
|
|
|
|
|
|
`geth-cas` is implemented locally first using BLAKE3 hashes and filesystem blob
|
2026-05-16 16:36:35 +02:00
|
|
|
storage. Local pin/unpin metadata is tracked in SQLite and surfaced in
|
2026-05-16 21:10:25 +02:00
|
|
|
`cas list`. `cas cleanup` removes unpinned local blobs while retaining pinned
|
2026-05-17 21:20:24 +02:00
|
|
|
blobs. The CAS crate can build deterministic tree objects that describe
|
|
|
|
|
directories, files, executable bits, and file blob hashes; those tree objects
|
2026-05-18 03:50:09 +02:00
|
|
|
are stored as CAS blobs. The daemon can register local file roots and scan them
|
|
|
|
|
into CAS tree objects while reporting create/update/delete/rename changes. These
|
2026-05-18 03:57:26 +02:00
|
|
|
scans are local metadata only and never overwrite the working tree. The daemon
|
|
|
|
|
also has durable local file-conflict records with explicit resolution choices;
|
|
|
|
|
future cross-node file sync will create those records automatically instead of
|
2026-05-18 17:18:25 +02:00
|
|
|
silently applying ambiguous remote changes.
|
|
|
|
|
|
|
|
|
|
As a bootstrap network path, `geth cas fetch <node-id> <hash>` dials an
|
|
|
|
|
imported signed peer card over the daemon-owned Iroh control ALPN. The serving
|
|
|
|
|
daemon validates the caller's peer-card signature and observed Iroh EndpointID,
|
|
|
|
|
then reduces local auth ops and requires `cas.fetch` on `resource:cas:local`
|
|
|
|
|
before returning blob bytes. The requester verifies that the returned bytes hash
|
|
|
|
|
to the requested BLAKE3 CAS hash before storing them. Iroh-blobs, provider
|
|
|
|
|
tracking, encrypted blobs, richer cache policies, cross-node file roots, and
|
|
|
|
|
automatic conflict detection are future work.
|
2026-05-15 15:08:20 +02:00
|
|
|
|
2026-05-16 21:13:33 +02:00
|
|
|
`geth-db` currently registers local SQLite paths as DB resources and reports
|
2026-05-17 20:01:36 +02:00
|
|
|
local-only sync status plus a read-only SQLite schema summary/hash. It also
|
|
|
|
|
inspects `crsql_changes` metadata when that table or view exists, reporting
|
2026-05-17 20:32:36 +02:00
|
|
|
change count, columns, and max `db_version`. The crate and local daemon can
|
|
|
|
|
extract read-only typed change batches from `crsql_changes` with schema metadata
|
2026-05-18 22:11:57 +02:00
|
|
|
through `db changes`. As a staged network path, `geth db sync <node-id> <name>`
|
|
|
|
|
uses the protected Iroh control ALPN to request remote typed change batches when
|
|
|
|
|
the caller has `db.sync` on the remote `resource:db:<name>`. The requester
|
|
|
|
|
checks remote schema metadata against its local DB before advancing its
|
|
|
|
|
per-peer/per-DB cursor. Loading cr-sqlite and applying remote changes through
|
|
|
|
|
`crsql_changes` are future work; the current path exchanges and cursors changes
|
|
|
|
|
but does not mutate the local application database.
|
2026-05-16 21:13:33 +02:00
|
|
|
|
2026-05-16 21:52:35 +02:00
|
|
|
`geth-kv` currently provides a SQLite-backed local fallback for named KV stores
|
2026-05-18 04:06:41 +02:00
|
|
|
through `kv create/set/get`. `kv set --subject <principal>` evaluates local auth
|
|
|
|
|
ops for `kv.write_key:<key>` so prefix grants can be tested before networked
|
|
|
|
|
callers exist. The local node/agent retains owner access for administration.
|
2026-05-18 18:36:03 +02:00
|
|
|
Iroh Documents namespaces remain the target backend, but the bootstrap can sync
|
|
|
|
|
named KV stores over the protected Iroh control ALPN. `geth kv sync <node-id>
|
|
|
|
|
<name>` requires `kv.read` on the remote `resource:kv:<name>`, transfers entries
|
|
|
|
|
at or beyond a per-peer/per-KV high-water cursor, and imports only values that
|
|
|
|
|
are at least as new as the local entry timestamp. The daemon background
|
|
|
|
|
live-sync loop runs the same KV sync for local KV stores and known peers.
|
|
|
|
|
Private value encryption should use resource secret epochs before payloads are
|
|
|
|
|
exposed to remote peers.
|
2026-05-16 21:52:35 +02:00
|
|
|
|
2026-05-17 19:59:03 +02:00
|
|
|
`geth-document` currently registers local document resources and stores
|
|
|
|
|
validated JSON state in the local SQLite metadata store through
|
|
|
|
|
`document create/status/set/get`. This is a bootstrap editing surface, not yet
|
2026-05-18 18:49:34 +02:00
|
|
|
Automerge CRDT state. `geth document sync <node-id> <name>` can pull remote JSON
|
|
|
|
|
state over the protected Iroh control ALPN when the peer grants `document.read`
|
|
|
|
|
on `resource:document:<name>`. The daemon background live-sync loop runs the same
|
|
|
|
|
sync for local documents and known peers using per-peer/per-document cursors.
|
|
|
|
|
The import rule is last-writer-wins by document timestamp. Automerge state
|
|
|
|
|
encoding and sync are future work.
|
2026-05-16 22:15:18 +02:00
|
|
|
|
2026-05-17 18:23:51 +02:00
|
|
|
`geth-pubsub` currently supports local publish/subscribe snapshots through the
|
|
|
|
|
daemon control protocol. Messages live in a bounded in-memory ring buffer and
|
|
|
|
|
are lost when the daemon stops. This is deliberate: pubsub is a lossy wakeup and
|
2026-05-18 18:41:04 +02:00
|
|
|
presence channel, not authoritative storage. `geth pubsub pub <topic> <message>
|
|
|
|
|
--node <node-id>` can publish to an imported peer over the protected Iroh
|
|
|
|
|
control ALPN. The remote daemon validates endpoint/card binding and requires
|
|
|
|
|
`pubsub.publish` on `resource:pubsub:<topic>` before recording the message in
|
|
|
|
|
its local ring buffer. Iroh-gossip replication and private topics are future
|
|
|
|
|
work.
|
2026-05-17 18:23:51 +02:00
|
|
|
|
2026-05-18 18:45:10 +02:00
|
|
|
`geth-pipe` currently supports `pipe listen/connect` against a daemon-lifetime
|
|
|
|
|
registry. `geth pipe connect <name> --node <node-id>` sends an authorized remote
|
|
|
|
|
connect request over the protected Iroh control ALPN. The remote daemon validates
|
|
|
|
|
endpoint/card binding and requires `pipe.connect` on `resource:pipe:<name>`
|
|
|
|
|
before recording the connection attempt and reporting whether a listener exists.
|
|
|
|
|
This is still a control-plane scaffold for names and connection attempts only;
|
|
|
|
|
it does not carry bytes or forward sockets yet.
|
2026-05-17 20:17:26 +02:00
|
|
|
|
|
|
|
|
`geth-ssh-proxy` currently defines types, command shape, and roadmap stubs.
|
2026-05-15 15:08:20 +02:00
|
|
|
|
2026-05-16 00:17:08 +02:00
|
|
|
`geth-ssh-identity` defines SSH trust namespaces plus certificate request,
|
|
|
|
|
approval, certificate import, and revocation-list data models. The bootstrap
|
2026-05-17 18:29:47 +02:00
|
|
|
persists these flows locally and exports revocations as JSONL or OpenSSH KRL
|
2026-05-18 11:51:12 +02:00
|
|
|
specification text. It can also invoke `ssh-keygen -k` to produce a binary
|
|
|
|
|
OpenSSH KRL; serial and key-ID KRL entries require a CA public key via
|
2026-05-18 11:56:42 +02:00
|
|
|
`--ca-public`, matching OpenSSH behavior. It can import geth JSONL revocation
|
|
|
|
|
exports and OpenSSH KRL specification source files. Binary OpenSSH KRL files are
|
|
|
|
|
not enumerable through OpenSSH tooling, so geth treats binary import as
|
|
|
|
|
unsupported and asks for JSONL or the spec source. Revocation lists are not yet
|
2026-05-18 17:24:10 +02:00
|
|
|
full CRDT-replicated resources, but the daemon can already pull cert-flow and
|
|
|
|
|
revocation metadata from authorized peers over the protected Iroh control ALPN.
|
2026-05-18 18:29:45 +02:00
|
|
|
Manual sync commands and the background live-sync loop share the same capability
|
2026-05-18 22:17:27 +02:00
|
|
|
checks and cursor state. The live-sync loop first asks for authorized stream
|
|
|
|
|
watermarks and skips module pulls whose remote high-water value has not advanced.
|
2026-05-16 00:17:08 +02:00
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
## Keychain, Auth, And Secrets
|
|
|
|
|
|
|
|
|
|
The identity plane is `geth-keychain`: admin keys, users, devices, nodes, agents,
|
|
|
|
|
and endpoint bindings. Endpoint rotation must not destroy higher-level node
|
2026-05-16 14:39:18 +02:00
|
|
|
identity. Keychain operations reduce into an active view containing current
|
|
|
|
|
admin keys, users, devices, node records, agent bindings, and endpoint-to-node
|
2026-05-16 16:34:20 +02:00
|
|
|
bindings. Revoked identity subtrees are excluded from that active view. The
|
|
|
|
|
daemon persists local keychain init/admin-key operations and `keychain status`
|
|
|
|
|
reports the reduced local view. OpenSSH signature capture and verification for
|
|
|
|
|
those operations is still future work.
|
2026-05-15 15:08:20 +02:00
|
|
|
|
|
|
|
|
The authorization plane is `geth-auth`: resource-local signed operation logs,
|
2026-05-16 14:41:23 +02:00
|
|
|
grants, revocations, groups, and `auth explain`. Auth operations reduce into a
|
|
|
|
|
current permission view for resources, grants, groups, and bearer access. The
|
2026-05-16 16:32:03 +02:00
|
|
|
library can explain direct and group grants. The daemon persists local auth
|
|
|
|
|
grant/revoke operations and `geth auth explain` evaluates that local operation
|
|
|
|
|
log. Signature validation, replication, and module enforcement are still future
|
|
|
|
|
work.
|
2026-05-15 15:08:20 +02:00
|
|
|
|
2026-05-17 18:26:30 +02:00
|
|
|
Capability evaluation supports exact matches plus explicit scoped forms. For KV,
|
|
|
|
|
`kv.write_prefix:<prefix>` grants writes requested as `kv.write_key:<key>` only
|
|
|
|
|
when the key is under that prefix; `kv.write` remains the broad write
|
|
|
|
|
capability. Command-level KV enforcement is still future work.
|
|
|
|
|
|
2026-05-16 14:37:16 +02:00
|
|
|
Both keychain and auth operations use `geth-codec` canonical envelopes for
|
|
|
|
|
signature payloads. The envelope includes a version, an explicit signature
|
|
|
|
|
namespace, and the operation payload encoded with postcard. JSON remains useful
|
|
|
|
|
for CLI/control output, but it is not the signed representation.
|
|
|
|
|
|
2026-05-15 15:08:20 +02:00
|
|
|
The payload access plane is `geth-secrets`: resource master secrets, epochs,
|
|
|
|
|
key envelopes, bearer secrets, and rotation. Revocation for private data is
|
2026-05-16 22:18:49 +02:00
|
|
|
modeled initially as secret epoch rotation. The daemon persists resource secret
|
2026-05-17 02:58:58 +02:00
|
|
|
epoch metadata through `secret create/rotate/status`. Bearer access is recorded
|
|
|
|
|
as resource-scoped auth operations and rejects trust-mutation capabilities such
|
|
|
|
|
as `auth.delegate`, `auth.revoke`, and `node.enroll`. The daemon does not yet
|
|
|
|
|
store payload key material, encrypt resource data, or distribute key envelopes.
|
2026-05-15 15:08:20 +02:00
|
|
|
|
|
|
|
|
## Multi-User Direction
|
|
|
|
|
|
|
|
|
|
The project is structured for future multi-user local-first authorization:
|
|
|
|
|
|
|
|
|
|
- authorization is replicated data, not one mutable ACL blob
|
|
|
|
|
- resources can carry or delegate to their own auth state
|
|
|
|
|
- users, devices, nodes, agents, and endpoints are separate principals
|
|
|
|
|
- capabilities are the underlying permission unit
|
|
|
|
|
- bearer access is resource-scoped and does not mutate the trust graph
|
|
|
|
|
- offline revocation is eventual
|
|
|
|
|
- encryption key distribution is part of authorization
|
|
|
|
|
|
|
|
|
|
## Keyhive/BeeKEM Roadmap
|
|
|
|
|
|
|
|
|
|
Resource secret epochs are the v0/v1 approximation for private payload access.
|
|
|
|
|
Later designs can add Keyhive-like convergent capabilities and BeeKEM/CGKA-style
|
|
|
|
|
group key evolution. The bootstrap does not implement BeeKEM and does not claim
|
|
|
|
|
strong forward secrecy or post-compromise security.
|
|
|
|
|
|
|
|
|
|
## Security Invariants
|
|
|
|
|
|
|
|
|
|
- All remote node-to-node communication is over Iroh.
|
|
|
|
|
- SSH is not a geth transport.
|
|
|
|
|
- SSH keys are admin trust anchors and signing identities.
|
|
|
|
|
- Agent/node keys handle routine local identity.
|
|
|
|
|
- Discovery is untrusted.
|
|
|
|
|
- Knowing an EndpointID does not grant access.
|
|
|
|
|
- Bearer secrets are resource-scoped capabilities.
|
|
|
|
|
- Bearer access does not imply trust graph mutation rights.
|
|
|
|
|
- Authorization is capability-based and resource-scoped.
|
2026-05-16 00:17:08 +02:00
|
|
|
- SSH certificate issuance must be explicitly approved by an authorized
|
|
|
|
|
principal before signing.
|
|
|
|
|
- SSH certificate and key revocations are durable metadata that should be
|
|
|
|
|
distributed over Iroh, not fetched through unauthenticated discovery.
|
2026-05-15 15:08:20 +02:00
|
|
|
- Network and control decoders treat input as untrusted.
|
2026-05-16 00:17:08 +02:00
|
|
|
- Service installation targets user service managers, not system service
|
|
|
|
|
managers.
|