geth/README.md

306 lines
15 KiB
Markdown
Raw Normal View History

2026-05-15 15:08:20 +02:00
# geth
`geth` is a personal, local-first mesh runtime for scripts, devices, databases,
documents, blobs, pipes, and future multi-user collaboration.
This project is not the Ethereum `geth` client. The project and executable are
still named `geth`.
## One Binary
There is one executable: `geth`.
It has daemon mode and control mode:
```sh
geth init
geth daemon run
geth daemon service install
2026-05-15 15:08:20 +02:00
geth status
geth node id
geth resource list
geth cas add ./file
```
2026-05-16 03:17:45 +02:00
The daemon owns local identity, the Iroh endpoint, trust state, resource
2026-05-15 15:08:20 +02:00
registry, module router, local metadata store, and synchronized data structures.
Most non-daemon commands talk to the daemon through a local Unix socket at
`$GETH_HOME/run/geth.sock`.
2026-05-19 16:04:20 +02:00
`geth keychain init --admin-key <public-key> --signing-key <private-key>` records
the keychain initialization/admin-key operations and signs their canonical
payloads through `ssh-keygen -Y sign` using the
`geth.keychain.v1@geth.local` namespace. This is the bootstrap path for
2026-05-19 16:05:57 +02:00
admin/YubiKey-rooted trust. `geth keychain status` reports the number of stored
2026-05-19 18:58:07 +02:00
keychain signatures plus how many currently verify with OpenSSH; rejecting
unsigned or invalid replicated keychain ops is still future work.
2026-05-19 16:04:20 +02:00
The daemon can also install itself as a user service:
```sh
geth daemon service install
geth daemon service status
geth daemon service uninstall
```
The bootstrap service managers are systemd user units on Linux, launchd user
agents on macOS, and per-user scheduled tasks on Windows. These are user-level
services, not system services.
2026-05-15 15:08:20 +02:00
## Transport And SSH
All remote node-to-node geth communication is designed to happen over Iroh only.
SSH is not a geth transport backend, and there is no SSH fallback transport.
2026-05-16 03:17:45 +02:00
The default node config uses Iroh's default relay policy for practical
connectivity; set `[iroh].relay_mode = "disabled"` for local-only/offline
2026-05-16 14:28:38 +02:00
development. Named custom relay maps can be selected with
2026-05-16 14:33:45 +02:00
`relay_mode = "custom"` and `relay_map = "<name>"`. Iroh local-network
discovery is enabled by default with `[iroh].local_discovery = true`.
2026-05-15 15:08:20 +02:00
SSH keys are used as admin trust anchors and ecosystem integration points.
OpenSSH, FIDO, and YubiKey-backed keys can sign geth trust objects through
canonical geth envelopes with explicit namespaces such as
`geth.keychain.v1@geth.local`. Future SSH proxying may carry SSH protocol bytes
over authorized Iroh streams, but the geth transport remains Iroh.
2026-05-15 15:08:20 +02:00
SSH certificate request and renewal flows are managed as geth metadata. A node
can create a certificate request, another machine can approve it and receive an
explicit `ssh-keygen -s ...` command suitable for a CA key or YubiKey-backed CA,
2026-05-19 15:56:47 +02:00
or pass `--sign` to run `ssh-keygen` immediately and import the resulting
`-cert.pub` for distribution. Certificate and key revocation entries are tracked
locally and can be exported as JSONL or as an OpenSSH KRL specification file or
a binary OpenSSH KRL generated through `ssh-keygen -k`. `geth ssh cert sync
<node-id>` and
2026-05-18 17:24:10 +02:00
`geth ssh revocation sync <node-id>` pull certificate-flow and revocation
metadata from an authorized peer over Iroh.
2026-05-15 15:08:20 +02:00
## MVP Features
The bootstrap implementation provides:
- `geth init`
- `geth daemon run`
- `geth daemon service install|uninstall|start|stop|status|print`
2026-05-15 15:08:20 +02:00
- `geth status`
- `geth node id`
2026-05-18 04:03:52 +02:00
- `geth peer export [--out <path>]`
- `geth peer import <path>`
- `geth peer list`
2026-05-18 12:09:50 +02:00
- `geth peer ping <node-id>`
2026-05-18 17:01:50 +02:00
- `geth peer auth-check <node-id> <resource> <capability>`
2026-05-15 15:08:20 +02:00
- `geth resource list`
- `geth resource create <kind> <name>`
2026-05-19 16:04:20 +02:00
- `geth keychain init [--admin-key <path>] [--signing-key <path>]`
2026-05-15 15:08:20 +02:00
- `geth keychain status`
2026-05-16 22:18:49 +02:00
- `geth secret status`
- `geth secret create <resource>`
- `geth secret rotate <resource>`
- `geth secret bearer create <resource> --capability <capability>`
- `geth secret bearer list`
2026-05-19 19:08:08 +02:00
- `geth secret bearer challenge <resource> --capability <capability>`
- `geth secret bearer prove <token> <resource> --nonce <nonce> --capability <capability>`
- `geth secret bearer verify <token> <resource> --nonce <nonce> --response <response> --capability <capability>`
- `geth secret bearer revoke <resource> <bearer-id>`
2026-05-15 15:08:20 +02:00
- `geth auth explain <subject> <resource> <capability>`
2026-05-16 16:32:03 +02:00
- `geth auth grant <subject> <resource> <capability> [--grant-id <id>]`
- `geth auth revoke <resource> <grant-id>`
2026-05-18 17:18:25 +02:00
- local filesystem CAS commands: `add`, `get`, `fetch`, `hash`, `has`, `pin`,
`unpin`, `cleanup`, `providers`, `list`; remote fetch accepts
`--bearer-secret <secret>`
2026-05-17 21:20:24 +02:00
- local CAS tree objects describe file trees and are stored as CAS blobs
2026-05-20 13:30:57 +02:00
- local file-root commands: `geth cas root add/list/scan/sync/apply`; root sync
pulls authorized remote tree metadata and CAS tree bytes into a peer-qualified
remote root, and apply materializes a tree without deleting files or
overwriting local edits
2026-05-18 03:57:26 +02:00
- local file conflict metadata commands:
`geth cas conflict record/list/resolve`
2026-05-16 21:13:33 +02:00
- local DB resource registration: `geth db add <name> <path>` and
2026-05-17 20:22:50 +02:00
`geth db status <name>` with schema and `crsql_changes` metadata; the DB
2026-05-17 20:32:36 +02:00
crate and daemon can extract typed local `crsql_changes` batches through
2026-05-18 22:11:57 +02:00
`geth db changes <name>` and exchange authorized remote batches with
`geth db sync <node-id> <name>`
2026-05-18 04:06:41 +02:00
- local SQLite-backed KV commands: `geth kv create/set/get`; `kv set` accepts
`--subject <principal>` to exercise local capability checks for non-local
callers; `geth kv sync <node-id> <name> [--bearer-secret <secret>]` pulls
authorized remote updates
2026-05-18 18:49:34 +02:00
- local JSON document commands: `geth document create/status/set/get`; `geth
document sync <node-id> <name> [--bearer-secret <secret>]` pulls authorized
remote JSON state
2026-05-18 18:41:04 +02:00
- local daemon-lifetime pubsub snapshots: `geth pubsub pub/sub`; `geth pubsub
2026-05-19 15:28:48 +02:00
pub <topic> <message> --node <node-id>` publishes to an authorized peer;
`geth pubsub sub <topic> --node <node-id>` reads an authorized peer snapshot
- SSH certificate flow metadata:
- `geth ssh cert request --public-key <path> --principal <name> [--subject <principal>]`
- `geth ssh cert requests [--subject <principal>]`
2026-05-19 15:56:47 +02:00
- `geth ssh cert approve <request-id> --ca-key <path> [--sign] [--subject <principal>]`
- `geth ssh cert import <request-id> --cert <path> [--subject <principal>]`
- `geth ssh cert list [--subject <principal>]`
- `geth ssh cert sync <node-id> [--bearer-secret <secret>]`
- `geth ssh revocation add <kind> <target> [--subject <principal>]`
- `geth ssh revocation list [--subject <principal>]`
- `geth ssh revocation export --out <path> [--format jsonl|openssh-krl-spec|openssh-krl] [--subject <principal>]`
- `geth ssh revocation import <path> [--format jsonl|openssh-krl-spec] [--subject <principal>]`
- `geth ssh revocation sync <node-id> [--bearer-secret <secret>]`
- SSH proxy authorization probe: `geth ssh proxy <node-id> [--bearer-secret <secret>]`
2026-05-20 13:57:14 +02:00
- pipe registry/message commands:
`geth pipe listen <name> [--node <node-id>] [--bearer-secret <secret>]`,
`geth pipe connect <name> [--node <node-id>] [--bearer-secret <secret>]`,
2026-05-20 13:59:41 +02:00
`geth pipe send <name> [message|--in <path>|--in -] [--node <node-id>] [--bearer-secret <secret>]`,
2026-05-20 13:57:14 +02:00
and `geth pipe recv <name> [--peek]`
2026-05-15 15:08:20 +02:00
2026-05-18 12:09:50 +02:00
`geth peer export/import/list` is for untrusted peer-card exchange. Peer cards
include the Iroh EndpointID plus currently known relay/direct addresses.
`geth peer ping <node-id>` uses the local daemon's Iroh endpoint to dial an
imported peer card and exchange a signed candidate-only peer-card ping.
2026-05-18 17:01:50 +02:00
`geth peer auth-check <node-id> <resource> <capability>` sends a protected
Iroh control request: the remote daemon verifies that the caller's signed peer
card binds the actual Iroh EndpointID before reducing resource-local auth ops.
2026-05-18 17:18:25 +02:00
`geth cas fetch <node-id> <hash>` uses the same protected Iroh control path to
request a blob from a peer. The remote daemon only returns bytes when the caller
has `cas.fetch` on `resource:cas:local`, and the caller verifies that the bytes
2026-05-19 15:37:02 +02:00
hash to the requested BLAKE3 CAS hash before storing them locally. Successful
fetches record the serving peer as a local provider, visible with
`geth cas providers <hash>`. This is the bootstrap transfer path; future work
will move provider/fetch behavior to `iroh-blobs`.
Remote resource commands that accept `--bearer-secret` can also authorize with a
resource-scoped bearer proof generated from the private bearer token returned at
creation time. The persisted auth log stores a public bearer id and token
verifier, not the private token. This does not enroll the caller as a trusted
node; it only unlocks the requested capability on that one resource.
2026-05-18 17:24:10 +02:00
`geth ssh cert sync <node-id>` requires `ssh_cert.sync` on `resource:ssh:certs`
at the peer. `geth ssh revocation sync <node-id>` requires
`ssh_revocation.sync` on `resource:ssh:revocations`. Both commands merge
authorized peer metadata into the local store for offline listing and later
2026-05-18 18:29:45 +02:00
approval/signing workflows. While the daemon is running, it also performs a
2026-05-20 13:34:16 +02:00
background live-sync tick for known peers. The default interval is 30 seconds
and can be changed in `config.toml` with `[sync] live_sync_enabled` and
`live_sync_interval_ms`. Live-sync stores per-peer high-water cursors in local
metadata so repeated ticks request only newer SSH certificate-flow and
revocation records. Sync import preserves local metadata by rejecting
conflicting records with ids that already exist locally.
2026-05-18 22:17:27 +02:00
Before probing individual modules, the daemon asks the peer for an authorized
sync-status summary over Iroh. The peer only returns stream watermarks for
resources where the caller already has the matching capability, letting the
local daemon skip unchanged or unauthorized streams.
2026-05-20 13:26:50 +02:00
File roots advertise `cas-tree:<name>` watermarks when the caller has
`cas.fetch`; background live-sync imports updated tree metadata and CAS tree
bytes into peer-qualified remote roots without writing files.
2026-05-20 13:30:57 +02:00
`geth cas root apply <root> --to <path>` can then materialize that tree locally:
it creates missing directories/files, never deletes extra files, never
overwrites differing local files, and records conflicts for manual resolution.
2026-05-18 18:36:03 +02:00
Named KV stores participate in the same live-sync loop once they exist locally:
manual `geth kv sync <node-id> <name>` and background ticks require `kv.read`
on the remote `resource:kv:<name>` and import only remote entries that are not
older than the local value.
2026-05-18 18:41:04 +02:00
Remote pubsub publish uses the protected Iroh control path too. The remote peer
requires `pubsub.publish` on `resource:pubsub:<topic>` before recording the
message in its local daemon-lifetime ring buffer. Pubsub remains lossy and is
2026-05-19 15:28:48 +02:00
not durable storage. Remote pubsub subscribe uses the same protected path and
requires `pubsub.subscribe` on `resource:pubsub:<topic>` before returning the
peer's current daemon-lifetime snapshot for that topic.
2026-05-18 18:45:10 +02:00
Remote pipe connect uses the same protected Iroh control path and requires
`pipe.connect` on `resource:pipe:<name>`. The current prototype records a remote
2026-05-20 13:57:14 +02:00
connection attempt and whether a listener exists. `geth pipe send <name>
2026-05-20 13:59:41 +02:00
[message|--in <path>|--in -] --node <node-id>` uses the dedicated `/geth/pipe/1`
Iroh ALPN to write a byte message to an authorized peer listener, and `geth pipe
recv <name>` drains local daemon-lifetime messages.
2026-05-19 19:21:42 +02:00
Remote pipe listen uses the same protected path:
`geth pipe listen <name> --node <node-id>` requires `pipe.listen` on
`resource:pipe:<name>` before registering a daemon-lifetime listener on the
2026-05-20 13:57:14 +02:00
peer. Long-lived stdin/stdout streaming and socket forwarding are still future
work.
2026-05-19 15:51:11 +02:00
`geth ssh proxy <node-id>` also uses the protected Iroh control path. The remote
peer validates the caller's endpoint/card binding and requires
`ssh_proxy.connect` on `resource:ssh-proxy:local` before returning proxy
connection metadata. The current prototype does not carry SSH bytes or connect
to remote sshd yet; it only proves the authorization gate.
2026-05-18 18:49:34 +02:00
Document sync is a bootstrap JSON last-writer-wins path before Automerge:
manual `geth document sync <node-id> <name>` and background live-sync require
`document.read` on `resource:document:<name>` and import only state that is not
older than the local document timestamp.
2026-05-18 22:11:57 +02:00
DB sync is a staged cr-sqlite path: manual `geth db sync <node-id> <name>` and
background live-sync require `db.sync` on `resource:db:<name>`, exchange typed
`crsql_changes` batches over the protected Iroh control path, and check remote
2026-05-19 19:02:39 +02:00
schema metadata against the local DB before applying and advancing the per-peer
cursor. Compatible batches are inserted into the local `crsql_changes` table or
view; for real cr-sqlite databases, loading/configuring cr-sqlite remains the
database owner's responsibility.
2026-05-18 12:09:50 +02:00
Importing or pinging a peer card never grants capabilities by itself.
2026-05-18 17:05:43 +02:00
When `[iroh].local_discovery = true`, the daemon also advertises and discovers
signed peer cards on LAN using a geth-specific mDNS TXT payload. That payload is
candidate metadata only; all geth node-to-node requests still run over Iroh.
2026-05-18 04:03:52 +02:00
2026-05-15 15:08:20 +02:00
## Resource Modules
Everything meaningful is modeled as a resource. Planned resource kinds are:
- `db`: SQLite/cr-sqlite synchronization
- `kv`: Iroh Documents backed key-value stores
2026-05-17 20:17:26 +02:00
- `pipe`: dumbpipe-like byte streams over Iroh; the bootstrap has a local
daemon registry only
2026-05-15 15:08:20 +02:00
- `document`: Automerge documents over Iroh streams
2026-05-17 18:23:51 +02:00
- `pubsub`: lossy notifications, not authoritative storage; the bootstrap
keeps only an in-memory daemon-lifetime ring buffer
2026-05-15 15:08:20 +02:00
- `cas`: content-addressed blob storage and distribution
- `ssh-proxy`: authorized SSH proxy/admin access over Iroh
Authorization is resource-scoped and capability-based. Bearer secrets may grant
2026-05-17 18:26:30 +02:00
specific resource capabilities but do not create trusted node identity. The auth
evaluator supports scoped KV write grants such as `kv.write_prefix:apps/foo/`
2026-05-18 04:06:41 +02:00
for `kv.write_key:apps/foo/config` explain checks. `geth kv set --subject
<principal>` enforces those local grants for test callers; the local node/agent
still has owner access for local administration. SSH certificate and revocation
commands also accept `--subject <principal>` on local metadata operations to
exercise the same capability checks: certificate requests/read/approval/import
use `ssh_cert.request`, `ssh_cert.read`, `ssh_cert.approve`, and
`ssh_cert.import` on `resource:ssh:certs`, while revocation publish/read/import
use `ssh_revocation.publish`, `ssh_revocation.read`, and
`ssh_revocation.import` on `resource:ssh:revocations`.
2026-05-15 15:08:20 +02:00
## Local State
If `GETH_HOME` is set, geth uses it. Otherwise it uses an OS-specific data
directory. The bootstrap layout is:
```text
$GETH_HOME/
geth.sqlite
config.toml
identity/agent.ed25519
2026-05-16 03:17:45 +02:00
identity/iroh.ed25519
2026-05-15 15:08:20 +02:00
cas/blobs/
run/geth.sock
```
## Quick Start
In one shell:
```sh
export GETH_HOME="$(mktemp -d)"
cargo run -p geth -- init
cargo run -p geth -- daemon run
```
In another shell:
```sh
export GETH_HOME="<same dir>"
cargo run -p geth -- status
cargo run -p geth -- node id
echo "hello geth" > /tmp/hello-geth.txt
cargo run -p geth -- cas add /tmp/hello-geth.txt
cargo run -p geth -- cas list
```
## Authorization Direction
The MVP defines the split between:
- keychain: SSH-rooted users, devices, nodes, agents, and endpoint bindings
- auth: resource-local signed authorization operations and capability grants
- secrets: resource master secrets, epochs, envelopes, and bearer access
The current code does not implement Keyhive, BeeKEM, strong forward secrecy, or
post-compromise security. It leaves room for future local-first, replicated auth
logs and BeeKEM/CGKA-style group key evolution.