295 lines
14 KiB
Markdown
295 lines
14 KiB
Markdown
# 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
|
|
geth status
|
|
geth node id
|
|
geth resource list
|
|
geth cas add ./file
|
|
```
|
|
|
|
The daemon owns local identity, the Iroh endpoint, trust state, resource
|
|
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`.
|
|
|
|
`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
|
|
admin/YubiKey-rooted trust. `geth keychain status` reports the number of stored
|
|
keychain signatures plus how many currently verify with OpenSSH; rejecting
|
|
unsigned or invalid replicated keychain ops is still future work.
|
|
|
|
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.
|
|
|
|
## 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.
|
|
The default node config uses Iroh's default relay policy for practical
|
|
connectivity; set `[iroh].relay_mode = "disabled"` for local-only/offline
|
|
development. Named custom relay maps can be selected with
|
|
`relay_mode = "custom"` and `relay_map = "<name>"`. Iroh local-network
|
|
discovery is enabled by default with `[iroh].local_discovery = true`.
|
|
|
|
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.
|
|
|
|
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,
|
|
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
|
|
`geth ssh revocation sync <node-id>` pull certificate-flow and revocation
|
|
metadata from an authorized peer over Iroh.
|
|
|
|
## MVP Features
|
|
|
|
The bootstrap implementation provides:
|
|
|
|
- `geth init`
|
|
- `geth daemon run`
|
|
- `geth daemon service install|uninstall|start|stop|status|print`
|
|
- `geth status`
|
|
- `geth node id`
|
|
- `geth peer export [--out <path>]`
|
|
- `geth peer import <path>`
|
|
- `geth peer list`
|
|
- `geth peer ping <node-id>`
|
|
- `geth peer auth-check <node-id> <resource> <capability>`
|
|
- `geth resource list`
|
|
- `geth resource create <kind> <name>`
|
|
- `geth keychain init [--admin-key <path>] [--signing-key <path>]`
|
|
- `geth keychain status`
|
|
- `geth secret status`
|
|
- `geth secret create <resource>`
|
|
- `geth secret rotate <resource>`
|
|
- `geth secret bearer create <resource> --capability <capability>`
|
|
- `geth secret bearer list`
|
|
- `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>`
|
|
- `geth auth explain <subject> <resource> <capability>`
|
|
- `geth auth grant <subject> <resource> <capability> [--grant-id <id>]`
|
|
- `geth auth revoke <resource> <grant-id>`
|
|
- local filesystem CAS commands: `add`, `get`, `fetch`, `hash`, `has`, `pin`,
|
|
`unpin`, `cleanup`, `providers`, `list`; remote fetch accepts
|
|
`--bearer-secret <secret>`
|
|
- local CAS tree objects describe file trees and are stored as CAS blobs
|
|
- local file-root commands: `geth cas root add/list/scan/sync`; root sync pulls
|
|
authorized remote tree metadata and CAS tree bytes into a peer-qualified
|
|
remote root without writing files
|
|
- local file conflict metadata commands:
|
|
`geth cas conflict record/list/resolve`
|
|
- local DB resource registration: `geth db add <name> <path>` and
|
|
`geth db status <name>` with schema and `crsql_changes` metadata; the DB
|
|
crate and daemon can extract typed local `crsql_changes` batches through
|
|
`geth db changes <name>` and exchange authorized remote batches with
|
|
`geth db sync <node-id> <name>`
|
|
- 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
|
|
- local JSON document commands: `geth document create/status/set/get`; `geth
|
|
document sync <node-id> <name> [--bearer-secret <secret>]` pulls authorized
|
|
remote JSON state
|
|
- local daemon-lifetime pubsub snapshots: `geth pubsub pub/sub`; `geth pubsub
|
|
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>]`
|
|
- `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>]`
|
|
- pipe registry/connect commands:
|
|
`geth pipe listen <name> [--node <node-id>] [--bearer-secret <secret>]` and
|
|
`geth pipe connect <name> [--node <node-id>] [--bearer-secret <secret>]`
|
|
|
|
`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.
|
|
`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.
|
|
`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
|
|
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.
|
|
`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
|
|
approval/signing workflows. While the daemon is running, it also performs a
|
|
background live-sync tick for known peers every 30 seconds. 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
Remote pipe connect uses the same protected Iroh control path and requires
|
|
`pipe.connect` on `resource:pipe:<name>`. The current prototype records a remote
|
|
connection attempt and whether a listener exists; byte streaming and forwarding
|
|
are still future work.
|
|
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
|
|
peer.
|
|
`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.
|
|
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.
|
|
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
|
|
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.
|
|
Importing or pinging a peer card never grants capabilities by itself.
|
|
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.
|
|
|
|
## 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
|
|
- `pipe`: dumbpipe-like byte streams over Iroh; the bootstrap has a local
|
|
daemon registry only
|
|
- `document`: Automerge documents over Iroh streams
|
|
- `pubsub`: lossy notifications, not authoritative storage; the bootstrap
|
|
keeps only an in-memory daemon-lifetime ring buffer
|
|
- `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
|
|
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/`
|
|
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`.
|
|
|
|
## 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
|
|
identity/iroh.ed25519
|
|
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.
|