2026-05-15 15:08:20 +02:00
use anyhow ::{ Context , Result , bail } ;
2026-05-20 13:57:14 +02:00
use base64 ::Engine ;
2026-05-23 02:40:03 +02:00
use clap ::{ Args , CommandFactory , Parser , Subcommand , ValueEnum } ;
use clap_complete ::{ Shell , generate } ;
2026-05-15 15:08:20 +02:00
use geth_config ::GethPaths ;
use geth_control ::{ ControlRequest , ControlResponse } ;
2026-05-16 00:17:08 +02:00
use geth_node ::service ::{ ServiceInstallOptions , ServiceManager , ServiceReport } ;
2026-05-23 02:40:03 +02:00
use std ::io ::{ Read , stdout } ;
2026-05-15 15:08:20 +02:00
use std ::path ::PathBuf ;
2026-05-23 01:17:30 +02:00
const TOP_LEVEL_AFTER_HELP : & str = r #" Common starts:
geth guide init
geth guide owner - setup
geth guide overlay
2026-05-23 02:40:03 +02:00
geth guide completions
2026-05-23 01:17:30 +02:00
geth init
geth init - - admin - key ~ / . ssh / id_ed25519_sk . pub - - signing - key ~ / . ssh / id_ed25519_sk - - node - name laptop
geth daemon run
geth status
Use ` geth < command > - - help ` for command - specific examples . " #;
const INIT_LONG_ABOUT : & str = r #" Initialize local geth state.
With no owner options , ` geth init ` creates local directories , config , metadata
store , and the daemon agent identity . This is enough for local CAS / KV / document
testing and for later enrollment into an owner ' s mesh .
Owner setup is SSH - admin - rooted . ` - - admin - key ` is the OpenSSH public key that is
recorded as an admin trust anchor . ` - - signing - key ` is the matching private SSH
key path used immediately through ` ssh - keygen - Y sign ` to sign the initial
keychain / auth statements . For security - key / YubiKey keys , use the private key
stub path such as ` ~ / . ssh / id_ed25519_sk ` ; ssh - keygen / ssh - agent will trigger the
hardware - backed signing flow .
If any owner setup option is used ( ` - - admin - key ` , ` - - signing - key ` , ` - - owner ` ,
` - - node - name ` , or ` - - capability ` ) , both ` - - admin - key ` and ` - - signing - key ` are
required so geth never creates unsigned owner / device / node statements by
accident . " #;
const INIT_AFTER_HELP : & str = r #" Examples:
# Local - only node for development or later enrollment :
geth init
# Owner / admin node using an SSH or YubiKey - backed admin key :
geth init \
- - admin - key ~ / . ssh / id_ed25519_sk . pub \
- - signing - key ~ / . ssh / id_ed25519_sk \
- - owner eric \
- - node - name laptop
# Owner node with initial resource grants :
geth init \
- - admin - key ~ / . ssh / id_ed25519 . pub \
- - signing - key ~ / . ssh / id_ed25519 \
- - node - name laptop \
- - capability resource :cas :local = cas . fetch \
- - capability resource :ssh - proxy :local = ssh_proxy . connect
Key paths :
- - admin - key OpenSSH public key , usually * . pub . Stored as the trust anchor .
- - signing - key Matching private key or security - key stub . Used to sign init ops .
Related :
geth guide owner - setup
geth keychain status
geth node list " #;
const GUIDE_INDEX : & str = r #" Usage: geth guide <topic>
Topics :
init Local - only init versus owner / admin init .
owner - setup First owner node with SSH / YubiKey admin trust .
enrollment Add another node / device to the owner mesh .
keys Meaning of - - admin - key and - - signing - key .
overlay Optional Iroh overlay network planning .
service Install and manage geth as a user service .
2026-05-23 02:40:03 +02:00
completions Shell completion installation examples .
2026-05-23 01:17:30 +02:00
smoke - test Minimal commands to verify a node and daemon . " #;
const GUIDE_INIT : & str = r #" geth init has two modes.
Local - only :
geth init
Creates GETH_HOME , config . toml , geth . sqlite , CAS directories , and a local agent
identity . Use this for local testing or for a node that will later request
enrollment into an owner ' s mesh .
Owner / admin :
geth init - - admin - key ~ / . ssh / id_ed25519_sk . pub - - signing - key ~ / . ssh / id_ed25519_sk - - node - name laptop
This records signed owner , device , node , and agent bindings . Use it on the
machine where you control the admin SSH / YubiKey key . Once initialized , inspect :
geth keychain status
geth node list
" #;
const GUIDE_OWNER_SETUP : & str = r #" Owner setup flow:
1. Pick or create an SSH admin key . Security - key / YubiKey - backed OpenSSH keys are
supported through ssh - keygen :
ssh - keygen - t ed25519 - sk - f ~ / . ssh / id_ed25519_sk
2. Initialize the owner node :
geth init \
- - admin - key ~ / . ssh / id_ed25519_sk . pub \
- - signing - key ~ / . ssh / id_ed25519_sk \
- - owner eric \
- - node - name owner - laptop
3. Start the daemon and export a peer card :
geth daemon run
geth peer export - - out / tmp / owner . peer . json
` - - admin - key ` is public and replicated as the admin trust anchor .
` - - signing - key ` is private and only used locally to sign canonical init ops .
" #;
const GUIDE_ENROLLMENT : & str = r #" Add another node/device:
On the new node :
geth init
geth keychain init - - admin - key ~ / . ssh / id_ed25519_sk . pub
geth peer import / tmp / owner . peer . json
geth node enroll request - - node - name workstation - - out / tmp / workstation - enrollment . json
geth node enroll submit owner - - path / tmp / workstation - enrollment . json
On the owner / YubiKey machine :
geth node enroll list
geth node enroll approve < request - id > - - signing - key ~ / . ssh / id_ed25519_sk
Back on the new node :
geth sync now owner
geth node list
Enrollment approval records signed keychain / auth operations . Discovery and peer
cards alone never grant trust or capabilities .
" #;
const GUIDE_KEYS : & str = r #" Key terminology:
- - admin - key
OpenSSH public key path , usually ending in . pub . This key is recorded in the
geth keychain as an admin trust anchor . It is safe to distribute .
- - signing - key
Matching private key path , or the OpenSSH security - key / YubiKey stub path . geth
shells out to ssh - keygen - Y sign with explicit namespaces to sign canonical
geth keychain / auth operations . The private key is not copied into geth state .
2026-05-27 00:59:52 +02:00
Signing sources :
Local key file :
- - signing - key ~ / . ssh / id_ed25519 - - admin - key ~ / . ssh / id_ed25519 . pub
Encrypted key file :
Load it into ssh - agent with ` ssh - add ~ / . ssh / id_ed25519 ` , then sign through
the agent by passing the public key path :
- - signing - key ~ / . ssh / id_ed25519 . pub
FIDO / YubiKey OpenSSH key :
Use the security - key stub or load it into ssh - agent :
- - signing - key ~ / . ssh / id_ed25519_sk - - admin - key ~ / . ssh / id_ed25519_sk . pub
PKCS #11 :
Direct ssh - keygen - Y signing does not expose a portable - D provider option .
Load the token key into ssh - agent with ` ssh - add - s < provider > ` , then pass
the public key path with - - signing - key .
2026-05-23 01:17:30 +02:00
Examples :
Software key :
- - admin - key ~ / . ssh / id_ed25519 . pub - - signing - key ~ / . ssh / id_ed25519
YubiKey / FIDO OpenSSH key :
- - admin - key ~ / . ssh / id_ed25519_sk . pub - - signing - key ~ / . ssh / id_ed25519_sk
2026-05-27 00:59:52 +02:00
Generate the active OpenSSH allowed_signers projection :
geth keychain allowed - signers - - out ~ / . config / geth / allowed_signers
Sign an arbitrary authorized_keys snapshot with an active admin key :
geth keychain sign - file - - in ~ / . ssh / authorized_keys - - out ~ / . ssh / authorized_keys . sig - - signing - key ~ / . ssh / id_ed25519_sk
Verify the snapshot signature against the current keychain trust root :
geth keychain verify - file - - in ~ / . ssh / authorized_keys - - signature ~ / . ssh / authorized_keys . sig
2026-05-23 01:17:30 +02:00
If you use - - owner , - - node - name , or - - capability during init , geth requires both
key options because those fields create signed owner / device / node statements .
" #;
const GUIDE_SERVICE : & str = r #" Install geth as a user service:
geth daemon service install - - start
geth daemon service status
geth daemon service stop
geth daemon service start
geth daemon service uninstall
Service installation targets user service managers , not system services :
Linux : systemd - - user
macOS : launchd user agent
Windows : current - user scheduled task
Preview definitions without installing :
geth daemon service print
" #;
2026-05-23 02:40:03 +02:00
const GUIDE_COMPLETIONS : & str = r #" Shell completions:
geth can print completions for bash , zsh , fish , PowerShell , and elvish . The
generated scripts are produced from the same Clap command tree as ` geth - - help ` ,
so subcommands and flags stay in sync with the executable .
Bash :
mkdir - p ~ / . local / share / bash - completion / completions
geth completions bash > ~ / . local / share / bash - completion / completions / geth
Zsh :
mkdir - p ~ / . zfunc
geth completions zsh > ~ / . zfunc / _geth
# Ensure ~ / . zfunc is in fpath , then run : compinit
Fish :
mkdir - p ~ / . config / fish / completions
geth completions fish > ~ / . config / fish / completions / geth . fish
PowerShell :
geth completions powershell > geth . ps1
# Source geth . ps1 from your PowerShell profile .
Elvish :
mkdir - p ~ / . elvish / lib
geth completions elvish > ~ / . elvish / lib / geth . elv
" #;
2026-05-23 01:17:30 +02:00
const GUIDE_OVERLAY : & str = r #" Optional overlay network:
geth has an experimental overlay - network design inspired by iroh - lan . The
intended future runtime is a private L3 - style packet overlay carried over geth ' s
daemon - owned Iroh endpoint .
Current prototype commands :
geth overlay status
geth overlay plan home
geth overlay plan home - - cidr 172.2 2. 0.0 / 24
geth overlay join home - - secret < resource - secret >
2026-05-23 02:08:51 +02:00
geth overlay interface - plan home - - platform linux
geth overlay up home
geth overlay down home
geth overlay send home < node > - - packet - base64 < ipv4 - packet >
geth overlay recv home
2026-05-23 01:17:30 +02:00
geth overlay leave home
Current limits :
2026-05-23 02:08:51 +02:00
- overlay up creates a real TUN / Wintun - style L3 device and usually needs
privileges or host network entitlements
- host network changes are explicit opt - in only
2026-05-23 01:17:30 +02:00
- discovery can suggest peers , but never grants overlay access
- overlay access must be resource - authorized with overlay . join / overlay . route
- all overlay packets must be carried over Iroh , not SSH or another transport
2026-05-30 12:24:40 +02:00
- release / platform notes live in docs / overlay - platforms . md
2026-05-23 02:08:51 +02:00
Bearer invite flow :
geth resource create overlay home
geth secret bearer create resource :overlay :home - - capability overlay . join
geth overlay join home - - secret < bearer - token >
2026-05-23 01:17:30 +02:00
" #;
const GUIDE_SMOKE_TEST : & str = r #" Minimal smoke test:
Terminal 1 :
export GETH_HOME = " $(mktemp -d) "
geth init
geth daemon run
Terminal 2 :
export GETH_HOME = " <same dir> "
geth status
geth node id
echo " hello geth " > / tmp / hello - geth . txt
geth cas add / tmp / hello - geth . txt
geth cas list
geth keychain status
For two - node owner / enrollment testing , use :
geth guide owner - setup
geth guide enrollment
" #;
2026-05-15 15:08:20 +02:00
#[ derive(Debug, Parser) ]
2026-05-23 01:17:30 +02:00
#[ command(
name = " geth " ,
about = " Personal local-first Iroh mesh runtime " ,
after_long_help = TOP_LEVEL_AFTER_HELP
) ]
2026-05-15 15:08:20 +02:00
pub struct Cli {
2026-05-23 01:17:30 +02:00
#[ arg(long, global = true, help = " Print machine-readable JSON output " ) ]
2026-05-15 15:08:20 +02:00
pub json : bool ,
2026-05-23 01:17:30 +02:00
#[ arg(
long ,
global = true ,
help = " Print newline-delimited JSON output for streaming commands "
) ]
2026-05-15 15:08:20 +02:00
pub jsonl : bool ,
#[ command(subcommand) ]
pub command : Command ,
}
#[ derive(Debug, Subcommand) ]
pub enum Command {
2026-05-23 01:17:30 +02:00
Guide {
#[ arg(value_enum) ]
topic : Option < GuideTopic > ,
} ,
2026-05-23 02:40:03 +02:00
Completions {
#[ arg(value_enum) ]
shell : Shell ,
} ,
2026-05-23 01:17:30 +02:00
#[ command(long_about = INIT_LONG_ABOUT, after_long_help = INIT_AFTER_HELP) ]
2026-05-21 11:29:29 +02:00
Init {
2026-05-23 01:17:30 +02:00
#[ arg(
long ,
value_name = " OPENSSH_PUBLIC_KEY " ,
help = " OpenSSH public key recorded as the owner/admin trust anchor " ,
long_help = " Path to the OpenSSH public key recorded as the owner/admin trust anchor, usually ~/.ssh/<key>.pub. This key is public and replicated in the geth keychain. "
) ]
2026-05-21 11:29:29 +02:00
admin_key : Option < PathBuf > ,
2026-05-23 01:17:30 +02:00
#[ arg(
long ,
value_name = " OPENSSH_PRIVATE_KEY " ,
help = " Matching private key or YubiKey/FIDO stub used to sign init statements " ,
long_help = " Path to the matching private OpenSSH key, or security-key/YubiKey stub such as ~/.ssh/id_ed25519_sk. geth uses ssh-keygen -Y sign with explicit geth namespaces; it does not copy the private key into geth state. "
) ]
2026-05-21 11:29:29 +02:00
signing_key : Option < PathBuf > ,
2026-05-23 01:17:30 +02:00
#[ arg(
long ,
default_value = " owner " ,
help = " Owner/user display name recorded during owner init "
) ]
2026-05-21 11:29:29 +02:00
owner : String ,
2026-05-23 01:17:30 +02:00
#[ arg(
long ,
default_value = " local " ,
help = " Friendly node name recorded during owner init "
) ]
2026-05-21 11:29:29 +02:00
node_name : String ,
2026-05-23 01:17:30 +02:00
#[ arg(
long = " capability " ,
value_name = " RESOURCE=CAPABILITY " ,
help = " Initial capability grant for this node; repeatable "
) ]
2026-05-21 11:29:29 +02:00
capabilities : Vec < String > ,
} ,
2026-05-15 15:08:20 +02:00
Daemon {
#[ command(subcommand) ]
command : DaemonCommand ,
} ,
Status ,
2026-05-21 19:31:05 +02:00
Sync {
#[ command(subcommand) ]
command : SyncCommand ,
} ,
2026-05-15 15:08:20 +02:00
Node {
#[ command(subcommand) ]
command : NodeCommand ,
} ,
2026-05-18 04:03:52 +02:00
Peer {
#[ command(subcommand) ]
command : PeerCommand ,
} ,
2026-05-23 01:17:30 +02:00
Overlay {
#[ command(subcommand) ]
command : OverlayCommand ,
} ,
2026-05-15 15:08:20 +02:00
Resource {
#[ command(subcommand) ]
command : ResourceCommand ,
} ,
Keychain {
#[ command(subcommand) ]
command : KeychainCommand ,
} ,
Auth {
#[ command(subcommand) ]
command : AuthCommand ,
} ,
Secret {
#[ command(subcommand) ]
command : SecretCommand ,
} ,
Cas {
#[ command(subcommand) ]
command : CasCommand ,
} ,
Kv {
#[ command(subcommand) ]
command : KvCommand ,
} ,
Pubsub {
#[ command(subcommand) ]
command : PubsubCommand ,
} ,
Pipe {
#[ command(subcommand) ]
command : PipeCommand ,
} ,
Db {
#[ command(subcommand) ]
command : DbCommand ,
} ,
Document {
#[ command(subcommand) ]
command : DocumentCommand ,
} ,
Ssh {
#[ command(subcommand) ]
command : SshCommand ,
} ,
}
2026-05-23 01:17:30 +02:00
#[ derive(Clone, Debug, ValueEnum) ]
pub enum GuideTopic {
Init ,
OwnerSetup ,
Enrollment ,
Keys ,
Overlay ,
Service ,
2026-05-23 02:40:03 +02:00
Completions ,
2026-05-23 01:17:30 +02:00
SmokeTest ,
}
2026-05-15 15:08:20 +02:00
#[ derive(Debug, Subcommand) ]
pub enum DaemonCommand {
Run ,
2026-05-16 00:17:08 +02:00
Service {
#[ command(subcommand) ]
command : ServiceCommand ,
} ,
}
#[ derive(Debug, Subcommand) ]
pub enum ServiceCommand {
Install {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
#[ arg(long) ]
bin : Option < PathBuf > ,
#[ arg(long) ]
start : bool ,
} ,
Uninstall {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
} ,
Start {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
} ,
Stop {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
} ,
Status {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
} ,
Print {
#[ arg(long, default_value = " auto " ) ]
manager : String ,
#[ arg(long) ]
bin : Option < PathBuf > ,
} ,
2026-05-15 15:08:20 +02:00
}
2026-05-21 19:31:05 +02:00
#[ derive(Debug, Subcommand) ]
pub enum SyncCommand {
Status ,
Now { node : Option < String > } ,
}
2026-05-15 15:08:20 +02:00
#[ derive(Debug, Subcommand) ]
pub enum NodeCommand {
Id ,
Status ,
2026-05-21 11:29:29 +02:00
List ,
2026-05-21 18:01:38 +02:00
Enroll {
#[ command(subcommand) ]
command : NodeEnrollCommand ,
} ,
2026-05-21 11:29:29 +02:00
Rename {
node : String ,
name : String ,
#[ arg(long) ]
signing_key : Option < PathBuf > ,
} ,
Revoke {
node : String ,
#[ arg(long) ]
signing_key : Option < PathBuf > ,
} ,
Grant {
node : String ,
resource : String ,
capability : String ,
#[ arg(long) ]
grant_id : Option < String > ,
2026-05-21 18:15:10 +02:00
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
2026-05-21 11:29:29 +02:00
} ,
RevokeGrant {
resource : String ,
grant_id : String ,
2026-05-21 18:15:10 +02:00
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
} ,
EndpointAdd {
node : String ,
endpoint : String ,
#[ arg(long) ]
signing_key : PathBuf ,
} ,
EndpointRevoke {
node : String ,
endpoint : String ,
#[ arg(long) ]
signing_key : PathBuf ,
2026-05-21 11:29:29 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
2026-05-21 18:01:38 +02:00
#[ derive(Debug, Subcommand) ]
pub enum NodeEnrollCommand {
Request {
#[ arg(long) ]
node_name : String ,
#[ arg(long = " capability " ) ]
capabilities : Vec < String > ,
#[ arg(long) ]
reason : Option < String > ,
#[ arg(long) ]
out : Option < PathBuf > ,
} ,
Submit {
owner_node : String ,
#[ arg(long) ]
request_id : Option < String > ,
#[ arg(long) ]
path : Option < PathBuf > ,
} ,
Import {
path : PathBuf ,
} ,
List {
#[ arg(long) ]
status : Option < String > ,
} ,
Approve {
request_id : String ,
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
#[ arg(long) ]
node_name : Option < String > ,
#[ arg(long = " capability " ) ]
capabilities : Vec < String > ,
} ,
Sync {
owner_node : String ,
} ,
}
2026-05-18 04:03:52 +02:00
#[ derive(Debug, Subcommand) ]
pub enum PeerCommand {
Export {
#[ arg(long) ]
out : Option < PathBuf > ,
} ,
Import {
path : PathBuf ,
} ,
List ,
2026-05-18 12:09:50 +02:00
Ping {
node : String ,
} ,
2026-05-18 17:01:50 +02:00
AuthCheck {
node : String ,
resource : String ,
capability : String ,
} ,
2026-05-18 04:03:52 +02:00
}
2026-05-23 01:17:30 +02:00
#[ derive(Debug, Subcommand) ]
pub enum OverlayCommand {
Status ,
Plan {
name : String ,
#[ arg(long) ]
cidr : Option < String > ,
} ,
Join {
name : String ,
#[ arg(long) ]
secret : String ,
#[ arg(long) ]
cidr : Option < String > ,
} ,
Leave {
name : String ,
} ,
2026-05-23 02:08:51 +02:00
InterfacePlan {
name : String ,
#[ arg(long) ]
platform : Option < String > ,
} ,
Up {
name : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
#[ arg(long) ]
mtu : Option < u16 > ,
} ,
Down {
name : String ,
} ,
Peers {
name : String ,
} ,
Send {
name : String ,
node : String ,
#[ arg(long) ]
packet_base64 : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
Recv {
name : String ,
#[ arg(long) ]
peek : bool ,
} ,
2026-05-23 01:17:30 +02:00
}
2026-05-15 15:08:20 +02:00
#[ derive(Debug, Subcommand) ]
pub enum ResourceCommand {
List ,
Create { kind : String , name : String } ,
}
#[ derive(Debug, Subcommand) ]
pub enum KeychainCommand {
2026-05-16 16:34:20 +02:00
Init {
#[ arg(long) ]
admin_key : Option < PathBuf > ,
2026-05-19 16:04:20 +02:00
#[ arg(long) ]
signing_key : Option < PathBuf > ,
2026-05-16 16:34:20 +02:00
} ,
2026-05-15 15:08:20 +02:00
Status ,
2026-05-26 18:53:20 +02:00
AdminAdd {
#[ arg(long) ]
admin_key : PathBuf ,
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
principal : Option < String > ,
#[ arg(long) ]
valid_after_ms : Option < i64 > ,
#[ arg(long) ]
valid_before_ms : Option < i64 > ,
} ,
AdminRevoke {
key : String ,
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
} ,
2026-05-27 00:59:52 +02:00
AllowedSigners {
#[ arg(long) ]
out : Option < PathBuf > ,
} ,
SignFile {
#[ arg(long = " in " ) ]
input : PathBuf ,
#[ arg(long) ]
out : Option < PathBuf > ,
#[ arg(long) ]
namespace : Option < String > ,
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
} ,
VerifyFile {
#[ arg(long = " in " ) ]
input : PathBuf ,
#[ arg(long) ]
signature : PathBuf ,
#[ arg(long) ]
namespace : Option < String > ,
#[ arg(long) ]
allowed_signers : Option < PathBuf > ,
#[ arg(long) ]
principal : Option < String > ,
} ,
Sigchain {
#[ arg(long) ]
out : Option < PathBuf > ,
} ,
PublishBundle {
#[ arg(long) ]
out : PathBuf ,
#[ arg(long, default_value = geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL) ]
base_url : String ,
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
#[ arg(long = " snapshot " ) ]
snapshots : Vec < String > ,
} ,
VerifySigchain {
#[ arg(long = " in " ) ]
input : PathBuf ,
} ,
ImportSigchain {
#[ arg(long = " in " ) ]
input : PathBuf ,
} ,
VerifyCheckpoint {
#[ arg(long) ]
checkpoint : PathBuf ,
#[ arg(long) ]
signature : PathBuf ,
#[ arg(long) ]
sigchain : PathBuf ,
#[ arg(long) ]
allowed_signers : PathBuf ,
#[ arg(long) ]
base_url : Option < String > ,
#[ arg(long) ]
principal : Option < String > ,
} ,
Fetch {
#[ arg(long, default_value = geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL) ]
url : String ,
#[ arg(long) ]
out : Option < PathBuf > ,
#[ arg(long) ]
import : bool ,
} ,
Explain {
op_id : String ,
} ,
ExplainSigner {
key : String ,
} ,
2026-05-26 18:53:20 +02:00
Verify ,
2026-05-21 11:29:29 +02:00
Sync {
node : String ,
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum AuthCommand {
Explain {
subject : String ,
resource : String ,
capability : String ,
} ,
2026-05-21 18:01:38 +02:00
Sync {
node : String ,
} ,
2026-05-16 16:32:03 +02:00
Grant {
subject : String ,
resource : String ,
capability : String ,
#[ arg(long) ]
grant_id : Option < String > ,
2026-05-21 18:15:10 +02:00
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
2026-05-16 16:32:03 +02:00
} ,
Revoke {
resource : String ,
grant_id : String ,
2026-05-21 18:15:10 +02:00
#[ arg(long) ]
signing_key : PathBuf ,
#[ arg(long) ]
admin_key : Option < PathBuf > ,
2026-05-16 16:32:03 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum SecretCommand {
Status ,
2026-05-17 02:58:58 +02:00
Create {
resource : String ,
} ,
Rotate {
resource : String ,
} ,
Bearer {
#[ command(subcommand) ]
command : SecretBearerCommand ,
} ,
}
#[ derive(Debug, Subcommand) ]
pub enum SecretBearerCommand {
Create {
resource : String ,
#[ arg(long = " capability " , required = true) ]
capabilities : Vec < String > ,
#[ arg(long) ]
expires_at_ms : Option < i64 > ,
} ,
List ,
2026-05-19 19:08:08 +02:00
Challenge {
resource : String ,
#[ arg(long = " capability " , required = true) ]
capabilities : Vec < String > ,
} ,
Prove {
2026-05-20 13:10:34 +02:00
token : String ,
2026-05-19 19:08:08 +02:00
resource : String ,
#[ arg(long) ]
nonce : String ,
#[ arg(long = " capability " , required = true) ]
capabilities : Vec < String > ,
} ,
Verify {
2026-05-20 13:10:34 +02:00
token : String ,
2026-05-19 19:08:08 +02:00
resource : String ,
#[ arg(long) ]
nonce : String ,
#[ arg(long) ]
response : String ,
#[ arg(long = " capability " , required = true) ]
capabilities : Vec < String > ,
} ,
2026-05-17 02:58:58 +02:00
Revoke {
resource : String ,
2026-05-20 13:10:34 +02:00
bearer_id : String ,
2026-05-17 02:58:58 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum CasCommand {
Add {
path : PathBuf ,
} ,
2026-05-21 01:35:00 +02:00
AddPrivate {
resource : String ,
path : PathBuf ,
} ,
2026-05-15 15:08:20 +02:00
Get {
hash : String ,
#[ arg(long) ]
out : PathBuf ,
} ,
2026-05-21 01:35:00 +02:00
GetPrivate {
resource : String ,
hash : String ,
#[ arg(long) ]
out : PathBuf ,
} ,
2026-05-18 17:18:25 +02:00
Fetch {
node : String ,
hash : String ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 17:18:25 +02:00
} ,
2026-05-15 15:08:20 +02:00
Hash {
path : PathBuf ,
} ,
Has {
hash : String ,
} ,
2026-05-16 16:36:35 +02:00
Pin {
hash : String ,
} ,
Unpin {
hash : String ,
} ,
2026-05-16 21:10:25 +02:00
Cleanup {
#[ arg(long) ]
dry_run : bool ,
} ,
2026-05-19 15:37:02 +02:00
Providers {
hash : String ,
} ,
2026-05-15 15:08:20 +02:00
List ,
2026-05-18 03:50:09 +02:00
Root {
#[ command(subcommand) ]
command : CasRootCommand ,
} ,
2026-05-18 03:57:26 +02:00
Conflict {
#[ command(subcommand) ]
command : CasConflictCommand ,
} ,
2026-05-18 03:50:09 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum CasRootCommand {
2026-05-20 13:22:40 +02:00
Add {
name : String ,
path : PathBuf ,
} ,
2026-05-18 03:50:09 +02:00
List ,
2026-05-20 13:22:40 +02:00
Scan {
name : String ,
} ,
Sync {
node : String ,
name : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
2026-05-20 13:30:57 +02:00
Apply {
source : String ,
#[ arg(long) ]
to : PathBuf ,
#[ arg(long) ]
dry_run : bool ,
} ,
2026-05-15 15:08:20 +02:00
}
2026-05-18 03:57:26 +02:00
#[ derive(Debug, Subcommand) ]
pub enum CasConflictCommand {
Record {
root : String ,
path : String ,
kind : String ,
#[ arg(long) ]
detail : String ,
#[ arg(long) ]
base_tree : Option < String > ,
#[ arg(long) ]
local_tree : Option < String > ,
#[ arg(long) ]
remote_tree : Option < String > ,
} ,
List {
#[ arg(long) ]
root : Option < String > ,
} ,
Resolve {
conflict_id : String ,
resolution : String ,
#[ arg(long) ]
note : Option < String > ,
} ,
}
2026-05-15 15:08:20 +02:00
#[ derive(Debug, Subcommand) ]
pub enum KvCommand {
Create {
name : String ,
} ,
Set {
name : String ,
key : String ,
value : String ,
2026-05-18 04:06:41 +02:00
#[ arg(long) ]
subject : Option < String > ,
2026-05-15 15:08:20 +02:00
} ,
Get {
name : String ,
key : String ,
} ,
2026-05-18 18:36:03 +02:00
Sync {
node : String ,
name : String ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 18:36:03 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum PubsubCommand {
2026-05-18 18:41:04 +02:00
Pub {
topic : String ,
message : String ,
#[ arg(long) ]
node : Option < String > ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 18:41:04 +02:00
} ,
Sub {
topic : String ,
2026-05-19 15:28:48 +02:00
#[ arg(long) ]
node : Option < String > ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 18:41:04 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum PipeCommand {
2026-05-18 18:45:10 +02:00
Listen {
name : String ,
2026-05-19 19:21:42 +02:00
#[ arg(long) ]
node : Option < String > ,
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 18:45:10 +02:00
} ,
Connect {
target : String ,
#[ arg(long) ]
node : Option < String > ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 18:45:10 +02:00
} ,
2026-05-21 01:12:01 +02:00
ForwardTcp {
#[ arg(long) ]
listen : String ,
#[ arg(long) ]
node : String ,
#[ arg(long) ]
target : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
2026-05-21 01:15:51 +02:00
ForwardUnix {
#[ arg(long) ]
listen : PathBuf ,
#[ arg(long) ]
node : String ,
#[ arg(long) ]
target : PathBuf ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
2026-05-20 13:57:14 +02:00
Send {
target : String ,
2026-05-20 13:59:41 +02:00
message : Option < String > ,
#[ arg(long = " in " , value_name = " PATH " ) ]
input : Option < PathBuf > ,
2026-05-20 13:57:14 +02:00
#[ arg(long) ]
node : Option < String > ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
Recv {
name : String ,
#[ arg(long) ]
peek : bool ,
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum DbCommand {
2026-05-17 20:32:36 +02:00
Add {
name : String ,
path : PathBuf ,
} ,
Status {
name : String ,
} ,
Changes {
name : String ,
#[ arg(long) ]
after_db_version : Option < i64 > ,
#[ arg(long, default_value_t = 100) ]
limit : u32 ,
} ,
2026-05-18 22:11:57 +02:00
Sync {
node : String ,
name : String ,
#[ arg(long, default_value_t = 100) ]
limit : u32 ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 22:11:57 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum DocumentCommand {
2026-05-19 19:16:30 +02:00
Create {
name : String ,
} ,
Status {
name : String ,
} ,
Set {
name : String ,
state_json : String ,
} ,
Get {
name : String ,
} ,
Sync {
node : String ,
name : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum SshCommand {
2026-05-16 00:17:08 +02:00
Proxy {
node : String ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-21 01:49:48 +02:00
AdminShell {
node : String ,
command : String ,
#[ arg(long) ]
bearer_secret : Option < String > ,
} ,
2026-05-16 00:17:08 +02:00
Cert {
#[ command(subcommand) ]
command : SshCertCommand ,
} ,
Revocation {
#[ command(subcommand) ]
command : SshRevocationCommand ,
} ,
}
#[ derive(Debug, Subcommand) ]
pub enum SshCertCommand {
Request {
#[ arg(long) ]
public_key : PathBuf ,
#[ arg(long, default_value = " user " ) ]
kind : String ,
#[ arg(long = " principal " , required = true) ]
principals : Vec < String > ,
#[ arg(long) ]
valid_for : Option < String > ,
#[ arg(long) ]
renewal_of : Option < String > ,
#[ arg(long) ]
reason : Option < String > ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
subject : Option < String > ,
} ,
Requests {
#[ arg(long) ]
subject : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
Approve {
request_id : String ,
#[ arg(long) ]
ca_key : PathBuf ,
#[ arg(long) ]
valid_for : Option < String > ,
#[ arg(long) ]
serial : Option < u64 > ,
#[ arg(long) ]
out : Option < PathBuf > ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
2026-05-19 15:56:47 +02:00
sign : bool ,
#[ arg(long) ]
2026-05-19 15:44:13 +02:00
subject : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
Import {
request_id : String ,
#[ arg(long) ]
cert : PathBuf ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
subject : Option < String > ,
} ,
List {
#[ arg(long) ]
subject : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-18 17:24:10 +02:00
Sync {
node : String ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 17:24:10 +02:00
} ,
2026-05-16 00:17:08 +02:00
}
#[ derive(Debug, Subcommand) ]
pub enum SshRevocationCommand {
Add {
kind : String ,
target : String ,
#[ arg(long) ]
reason : Option < String > ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
subject : Option < String > ,
} ,
List {
#[ arg(long) ]
subject : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
Export {
#[ arg(long) ]
out : PathBuf ,
2026-05-17 18:29:47 +02:00
#[ arg(long, default_value = " jsonl " ) ]
format : String ,
2026-05-18 11:51:12 +02:00
#[ arg(long) ]
ca_public : Option < PathBuf > ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
subject : Option < String > ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-18 11:56:42 +02:00
Import {
path : PathBuf ,
#[ arg(long, default_value = " jsonl " ) ]
format : String ,
2026-05-19 15:44:13 +02:00
#[ arg(long) ]
subject : Option < String > ,
2026-05-18 11:56:42 +02:00
} ,
2026-05-18 17:24:10 +02:00
Sync {
node : String ,
2026-05-19 19:16:30 +02:00
#[ arg(long) ]
bearer_secret : Option < String > ,
2026-05-18 17:24:10 +02:00
} ,
2026-05-15 15:08:20 +02:00
}
#[ derive(Debug, Args) ]
pub struct EmptyArgs { }
pub async fn run ( ) -> Result < ( ) > {
let cli = Cli ::parse ( ) ;
2026-05-23 02:40:03 +02:00
if let Command ::Completions { shell } = & cli . command {
print_completions ( * shell ) ;
return Ok ( ( ) ) ;
}
2026-05-15 15:08:20 +02:00
let paths = GethPaths ::resolve ( ) . context ( " resolve geth paths " ) ? ;
match cli . command {
2026-05-23 01:17:30 +02:00
Command ::Guide { topic } = > {
print_guide ( topic , cli . json | | cli . jsonl ) ? ;
}
2026-05-21 11:29:29 +02:00
Command ::Init {
admin_key ,
signing_key ,
owner ,
node_name ,
capabilities ,
} = > {
let node = geth_node ::init_owned_node (
& paths ,
geth_node ::InitOwnerOptions {
admin_key_path : admin_key ,
signing_key_path : signing_key ,
owner_name : owner ,
node_name ,
capabilities ,
} ,
)
. context ( " initialize geth node " ) ? ;
2026-05-15 15:08:20 +02:00
println! ( " initialized geth home: {} " , node . paths . home ( ) . display ( ) ) ;
println! ( " agent: {} " , node . agent_id ) ;
println! ( " node: {} " , node . node_id ) ;
}
Command ::Daemon {
command : DaemonCommand ::Run ,
} = > {
geth_node ::run_daemon ( paths )
. await
. context ( " run geth daemon " ) ? ;
}
2026-05-16 00:17:08 +02:00
Command ::Daemon {
command : DaemonCommand ::Service { command } ,
} = > {
let report =
run_service_command ( & paths , command ) . context ( " manage geth user service " ) ? ;
print_service_report ( report , cli . json | | cli . jsonl ) ? ;
}
2026-05-21 01:03:38 +02:00
Command ::Ssh {
command :
SshCommand ::Proxy {
node ,
bearer_secret ,
} ,
} if ! cli . json & & ! cli . jsonl = > {
geth_node ::stream_ssh_proxy ( & paths , node , bearer_secret )
. await
. context ( " stream SSH proxy through geth daemon " ) ? ;
}
2026-05-21 01:12:01 +02:00
Command ::Pipe {
command :
PipeCommand ::ForwardTcp {
listen ,
node ,
target ,
bearer_secret ,
} ,
} if ! cli . json & & ! cli . jsonl = > {
println! ( " forwarding tcp {listen} -> {node} : {target} " ) ;
geth_node ::run_tcp_forward ( & paths , listen , node , target , bearer_secret )
. await
. context ( " run TCP forward through geth daemon " ) ? ;
}
2026-05-21 01:15:51 +02:00
Command ::Pipe {
command :
PipeCommand ::ForwardUnix {
listen ,
node ,
target ,
bearer_secret ,
} ,
} if ! cli . json & & ! cli . jsonl = > {
println! (
" forwarding unix {} -> {node}:{} " ,
listen . display ( ) ,
target . display ( )
) ;
geth_node ::run_unix_forward ( & paths , listen , node , target , bearer_secret )
. await
. context ( " run Unix socket forward through geth daemon " ) ? ;
}
2026-05-15 15:08:20 +02:00
command = > {
let request = request_for_command ( command ) ? ;
let response = geth_node ::send_control ( & paths , request )
. await
. with_context ( | | {
format! ( " connect to daemon at {} " , paths . socket_path ( ) . display ( ) )
} ) ? ;
print_response ( response , cli . json | | cli . jsonl ) ? ;
}
}
Ok ( ( ) )
}
2026-05-23 01:17:30 +02:00
fn print_guide ( topic : Option < GuideTopic > , json : bool ) -> Result < ( ) > {
let ( name , body ) = match topic {
None = > ( " index " , GUIDE_INDEX ) ,
Some ( GuideTopic ::Init ) = > ( " init " , GUIDE_INIT ) ,
Some ( GuideTopic ::OwnerSetup ) = > ( " owner-setup " , GUIDE_OWNER_SETUP ) ,
Some ( GuideTopic ::Enrollment ) = > ( " enrollment " , GUIDE_ENROLLMENT ) ,
Some ( GuideTopic ::Keys ) = > ( " keys " , GUIDE_KEYS ) ,
Some ( GuideTopic ::Overlay ) = > ( " overlay " , GUIDE_OVERLAY ) ,
Some ( GuideTopic ::Service ) = > ( " service " , GUIDE_SERVICE ) ,
2026-05-23 02:40:03 +02:00
Some ( GuideTopic ::Completions ) = > ( " completions " , GUIDE_COMPLETIONS ) ,
2026-05-23 01:17:30 +02:00
Some ( GuideTopic ::SmokeTest ) = > ( " smoke-test " , GUIDE_SMOKE_TEST ) ,
} ;
if json {
println! (
" {} " ,
serde_json ::json! ( {
" topic " : name ,
" body " : body ,
} )
) ;
} else {
print! ( " {body} " ) ;
}
Ok ( ( ) )
}
2026-05-23 02:40:03 +02:00
fn print_completions ( shell : Shell ) {
let mut command = Cli ::command ( ) ;
generate ( shell , & mut command , " geth " , & mut stdout ( ) ) ;
}
2026-05-15 15:08:20 +02:00
fn request_for_command ( command : Command ) -> Result < ControlRequest > {
Ok ( match command {
2026-05-23 02:40:03 +02:00
Command ::Completions { .. } = > bail! ( " completion generation does not use the daemon " ) ,
2026-05-15 15:08:20 +02:00
Command ::Status = > ControlRequest ::Status ,
2026-05-21 19:31:05 +02:00
Command ::Sync {
command : SyncCommand ::Status ,
} = > ControlRequest ::SyncStatus ,
Command ::Sync {
command : SyncCommand ::Now { node } ,
} = > ControlRequest ::SyncNow { node } ,
2026-05-15 15:08:20 +02:00
Command ::Node {
command : NodeCommand ::Id ,
} = > ControlRequest ::NodeId ,
Command ::Node {
command : NodeCommand ::Status ,
} = > ControlRequest ::Status ,
2026-05-21 11:29:29 +02:00
Command ::Node {
command : NodeCommand ::List ,
} = > ControlRequest ::NodeList ,
2026-05-21 18:01:38 +02:00
Command ::Node {
command : NodeCommand ::Enroll { command } ,
} = > match command {
NodeEnrollCommand ::Request {
node_name ,
capabilities ,
reason ,
out ,
} = > ControlRequest ::NodeEnrollRequest {
node_name ,
capabilities ,
reason ,
out ,
} ,
NodeEnrollCommand ::Submit {
owner_node ,
request_id ,
path ,
} = > ControlRequest ::NodeEnrollSubmit {
owner_node ,
request_id ,
path ,
} ,
NodeEnrollCommand ::Import { path } = > ControlRequest ::NodeEnrollImport { path } ,
NodeEnrollCommand ::List { status } = > ControlRequest ::NodeEnrollList { status } ,
NodeEnrollCommand ::Approve {
request_id ,
signing_key ,
admin_key ,
node_name ,
capabilities ,
} = > ControlRequest ::NodeEnrollApprove {
request_id ,
signing_key_path : signing_key ,
admin_key_path : admin_key ,
node_name ,
capabilities ,
} ,
NodeEnrollCommand ::Sync { owner_node } = > ControlRequest ::NodeEnrollSync { owner_node } ,
} ,
2026-05-21 11:29:29 +02:00
Command ::Node {
command :
NodeCommand ::Rename {
node ,
name ,
signing_key ,
} ,
} = > ControlRequest ::NodeRename {
node ,
name ,
signing_key_path : signing_key ,
} ,
Command ::Node {
command : NodeCommand ::Revoke { node , signing_key } ,
} = > ControlRequest ::NodeRevoke {
node ,
signing_key_path : signing_key ,
} ,
Command ::Node {
command :
NodeCommand ::Grant {
node ,
resource ,
capability ,
grant_id ,
2026-05-21 18:15:10 +02:00
signing_key ,
admin_key ,
2026-05-21 11:29:29 +02:00
} ,
} = > ControlRequest ::NodeGrant {
node ,
resource ,
capability ,
grant_id ,
2026-05-21 18:15:10 +02:00
signing_key_path : Some ( signing_key ) ,
admin_key_path : admin_key ,
2026-05-21 11:29:29 +02:00
} ,
Command ::Node {
2026-05-21 18:15:10 +02:00
command :
NodeCommand ::RevokeGrant {
resource ,
grant_id ,
signing_key ,
admin_key ,
} ,
} = > ControlRequest ::NodeRevokeGrant {
resource ,
grant_id ,
signing_key_path : Some ( signing_key ) ,
admin_key_path : admin_key ,
} ,
Command ::Node {
command :
NodeCommand ::EndpointAdd {
node ,
endpoint ,
signing_key ,
} ,
} = > ControlRequest ::NodeEndpointAdd {
node ,
endpoint ,
signing_key_path : Some ( signing_key ) ,
} ,
Command ::Node {
command :
NodeCommand ::EndpointRevoke {
node ,
endpoint ,
signing_key ,
} ,
} = > ControlRequest ::NodeEndpointRevoke {
node ,
endpoint ,
signing_key_path : Some ( signing_key ) ,
} ,
2026-05-18 04:03:52 +02:00
Command ::Peer { command } = > match command {
PeerCommand ::Export { out } = > ControlRequest ::PeerCardExport { out } ,
PeerCommand ::Import { path } = > ControlRequest ::PeerCardImport { path } ,
PeerCommand ::List = > ControlRequest ::PeerCardList ,
2026-05-18 12:09:50 +02:00
PeerCommand ::Ping { node } = > ControlRequest ::PeerPing { node } ,
2026-05-18 17:01:50 +02:00
PeerCommand ::AuthCheck {
node ,
resource ,
capability ,
} = > ControlRequest ::PeerAuthCheck {
node ,
resource ,
capability ,
} ,
2026-05-18 04:03:52 +02:00
} ,
2026-05-23 01:17:30 +02:00
Command ::Overlay { command } = > match command {
OverlayCommand ::Status = > ControlRequest ::OverlayStatus ,
OverlayCommand ::Plan { name , cidr } = > ControlRequest ::OverlayPlan { name , cidr } ,
OverlayCommand ::Join { name , secret , cidr } = > {
ControlRequest ::OverlayJoin { name , secret , cidr }
}
OverlayCommand ::Leave { name } = > ControlRequest ::OverlayLeave { name } ,
2026-05-23 02:08:51 +02:00
OverlayCommand ::InterfacePlan { name , platform } = > {
ControlRequest ::OverlayInterfacePlan { name , platform }
}
OverlayCommand ::Up {
name ,
bearer_secret ,
mtu ,
} = > ControlRequest ::OverlayUp {
name ,
bearer_secret ,
mtu ,
} ,
OverlayCommand ::Down { name } = > ControlRequest ::OverlayDown { name } ,
OverlayCommand ::Peers { name } = > ControlRequest ::OverlayPeers { name } ,
OverlayCommand ::Send {
name ,
node ,
packet_base64 ,
bearer_secret ,
} = > ControlRequest ::OverlaySend {
name ,
node ,
packet_base64 ,
bearer_secret ,
} ,
OverlayCommand ::Recv { name , peek } = > ControlRequest ::OverlayRecv { name , peek } ,
2026-05-23 01:17:30 +02:00
} ,
2026-05-15 15:08:20 +02:00
Command ::Resource {
command : ResourceCommand ::List ,
} = > ControlRequest ::ResourceList ,
Command ::Resource {
command : ResourceCommand ::Create { kind , name } ,
} = > ControlRequest ::ResourceCreate { kind , name } ,
Command ::Keychain {
2026-05-19 16:04:20 +02:00
command :
KeychainCommand ::Init {
admin_key ,
signing_key ,
} ,
2026-05-16 16:34:20 +02:00
} = > ControlRequest ::KeychainInit {
admin_key_path : admin_key ,
2026-05-19 16:04:20 +02:00
signing_key_path : signing_key ,
2026-05-15 15:08:20 +02:00
} ,
Command ::Keychain {
command : KeychainCommand ::Status ,
} = > ControlRequest ::KeychainStatus ,
2026-05-26 18:53:20 +02:00
Command ::Keychain {
command :
KeychainCommand ::AdminAdd {
admin_key ,
signing_key ,
principal ,
valid_after_ms ,
valid_before_ms ,
} ,
} = > ControlRequest ::KeychainAdminAdd {
admin_key_path : admin_key ,
signing_key_path : signing_key ,
principal ,
valid_after_ms ,
valid_before_ms ,
} ,
Command ::Keychain {
command :
KeychainCommand ::AdminRevoke {
key ,
signing_key ,
admin_key ,
} ,
} = > ControlRequest ::KeychainAdminRevoke {
key ,
signing_key_path : signing_key ,
admin_key_path : admin_key ,
} ,
Command ::Keychain {
2026-05-27 00:59:52 +02:00
command : KeychainCommand ::AllowedSigners { out } ,
} = > ControlRequest ::KeychainAllowedSigners { out } ,
Command ::Keychain {
command :
KeychainCommand ::SignFile {
input ,
out ,
namespace ,
signing_key ,
admin_key ,
} ,
} = > ControlRequest ::KeychainSignFile {
input ,
out ,
namespace ,
signing_key_path : Some ( signing_key ) ,
admin_key_path : admin_key ,
} ,
Command ::Keychain {
command :
KeychainCommand ::VerifyFile {
input ,
signature ,
namespace ,
allowed_signers ,
principal ,
} ,
} = > ControlRequest ::KeychainVerifyFile {
input ,
signature ,
namespace ,
allowed_signers_path : allowed_signers ,
principal ,
} ,
Command ::Keychain {
command : KeychainCommand ::Sigchain { out } ,
} = > ControlRequest ::KeychainSigchainExport { out } ,
Command ::Keychain {
command :
KeychainCommand ::PublishBundle {
out ,
base_url ,
signing_key ,
admin_key ,
snapshots ,
} ,
} = > ControlRequest ::KeychainPublishBundle {
out ,
base_url : Some ( base_url ) ,
signing_key_path : signing_key ,
admin_key_path : admin_key ,
snapshots ,
} ,
Command ::Keychain {
command : KeychainCommand ::VerifySigchain { input } ,
} = > ControlRequest ::KeychainVerifySigchain { input } ,
Command ::Keychain {
command : KeychainCommand ::ImportSigchain { input } ,
} = > ControlRequest ::KeychainImportSigchain { input } ,
Command ::Keychain {
command :
KeychainCommand ::VerifyCheckpoint {
checkpoint ,
signature ,
sigchain ,
allowed_signers ,
base_url ,
principal ,
} ,
} = > ControlRequest ::KeychainVerifyCheckpoint {
checkpoint ,
signature ,
sigchain ,
allowed_signers ,
base_url ,
principal ,
} ,
Command ::Keychain {
command : KeychainCommand ::Fetch { url , out , import } ,
} = > ControlRequest ::KeychainFetch { url , out , import } ,
Command ::Keychain {
command : KeychainCommand ::Explain { op_id } ,
} = > ControlRequest ::KeychainExplain { op_id } ,
Command ::Keychain {
command : KeychainCommand ::ExplainSigner { key } ,
} = > ControlRequest ::KeychainExplainSigner { key } ,
2026-05-26 18:53:20 +02:00
Command ::Keychain {
command : KeychainCommand ::Verify ,
} = > ControlRequest ::KeychainVerify ,
2026-05-21 11:29:29 +02:00
Command ::Keychain {
command : KeychainCommand ::Sync { node } ,
} = > ControlRequest ::KeychainSync { node } ,
2026-05-21 18:01:38 +02:00
Command ::Auth {
command : AuthCommand ::Sync { node } ,
} = > ControlRequest ::AuthSync { node } ,
2026-05-15 15:08:20 +02:00
Command ::Auth {
command :
AuthCommand ::Explain {
subject ,
resource ,
capability ,
} ,
} = > ControlRequest ::AuthExplain {
subject ,
resource ,
capability ,
} ,
2026-05-16 16:32:03 +02:00
Command ::Auth {
command :
AuthCommand ::Grant {
subject ,
resource ,
capability ,
grant_id ,
2026-05-21 18:15:10 +02:00
signing_key ,
admin_key ,
2026-05-16 16:32:03 +02:00
} ,
} = > ControlRequest ::AuthGrant {
subject ,
resource ,
capability ,
grant_id ,
2026-05-21 18:15:10 +02:00
signing_key_path : Some ( signing_key ) ,
admin_key_path : admin_key ,
2026-05-16 16:32:03 +02:00
} ,
Command ::Auth {
2026-05-21 18:15:10 +02:00
command :
AuthCommand ::Revoke {
resource ,
grant_id ,
signing_key ,
admin_key ,
} ,
} = > ControlRequest ::AuthRevoke {
resource ,
grant_id ,
signing_key_path : Some ( signing_key ) ,
admin_key_path : admin_key ,
} ,
2026-05-16 22:18:49 +02:00
Command ::Secret { command } = > match command {
SecretCommand ::Status = > ControlRequest ::SecretStatus ,
SecretCommand ::Create { resource } = > ControlRequest ::SecretCreate { resource } ,
SecretCommand ::Rotate { resource } = > ControlRequest ::SecretRotate { resource } ,
2026-05-17 02:58:58 +02:00
SecretCommand ::Bearer { command } = > match command {
SecretBearerCommand ::Create {
resource ,
capabilities ,
expires_at_ms ,
} = > ControlRequest ::SecretBearerCreate {
resource ,
capabilities ,
expires_at_ms ,
} ,
SecretBearerCommand ::List = > ControlRequest ::SecretBearerList ,
2026-05-19 19:08:08 +02:00
SecretBearerCommand ::Challenge {
resource ,
capabilities ,
} = > ControlRequest ::SecretBearerChallenge {
resource ,
capabilities ,
} ,
SecretBearerCommand ::Prove {
2026-05-20 13:10:34 +02:00
token ,
2026-05-19 19:08:08 +02:00
resource ,
capabilities ,
nonce ,
} = > ControlRequest ::SecretBearerProve {
2026-05-20 13:10:34 +02:00
secret : token ,
2026-05-19 19:08:08 +02:00
resource ,
capabilities ,
nonce ,
} ,
SecretBearerCommand ::Verify {
2026-05-20 13:10:34 +02:00
token ,
2026-05-19 19:08:08 +02:00
resource ,
capabilities ,
nonce ,
response ,
} = > ControlRequest ::SecretBearerVerify {
2026-05-20 13:10:34 +02:00
secret : token ,
2026-05-19 19:08:08 +02:00
resource ,
capabilities ,
nonce ,
response ,
} ,
2026-05-20 13:10:34 +02:00
SecretBearerCommand ::Revoke {
resource ,
bearer_id ,
} = > ControlRequest ::SecretBearerRevoke {
resource ,
secret : bearer_id ,
} ,
2026-05-17 02:58:58 +02:00
} ,
2026-05-15 15:08:20 +02:00
} ,
Command ::Cas { command } = > match command {
CasCommand ::Add { path } = > ControlRequest ::CasAdd { path } ,
2026-05-21 01:35:00 +02:00
CasCommand ::AddPrivate { resource , path } = > {
ControlRequest ::CasAddPrivate { resource , path }
}
2026-05-15 15:08:20 +02:00
CasCommand ::Get { hash , out } = > ControlRequest ::CasGet {
hash : hash . into ( ) ,
out ,
} ,
2026-05-21 01:35:00 +02:00
CasCommand ::GetPrivate {
resource ,
hash ,
out ,
} = > ControlRequest ::CasGetPrivate {
resource ,
hash : hash . into ( ) ,
out ,
} ,
2026-05-19 19:16:30 +02:00
CasCommand ::Fetch {
node ,
hash ,
bearer_secret ,
} = > ControlRequest ::CasFetch {
2026-05-18 17:18:25 +02:00
node ,
hash : hash . into ( ) ,
2026-05-19 19:16:30 +02:00
bearer_secret ,
2026-05-18 17:18:25 +02:00
} ,
2026-05-15 15:08:20 +02:00
CasCommand ::Hash { path } = > ControlRequest ::CasHash { path } ,
CasCommand ::Has { hash } = > ControlRequest ::CasHas { hash : hash . into ( ) } ,
2026-05-16 16:36:35 +02:00
CasCommand ::Pin { hash } = > ControlRequest ::CasPin { hash : hash . into ( ) } ,
CasCommand ::Unpin { hash } = > ControlRequest ::CasUnpin { hash : hash . into ( ) } ,
2026-05-16 21:10:25 +02:00
CasCommand ::Cleanup { dry_run } = > ControlRequest ::CasCleanup { dry_run } ,
2026-05-19 15:37:02 +02:00
CasCommand ::Providers { hash } = > ControlRequest ::CasProviders { hash : hash . into ( ) } ,
2026-05-15 15:08:20 +02:00
CasCommand ::List = > ControlRequest ::CasList ,
2026-05-18 03:50:09 +02:00
CasCommand ::Root { command } = > match command {
CasRootCommand ::Add { name , path } = > ControlRequest ::CasRootAdd { name , path } ,
CasRootCommand ::List = > ControlRequest ::CasRootList ,
CasRootCommand ::Scan { name } = > ControlRequest ::CasRootScan { name } ,
2026-05-20 13:22:40 +02:00
CasRootCommand ::Sync {
node ,
name ,
bearer_secret ,
} = > ControlRequest ::CasRootSync {
node ,
name ,
bearer_secret ,
} ,
2026-05-20 13:30:57 +02:00
CasRootCommand ::Apply {
source ,
to ,
dry_run ,
} = > ControlRequest ::CasRootApply {
source ,
target : to ,
dry_run ,
} ,
2026-05-18 03:50:09 +02:00
} ,
2026-05-18 03:57:26 +02:00
CasCommand ::Conflict { command } = > match command {
CasConflictCommand ::Record {
root ,
path ,
kind ,
detail ,
base_tree ,
local_tree ,
remote_tree ,
} = > ControlRequest ::CasConflictRecord {
root ,
path ,
kind ,
detail ,
base_tree : base_tree . map ( Into ::into ) ,
local_tree : local_tree . map ( Into ::into ) ,
remote_tree : remote_tree . map ( Into ::into ) ,
} ,
CasConflictCommand ::List { root } = > ControlRequest ::CasConflictList { root } ,
CasConflictCommand ::Resolve {
conflict_id ,
resolution ,
note ,
} = > ControlRequest ::CasConflictResolve {
conflict_id ,
resolution ,
note ,
} ,
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-16 21:52:35 +02:00
Command ::Kv { command } = > match command {
KvCommand ::Create { name } = > ControlRequest ::KvCreate { name } ,
2026-05-18 04:06:41 +02:00
KvCommand ::Set {
name ,
key ,
value ,
subject ,
} = > ControlRequest ::KvSet {
name ,
key ,
value ,
subject ,
} ,
2026-05-16 21:52:35 +02:00
KvCommand ::Get { name , key } = > ControlRequest ::KvGet { name , key } ,
2026-05-19 19:16:30 +02:00
KvCommand ::Sync {
node ,
name ,
bearer_secret ,
} = > ControlRequest ::KvSync {
node ,
name ,
bearer_secret ,
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-17 18:23:51 +02:00
Command ::Pubsub { command } = > match command {
2026-05-18 18:41:04 +02:00
PubsubCommand ::Pub {
topic ,
message ,
node ,
2026-05-19 19:16:30 +02:00
bearer_secret ,
2026-05-18 18:41:04 +02:00
} = > ControlRequest ::PubsubPub {
topic ,
message ,
node ,
2026-05-19 19:16:30 +02:00
bearer_secret ,
} ,
PubsubCommand ::Sub {
topic ,
node ,
bearer_secret ,
} = > ControlRequest ::PubsubSub {
topic ,
node ,
bearer_secret ,
2026-05-18 18:41:04 +02:00
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-17 20:17:26 +02:00
Command ::Pipe { command } = > match command {
2026-05-19 19:21:42 +02:00
PipeCommand ::Listen {
name ,
node ,
bearer_secret ,
} = > ControlRequest ::PipeListen {
name ,
node ,
bearer_secret ,
} ,
2026-05-19 19:16:30 +02:00
PipeCommand ::Connect {
target ,
node ,
bearer_secret ,
} = > ControlRequest ::PipeConnect {
target ,
node ,
bearer_secret ,
} ,
2026-05-21 01:12:01 +02:00
PipeCommand ::ForwardTcp {
listen ,
node ,
target ,
bearer_secret ,
} = > ControlRequest ::PipeTcpForward {
listen_addr : listen ,
node ,
target_addr : target ,
bearer_secret ,
} ,
2026-05-21 01:15:51 +02:00
PipeCommand ::ForwardUnix {
listen ,
node ,
target ,
bearer_secret ,
} = > ControlRequest ::PipeUnixForward {
listen_path : listen ,
node ,
target_path : target ,
bearer_secret ,
} ,
2026-05-20 13:57:14 +02:00
PipeCommand ::Send {
target ,
message ,
2026-05-20 13:59:41 +02:00
input ,
2026-05-20 13:57:14 +02:00
node ,
bearer_secret ,
} = > ControlRequest ::PipeSend {
target ,
2026-05-20 13:59:41 +02:00
data_base64 : pipe_send_payload_base64 ( message , input ) ? ,
2026-05-20 13:57:14 +02:00
node ,
bearer_secret ,
} ,
PipeCommand ::Recv { name , peek } = > ControlRequest ::PipeRecv { name , peek } ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-16 21:13:33 +02:00
Command ::Db { command } = > match command {
DbCommand ::Add { name , path } = > ControlRequest ::DbAdd { name , path } ,
DbCommand ::Status { name } = > ControlRequest ::DbStatus { name } ,
2026-05-17 20:32:36 +02:00
DbCommand ::Changes {
name ,
after_db_version ,
limit ,
} = > ControlRequest ::DbChanges {
name ,
after_db_version ,
limit ,
} ,
2026-05-19 19:16:30 +02:00
DbCommand ::Sync {
node ,
name ,
limit ,
bearer_secret ,
} = > ControlRequest ::DbSync {
node ,
name ,
limit ,
bearer_secret ,
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-16 22:15:18 +02:00
Command ::Document { command } = > match command {
DocumentCommand ::Create { name } = > ControlRequest ::DocumentCreate { name } ,
DocumentCommand ::Status { name } = > ControlRequest ::DocumentStatus { name } ,
2026-05-17 19:59:03 +02:00
DocumentCommand ::Set { name , state_json } = > {
ControlRequest ::DocumentSet { name , state_json }
}
DocumentCommand ::Get { name } = > ControlRequest ::DocumentGet { name } ,
2026-05-19 19:16:30 +02:00
DocumentCommand ::Sync {
node ,
name ,
bearer_secret ,
} = > ControlRequest ::DocumentSync {
node ,
name ,
bearer_secret ,
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-16 00:17:08 +02:00
Command ::Ssh { command } = > match command {
2026-05-19 19:16:30 +02:00
SshCommand ::Proxy {
node ,
bearer_secret ,
} = > ControlRequest ::SshProxyConnect {
node ,
bearer_secret ,
} ,
2026-05-21 01:49:48 +02:00
SshCommand ::AdminShell {
node ,
command ,
bearer_secret ,
} = > ControlRequest ::SshAdminShell {
node ,
command ,
bearer_secret ,
} ,
2026-05-16 00:17:08 +02:00
SshCommand ::Cert { command } = > match command {
SshCertCommand ::Request {
public_key ,
kind ,
principals ,
valid_for ,
renewal_of ,
reason ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} = > ControlRequest ::SshCertRequest {
public_key_path : public_key ,
cert_kind : kind ,
principals ,
requested_validity : valid_for ,
renewal_of ,
reason ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-19 15:44:13 +02:00
SshCertCommand ::Requests { subject } = > ControlRequest ::SshCertRequests { subject } ,
2026-05-16 00:17:08 +02:00
SshCertCommand ::Approve {
request_id ,
ca_key ,
valid_for ,
serial ,
out ,
2026-05-19 15:56:47 +02:00
sign ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} = > ControlRequest ::SshCertApprove {
request_id ,
ca_key_path : ca_key ,
valid_for ,
serial ,
out ,
2026-05-19 15:56:47 +02:00
sign ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-19 15:44:13 +02:00
SshCertCommand ::Import {
request_id ,
cert ,
subject ,
} = > ControlRequest ::SshCertImport {
2026-05-16 00:17:08 +02:00
request_id ,
cert_path : cert ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-19 15:44:13 +02:00
SshCertCommand ::List { subject } = > ControlRequest ::SshCertList { subject } ,
2026-05-19 19:16:30 +02:00
SshCertCommand ::Sync {
node ,
bearer_secret ,
} = > ControlRequest ::SshCertSync {
node ,
bearer_secret ,
} ,
2026-05-16 00:17:08 +02:00
} ,
SshCommand ::Revocation { command } = > match command {
SshRevocationCommand ::Add {
kind ,
target ,
reason ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} = > ControlRequest ::SshRevocationAdd {
kind ,
target ,
reason ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-19 15:44:13 +02:00
SshRevocationCommand ::List { subject } = > {
ControlRequest ::SshRevocationList { subject }
}
2026-05-18 11:51:12 +02:00
SshRevocationCommand ::Export {
out ,
format ,
ca_public ,
2026-05-19 15:44:13 +02:00
subject ,
2026-05-18 11:51:12 +02:00
} = > ControlRequest ::SshRevocationExport {
out ,
format ,
ca_public ,
2026-05-19 15:44:13 +02:00
subject ,
} ,
SshRevocationCommand ::Import {
path ,
format ,
subject ,
} = > ControlRequest ::SshRevocationImport {
path ,
format ,
subject ,
2026-05-18 11:51:12 +02:00
} ,
2026-05-19 19:16:30 +02:00
SshRevocationCommand ::Sync {
node ,
bearer_secret ,
} = > ControlRequest ::SshRevocationSync {
node ,
bearer_secret ,
} ,
2026-05-16 00:17:08 +02:00
} ,
2026-05-15 15:08:20 +02:00
} ,
2026-05-23 01:17:30 +02:00
Command ::Guide { .. } | Command ::Init { .. } | Command ::Daemon { .. } = > {
bail! ( " command is handled directly " )
}
2026-05-15 15:08:20 +02:00
} )
}
2026-05-16 00:17:08 +02:00
fn run_service_command ( paths : & GethPaths , command : ServiceCommand ) -> Result < ServiceReport > {
Ok ( match command {
ServiceCommand ::Install {
manager ,
bin ,
start ,
} = > {
geth_node ::init_node ( paths ) . context ( " initialize geth home before service install " ) ? ;
let manager = manager . parse ::< ServiceManager > ( ) ? ;
let executable = service_executable ( bin ) ? ;
geth_node ::service ::install_user_service (
paths ,
ServiceInstallOptions {
manager ,
executable ,
start ,
} ,
) ?
}
ServiceCommand ::Uninstall { manager } = > {
geth_node ::service ::uninstall_user_service ( manager . parse ::< ServiceManager > ( ) ? ) ?
}
ServiceCommand ::Start { manager } = > {
geth_node ::service ::start_user_service ( manager . parse ::< ServiceManager > ( ) ? ) ?
}
ServiceCommand ::Stop { manager } = > {
geth_node ::service ::stop_user_service ( manager . parse ::< ServiceManager > ( ) ? ) ?
}
ServiceCommand ::Status { manager } = > {
geth_node ::service ::status_user_service ( manager . parse ::< ServiceManager > ( ) ? ) ?
}
ServiceCommand ::Print { manager , bin } = > {
let executable = service_executable ( bin ) ? ;
geth_node ::service ::print_user_service (
paths ,
manager . parse ::< ServiceManager > ( ) ? ,
& executable ,
) ?
}
} )
}
fn service_executable ( bin : Option < PathBuf > ) -> Result < PathBuf > {
bin . map ( Ok )
. unwrap_or_else ( std ::env ::current_exe )
. context ( " resolve current geth executable " )
}
2026-05-27 00:59:52 +02:00
fn print_keychain_sigchain_report ( report : & geth_keychain ::KeychainSigchainReport ) {
println! ( " ops: {} " , report . ops ) ;
println! ( " signatures: {} " , report . signatures ) ;
println! ( " accepted_ops: {} " , report . accepted_ops ) ;
println! ( " rejected_ops: {} " , report . rejected_ops ) ;
println! ( " active_admin_keys: {} " , report . active_admin_keys ) ;
println! (
" accepted_head: {} " ,
report
. accepted_head
. as_ref ( )
. map ( | head | head . as_str ( ) )
. unwrap_or ( " none " )
) ;
println! ( " note: {} " , report . note ) ;
}
2026-05-15 15:08:20 +02:00
fn print_response ( response : ControlResponse , json : bool ) -> Result < ( ) > {
if json {
println! ( " {} " , serde_json ::to_string_pretty ( & response ) ? ) ;
return Ok ( ( ) ) ;
}
match response {
ControlResponse ::Status ( status ) = > {
println! ( " geth daemon: running " ) ;
println! ( " home: {} " , status . home . display ( ) ) ;
println! ( " socket: {} " , status . socket . display ( ) ) ;
println! ( " agent: {} " , status . agent_id ) ;
println! ( " node: {} " , status . node_id ) ;
2026-05-16 01:54:00 +02:00
println! (
" endpoint: {} " ,
status . endpoint_id . as_deref ( ) . unwrap_or ( " not started " )
) ;
2026-05-16 03:17:45 +02:00
println! ( " iroh relay: {} " , status . iroh_relay_mode ) ;
2026-05-16 14:33:45 +02:00
println! (
" iroh discovery: {} " ,
if status . iroh_local_discovery {
" local-network enabled "
} else {
" local-network disabled "
}
) ;
2026-05-15 15:08:20 +02:00
println! ( " iroh: {} " , status . iroh ) ;
2026-05-22 14:33:46 +02:00
for backend in status . native_backends {
println! (
" native backend {}: {} target {} {} ({}) " ,
backend . module ,
backend . current_backend ,
backend . target_crate ,
backend . target_version ,
backend . status
) ;
2026-05-22 15:28:52 +02:00
if ! backend . blocker . is_empty ( ) {
println! (
" native backend {} note: {} " ,
backend . module , backend . blocker
) ;
}
2026-05-22 14:33:46 +02:00
}
2026-05-15 15:08:20 +02:00
}
ControlResponse ::NodeId ( node ) = > {
println! ( " agent: {} " , node . agent_id ) ;
println! ( " node: {} " , node . node_id ) ;
println! (
" endpoint: {} " ,
node . endpoint_id
. as_deref ( )
. unwrap_or ( " not started in bootstrap " )
) ;
}
2026-05-18 04:03:52 +02:00
ControlResponse ::PeerCardExported { card , out , note } = > {
println! ( " peer card: {} " , card . node_id ) ;
println! ( " agent: {} " , card . agent_id ) ;
println! ( " endpoints: {} " , card . endpoints . len ( ) ) ;
if let Some ( path ) = out {
println! ( " wrote: {} " , path . display ( ) ) ;
} else {
println! ( " {} " , serde_json ::to_string_pretty ( & card ) ? ) ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::PeerCardImported { peer , note } = > {
println! ( " imported peer: {} " , peer . card . node_id ) ;
println! ( " agent: {} " , peer . card . agent_id ) ;
println! ( " trust: candidate-only " ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::PeerCardList { peers , note } = > {
if peers . is_empty ( ) {
println! ( " no peer candidates " ) ;
} else {
for peer in peers {
println! (
" {} \t {} \t {} endpoints \t candidate-only " ,
peer . card . node_id ,
peer . card . agent_id ,
peer . card . endpoints . len ( )
) ;
}
}
println! ( " note: {note} " ) ;
}
2026-05-18 12:09:50 +02:00
ControlResponse ::PeerPinged {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
alpn ,
note ,
} = > {
println! ( " peer pong: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " alpn: {alpn} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-18 17:01:50 +02:00
ControlResponse ::PeerAuthChecked {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
resource ,
capability ,
allowed ,
reason ,
evaluated_ops ,
note ,
} = > {
println! ( " peer auth: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " resource: {resource} " ) ;
println! ( " capability: {capability} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " evaluated_ops: {evaluated_ops} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::ResourceList { resources } = > {
if resources . is_empty ( ) {
println! ( " no resources " ) ;
} else {
for resource in resources {
println! ( " {} \t {} \t {} " , resource . kind , resource . name , resource . id ) ;
}
}
}
ControlResponse ::ResourceCreated { resource } = > {
println! (
" created resource: {} {} ({}) " ,
resource . kind , resource . name , resource . id
) ;
}
2026-05-23 01:17:30 +02:00
ControlResponse ::OverlayStatus { networks , note } = > {
if networks . is_empty ( ) {
println! ( " no overlay networks active " ) ;
} else {
for network in networks {
println! (
" {} \t {} \t {} \t {:?} \t {} peers " ,
network . name ,
network . resource ,
network . cidr ,
network . state ,
network . peers . len ( )
) ;
}
}
println! ( " note: {note} " ) ;
}
ControlResponse ::OverlayPlanned { plan } = > {
println! ( " overlay: {} " , plan . name ) ;
println! ( " resource: {} " , plan . resource ) ;
println! ( " cidr: {} " , plan . cidr ) ;
println! ( " alpn: {} " , plan . alpn ) ;
println! ( " capabilities: {} " , plan . capabilities . join ( " , " ) ) ;
println! ( " discovery: {} " , plan . discovery ) ;
println! ( " runtime: {} " , plan . runtime ) ;
for note in plan . security {
println! ( " security: {note} " ) ;
}
for note in plan . implementation_notes {
println! ( " implementation: {note} " ) ;
}
}
ControlResponse ::OverlayJoined { join } = > {
println! ( " overlay: {} " , join . plan . name ) ;
println! ( " resource: {} " , join . plan . resource ) ;
2026-05-23 02:08:51 +02:00
println! ( " cidr: {} " , join . network . cidr ) ;
println! (
" virtual_ip: {} " ,
join . network . virtual_ip . as_deref ( ) . unwrap_or ( " unassigned " )
) ;
println! ( " state: {:?} " , join . network . state ) ;
2026-05-23 01:17:30 +02:00
println! ( " enabled: {} " , join . enabled ) ;
println! ( " note: {} " , join . note ) ;
}
ControlResponse ::OverlayLeft {
name ,
stopped ,
note ,
} = > {
println! ( " overlay: {name} " ) ;
println! ( " stopped: {stopped} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-23 02:08:51 +02:00
ControlResponse ::OverlayInterfacePlanned { plan } = > {
println! ( " overlay: {} " , plan . name ) ;
println! ( " platform: {} " , plan . platform ) ;
println! ( " interface: {} " , plan . interface_name ) ;
println! ( " cidr: {} " , plan . cidr ) ;
println! (
" virtual_ip: {} " ,
plan . virtual_ip . as_deref ( ) . unwrap_or ( " unassigned " )
) ;
println! ( " requires_privileges: {} " , plan . requires_privileges ) ;
for command in plan . commands {
println! ( " command: {command} " ) ;
}
for note in plan . notes {
println! ( " note: {note} " ) ;
}
}
ControlResponse ::OverlayRuntimeStarted { status } = > {
println! ( " overlay: {} " , status . name ) ;
println! ( " interface: {} " , status . interface_name ) ;
println! ( " virtual_ip: {} " , status . virtual_ip ) ;
println! ( " cidr: {} " , status . cidr ) ;
println! ( " mtu: {} " , status . mtu ) ;
println! ( " packets_from_tun: {} " , status . packets_from_tun ) ;
println! ( " packets_to_tun: {} " , status . packets_to_tun ) ;
println! ( " packets_to_peers: {} " , status . packets_to_peers ) ;
if let Some ( error ) = status . last_error {
println! ( " last_error: {error} " ) ;
}
println! ( " note: {} " , status . note ) ;
}
ControlResponse ::OverlayRuntimeStopped {
name ,
stopped ,
note ,
} = > {
println! ( " overlay: {name} " ) ;
println! ( " stopped: {stopped} " ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::OverlayPeers { name , peers , note } = > {
println! ( " overlay: {name} " ) ;
if peers . is_empty ( ) {
println! ( " no overlay peer candidates " ) ;
} else {
for peer in peers {
println! (
" {} \t {} \t {} \t {} " ,
peer . node_id ,
peer . endpoint_id . as_deref ( ) . unwrap_or ( " no-endpoint " ) ,
peer . virtual_ip . as_deref ( ) . unwrap_or ( " no-virtual-ip " ) ,
peer . state
) ;
}
}
println! ( " note: {note} " ) ;
}
ControlResponse ::OverlayPacketSent {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
packet ,
allowed ,
reason ,
note ,
} = > {
println! ( " peer: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
if let Some ( packet ) = packet {
println! ( " packet: {} " , packet . id ) ;
println! ( " size_bytes: {} " , packet . size_bytes ) ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::OverlayPackets {
name ,
packets ,
drained ,
note ,
} = > {
println! ( " overlay: {name} " ) ;
println! ( " drained: {drained} " ) ;
for packet in packets {
println! (
" {} \t {} \t {} \t {} bytes " ,
packet . id , packet . source_node , packet . destination_node , packet . size_bytes
) ;
}
println! ( " note: {note} " ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::CasAdded { hash , size_bytes } = > {
println! ( " {hash} {size_bytes} bytes " ) ;
}
2026-05-21 01:35:00 +02:00
ControlResponse ::CasPrivateAdded {
resource ,
epoch ,
plaintext_hash ,
encrypted_hash ,
size_bytes ,
note ,
} = > {
println! ( " encrypted_hash: {encrypted_hash} " ) ;
println! ( " plaintext_hash: {plaintext_hash} " ) ;
println! ( " resource: {resource} " ) ;
println! ( " epoch: {epoch} " ) ;
println! ( " size_bytes: {size_bytes} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::CasGot {
hash ,
out ,
size_bytes ,
} = > {
println! ( " wrote {hash} to {} ( {size_bytes} bytes) " , out . display ( ) ) ;
}
2026-05-21 01:35:00 +02:00
ControlResponse ::CasPrivateGot {
resource ,
hash ,
plaintext_hash ,
out ,
size_bytes ,
note ,
} = > {
println! (
" decrypted {hash} for {resource} to {} ({size_bytes} bytes) " ,
out . display ( )
) ;
println! ( " plaintext_hash: {plaintext_hash} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-18 17:18:25 +02:00
ControlResponse ::CasFetched {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
hash ,
size_bytes ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " fetched {hash} from {peer_node_id} ( {size_bytes} bytes) " ) ;
} else {
println! ( " fetch denied for {hash} from {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::CasHash { hash } = > println! ( " {hash} " ) ,
ControlResponse ::CasHas { hash , present } = > println! ( " {hash} : {present} " ) ,
2026-05-16 16:36:35 +02:00
ControlResponse ::CasPinned { hash , pinned } = > {
println! ( " {hash} : pinned= {pinned} " ) ;
}
2026-05-16 21:10:25 +02:00
ControlResponse ::CasCleanup {
removed ,
retained_pinned ,
dry_run ,
} = > {
let action = if dry_run { " would remove " } else { " removed " } ;
println! ( " {action} : {} " , removed . len ( ) ) ;
for hash in removed {
println! ( " {hash} " ) ;
}
println! ( " retained_pinned: {} " , retained_pinned . len ( ) ) ;
for hash in retained_pinned {
println! ( " {hash} " ) ;
}
}
2026-05-15 15:08:20 +02:00
ControlResponse ::CasList { blobs } = > {
for blob in blobs {
2026-05-16 16:36:35 +02:00
let pin = if blob . pinned { " pinned " } else { " unpinned " } ;
println! ( " {} \t {} bytes \t {} " , blob . hash , blob . size_bytes , pin ) ;
2026-05-15 15:08:20 +02:00
}
}
2026-05-19 15:37:02 +02:00
ControlResponse ::CasProviders { hash , providers } = > {
println! ( " hash: {hash} " ) ;
println! ( " providers: {} " , providers . len ( ) ) ;
for provider in providers {
println! (
" {} \t {} \t {} " ,
provider . peer_node_id , provider . endpoint_id , provider . last_seen_ms
) ;
}
}
2026-05-18 03:50:09 +02:00
ControlResponse ::CasRootAdded { root } = > {
println! ( " added file root: {} " , root . name ) ;
println! ( " id: {} " , root . id ) ;
println! ( " resource: {} " , root . resource ) ;
println! ( " path: {} " , root . path ) ;
}
ControlResponse ::CasRootList { roots } = > {
if roots . is_empty ( ) {
println! ( " no file roots " ) ;
} else {
for root in roots {
println! (
" {} \t {} \t {} " ,
root . name ,
root . path ,
root . latest_tree
. map ( | hash | hash . to_string ( ) )
. unwrap_or_else ( | | " unscanned " . to_owned ( ) )
) ;
}
}
}
ControlResponse ::CasRootScanned { scan } = > {
println! ( " file root: {} " , scan . root . name ) ;
println! ( " tree: {} " , scan . tree . hash ) ;
println! ( " tree_bytes: {} " , scan . tree . size_bytes ) ;
println! ( " changes: {} " , scan . changes . len ( ) ) ;
for change in scan . changes {
println! ( " {change:?} " ) ;
}
println! ( " note: {} " , scan . note ) ;
}
2026-05-20 13:22:40 +02:00
ControlResponse ::CasRootSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
name ,
root ,
tree_bytes_imported ,
2026-05-21 01:40:50 +02:00
sync_conflicts ,
2026-05-20 13:22:40 +02:00
allowed ,
reason ,
note ,
} = > {
if let Some ( root ) = root {
println! ( " synced file root: {name} " ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " path: {} " , root . path ) ;
println! (
" tree: {} " ,
root . latest_tree
. map ( | hash | hash . to_string ( ) )
. unwrap_or_else ( | | " unscanned " . to_owned ( ) )
) ;
println! ( " tree_bytes_imported: {tree_bytes_imported} " ) ;
2026-05-21 01:40:50 +02:00
println! ( " sync_conflicts: {} " , sync_conflicts . len ( ) ) ;
for conflict in sync_conflicts {
println! (
" {} \t {} \t {} \t {} " ,
conflict . id ,
conflict . path ,
conflict . kind . as_str ( ) ,
conflict . status . as_str ( )
) ;
}
2026-05-20 13:22:40 +02:00
} else {
println! ( " file root sync denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-20 13:30:57 +02:00
ControlResponse ::CasRootApplied {
source ,
target ,
files_written ,
dirs_created ,
conflicts ,
dry_run ,
note ,
} = > {
println! ( " applied file root: {source} " ) ;
println! ( " target: {} " , target . display ( ) ) ;
println! ( " dry_run: {dry_run} " ) ;
println! ( " files_written: {files_written} " ) ;
println! ( " dirs_created: {dirs_created} " ) ;
println! ( " conflicts: {} " , conflicts . len ( ) ) ;
for conflict in conflicts {
println! (
" {} \t {} \t {} \t {} " ,
conflict . id ,
conflict . path ,
conflict . kind . as_str ( ) ,
conflict . status . as_str ( )
) ;
}
println! ( " note: {note} " ) ;
}
2026-05-18 03:57:26 +02:00
ControlResponse ::CasConflictRecorded { conflict } = > {
println! ( " recorded conflict: {} " , conflict . id ) ;
print_file_conflict ( & conflict ) ;
}
ControlResponse ::CasConflictList { conflicts } = > {
if conflicts . is_empty ( ) {
println! ( " no file conflicts " ) ;
} else {
for conflict in conflicts {
println! (
" {} \t {} \t {} \t {} \t {} " ,
conflict . id ,
conflict . root ,
conflict . path ,
conflict . kind . as_str ( ) ,
conflict . status . as_str ( )
) ;
}
}
}
ControlResponse ::CasConflictResolved { conflict } = > {
println! ( " resolved conflict: {} " , conflict . id ) ;
print_file_conflict ( & conflict ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::KeychainStatus ( status ) = > {
println! ( " initialized: {} " , status . initialized ) ;
println! ( " admin_keys: {} " , status . admin_keys ) ;
2026-05-19 16:05:57 +02:00
println! ( " signatures: {} " , status . signatures ) ;
2026-05-19 18:58:07 +02:00
println! ( " verified_signatures: {} " , status . verified_signatures ) ;
println! ( " failed_signatures: {} " , status . failed_signatures ) ;
2026-05-15 15:08:20 +02:00
println! ( " users: {} " , status . users ) ;
println! ( " devices: {} " , status . devices ) ;
println! ( " nodes: {} " , status . nodes ) ;
}
2026-05-19 16:04:20 +02:00
ControlResponse ::KeychainInitialized { ops , signatures } = > {
2026-05-16 16:34:20 +02:00
println! ( " initialized keychain " ) ;
for op in ops {
println! ( " recorded keychain op: {} " , op . id ) ;
}
2026-05-19 16:04:20 +02:00
for signature in signatures {
println! (
" signed keychain op: {} by {} ({}) " ,
signature . op_id , signature . signer , signature . namespace
) ;
}
2026-05-16 16:34:20 +02:00
}
2026-05-26 18:53:20 +02:00
ControlResponse ::KeychainAdminUpdated {
op ,
signatures ,
note ,
} = > {
println! ( " recorded keychain op: {} " , op . id ) ;
for signature in signatures {
println! (
" signed keychain op: {} by {} ({}) " ,
signature . op_id , signature . signer , signature . namespace
) ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::KeychainAllowedSigners {
allowed_signers ,
2026-05-27 00:59:52 +02:00
out ,
2026-05-26 18:53:20 +02:00
note ,
..
} = > {
2026-05-27 00:59:52 +02:00
if let Some ( out ) = out {
println! ( " wrote allowed_signers: {} " , out . display ( ) ) ;
if allowed_signers . is_empty ( ) {
println! ( " warning: generated file has no active admin public keys " ) ;
}
} else {
print! ( " {allowed_signers} " ) ;
if allowed_signers . is_empty ( ) {
println! ( " no active admin public keys available " ) ;
}
2026-05-26 18:53:20 +02:00
}
eprintln! ( " note: {note} " ) ;
}
2026-05-27 00:59:52 +02:00
ControlResponse ::KeychainFileSigned {
input ,
out ,
namespace ,
signer ,
note ,
} = > {
println! ( " signed file: {} " , input . display ( ) ) ;
println! (
" signature: {} " ,
out . map ( | path | path . display ( ) . to_string ( ) )
. unwrap_or_else ( | | " none " . to_owned ( ) )
) ;
println! ( " namespace: {namespace} " ) ;
println! ( " signer: {signer} " ) ;
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainFileVerified {
input ,
signature ,
namespace ,
verified ,
principal ,
note ,
} = > {
println! ( " file: {} " , input . display ( ) ) ;
println! ( " signature: {} " , signature . display ( ) ) ;
println! ( " namespace: {namespace} " ) ;
println! ( " principal: {} " , principal . as_deref ( ) . unwrap_or ( " none " ) ) ;
println! ( " verified: {verified} " ) ;
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainSigchainExported {
jsonl , out , note , ..
} = > {
if let Some ( out ) = out {
println! ( " wrote keychain sigchain: {} " , out . display ( ) ) ;
} else {
print! ( " {jsonl} " ) ;
}
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainBundlePublished {
out ,
base_url ,
allowed_signers_path ,
sigchain_path ,
checkpoint_path ,
checkpoint_signature_path ,
snapshots ,
note ,
..
} = > {
println! ( " bundle: {} " , out . display ( ) ) ;
println! ( " base_url: {base_url} " ) ;
println! ( " allowed_signers: {} " , allowed_signers_path . display ( ) ) ;
println! ( " sigchain: {} " , sigchain_path . display ( ) ) ;
println! ( " checkpoint: {} " , checkpoint_path . display ( ) ) ;
2026-05-26 18:53:20 +02:00
println! (
2026-05-27 00:59:52 +02:00
" checkpoint_signature: {} " ,
checkpoint_signature_path . display ( )
) ;
for snapshot in snapshots {
println! (
" snapshot: {} {} {} " ,
snapshot . name ,
snapshot . path . display ( ) ,
snapshot . signature_path . display ( )
) ;
}
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainSigchainFileVerified {
input ,
report ,
note ,
} = > {
println! ( " sigchain: {} " , input . display ( ) ) ;
print_keychain_sigchain_report ( & report ) ;
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainSigchainImported {
input ,
ops_imported ,
signatures_imported ,
invalid_ops_rejected ,
note ,
} = > {
println! ( " sigchain: {} " , input . display ( ) ) ;
println! ( " ops_imported: {ops_imported} " ) ;
println! ( " signatures_imported: {signatures_imported} " ) ;
println! ( " invalid_ops_rejected: {invalid_ops_rejected} " ) ;
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainCheckpointVerified {
checkpoint ,
verified ,
principal ,
note ,
} = > {
println! (
" checkpoint_head: {} " ,
checkpoint
. head
2026-05-26 18:53:20 +02:00
. as_ref ( )
2026-05-27 00:59:52 +02:00
. map ( | h | h . as_str ( ) )
2026-05-26 18:53:20 +02:00
. unwrap_or ( " none " )
) ;
2026-05-27 00:59:52 +02:00
println! ( " base_url: {} " , checkpoint . base_url ) ;
println! ( " verified: {verified} " ) ;
println! ( " principal: {} " , principal . as_deref ( ) . unwrap_or ( " none " ) ) ;
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainFetched {
url ,
out ,
checkpoint ,
imported ,
note ,
} = > {
println! ( " url: {url} " ) ;
println! ( " out: {} " , out . display ( ) ) ;
println! (
" checkpoint_head: {} " ,
checkpoint
. head
. as_ref ( )
. map ( | h | h . as_str ( ) )
. unwrap_or ( " none " )
) ;
if let Some ( imported ) = imported {
println! ( " ops_imported: {} " , imported . ops_imported ) ;
println! ( " signatures_imported: {} " , imported . signatures_imported ) ;
println! ( " invalid_ops_rejected: {} " , imported . invalid_ops_rejected ) ;
}
eprintln! ( " note: {note} " ) ;
}
ControlResponse ::KeychainExplained { subject , lines } = > {
println! ( " subject: {subject} " ) ;
for line in lines {
println! ( " {line} " ) ;
}
}
ControlResponse ::KeychainVerified { report } = > {
print_keychain_sigchain_report ( & report ) ;
2026-05-26 18:53:20 +02:00
}
2026-05-21 11:29:29 +02:00
ControlResponse ::KeychainSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
ops_imported ,
signatures_imported ,
invalid_ops_rejected ,
2026-05-21 19:31:05 +02:00
high_water_ms ,
2026-05-21 11:29:29 +02:00
note ,
} = > {
println! ( " synced keychain from: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " ops_imported: {ops_imported} " ) ;
println! ( " signatures_imported: {signatures_imported} " ) ;
println! ( " invalid_ops_rejected: {invalid_ops_rejected} " ) ;
2026-05-21 19:31:05 +02:00
println! ( " high_water_ms: {high_water_ms} " ) ;
2026-05-21 11:29:29 +02:00
println! ( " note: {note} " ) ;
}
2026-05-21 18:01:38 +02:00
ControlResponse ::AuthSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
ops_imported ,
signatures_imported ,
invalid_ops_rejected ,
2026-05-21 19:31:05 +02:00
high_water_ms ,
2026-05-21 18:01:38 +02:00
note ,
} = > {
println! ( " synced auth from: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " ops_imported: {ops_imported} " ) ;
println! ( " signatures_imported: {signatures_imported} " ) ;
println! ( " invalid_ops_rejected: {invalid_ops_rejected} " ) ;
2026-05-21 19:31:05 +02:00
println! ( " high_water_ms: {high_water_ms} " ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::SyncStatus { peers , note } = > {
if peers . is_empty ( ) {
println! ( " no sync peers " ) ;
} else {
for peer in peers {
println! ( " peer: {} " , peer . peer_node_id ) ;
if peer . streams . is_empty ( ) {
println! ( " no sync attempts recorded " ) ;
}
for stream in peer . streams {
println! (
2026-05-22 15:00:09 +02:00
" {} \t state={} \t stale={} \t cursor={} \t last_attempt={} \t last_success={} \t imported={} \t rejected={} \t error={} \t next={} " ,
2026-05-21 19:31:05 +02:00
stream . stream ,
2026-05-22 15:00:09 +02:00
stream . state ,
stream . stale ,
2026-05-21 19:31:05 +02:00
stream . cursor_ms ,
stream
. last_attempt_ms
. map ( | value | value . to_string ( ) )
. unwrap_or_else ( | | " never " . to_owned ( ) ) ,
stream
. last_success_ms
. map ( | value | value . to_string ( ) )
. unwrap_or_else ( | | " never " . to_owned ( ) ) ,
stream . last_imported ,
stream . last_rejected ,
2026-05-22 15:00:09 +02:00
stream . last_error . unwrap_or_else ( | | " - " . to_owned ( ) ) ,
stream . next_action
2026-05-21 19:31:05 +02:00
) ;
}
}
}
println! ( " note: {note} " ) ;
}
ControlResponse ::SyncRan { peers , note } = > {
if peers . is_empty ( ) {
println! ( " no sync peers " ) ;
} else {
for peer in peers {
println! ( " peer: {} " , peer . peer_node_id ) ;
for stream in peer . streams {
let state = if ! stream . attempted {
" skipped "
} else if stream . success {
" ok "
} else {
" failed "
} ;
println! (
" {} \t {} \t cursor={} \t imported={} \t rejected={} \t error={} " ,
stream . stream ,
state ,
stream . cursor_ms ,
stream . imported ,
stream . rejected ,
stream . error . unwrap_or_else ( | | " - " . to_owned ( ) )
) ;
}
}
}
2026-05-21 18:01:38 +02:00
println! ( " note: {note} " ) ;
}
2026-05-16 22:18:49 +02:00
ControlResponse ::SecretStatus { secrets } = > {
if secrets . is_empty ( ) {
println! ( " no resource secrets " ) ;
} else {
for secret in secrets {
println! ( " {} \t {} \t epoch {} " , secret . id , secret . resource , secret . epoch ) ;
}
}
}
ControlResponse ::SecretCreated { secret } = > {
println! ( " resource secret: {} " , secret . id ) ;
println! ( " resource: {} " , secret . resource ) ;
println! ( " epoch: {} " , secret . epoch ) ;
}
2026-05-17 02:58:58 +02:00
ControlResponse ::SecretBearerCreated { access } = > {
2026-05-20 13:10:34 +02:00
println! ( " bearer id: {} " , access . secret ) ;
if let Some ( token ) = access . token {
println! ( " bearer token: {token} " ) ;
}
2026-05-17 02:58:58 +02:00
println! ( " resource: {} " , access . resource ) ;
println! (
" capabilities: {} " ,
access
. capabilities
. iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < _ > > ( )
. join ( " , " )
) ;
if let Some ( expires_at ) = access . expires_at {
println! ( " expires_at_ms: {} " , expires_at . 0 ) ;
}
println! ( " may_delegate: {} " , access . may_delegate ) ;
}
ControlResponse ::SecretBearerList { access } = > {
if access . is_empty ( ) {
println! ( " no bearer access " ) ;
} else {
for item in access {
println! (
" {} \t {} \t {} \t may_delegate={} " ,
item . secret ,
item . resource ,
item . capabilities
. iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < _ > > ( )
. join ( " , " ) ,
item . may_delegate
) ;
}
}
}
2026-05-19 19:08:08 +02:00
ControlResponse ::SecretBearerChallenge { challenge } = > {
println! ( " bearer challenge " ) ;
println! ( " resource: {} " , challenge . resource ) ;
println! ( " nonce: {} " , challenge . nonce ) ;
println! ( " issued_at_ms: {} " , challenge . issued_at . 0 ) ;
println! (
" capabilities: {} " ,
challenge
. capabilities
. iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < _ > > ( )
. join ( " , " )
) ;
}
ControlResponse ::SecretBearerProof { proof } = > {
println! ( " bearer proof " ) ;
println! ( " secret: {} " , proof . secret ) ;
println! ( " resource: {} " , proof . resource ) ;
println! ( " nonce: {} " , proof . nonce ) ;
println! ( " response: {} " , proof . response ) ;
println! (
" capabilities: {} " ,
proof
. capabilities
. iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < _ > > ( )
. join ( " , " )
) ;
}
ControlResponse ::SecretBearerVerified {
secret ,
resource ,
capabilities ,
verified ,
reason ,
} = > {
println! ( " bearer verified: {verified} " ) ;
println! ( " secret: {secret} " ) ;
println! ( " resource: {resource} " ) ;
println! ( " capabilities: {} " , capabilities . join ( " , " ) ) ;
println! ( " reason: {reason} " ) ;
}
2026-05-17 02:58:58 +02:00
ControlResponse ::SecretBearerRevoked { resource , secret } = > {
println! ( " revoked bearer secret: {secret} " ) ;
println! ( " resource: {resource} " ) ;
}
2026-05-15 15:08:20 +02:00
ControlResponse ::AuthExplain ( explain ) = > {
println! ( " allowed: {} " , explain . allowed ) ;
println! ( " subject: {} " , explain . subject ) ;
println! ( " resource: {} " , explain . resource ) ;
println! ( " capability: {} " , explain . capability ) ;
println! ( " reason: {} " , explain . reason ) ;
println! ( " evaluated_ops: {} " , explain . evaluated_ops ) ;
2026-05-22 14:28:44 +02:00
if ! explain . diagnostics . is_empty ( ) {
println! ( " diagnostics: {} " , explain . diagnostics . join ( " , " ) ) ;
}
2026-05-15 15:08:20 +02:00
}
2026-05-21 18:15:10 +02:00
ControlResponse ::AuthOpRecorded { op , signatures } = > {
2026-05-16 16:32:03 +02:00
println! ( " recorded auth op: {} " , op . id ) ;
println! ( " resource: {} " , op . resource ) ;
2026-05-21 18:15:10 +02:00
for signature in signatures {
println! (
" signed auth op: {} by {} ({}) " ,
signature . op_id , signature . signer , signature . namespace
) ;
}
2026-05-16 16:32:03 +02:00
}
2026-05-21 11:29:29 +02:00
ControlResponse ::NodeList { nodes , note } = > {
if nodes . is_empty ( ) {
println! ( " no enrolled nodes " ) ;
} else {
for node in nodes {
println! (
" {} \t {} \t device {} \t {} endpoints " ,
node . name ,
node . id ,
node . device ,
node . endpoints . len ( )
) ;
}
}
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeKeychainUpdated {
ops ,
signatures ,
note ,
} = > {
for op in ops {
println! ( " recorded keychain op: {} " , op . id ) ;
}
for signature in signatures {
println! (
" signed keychain op: {} by {} ({}) " ,
signature . op_id , signature . signer , signature . namespace
) ;
}
println! ( " note: {note} " ) ;
}
2026-05-21 18:01:38 +02:00
ControlResponse ::NodeGrantUpdated {
op ,
signatures ,
note ,
} = > {
2026-05-21 11:29:29 +02:00
println! ( " recorded auth op: {} " , op . id ) ;
println! ( " resource: {} " , op . resource ) ;
2026-05-21 18:01:38 +02:00
for signature in signatures {
println! (
" signed auth op: {} by {} ({}) " ,
signature . op_id , signature . signer , signature . namespace
) ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentRequested { request , out , note } = > {
println! ( " node enrollment request: {} " , request . id ) ;
println! ( " node: {} " , request . requester_node ) ;
println! ( " requested_name: {} " , request . requested_node_name ) ;
println! ( " status: {} " , request . status ) ;
if let Some ( out ) = out {
println! ( " written: {} " , out . display ( ) ) ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentSubmitted {
request_id ,
owner_node_id ,
accepted ,
note ,
} = > {
println! ( " submitted enrollment request: {request_id} " ) ;
println! ( " owner_node: {owner_node_id} " ) ;
println! ( " accepted: {accepted} " ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentImported { request , note } = > {
println! ( " imported enrollment request: {} " , request . id ) ;
println! ( " node: {} " , request . requester_node ) ;
println! ( " requested_name: {} " , request . requested_node_name ) ;
println! ( " status: {} " , request . status ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentList { requests , note } = > {
if requests . is_empty ( ) {
println! ( " no node enrollment requests " ) ;
} else {
for request in requests {
println! (
" {} \t {} \t {} \t {} capabilities " ,
request . id ,
request . status ,
request . requested_node_name ,
request . requested_capabilities . len ( )
) ;
}
}
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentApproved {
request ,
keychain_ops ,
keychain_signatures ,
auth_ops ,
auth_signatures ,
note ,
} = > {
println! ( " approved enrollment request: {} " , request . id ) ;
println! ( " node: {} " , request . requester_node ) ;
println! ( " keychain_ops: {} " , keychain_ops . len ( ) ) ;
println! ( " keychain_signatures: {} " , keychain_signatures . len ( ) ) ;
println! ( " auth_ops: {} " , auth_ops . len ( ) ) ;
println! ( " auth_signatures: {} " , auth_signatures . len ( ) ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::NodeEnrollmentSynced {
owner_node ,
keychain_ops_imported ,
keychain_signatures_imported ,
auth_ops_imported ,
auth_signatures_imported ,
invalid_ops_rejected ,
note ,
} = > {
println! ( " synced enrollment from: {owner_node} " ) ;
println! ( " keychain_ops_imported: {keychain_ops_imported} " ) ;
println! ( " keychain_signatures_imported: {keychain_signatures_imported} " ) ;
println! ( " auth_ops_imported: {auth_ops_imported} " ) ;
println! ( " auth_signatures_imported: {auth_signatures_imported} " ) ;
println! ( " invalid_ops_rejected: {invalid_ops_rejected} " ) ;
2026-05-21 11:29:29 +02:00
println! ( " note: {note} " ) ;
}
2026-05-16 00:17:08 +02:00
ControlResponse ::SshCertRequested { request } = > {
println! ( " ssh cert request: {} " , request . id ) ;
println! ( " status: {} " , request . status ) ;
println! ( " kind: {} " , request . cert_kind ) ;
println! ( " principals: {} " , request . principals . join ( " , " ) ) ;
println! ( " public_key_fingerprint: {} " , request . public_key_fingerprint ) ;
}
ControlResponse ::SshCertRequests { requests } = > {
if requests . is_empty ( ) {
println! ( " no ssh certificate requests " ) ;
} else {
for request in requests {
println! (
" {} \t {} \t {} \t {} \t {} " ,
request . id ,
request . status ,
request . cert_kind ,
request . principals . join ( " , " ) ,
request . public_key_fingerprint
) ;
}
}
}
ControlResponse ::SshCertApproved { approval } = > {
println! ( " approved ssh cert request: {} " , approval . request_id ) ;
println! ( " valid_for: {} " , approval . valid_for ) ;
if let Some ( serial ) = approval . serial {
println! ( " serial: {serial} " ) ;
}
if let Some ( output_path ) = approval . output_path {
println! ( " expected_certificate: {output_path} " ) ;
}
println! ( " signing_command: " ) ;
println! ( " {} " , shell_quote_command ( & approval . signing_command ) ) ;
2026-05-19 15:56:47 +02:00
println! ( " signed: {} " , approval . signed ) ;
if let Some ( certificate_id ) = approval . certificate_id {
println! ( " certificate_id: {certificate_id} " ) ;
}
2026-05-16 00:17:08 +02:00
println! ( " note: {} " , approval . note ) ;
}
ControlResponse ::SshCertImported { certificate } = > {
println! ( " imported ssh certificate: {} " , certificate . id ) ;
println! ( " request: {} " , certificate . request_id ) ;
println! ( " fingerprint: {} " , certificate . certificate_fingerprint ) ;
}
ControlResponse ::SshCertList {
requests ,
certificates ,
} = > {
println! ( " requests: " ) ;
if requests . is_empty ( ) {
println! ( " none " ) ;
} else {
for request in requests {
println! (
" {} \t {} \t {} \t {} " ,
request . id ,
request . status ,
request . cert_kind ,
request . principals . join ( " , " )
) ;
}
}
println! ( " certificates: " ) ;
if certificates . is_empty ( ) {
println! ( " none " ) ;
} else {
for certificate in certificates {
println! (
" {} \t {} \t {} " ,
certificate . id , certificate . request_id , certificate . certificate_fingerprint
) ;
}
}
}
2026-05-18 17:24:10 +02:00
ControlResponse ::SshCertSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
requests_imported ,
certificates_imported ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! (
" synced ssh cert metadata from {peer_node_id}: {requests_imported} requests, {certificates_imported} certificates "
) ;
} else {
println! ( " ssh cert metadata sync denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-16 00:17:08 +02:00
ControlResponse ::SshRevocationAdded { revocation } = > {
println! ( " added ssh revocation: {} " , revocation . id ) ;
println! ( " kind: {} " , revocation . kind ) ;
println! ( " target: {} " , revocation . target ) ;
if let Some ( reason ) = revocation . reason {
println! ( " reason: {reason} " ) ;
}
}
ControlResponse ::SshRevocationList { revocations } = > {
if revocations . is_empty ( ) {
println! ( " no ssh revocations " ) ;
} else {
for revocation in revocations {
println! (
" {} \t {} \t {} \t {} " ,
revocation . id ,
revocation . kind ,
revocation . target ,
revocation . reason . unwrap_or_default ( )
) ;
}
}
}
2026-05-17 18:29:47 +02:00
ControlResponse ::SshRevocationExported {
out ,
format ,
count ,
note ,
} = > {
2026-05-16 00:17:08 +02:00
println! ( " exported {count} ssh revocations to {} " , out . display ( ) ) ;
2026-05-17 18:29:47 +02:00
println! ( " format: {format} " ) ;
println! ( " note: {note} " ) ;
2026-05-16 00:17:08 +02:00
}
2026-05-18 11:56:42 +02:00
ControlResponse ::SshRevocationImported {
revocations ,
format ,
count ,
note ,
} = > {
println! ( " imported {count} ssh revocations " ) ;
println! ( " format: {format} " ) ;
for revocation in revocations {
println! (
" {} \t {} \t {} " ,
revocation . id , revocation . kind , revocation . target
) ;
}
println! ( " note: {note} " ) ;
}
2026-05-18 17:24:10 +02:00
ControlResponse ::SshRevocationSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
revocations_imported ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " synced {revocations_imported} ssh revocations from {peer_node_id} " ) ;
} else {
println! ( " ssh revocation sync denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-16 21:13:33 +02:00
ControlResponse ::DbAdded { db } = > {
println! ( " registered db: {} " , db . name ) ;
println! ( " id: {} " , db . id ) ;
println! ( " resource: {} " , db . resource ) ;
println! ( " path: {} " , db . path ) ;
println! ( " sync_status: {} " , db . sync_status ) ;
}
ControlResponse ::DbStatus { db } = > {
println! ( " db: {} " , db . name ) ;
println! ( " id: {} " , db . id ) ;
println! ( " resource: {} " , db . resource ) ;
println! ( " path: {} " , db . path ) ;
println! ( " path_exists: {} " , db . path_exists ) ;
println! (
" size_bytes: {} " ,
db . size_bytes
. map ( | size | size . to_string ( ) )
. unwrap_or_else ( | | " unknown " . to_owned ( ) )
) ;
println! ( " schema_metadata: {} " , db . schema_metadata ) ;
2026-05-17 20:01:36 +02:00
println! (
" crsqlite_changes_available: {} " ,
db . crsqlite_changes . available
) ;
if let Some ( count ) = db . crsqlite_changes . change_count {
println! ( " crsqlite_change_count: {count} " ) ;
}
if let Some ( version ) = db . crsqlite_changes . max_db_version {
println! ( " crsqlite_max_db_version: {version} " ) ;
}
if let Some ( error ) = db . crsqlite_changes . error {
println! ( " crsqlite_changes_error: {error} " ) ;
}
2026-05-16 21:13:33 +02:00
println! ( " sync_status: {} " , db . sync_status ) ;
}
2026-05-17 20:32:36 +02:00
ControlResponse ::DbChanges { db , batch } = > {
println! ( " db: {} " , db . name ) ;
println! ( " changes: {} " , batch . changes . len ( ) ) ;
println! (
" max_db_version: {} " ,
batch
. max_db_version
. map ( | version | version . to_string ( ) )
. unwrap_or_else ( | | " none " . to_owned ( ) )
) ;
println! ( " schema_metadata: {} " , batch . schema_metadata ) ;
for change in batch . changes {
println! (
" {} \t {} \t {} " ,
change . db_version , change . table_name , change . column_id
) ;
}
}
2026-05-18 22:11:57 +02:00
ControlResponse ::DbSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
name ,
changes_received ,
2026-05-19 19:02:39 +02:00
changes_applied ,
2026-05-18 22:11:57 +02:00
max_db_version ,
schema_match ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " synced db changes for {name} from {peer_node_id} " ) ;
} else {
println! ( " db sync denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " changes_received: {changes_received} " ) ;
2026-05-19 19:02:39 +02:00
println! ( " changes_applied: {changes_applied} " ) ;
2026-05-18 22:11:57 +02:00
println! (
" max_db_version: {} " ,
max_db_version
. map ( | version | version . to_string ( ) )
. unwrap_or_else ( | | " none " . to_owned ( ) )
) ;
println! ( " schema_match: {schema_match} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-16 21:52:35 +02:00
ControlResponse ::KvCreated { kv } = > {
println! ( " created kv: {} " , kv . name ) ;
println! ( " id: {} " , kv . id ) ;
println! ( " resource: {} " , kv . resource ) ;
println! ( " sync_status: {} " , kv . sync_status ) ;
}
ControlResponse ::KvSet { entry } = > {
println! ( " set {} {} " , entry . store , entry . key ) ;
}
ControlResponse ::KvGet { entry } = > {
if let Some ( entry ) = entry {
println! ( " {} " , entry . value ) ;
} else {
println! ( " not found " ) ;
}
}
2026-05-18 18:36:03 +02:00
ControlResponse ::KvSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
name ,
entries_imported ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " synced kv {name} from {peer_node_id} : {entries_imported} entries " ) ;
} else {
println! ( " kv sync denied for {name} by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-16 22:15:18 +02:00
ControlResponse ::DocumentCreated { document } = > {
println! ( " created document: {} " , document . name ) ;
println! ( " id: {} " , document . id ) ;
println! ( " resource: {} " , document . resource ) ;
println! ( " sync_status: {} " , document . sync_status ) ;
println! ( " state_bytes: {} " , document . state_bytes ) ;
}
ControlResponse ::DocumentStatus { document } = > {
println! ( " document: {} " , document . name ) ;
println! ( " id: {} " , document . id ) ;
println! ( " resource: {} " , document . resource ) ;
println! ( " sync_status: {} " , document . sync_status ) ;
println! ( " state_bytes: {} " , document . state_bytes ) ;
}
2026-05-17 19:59:03 +02:00
ControlResponse ::DocumentSet { state } = > {
println! ( " updated document: {} " , state . document . name ) ;
println! ( " state_bytes: {} " , state . document . state_bytes ) ;
println! ( " updated_at_ms: {} " , state . updated_at . 0 ) ;
}
ControlResponse ::DocumentGet { state } = > {
println! ( " {} " , state . state_json ) ;
}
2026-05-18 18:49:34 +02:00
ControlResponse ::DocumentSynced {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
name ,
updated ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " synced document {name} from {peer_node_id} : updated= {updated} " ) ;
} else {
println! ( " document sync denied for {name} by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-17 18:23:51 +02:00
ControlResponse ::PubsubPublished { message } = > {
println! ( " published: {} " , message . topic ) ;
println! ( " published_at_ms: {} " , message . published_at . 0 ) ;
}
2026-05-18 18:41:04 +02:00
ControlResponse ::PubsubRemotePublished {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
message ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " published: {} " , message . topic ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " published_at_ms: {} " , message . published_at . 0 ) ;
} else {
println! ( " pubsub publish denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-17 18:23:51 +02:00
ControlResponse ::PubsubMessages {
topic ,
messages ,
note ,
} = > {
println! ( " topic: {topic} " ) ;
println! ( " messages: {} " , messages . len ( ) ) ;
for message in messages {
println! ( " {} \t {} " , message . published_at . 0 , message . message ) ;
}
println! ( " note: {note} " ) ;
}
2026-05-19 15:28:48 +02:00
ControlResponse ::PubsubRemoteMessages {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
topic ,
messages ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " topic: {topic} " ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " messages: {} " , messages . len ( ) ) ;
for message in messages {
println! ( " {} \t {} " , message . published_at . 0 , message . message ) ;
}
} else {
println! ( " pubsub subscribe denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-17 20:17:26 +02:00
ControlResponse ::PipeListening { listener } = > {
println! ( " listening pipe: {} " , listener . name ) ;
println! ( " id: {} " , listener . id ) ;
println! ( " listened_at_ms: {} " , listener . listened_at . 0 ) ;
println! ( " note: {} " , listener . note ) ;
}
ControlResponse ::PipeConnected { connection } = > {
println! ( " pipe target: {} " , connection . target ) ;
println! ( " local_listener_found: {} " , connection . local_listener_found ) ;
println! ( " connected_at_ms: {} " , connection . connected_at . 0 ) ;
println! ( " note: {} " , connection . note ) ;
}
2026-05-19 19:21:42 +02:00
ControlResponse ::PipeRemoteListening {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
listener ,
allowed ,
reason ,
note ,
} = > {
if let Some ( listener ) = listener {
println! ( " listening pipe: {} " , listener . name ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " id: {} " , listener . id ) ;
println! ( " listened_at_ms: {} " , listener . listened_at . 0 ) ;
} else {
println! ( " pipe listen denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-18 18:45:10 +02:00
ControlResponse ::PipeRemoteConnected {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
connection ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " pipe target: {} " , connection . target ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " remote_listener_found: {} " , connection . local_listener_found ) ;
println! ( " connected_at_ms: {} " , connection . connected_at . 0 ) ;
} else {
println! ( " pipe connect denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
2026-05-20 13:57:14 +02:00
ControlResponse ::PipeSent {
message ,
listener_found ,
note ,
} = > {
println! ( " listener_found: {listener_found} " ) ;
if let Some ( message ) = message {
println! ( " pipe: {} " , message . pipe ) ;
println! ( " received_at_ms: {} " , message . received_at . 0 ) ;
print_pipe_message_data ( & message ) ? ;
}
println! ( " note: {note} " ) ;
}
ControlResponse ::PipeRemoteSent {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
message ,
listener_found ,
allowed ,
reason ,
note ,
} = > {
println! ( " peer: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " listener_found: {listener_found} " ) ;
if let Some ( message ) = message {
println! ( " pipe: {} " , message . pipe ) ;
println! ( " received_at_ms: {} " , message . received_at . 0 ) ;
}
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
}
ControlResponse ::PipeMessages {
name ,
messages ,
drained ,
note ,
} = > {
println! ( " pipe: {name} " ) ;
println! ( " messages: {} " , messages . len ( ) ) ;
println! ( " drained: {drained} " ) ;
for message in messages {
print_pipe_message_data ( & message ) ? ;
}
println! ( " note: {note} " ) ;
}
2026-05-19 15:51:11 +02:00
ControlResponse ::SshProxyConnected {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
connection ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " ssh proxy target: {peer_node_id} " ) ;
if let Some ( connection ) = connection {
println! ( " connected_at_ms: {} " , connection . connected_at . 0 ) ;
if let Some ( local_sshd_target ) = connection . local_sshd_target {
println! ( " remote_sshd_target: {local_sshd_target} " ) ;
}
println! (
" admin_shell_available: {} " ,
connection . admin_shell_available
) ;
println! ( " connection_note: {} " , connection . note ) ;
}
} else {
println! ( " ssh proxy denied by {peer_node_id} " ) ;
}
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
2026-05-21 01:49:48 +02:00
}
ControlResponse ::SshAdminShellOutput {
peer_node_id ,
peer_agent_id ,
endpoint_id ,
command ,
output ,
allowed ,
reason ,
note ,
} = > {
if allowed {
println! ( " {output} " ) ;
} else {
println! ( " ssh admin shell denied by {peer_node_id} " ) ;
}
println! ( " command: {command} " ) ;
println! ( " peer: {peer_node_id} " ) ;
println! ( " agent: {peer_agent_id} " ) ;
println! ( " endpoint: {endpoint_id} " ) ;
println! ( " allowed: {allowed} " ) ;
println! ( " reason: {reason} " ) ;
println! ( " note: {note} " ) ;
2026-05-19 15:51:11 +02:00
}
2026-05-15 15:08:20 +02:00
ControlResponse ::NotImplemented { module , command } = > {
println! ( " {module} {command} : not implemented yet " ) ;
}
ControlResponse ::Error { message } = > bail! ( message ) ,
}
Ok ( ( ) )
}
2026-05-16 00:17:08 +02:00
fn print_service_report ( report : ServiceReport , json : bool ) -> Result < ( ) > {
if json {
println! (
" {} " ,
serde_json ::json! ( {
" manager " : report . manager . to_string ( ) ,
" action " : format ! ( " {:?} " , report . action ) ,
" service_name " : report . service_name ,
" definition_path " : report . definition_path ,
" definition " : report . definition ,
" commands " : report . commands ,
" note " : report . note ,
} )
) ;
return Ok ( ( ) ) ;
}
println! ( " service: {} " , report . service_name ) ;
println! ( " manager: {} " , report . manager ) ;
println! ( " action: {:?} " , report . action ) ;
if let Some ( path ) = report . definition_path {
println! ( " definition: {} " , path . display ( ) ) ;
}
if ! report . commands . is_empty ( ) {
println! ( " commands: " ) ;
for command in report . commands {
println! ( " {} " , shell_quote_command ( & command ) ) ;
}
}
if let Some ( definition ) = report . definition {
println! ( " definition_body: " ) ;
print! ( " {definition} " ) ;
}
println! ( " note: {} " , report . note ) ;
Ok ( ( ) )
}
2026-05-18 03:57:26 +02:00
fn print_file_conflict ( conflict : & geth_cas ::FileConflict ) {
println! ( " root: {} " , conflict . root ) ;
println! ( " resource: {} " , conflict . resource ) ;
println! ( " path: {} " , conflict . path ) ;
println! ( " kind: {} " , conflict . kind . as_str ( ) ) ;
println! ( " status: {} " , conflict . status . as_str ( ) ) ;
if let Some ( hash ) = & conflict . base_tree {
println! ( " base_tree: {hash} " ) ;
}
if let Some ( hash ) = & conflict . local_tree {
println! ( " local_tree: {hash} " ) ;
}
if let Some ( hash ) = & conflict . remote_tree {
println! ( " remote_tree: {hash} " ) ;
}
println! ( " detail: {} " , conflict . detail ) ;
if let Some ( resolution ) = & conflict . resolution {
println! ( " resolution: {} " , resolution . as_str ( ) ) ;
}
if let Some ( note ) = & conflict . resolution_note {
println! ( " resolution_note: {note} " ) ;
}
}
2026-05-20 13:57:14 +02:00
fn print_pipe_message_data ( message : & geth_pipe ::PipeMessage ) -> Result < ( ) > {
let bytes = base64 ::engine ::general_purpose ::STANDARD
. decode ( & message . data_base64 )
. context ( " decode pipe message " ) ? ;
match String ::from_utf8 ( bytes ) {
Ok ( text ) = > println! ( " {text} " ) ,
Err ( error ) = > println! (
" base64:{} " ,
base64 ::engine ::general_purpose ::STANDARD . encode ( error . into_bytes ( ) )
) ,
}
Ok ( ( ) )
}
2026-05-20 13:59:41 +02:00
fn pipe_send_payload_base64 ( message : Option < String > , input : Option < PathBuf > ) -> Result < String > {
match ( message , input ) {
( Some ( message ) , None ) = > Ok ( base64 ::engine ::general_purpose ::STANDARD . encode ( message ) ) ,
( None , Some ( path ) ) if path . as_os_str ( ) = = " - " = > {
let mut bytes = Vec ::new ( ) ;
std ::io ::stdin ( )
. read_to_end ( & mut bytes )
. context ( " read pipe payload from stdin " ) ? ;
Ok ( base64 ::engine ::general_purpose ::STANDARD . encode ( bytes ) )
}
( None , Some ( path ) ) = > {
let bytes = std ::fs ::read ( & path ) . with_context ( | | format! ( " read {} " , path . display ( ) ) ) ? ;
Ok ( base64 ::engine ::general_purpose ::STANDARD . encode ( bytes ) )
}
( Some ( _ ) , Some ( _ ) ) = > bail! ( " pipe send accepts either MESSAGE or --in, not both " ) ,
( None , None ) = > bail! ( " pipe send requires MESSAGE or --in <path>; use --in - for stdin " ) ,
}
}
2026-05-16 00:17:08 +02:00
fn shell_quote_command ( command : & [ String ] ) -> String {
command
. iter ( )
. map ( | arg | {
if arg
. bytes ( )
. all ( | byte | byte . is_ascii_alphanumeric ( ) | | b " -_./:=+@, " . contains ( & byte ) )
{
arg . clone ( )
} else {
format! ( " ' {} ' " , arg . replace ( '\'' , " ' \\ '' " ) )
}
} )
. collect ::< Vec < _ > > ( )
. join ( " " )
}