- Rust 99.3%
- Shell 0.4%
- PowerShell 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
CI / fmt, clippy, docs (push) Failing after 5s
CI / test (ubuntu-latest) (push) Failing after 5s
CI / iroh integration smoke tests (push) Failing after 5s
CodeQL / Analyze Rust (push) Failing after 4s
Security / RustSec cargo-audit (push) Failing after 5s
CI / test (macos-latest) (push) Has been cancelled
CI / test (windows-latest) (push) Has been cancelled
|
||
| .github | ||
| crates | ||
| docs | ||
| scripts | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| clippy.toml | ||
| justfile | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| mise.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
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.
Install A Release
Choose an explicit release tag. On Linux x86_64 or macOS x86_64/arm64:
version=v0.1.0
curl -fLO "https://forge.tionis.dev/eric/geth/releases/download/$version/install.sh"
sh install.sh --version "$version"
On Windows x86_64 in PowerShell:
$Version = 'v0.1.0'
Invoke-WebRequest "https://forge.tionis.dev/eric/geth/releases/download/$Version/install.ps1" -OutFile install.ps1
Unblock-File .\install.ps1
.\install.ps1 -Version $Version
Replace v0.1.0 with the release you intend to install. Both installers fetch
the platform archive and its .sha256 file, reject a checksum mismatch, and
install only the single geth executable. They do not initialize trust state
or start a daemon. The Unix default is $HOME/.local/bin; the installer prints
the profile it updates when that directory is not already on PATH. Windows
defaults to the current user's local program directory and adds that directory
to the user PATH. Pass --no-modify-path or -NoModifyPath to opt out and
receive manual setup guidance. A mirror can be selected with
GETH_RELEASE_BASE_URL, and downloaded archives can be verified and installed
offline with --archive or -ArchivePath.
For an upgrade, create a backup, stop the daemon, rerun the installer with the new explicit tag, then start and check it:
geth backup create --out ./geth-backup
geth daemon stop
sh install.sh --version v0.2.0
geth daemon start
geth wait daemon
geth status
Uninstall the user service first with geth daemon uninstall, then remove the
installed geth/geth.exe file. This intentionally preserves the selected
geth home. Back it up and remove it separately only when you explicitly want to
delete identity, trust, metadata, and CAS state.
First 10 Minutes
Build the single binary and start a disposable daemon:
cargo build -p geth
./target/debug/geth daemon run --ephemeral
Keep the daemon running. In a second shell, reuse the home it printed:
./target/debug/geth --home <printed-path> wait daemon
./target/debug/geth --home <printed-path> status --json
./target/debug/geth --home <printed-path> doctor --json
Ctrl-C stops the daemon and removes its temporary state. For a persistent
background node, run geth daemon install; this initializes local state,
installs a service for the current user, starts it, and waits for the daemon to
answer on local control. Before
enrolling other machines, use geth guide owner-setup; it records an
OpenSSH admin public key as the trust anchor and signs the initial keychain
statements without copying the private key into geth state.
Start with these documents when moving beyond the local smoke test:
docs/architecture.md: ownership, trust, transport, and module boundaries.docs/command-stability.md: commands suitable for automation and commands that remain experimental.docs/compatibility.md: CLI, JSON, protocol, store, and signed-operation compatibility rules.docs/automation-examples.md: shell, Python, and user-service examples.docs/user-workflows.md: operator stories from first run through enrollment, sync, automation, and recovery.docs/production-readiness-roadmap.md: the pre-deployment gate and its current status.docs/dogfood-checklist.md: required two-machine evidence before broader deployment.
One Binary
There is one executable: geth.
It has daemon mode and control mode:
geth init
geth daemon run
geth daemon 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 on Linux/macOS or a per-home Windows named pipe.
geth status and geth status --json report daemon uptime, store schema and
durability settings, Iroh endpoint/relay/discovery state, and native backend
health for automation.
For automation, --json emits one pretty JSON document and --jsonl emits one
compact physical line per result. They are mutually exclusive; both success
and error objects carry a stable top-level type, and machine-mode failures
exit nonzero without mixing human prose into stdout. The detailed compatibility
contract and examples are in docs/compatibility.md
and docs/automation-examples.md.
geth init --admin-key <public-key> --signing-key <private-key> --node-name <name> records an owner/admin keychain, the local user/device/node binding, and
signs canonical keychain 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 sync <node> pulls the signed
keychain operation log from an imported peer and imports only operations with
valid OpenSSH signatures from currently trusted admin keys. geth node list
shows the active reduced node view, and geth node rename/revoke require
--signing-key so device-management changes can replicate as verified admin
statements. geth node grant/revoke-grant and geth auth grant/revoke also
require --signing-key in the CLI and store signed auth operations for
replication.
SSH certificate-flow and revocation records carry agent-key signed provenance
over canonical payloads, and sync import rejects new unsigned or invalidly
signed records.
The daemon can also install itself as a user service:
geth daemon install
geth daemon status
geth daemon logs
geth daemon 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. The longer geth daemon service ... family is
retained for compatibility and advanced options.
daemon install canonicalizes and validates the executable recorded in the
service definition. When invoked from a Cargo target directory or another
temporary location, it atomically copies the binary into
<geth-home>/bin/geth first so cleanup cannot leave a broken service. Use
--allow-transient-binary only for deliberate development setups. Installation
waits up to 30 seconds for local daemon readiness by default; use --no-wait or
--timeout-ms when automation needs different behavior. geth daemon logs
shows the systemd user journal or the selected home's launchd/Windows log files;
add --follow to stream new entries.
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. SSH proxying carries SSH protocol bytes over an
authorized Iroh stream, but SSH is still not a geth transport backend.
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.
Current Feature Surface
The bootstrap implementation provides:
geth guide [quickstart|init|owner-setup|enrollment|keys|overlay|service|completions|smoke-test]for embedded workflow help, including--admin-key/--signing-keysetup examplesgeth completions <bash|zsh|fish|powershell|elvish>for shell completion scripts generated from the live CLI command treegeth initgeth init --admin-key <public-key> --signing-key <private-key> --node-name <name>geth daemon run [--ephemeral]geth daemon install|start|stop|status|logs|uninstallgeth daemon service install|uninstall|start|stop|status|printgeth config path|show|validate|setgeth statusgeth wait daemon|peer|sync --timeout-ms <ms>geth doctorgeth backup create --out <dir>geth backup restore <backup-dir> --target-home <dir>geth node idgeth node listgeth node enroll join <owner-peer-card> --admin-key <owner-admin.pub> --node-name <name>geth node enroll request --node-name <name> --capability <resource=capability> [--out <path>]geth node enroll submit <owner-node> [--request-id <id>|--path <path>]geth node enroll import <path>geth node enroll list [--status pending|approved|rejected]geth node enroll approve <request-id> --signing-key <private-key>geth node enroll sync <owner-node>geth node rename <node-or-name> <name> --signing-key <private-key>geth node revoke <node-or-name> --signing-key <private-key>geth node endpoint-add <node-or-name> <endpoint-id> --signing-key <private-key>geth node endpoint-revoke <node-or-name> <endpoint-id> --signing-key <private-key>geth node grant <node-or-name> <resource> <capability> --signing-key <private-key> [--grant-id <id>]geth node revoke-grant <resource> <grant-id> --signing-key <private-key>geth peer export [--out <path>]geth peer import <path>geth peer listgeth peer ping <node-id>geth peer auth-check <node-id> <resource> <capability>- optional overlay-network planning:
geth overlay status,geth overlay plan <name> [--cidr 172.22.0.0/24],geth overlay join <name> --secret <resource-secret> [--cidr 172.22.0.0/24],geth overlay interface-plan <name> [--platform linux|macos|windows],geth overlay up <name> [--bearer-secret <route-token>] [--mtu 1280],geth overlay down <name>,geth overlay peers <name>,geth overlay send <name> <node> --packet-base64 <ipv4-packet>,geth overlay recv <name>, andgeth overlay leave <name>. Join persists local overlay membership, creates the overlay resource when needed, assigns a deterministic virtual IP, and stores only a BLAKE3 fingerprint of the supplied secret. If the overlay resource already has bearer invites, join requires a bearer token withoverlay.join. Packet send validates IPv4 packets and carries them over the dedicated/geth/overlay/1Iroh ALPN afteroverlay.routeauthorization.overlay upcreates a real L3 TUN/Wintun-style interface throughtun-rs, reads IPv4 packets from that interface, maps destination overlay IPs to imported peer cards, and routes packets over/geth/overlay/1. Creating the interface is explicit opt-in and may requireCAP_NET_ADMIN, sudo, or platform-specific network entitlements. Release archives includedocs/overlay-platforms.mdwith Linux TUN, macOS entitlement, and Windows Wintun guidance. geth resource listgeth resource create <kind> <name>geth resource capabilities [family]geth keychain init [--admin-key <path>] [--signing-key <path>]geth keychain statusgeth keychain admin-add --admin-key <pub> --signing-key <private> [--principal <name>]geth keychain admin-revoke <key-fingerprint> --signing-key <private>geth keychain allowed-signersgeth keychain verifygeth keychain sync <node-id-or-name>geth auth sync <node-id-or-name>geth sync statusgeth sync now [node-id-or-name]geth wait daemon --timeout-ms <ms>geth wait peer <node-id-or-name> --timeout-ms <ms>geth wait sync <node-id-or-name> --timeout-ms <ms>geth secret statusgeth secret create <resource>geth secret rotate <resource>geth secret bearer create <resource> --capability <capability>geth secret bearer listgeth 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> --signing-key <private-key> [--grant-id <id>]geth auth revoke <resource> <grant-id> --signing-key <private-key>- local filesystem CAS commands:
add,get,fetch,hash,has,pin,unpin,cleanup,providers,list; remote fetch accepts--bearer-secret <secret> - private CAS envelope commands:
geth cas add-private <resource> <path>andgeth cas get-private <resource> <hash> --out <path>. New writes use an AES-256-GCM envelope bound to the resource and local secret epoch. This does not claim forward secrecy or post-compromise security. - local CAS tree objects describe file trees and are stored as CAS blobs
- 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. Repeated syncs keep the previous imported remote tree as the base and record durable conflicts when local and remote roots both changed. - local file conflict metadata commands:
geth cas conflict record/list/resolve - local DB resource registration:
geth db add <name> <path>andgeth db status <name>with schema andcrsql_changesmetadata; the DB crate and daemon can extract typed localcrsql_changesbatches throughgeth db changes <name>and exchange authorized remote batches withgeth db sync <node-id> <name> - local SQLite-backed KV commands:
geth kv create/set/get;kv setaccepts--subject <principal>to exercise local capability checks for non-local callers. The daemon mirrors named KV stores into Iroh Documents andgeth kv sync <node-id> <name> [--bearer-secret <secret>]pulls authorized remote updates after receiving a read-only docs ticket through geth control. - local Automerge document commands:
geth document create/status/set/get; CLI input and output are JSON views, while the store keeps durable Automerge save bytes.geth document sync <node-id> <name> [--bearer-secret <secret>]pulls authorized remote Automerge state. - lossy pubsub wakeups:
geth pubsub pub/sub; local messages are retained in a daemon-lifetime ring buffer, while authorized remote publish/subscribe joins deterministic nativeiroh-gossiptopics after geth control authorization - 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/admin over Iroh:
geth ssh proxy <node-id> [--bearer-secret <secret>]andgeth ssh admin-shell <node-id> <help|status|node-id> [--bearer-secret <secret>] - pipe registry/message commands:
geth pipe listen <name> [--node <node-id>] [--bearer-secret <secret>],geth pipe connect <name> [--node <node-id>] [--bearer-secret <secret>],geth pipe send <name> [message|--in <path>|--in -] [--node <node-id>] [--bearer-secret <secret>],geth pipe recv <name> [--peek], andgeth pipe forward-tcp --listen 127.0.0.1:<port> --node <node-id> --target 127.0.0.1:<port>orgeth pipe forward-unix --listen /tmp/local.sock --node <node-id> --target /tmp/remote.sock
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 as
an authorization preflight. The remote daemon verifies the caller's signed peer
card against the observed Iroh EndpointID and requires cas.fetch on
resource:cas:local. After that preflight succeeds, the requester fetches the
blob payload over native iroh-blobs (/iroh-bytes/4) on the same daemon-owned
Iroh endpoint, verifies the BLAKE3 hash, stores it in local CAS, and records the
serving peer as a provider visible with geth cas providers <hash>.
geth-iroh is pinned to iroh 1.0.0 and compiles the native backend
libraries iroh-blobs 0.103.0, iroh-docs 0.101.0, and iroh-gossip 0.101.0
against the same daemon-owned endpoint generation. KV stores are mirrored into
native iroh-docs namespaces and peers receive read-only document tickets only
after geth authorization succeeds. Pubsub joins native iroh-gossip topics
only after the geth control path has authenticated the peer-card endpoint
binding and checked the topic capability.
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 SSH distribution log entries into the local store for offline
listing and later approval/signing workflows. The current log is materialized
from signed certificate requests, signed certificate imports, and signed
revocation records, then reduced locally; it is not a mutable remote ACL blob.
While the daemon is running, it also performs a background live-sync tick for
known peers. The default interval is 30 seconds and can be changed with the
validated configuration commands, for example
geth config set sync.live_sync_interval_ms 10000 or
geth config set sync.live_sync_enabled false.
Live-sync stores per-peer high-water cursors in local metadata so repeated ticks
request only newer SSH certificate-flow and revocation log entries. 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.
When a previous imported remote tree is available, sync compares that base
against the current local same-named root and the newly imported remote tree.
Concurrent edit, delete/edit, and divergent rename conflicts are recorded in
the local conflict table for later resolution.
geth cas root apply <root> --to <path> can then materialize that tree locally:
without a registered local root base it creates missing directories/files, never
deletes extra files, never overwrites differing local files, and records
conflicts for manual resolution. When the target path is a registered local file
root with a previous scan, apply uses a three-way base/local/remote check and
can safely apply non-conflicting remote creates, updates, deletes, and renames
only where local state still matches the recorded base.
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>. Authorized sync imports from the remote
Iroh Documents namespace where available, keeps SQLite as the durable local
index, and imports only remote entries that are not older than the local value.
Remote pubsub publish uses the protected Iroh control path as an authorization
preflight. The remote peer requires pubsub.publish on
resource:pubsub:<topic> before recording the message and broadcasting it on a
deterministic native iroh-gossip topic. Pubsub remains lossy and is not
durable storage; facts that must survive restart or reconcile offline belong in
CAS, KV, document, or DB resources. Remote pubsub subscribe uses the same
protected path, requires pubsub.subscribe on resource:pubsub:<topic>, joins
the gossip topic, and returns 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. geth pipe send <name> [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.
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 pipe forward-tcp --listen 127.0.0.1:<local-port> --node <node-id> --target 127.0.0.1:<remote-port> starts a local loopback TCP listener. Each accepted
connection asks the local daemon to open an authorized /geth/pipe/1 byte
stream to the peer. The remote daemon validates the signed endpoint/card binding
and requires pipe.forward on resource:pipe-tcp:<target> before connecting to
the remote loopback TCP target. This is loopback-only in the prototype to avoid
turning geth into an accidental open proxy.
geth pipe forward-unix --listen <local-socket> --node <node-id> --target <remote-socket> uses the same /geth/pipe/1 byte stream and requires
pipe.forward on resource:pipe-unix:<target> before connecting to the remote
Unix socket. Unix socket paths must be absolute.
geth ssh proxy <node-id> is usable as an OpenSSH ProxyCommand: the CLI opens
a local daemon stream, the daemon opens the dedicated /geth/ssh-proxy/1 Iroh
ALPN, the remote daemon validates the caller's endpoint/card binding and
requires ssh_proxy.connect on resource:ssh-proxy:local, and only then
connects the stream to 127.0.0.1:22. SSH remains normal OpenSSH on top of that
byte stream; SSH is not a geth transport backend.
geth ssh admin-shell <node-id> <command> is a restricted geth admin workflow
over the protected Iroh control path. It requires ssh_proxy.admin_shell on the
same resource and supports only built-in commands (help, status, node-id);
it does not execute host shell commands.
Document sync exchanges durable Automerge state with a JSON view for CLI output:
manual geth document sync <node-id> <name> and background live-sync require
document.read on resource:document:<name> and merge authorized remote state
when the peer advertises a state timestamp at or after the local document.
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. The prototype does not use CAS-backed DB
snapshots or change-batch blobs; that is deferred until large initial catch-up
needs it. The automated tests use deterministic crsql_changes fixtures because
this dev environment does not provide a sqlite3 CLI or cr-sqlite extension
artifact for a real extension-backed integration test.
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 synchronizationkv: Iroh Documents backed key-value storespipe: dumbpipe-like byte streams over Iroh; the bootstrap has a local daemon registry onlydocument: Automerge documents over Iroh streamspubsub: lossy notifications over nativeiroh-gossipafter geth authorization, with only an in-memory daemon-lifetime ring buffercas: content-addressed blob storage and distributionssh-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.
geth auth explain <subject> <resource> <capability> is the operator-facing
debug path for those decisions. Human output includes the allow/deny result,
the reason, evaluated auth-op count, and compact diagnostics. JSON output
includes the same diagnostics so scripts can distinguish discovered-only peers,
unknown subjects, missing or matched endpoint bindings, missing grants, revoked
grants, and bearer-secret access without scraping prose.
Local State
Use geth config path to find the selected configuration file, geth config show to inspect either the file or the effective built-in defaults, and geth config validate before restarting a daemon after manual edits. The safe setter
supports these dotted keys:
iroh.relay_mode(default,staging,disabled, orcustom)iroh.relay_mapiroh.local_discovery(trueorfalse)sync.live_sync_enabled(trueorfalse)sync.live_sync_interval_ms(at least100)
geth config set <key> <value> preserves unrelated TOML and comments, validates
the complete prospective config, and reports that the daemon must be restarted.
Custom relay-map URL tables remain a deliberate manual TOML edit; validate them
with geth config validate.
If GETH_HOME is set, geth uses it. Otherwise it uses an OS-specific data
directory. The bootstrap layout is:
$GETH_HOME/
geth.sqlite
config.toml
identity/agent.ed25519
identity/iroh.ed25519
cas/blobs/
run/geth.sock # Linux/macOS local control; Windows uses a named pipe
Quick Start
For a disposable evaluation, run this in one shell:
cargo run -p geth -- daemon run --ephemeral
In another shell, use the home it prints:
cargo run -p geth -- --home <printed-path> status
cargo run -p geth -- --home <printed-path> node id
echo "hello geth" > /tmp/hello-geth.txt
cargo run -p geth -- --home <printed-path> cas add /tmp/hello-geth.txt
cargo run -p geth -- --home <printed-path> cas list
For normal persistent use, geth daemon install initializes and starts a
background user service. Run geth guide quickstart to compare startup modes.
Backup And Restore
geth backup create --out <dir> creates an offline directory backup with a
manifest.json plus a home/ payload. The backup includes config.toml,
geth.sqlite and SQLite WAL sidecars when present, and local CAS blobs. It
records public geth identity material in the manifest when available.
The backup intentionally excludes daemon runtime files, private geth identity
keys under identity/*.ed25519, and private SSH admin keys. SSH admin keys are
external trust anchors; geth stores public admin material and signatures, not
the private SSH keys.
geth backup restore <backup-dir> --target-home <dir> restores into a separate
empty target home for validation. It refuses to overwrite a non-empty target.
Doctor
geth doctor is a local operational check that works even when the daemon is
not reachable. It checks config parsing, local daemon control-endpoint health,
ssh-keygen availability, metadata-store readability, imported peer-card
validity, and representative peer grants when local metadata is available. Use
--json for scripts.
Daemon logs are emitted through tracing and are controlled with standard
RUST_LOG filters. Local control requests log structured fields for command,
peer node, resource, capability, stream, and stable error code where those
fields apply. Logs intentionally classify requests instead of formatting full
payloads, so bearer tokens, private key paths, packet/message payloads, and
document JSON are not logged by the request tracer.
See docs/automation-examples.md for shell, Python JSON, and user-service
automation examples.
See docs/conflict-semantics.md for the current per-resource conflict behavior
used by CAS/file roots, KV, documents, DB sync, and signed metadata logs.
Owner And Node Management
The intended owner setup is SSH-admin-rooted:
geth init \
--admin-key ~/.ssh/id_ed25519_sk.pub \
--signing-key ~/.ssh/id_ed25519_sk \
--node-name laptop \
--capability resource:ssh-proxy:local=ssh_proxy.admin_shell
When any owner setup option is used, both --admin-key and --signing-key are
required. This prevents accidentally creating an unsigned owner/device/node
statement that cannot be accepted by another node during keychain sync.
This records signed keychain operations for KeychainInit, AdminKeyAdd,
UserAdd, DeviceAdd, NodeAdd, and AgentBind. The current node identity is
stable above endpoint rotation: future endpoint bindings should attach to the
node, not replace it. Node management is done through the reduced keychain view:
geth node list
geth node rename laptop work-laptop --signing-key ~/.ssh/id_ed25519_sk
geth node endpoint-add work-laptop <iroh-endpoint-id> --signing-key ~/.ssh/id_ed25519_sk
geth node grant work-laptop resource:ssh-proxy:local ssh_proxy.connect \
--signing-key ~/.ssh/id_ed25519_sk
geth node revoke-grant resource:ssh-proxy:local <grant-id> \
--signing-key ~/.ssh/id_ed25519_sk
geth node revoke work-laptop --signing-key ~/.ssh/id_ed25519_sk
Admin SSH keys are managed through the same signed keychain log:
geth keychain admin-add \
--admin-key ~/.ssh/new_admin.pub \
--signing-key ~/.ssh/id_ed25519_sk \
--principal admin
geth keychain allowed-signers > /tmp/geth.allowed_signers
geth keychain allowed-signers --out /tmp/geth.allowed_signers
geth keychain sign-file \
--in /tmp/authorized_keys \
--out /tmp/authorized_keys.sig \
--signing-key ~/.ssh/id_ed25519_sk
geth keychain verify-file \
--in /tmp/authorized_keys \
--signature /tmp/authorized_keys.sig
geth keychain explain <op-id>
geth keychain explain-signer <key-id>
geth keychain verify
The previous test-only static sigchain commands (sigchain, publish-bundle,
import-sigchain, verify-checkpoint, and fetch) were removed. Their
downloaded allowed_signers projection could establish the trust that
validated its own chain. There is no compatibility mode for that workflow.
The replacement is the small, transport-neutral
SSHSIGCHAIN v1 specification. It starts from an
operator-pinned chain ID, OpenSSH root public key, and namespace. Parent hashes
define order without a redundant sequence counter. Every record carries a
public authority transition for devices, keys, causal revocation, scoped
permissions, and anchor policy, plus optional profile commitments whose payloads
can be selectively disclosed. Geth already provides a verifier for independently
produced JSONL transport files:
geth keychain verify-sigchain \
--in ./geth.sshsigchain.v1.jsonl \
--chain-id <64-hex-character-chain-id> \
--root-key ~/.ssh/geth-root.pub
The verifier reports active authority devices/keys, disclosed and incomplete profiles, the head digest, and current attester/backend anchor thresholds. SSHSIGCHAIN local record storage, signing, publication, import, accepted-head persistence, and concrete anchor adapters remain follow-up work. Until they exist, do not substitute an unpinned checkpoint or a local operation-log view for the SSHSIGCHAIN trust tuple.
Signing is mediated by OpenSSH. --signing-key may point at a private key file,
a FIDO/YubiKey OpenSSH security-key stub, or a public key whose private half is
available in ssh-agent. For encrypted private keys, the recommended workflow
is to unlock the key with ssh-add and pass the public key path. For PKCS#11
tokens, load the key into ssh-agent with ssh-add -s <provider> and use the
exported public key path; direct PKCS#11 signing is not exposed by
ssh-keygen -Y sign in a portable way.
The enrollment flow for a new node is:
# On the new node, initialize local state and trust the owner's admin public key:
geth init
geth daemon run
geth keychain init --admin-key ~/.ssh/id_ed25519_sk.pub
# Then create a signed enrollment request:
geth node enroll request \
--node-name workstation \
--capability resource:ssh-proxy:local=ssh_proxy.connect \
--out /tmp/workstation-enrollment.json
# Either submit over Iroh to an imported owner peer:
geth node enroll submit owner-laptop --path /tmp/workstation-enrollment.json
# Or import the JSON on the owner/YubiKey machine:
geth node enroll import /tmp/workstation-enrollment.json
geth node enroll list --status pending
geth node enroll approve <request-id> --signing-key ~/.ssh/id_ed25519_sk
# Back on the new node, pull signed identity and authorization state:
geth sync now owner-laptop
geth sync status
Enrollment requests are signed by the requesting agent key. Approval records
signed keychain operations for the new device/node/agent binding and signed auth
operations for requested resource capabilities. The requesting node must already
know the owner's admin public key so it can verify the signed operation logs
before importing them. geth sync now pulls both signed logs from the owner
node through the same path used by background live sync.
geth keychain sync <node> pulls signed keychain operations from an imported
peer over Iroh and rejects operations that do not have a valid OpenSSH signature
from a currently trusted admin key over the canonical keychain payload. This is
the current replicated device-management substrate. It is still a pull-based
operation log, not yet a CRDT or Keyhive-style convergent authority.
The daemon also runs best-effort live sync for imported peers. geth sync now [node] triggers the same sync pass immediately, and geth sync status reports
the last local attempt, success, cursor, import count, rejection count, and
error per peer stream. geth sync status --json also includes per-stream
state, stale, stale_after_ms, consecutive_failures, retry_after_ms,
retry_in_ms, and next_action fields so smoke tests can fail on stale or
failed streams. Background live-sync backs off failed streams, while
geth sync now [node] is an immediate operator retry. Keychain and auth sync
now use per-peer high-water cursors, while receivers still verify every imported
signed operation before it can affect the reduced keychain or authorization
views.
Two-Machine Smoke Test
Use two terminals or machines with different GETH_HOME values.
Owner machine:
export GETH_HOME=/tmp/geth-owner
geth init --admin-key ~/.ssh/id_ed25519_sk.pub \
--signing-key ~/.ssh/id_ed25519_sk \
--node-name owner
geth daemon run
geth peer export --out /tmp/owner.peer.json
New node:
export GETH_HOME=/tmp/geth-node
geth init
geth daemon run
geth keychain init --admin-key ~/.ssh/id_ed25519_sk.pub
geth peer import /tmp/owner.peer.json
geth node enroll request --node-name workstation \
--capability resource:cas:local=cas.fetch \
--capability resource:kv:notes=kv.read \
--capability resource:document:notes=document.read \
--capability resource:db:notes=db.sync \
--capability resource:ssh-proxy:local=ssh_proxy.connect \
--out /tmp/workstation-enrollment.json
geth node enroll submit owner --path /tmp/workstation-enrollment.json
Owner machine:
geth node enroll list --status pending
geth node enroll approve <request-id> --signing-key ~/.ssh/id_ed25519_sk
geth node grant workstation resource:ssh-proxy:local ssh_proxy.connect \
--signing-key ~/.ssh/id_ed25519_sk
echo "hello geth" > /tmp/hello-geth.txt
geth cas add /tmp/hello-geth.txt
geth kv create notes
geth kv set notes greeting "hello geth"
geth document create notes
geth document set notes '{"greeting":"hello geth"}'
geth ssh cert requests
New node:
geth sync now owner
geth sync status --json
geth peer ping owner
geth cas fetch owner <hash-from-owner-cas-add>
geth kv sync owner notes
geth kv get notes greeting
geth document sync owner notes
geth document get notes
geth db add notes /path/to/crsqlite-notes.sqlite
geth db sync owner notes
geth ssh cert request --public-key ~/.ssh/id_ed25519.pub --principal "$USER"
geth ssh cert sync owner
geth ssh proxy owner
If a command fails, the daemon error includes a next: line for common recovery
paths such as importing a peer card, running auth explain, granting a missing
capability, or creating/registering a missing resource.
CI, Security, And Releases
The automation-facing compatibility policy is documented in
docs/compatibility.md. It defines the intended
stability rules for CLI commands, --json output, the local control JSONL
protocol, Iroh peer wire protocols, SQLite metadata, and signed operation logs.
Command-family stability levels are tracked in
docs/command-stability.md.
Per-resource conflict behavior is tracked in
docs/conflict-semantics.md.
GitHub Actions workflows live under .github/workflows/:
ci.ymlruns formatting, clippy, docs,cargo check, and workspace tests on Linux, macOS, and Windows. Cross-platform test jobs setGETH_TEST_SKIP_IROH=1so deterministic unit and integration coverage stays stable across host networking differences.ci.ymlalso runs a required Ubuntu Iroh integration smoke job for the live daemon-to-daemon paths, including peer control, sync, native CAS/KV/pubsub, pipe forwarding, SSH proxy handshakes, and overlay ALPN authorization.security.ymlruns RustSeccargo auditon pushes, pull requests, manual dispatch, and a weekly schedule.codeql.ymlbuilds the Rust workspace for GitHub CodeQL analysis.dependency-review.ymlblocks pull requests that introduce vulnerable dependency changes at moderate severity or higher.release.ymlbuilds release archives for Linux, Intel/Apple Silicon macOS, and Windows, includes README/docs/license files, creates individual and aggregate SHA-256 checksums, smoke-tests the packaged binary through the release installers, and publishes them onv*tags or manual dispatch.docs/release-support-policy.mddefines supported platforms, compatibility expectations, security update handling, and the user-level service boundary..github/dependabot.ymlopens weekly Cargo and GitHub Actions update PRs.
Local equivalents remain:
cargo fmt --all -- --check
cargo check --workspace --all-targets
cargo clippy --workspace --all-targets -- -D warnings
GETH_TEST_SKIP_IROH=1 cargo test --workspace
Run the Iroh-heavy tests without GETH_TEST_SKIP_IROH when working on endpoint,
peer-card, sync, overlay, or remote module behavior.
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.