#[cfg(unix)]
use std::time::Duration;
use anyhow::Result;
use serde::Serialize;
use super::super::next_action::{NextActionValidationContext, write_full_command_json};
use crate::cli::{Cli, should_output_json};
#[cfg(unix)]
use crate::cli::commands::netdaemon::proto::{
NETWORK_DAEMON_PROTOCOL_VERSION, NetworkDaemonRequest, NetworkDaemonResponse,
network_daemon_endpoint_path,
};
#[derive(Debug, Serialize)]
struct NetdStatusOutput {
output_kind: &'static str,
status: &'static str,
running: bool,
endpoint_path: String,
node_id: Option<String>,
version: Option<u32>,
uptime_s: Option<u64>,
}
#[derive(Debug, Serialize)]
struct NetdStopOutput {
output_kind: &'static str,
action: &'static str,
status: &'static str,
}
#[cfg(all(unix, feature = "client"))]
pub async fn cmd_netd_serve(_cli: &Cli) -> Result<()> {
super::server::run_network_daemon().await
}
#[cfg(not(all(unix, feature = "client")))]
pub async fn cmd_netd_serve(_cli: &Cli) -> Result<()> {
Err(netd_unsupported_error())
}
#[cfg(unix)]
pub fn cmd_netd_status(cli: &Cli) -> Result<()> {
use repo::daemon::send_json_request_unix;
let heddle_home = repo::identity::heddle_home_dir();
let endpoint_path = network_daemon_endpoint_path(&heddle_home);
let endpoint_display = endpoint_path.display().to_string();
let json = should_output_json(cli, None);
let live = read_live_netd(&endpoint_path);
let (running, node_id, version, uptime_s) = match live {
Some(endpoint) => {
let health = endpoint
.socket_path
.as_deref()
.and_then(|socket| {
send_json_request_unix::<_, NetworkDaemonResponse>(
socket,
&NetworkDaemonRequest::Health {},
)
.ok()
})
.and_then(|response| match response {
NetworkDaemonResponse::Health {
node_id, uptime_s, ..
} => Some((node_id, uptime_s)),
_ => None,
});
let node_id = health
.as_ref()
.map(|(node_id, _)| node_id.clone())
.or_else(|| endpoint.node_id.clone());
let uptime_s = health.map(|(_, uptime_s)| uptime_s);
(true, node_id, Some(endpoint.version), uptime_s)
}
None => (false, None, None, None),
};
if json {
let output = NetdStatusOutput {
output_kind: "netd_status",
status: if running { "running" } else { "not_running" },
running,
endpoint_path: endpoint_display,
node_id,
version,
uptime_s,
};
return write_full_command_json(
&output,
NextActionValidationContext::without_repo(&["netd", "status"]),
);
}
if running {
let node = node_id.as_deref().unwrap_or("<unknown>");
match uptime_s {
Some(uptime) => {
println!("network daemon running (node {node}, uptime {uptime}s)")
}
None => println!("network daemon running (node {node})"),
}
} else {
println!("network daemon not running ({endpoint_display})");
}
Ok(())
}
#[cfg(not(unix))]
pub fn cmd_netd_status(_cli: &Cli) -> Result<()> {
Err(netd_unsupported_error())
}
#[cfg(unix)]
pub fn cmd_netd_stop(cli: &Cli) -> Result<()> {
use repo::daemon::{load_endpoint, pid_alive, send_json_request_unix};
let heddle_home = repo::identity::heddle_home_dir();
let endpoint_path = network_daemon_endpoint_path(&heddle_home);
let json = should_output_json(cli, None);
let existing = load_endpoint(&endpoint_path).ok();
let recorded_pid = existing.as_ref().and_then(|endpoint| endpoint.pid);
let socket_path = existing.as_ref().and_then(|endpoint| endpoint.socket_path.clone());
let daemon_running = recorded_pid.map(pid_alive).unwrap_or(false);
if !daemon_running {
return report_stop(json, false);
}
if let Some(socket) = socket_path.as_deref() {
let _ = send_json_request_unix::<_, NetworkDaemonResponse>(
socket,
&NetworkDaemonRequest::Shutdown {},
);
}
for _ in 0..40 {
if !endpoint_path.exists() {
break;
}
std::thread::sleep(Duration::from_millis(50));
}
if let Some(pid) = recorded_pid {
for _ in 0..40 {
if !pid_alive(pid) {
break;
}
std::thread::sleep(Duration::from_millis(50));
}
}
report_stop(json, true)
}
#[cfg(not(unix))]
pub fn cmd_netd_stop(_cli: &Cli) -> Result<()> {
Err(netd_unsupported_error())
}
fn report_stop(json: bool, stopped: bool) -> Result<()> {
if json {
return write_full_command_json(
&NetdStopOutput {
output_kind: "netd_stop",
action: "netd stop",
status: if stopped { "stopped" } else { "not_running" },
},
NextActionValidationContext::without_repo(&["netd", "stop"]),
);
}
if stopped {
println!("network daemon stopped");
} else {
println!("network daemon not running");
}
Ok(())
}
#[cfg(unix)]
fn read_live_netd(endpoint_path: &std::path::Path) -> Option<repo::daemon::EndpointState> {
use repo::daemon::{load_endpoint, pid_alive};
let endpoint = load_endpoint(endpoint_path).ok()?;
if endpoint.version != NETWORK_DAEMON_PROTOCOL_VERSION {
return None;
}
match endpoint.pid {
Some(pid) if pid_alive(pid) => Some(endpoint),
_ => None,
}
}
#[cfg(not(all(unix, feature = "client")))]
fn netd_unsupported_error() -> anyhow::Error {
anyhow::anyhow!(
"the heddle network daemon requires a Unix host built with the `client` feature"
)
}