Improve CLI and daemon lifecycle usability
This commit is contained in:
parent
afec7063db
commit
f61cb44dad
14 changed files with 1028 additions and 204 deletions
|
|
@ -92,7 +92,7 @@ async fn serve_local_control(node: LocalNode, listener: UnixListener) -> Result<
|
|||
}
|
||||
});
|
||||
}
|
||||
signal = tokio::signal::ctrl_c() => {
|
||||
signal = shutdown_signal() => {
|
||||
signal?;
|
||||
tracing::info!("shutdown signal received");
|
||||
return Ok(());
|
||||
|
|
@ -101,6 +101,22 @@ async fn serve_local_control(node: LocalNode, listener: UnixListener) -> Result<
|
|||
}
|
||||
}
|
||||
|
||||
async fn shutdown_signal() -> Result<(), std::io::Error> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut terminate =
|
||||
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
|
||||
tokio::select! {
|
||||
result = tokio::signal::ctrl_c() => result,
|
||||
_ = terminate.recv() => Ok(()),
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
tokio::signal::ctrl_c().await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_iroh_control_accept_loop(
|
||||
node: LocalNode,
|
||||
endpoint: GethIrohEndpoint,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ pub struct ServiceReport {
|
|||
pub manager: ServiceManager,
|
||||
pub action: ServiceAction,
|
||||
pub service_name: String,
|
||||
pub state: Option<String>,
|
||||
pub definition_path: Option<PathBuf>,
|
||||
pub definition: Option<String>,
|
||||
pub commands: Vec<Vec<String>>,
|
||||
|
|
@ -223,39 +224,37 @@ pub fn status_user_service(manager: ServiceManager) -> Result<ServiceReport, Ser
|
|||
let manager = manager.resolve()?;
|
||||
match manager {
|
||||
ServiceManager::SystemdUser => {
|
||||
let command = run_command(
|
||||
let (command, code, detail) = inspect_command(
|
||||
"systemctl",
|
||||
&["--user", "status", SYSTEMD_UNIT, "--no-pager"],
|
||||
)?;
|
||||
Ok(report(
|
||||
Ok(status_report(
|
||||
manager,
|
||||
ServiceAction::Status,
|
||||
Some(systemd_unit_path()?),
|
||||
None,
|
||||
vec![command],
|
||||
"queried systemd user service",
|
||||
command,
|
||||
systemd_state(code),
|
||||
&detail,
|
||||
))
|
||||
}
|
||||
ServiceManager::LaunchdUser => {
|
||||
let command = run_command("launchctl", &["list", LAUNCHD_LABEL])?;
|
||||
Ok(report(
|
||||
let (command, code, detail) = inspect_command("launchctl", &["list", LAUNCHD_LABEL])?;
|
||||
Ok(status_report(
|
||||
manager,
|
||||
ServiceAction::Status,
|
||||
Some(launchd_plist_path()?),
|
||||
None,
|
||||
vec![command],
|
||||
"queried launchd user agent",
|
||||
command,
|
||||
launchd_state(code, &detail),
|
||||
&detail,
|
||||
))
|
||||
}
|
||||
ServiceManager::WindowsTask => {
|
||||
let command = run_command("schtasks", &["/Query", "/TN", WINDOWS_TASK_NAME])?;
|
||||
Ok(report(
|
||||
let (command, code, detail) =
|
||||
inspect_command("schtasks", &["/Query", "/TN", WINDOWS_TASK_NAME])?;
|
||||
Ok(status_report(
|
||||
manager,
|
||||
ServiceAction::Status,
|
||||
None,
|
||||
None,
|
||||
vec![command],
|
||||
"queried Windows per-user scheduled task",
|
||||
command,
|
||||
windows_task_state(code, &detail),
|
||||
&detail,
|
||||
))
|
||||
}
|
||||
ServiceManager::Auto => unreachable!("auto is resolved above"),
|
||||
|
|
@ -358,12 +357,12 @@ fn install_launchd_user(
|
|||
}
|
||||
let definition = launchd_plist(paths, executable);
|
||||
std::fs::write(&plist_path, &definition)?;
|
||||
let mut commands = vec![run_command(
|
||||
"launchctl",
|
||||
&["load", "-w", &plist_path.display().to_string()],
|
||||
)?];
|
||||
let mut commands = Vec::new();
|
||||
if start {
|
||||
commands.push(run_command("launchctl", &["start", LAUNCHD_LABEL])?);
|
||||
commands.push(run_command(
|
||||
"launchctl",
|
||||
&["load", "-w", &plist_path.display().to_string()],
|
||||
)?);
|
||||
}
|
||||
Ok(report(
|
||||
ServiceManager::LaunchdUser,
|
||||
|
|
@ -379,10 +378,9 @@ fn uninstall_launchd_user() -> Result<ServiceReport, ServiceError> {
|
|||
let plist_path = launchd_plist_path()?;
|
||||
let mut commands = Vec::new();
|
||||
if plist_path.exists() {
|
||||
commands.push(run_command(
|
||||
"launchctl",
|
||||
&["unload", &plist_path.display().to_string()],
|
||||
)?);
|
||||
let (command, _, _) =
|
||||
inspect_command("launchctl", &["unload", &plist_path.display().to_string()])?;
|
||||
commands.push(command);
|
||||
std::fs::remove_file(&plist_path)?;
|
||||
}
|
||||
Ok(report(
|
||||
|
|
@ -546,6 +544,83 @@ fn run_command(program: &str, args: &[&str]) -> Result<Vec<String>, ServiceError
|
|||
}
|
||||
}
|
||||
|
||||
fn inspect_command(
|
||||
program: &str,
|
||||
args: &[&str],
|
||||
) -> Result<(Vec<String>, Option<i32>, String), ServiceError> {
|
||||
let output = Command::new(program).args(args).output()?;
|
||||
let command = std::iter::once(program.to_owned())
|
||||
.chain(args.iter().map(|arg| (*arg).to_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned();
|
||||
let detail = if stdout.is_empty() { stderr } else { stdout }
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_owned();
|
||||
Ok((command, output.status.code(), detail))
|
||||
}
|
||||
|
||||
fn systemd_state(code: Option<i32>) -> &'static str {
|
||||
match code {
|
||||
Some(0) => "running",
|
||||
Some(3) => "not-running",
|
||||
Some(4) => "not-installed",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn launchd_state(code: Option<i32>, detail: &str) -> &'static str {
|
||||
if code == Some(0) {
|
||||
"loaded"
|
||||
} else if detail
|
||||
.to_ascii_lowercase()
|
||||
.contains("could not find service")
|
||||
{
|
||||
"not-loaded"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
fn windows_task_state(code: Option<i32>, detail: &str) -> &'static str {
|
||||
if code == Some(0) {
|
||||
"installed"
|
||||
} else {
|
||||
let detail = detail.to_ascii_lowercase();
|
||||
if detail.contains("cannot find") || detail.contains("does not exist") {
|
||||
"not-installed"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_report(
|
||||
manager: ServiceManager,
|
||||
definition_path: Option<PathBuf>,
|
||||
command: Vec<String>,
|
||||
state: &str,
|
||||
detail: &str,
|
||||
) -> ServiceReport {
|
||||
let note = if detail.is_empty() {
|
||||
format!("user service is {state}")
|
||||
} else {
|
||||
format!("user service is {state}: {detail}")
|
||||
};
|
||||
let mut report = report(
|
||||
manager,
|
||||
ServiceAction::Status,
|
||||
definition_path,
|
||||
None,
|
||||
vec![command],
|
||||
¬e,
|
||||
);
|
||||
report.state = Some(state.to_owned());
|
||||
report
|
||||
}
|
||||
|
||||
fn report(
|
||||
manager: ServiceManager,
|
||||
action: ServiceAction,
|
||||
|
|
@ -563,6 +638,7 @@ fn report(
|
|||
},
|
||||
manager,
|
||||
action,
|
||||
state: None,
|
||||
definition_path,
|
||||
definition,
|
||||
commands,
|
||||
|
|
@ -619,4 +695,35 @@ mod tests {
|
|||
assert!(command.contains("GETH_HOME="));
|
||||
assert!(command.contains("daemon run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_status_reports_non_running_state_without_an_action_error() {
|
||||
let report = status_report(
|
||||
ServiceManager::SystemdUser,
|
||||
Some(PathBuf::from("/tmp/geth.service")),
|
||||
vec!["systemctl".to_owned(), "status".to_owned()],
|
||||
"not-running",
|
||||
"Unit geth.service could not be found.",
|
||||
);
|
||||
assert_eq!(report.action, ServiceAction::Status);
|
||||
assert_eq!(report.state.as_deref(), Some("not-running"));
|
||||
assert!(report.note.contains("could not be found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_status_classification_preserves_manager_errors_as_unknown() {
|
||||
assert_eq!(systemd_state(Some(0)), "running");
|
||||
assert_eq!(systemd_state(Some(3)), "not-running");
|
||||
assert_eq!(systemd_state(Some(4)), "not-installed");
|
||||
assert_eq!(systemd_state(Some(1)), "unknown");
|
||||
assert_eq!(
|
||||
launchd_state(Some(1), "Could not find service local.geth.daemon"),
|
||||
"not-loaded"
|
||||
);
|
||||
assert_eq!(launchd_state(Some(1), "operation not permitted"), "unknown");
|
||||
assert_eq!(
|
||||
windows_task_state(Some(1), "ERROR: The system cannot find the file specified."),
|
||||
"not-installed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue