diff --git a/README.md b/README.md index fafe507..db805ab 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ The daemon owns local identity, the Iroh endpoint, trust state, resource registry, module router, local metadata store, and synchronized data structures. Most non-daemon commands talk to the daemon through a local Unix socket at `$GETH_HOME/run/geth.sock`. +`geth status` and `geth status --json` report daemon uptime, store schema and +durability settings, Iroh endpoint/relay/discovery state, and native backend +health for automation. `geth init --admin-key --signing-key --node-name ` records an owner/admin keychain, the local user/device/node binding, and diff --git a/crates/geth-cli/src/lib.rs b/crates/geth-cli/src/lib.rs index 5a389a0..47cfdb9 100644 --- a/crates/geth-cli/src/lib.rs +++ b/crates/geth-cli/src/lib.rs @@ -2210,6 +2210,7 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> { println!("socket: {}", status.socket.display()); println!("agent: {}", status.agent_id); println!("node: {}", status.node_id); + println!("uptime seconds: {}", status.daemon_uptime_seconds); println!( "store schema: {}/{}", status.store_schema_version, status.store_current_schema_version diff --git a/crates/geth-control/src/lib.rs b/crates/geth-control/src/lib.rs index 0116be3..6b980f6 100644 --- a/crates/geth-control/src/lib.rs +++ b/crates/geth-control/src/lib.rs @@ -1156,6 +1156,8 @@ pub struct StatusResponse { pub socket: PathBuf, pub agent_id: String, pub node_id: String, + pub daemon_started_at_ms: i64, + pub daemon_uptime_seconds: u64, pub store_schema_version: i64, pub store_current_schema_version: i64, pub store_journal_mode: String, diff --git a/crates/geth-node/src/lib.rs b/crates/geth-node/src/lib.rs index 1d4cb95..e22164e 100644 --- a/crates/geth-node/src/lib.rs +++ b/crates/geth-node/src/lib.rs @@ -68,7 +68,7 @@ use runtime::{ use std::collections::{BTreeMap, BTreeSet}; use std::path::{Component, Path, PathBuf}; use std::sync::{Arc, Mutex}; -use std::time::Duration; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use sync::{ KvDocsState, load_live_sync_cursor, store_live_sync_cursor, sync_now, sync_status_local, }; @@ -185,6 +185,7 @@ pub struct LocalNode { pub paths: GethPaths, pub agent_id: String, pub node_id: String, + pub daemon_started_at_ms: i64, pub iroh_status: EndpointStatus, iroh_endpoint: Arc>>, iroh_blob_store: Arc>>, @@ -262,6 +263,7 @@ pub fn init_node(paths: &GethPaths) -> Result { paths: paths.clone(), agent_id, node_id, + daemon_started_at_ms: current_unix_ms(), iroh_status: EndpointStatus::scaffolded(), iroh_endpoint: Arc::new(Mutex::new(None)), iroh_blob_store: Arc::new(Mutex::new(None)), @@ -275,6 +277,14 @@ pub fn init_node(paths: &GethPaths) -> Result { }) } +fn current_unix_ms() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() + .min(i64::MAX as u128) as i64 +} + pub fn init_owned_node( paths: &GethPaths, options: InitOwnerOptions, @@ -5880,6 +5890,11 @@ pub fn handle_request( socket: node.paths.socket_path(), agent_id: node.agent_id.clone(), node_id: node.node_id.clone(), + daemon_started_at_ms: node.daemon_started_at_ms, + daemon_uptime_seconds: current_unix_ms() + .saturating_sub(node.daemon_started_at_ms) + .max(0) as u64 + / 1000, store_schema_version, store_current_schema_version: geth_store::CURRENT_SCHEMA_VERSION, store_journal_mode, diff --git a/crates/geth/tests/bootstrap.rs b/crates/geth/tests/bootstrap.rs index a4bb33c..3778f86 100644 --- a/crates/geth/tests/bootstrap.rs +++ b/crates/geth/tests/bootstrap.rs @@ -257,6 +257,7 @@ fn geth_status_against_running_daemon() { wait_for_socket(&home.path().join("run/geth.sock")); let output = run_geth(home.path(), &["status"]); + let json_output = run_geth(home.path(), &["--json", "status"]); let _ = daemon.kill(); let _ = daemon.wait(); @@ -268,6 +269,7 @@ fn geth_status_against_running_daemon() { let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("geth daemon: running")); assert!(stdout.contains("agent:")); + assert!(stdout.contains("uptime seconds:")); assert!(stdout.contains("store schema:")); assert!(stdout.contains("store journal: wal")); assert!(stdout.contains("store synchronous: normal")); @@ -284,6 +286,25 @@ fn geth_status_against_running_daemon() { assert!(stdout.contains( "native backend pubsub: shared-iroh-endpoint-ready target iroh-gossip 0.101.0 (wired)" )); + + let status_json = command_json(&json_output); + assert_eq!(status_json["type"], "status"); + assert!( + status_json["daemon_started_at_ms"] + .as_i64() + .expect("daemon started at ms") + > 0 + ); + assert!( + status_json["daemon_uptime_seconds"] + .as_u64() + .expect("daemon uptime seconds") + < 60 + ); + assert_eq!(status_json["store_journal_mode"], "wal"); + assert_eq!(status_json["store_synchronous"], "normal"); + assert_eq!(status_json["store_status"], "ok"); + assert!(status_json["native_backends"].is_array()); } #[test] diff --git a/docs/architecture.md b/docs/architecture.md index 79407b0..91e3221 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -14,8 +14,8 @@ SQLite transaction where SQLite supports it. File-backed stores deliberately use SQLite WAL mode with `synchronous=NORMAL`: committed transactions remain consistent after process crashes, while the most recent transaction can be lost on an OS crash or power loss before the WAL is durable. `geth status` reports -the observed schema version, journal mode, synchronous mode, and whether those -values match the expected store policy. +daemon uptime, the observed schema version, journal mode, synchronous mode, and +whether those values match the expected store policy. Service management is also exposed through the single binary. `geth daemon service ...` installs and controls a user-level service definition for the local diff --git a/docs/production-readiness-roadmap.md b/docs/production-readiness-roadmap.md index eb20601..82fc000 100644 --- a/docs/production-readiness-roadmap.md +++ b/docs/production-readiness-roadmap.md @@ -275,12 +275,12 @@ Goal: make production failures diagnosable from the CLI and logs. stream, cursor, and stable error code where applicable. - `[ ]` Sensitive bearer tokens and private material are not logged. -- `[ ]` Expand health status. +- `[x]` Expand health status. Acceptance criteria: - - `[ ]` `geth status --json` reports daemon uptime. - - `[ ]` Store schema version is visible. - - `[ ]` Native backend health is visible. - - `[ ]` Iroh relay/local-discovery state is visible without exposing + - `[x]` `geth status --json` reports daemon uptime. + - `[x]` Store schema version is visible. + - `[x]` Native backend health is visible. + - `[x]` Iroh relay/local-discovery state is visible without exposing unrelated config secrets. - `[ ]` Add `geth doctor`.