From a99a21a2ea68ef7e6d3f2264d94cc07cd0b340e3 Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Thu, 16 Jul 2026 10:55:22 +0200 Subject: [PATCH] feat!: move internal services to health instead of status For the normal use cases and day-to-day use, it feels like there is no need to have the internal Linkup services listed on `status`. Since they are more used to debug internal Linkup issues and not really the user normal uses, seems like `health` is a more appropriate place for it. This changes proposes removing internal services from the `status` command, and instead have them be shown on the `health` command. --- linkup-cli/src/commands/health.rs | 83 +++++++++++++++++++++++- linkup-cli/src/commands/status/health.rs | 2 +- linkup-cli/src/commands/status/mod.rs | 42 +----------- 3 files changed, 85 insertions(+), 42 deletions(-) diff --git a/linkup-cli/src/commands/health.rs b/linkup-cli/src/commands/health.rs index 394ae9dd..7fa6f26c 100644 --- a/linkup-cli/src/commands/health.rs +++ b/linkup-cli/src/commands/health.rs @@ -2,6 +2,7 @@ use std::{ collections::BTreeMap, env, io::{Write, stdout}, + thread, }; use anyhow::Result; @@ -10,13 +11,17 @@ use colored::Colorize; use linkup::Session; use linkup_clients::LocalServerClient; use serde::Serialize; +use url::Url; use crate::{ services::{cloudflared, local_server}, state::State, }; -use super::local_dns; +use super::{ + local_dns, + status::{ServerStatus, server_status}, +}; #[derive(clap::Args)] pub struct Args { @@ -56,6 +61,18 @@ struct States { items: BTreeMap, } +#[derive(Serialize)] +struct LinkupService { + url: Url, + status: ServerStatus, +} + +#[derive(Serialize)] +struct LinkupServices { + remote_server: Option, + tunnel: Option, +} + #[derive(Serialize)] enum LocalServer { Stopped, @@ -78,6 +95,7 @@ struct Health { cli: Cli, system: System, states: States, + linkup_services: LinkupServices, local_server: LocalServer, cloudflared: Cloudflared, } @@ -152,6 +170,65 @@ impl States { } } +impl LinkupService { + fn load(url: Url) -> Self { + let status = server_status(url.as_str(), None, None); + + Self { url, status } + } + + fn write(&self, writer: &mut impl Write, name: &str, offset: usize) -> Result<()> { + writeln!( + writer, + "{:>offset$}{}: {} ({})", + "", + name, + self.status.colored().to_uppercase(), + self.url + )?; + + Ok(()) + } +} + +impl LinkupServices { + fn load() -> Result { + let state = State::load().ok(); + + let remote_server_url = state + .as_ref() + .map(|state| state.linkup.worker_url.join("/linkup/check")) + .transpose()?; + let remote_server = + remote_server_url.map(|url| thread::spawn(move || LinkupService::load(url))); + + let tunnel_url = state + .as_ref() + .filter(|state| state.should_use_tunnel()) + .map(|state| state.get_tunnel_url().join("/linkup/check")) + .transpose()?; + let tunnel = tunnel_url.map(|url| thread::spawn(move || LinkupService::load(url))); + + Ok(Self { + remote_server: remote_server + .map(|handle| handle.join().expect("Linkup service check panicked")), + tunnel: tunnel.map(|handle| handle.join().expect("Linkup service check panicked")), + }) + } + + fn write(&self, writer: &mut impl Write, offset: usize) -> Result<()> { + if let Some(remote_server) = &self.remote_server { + remote_server.write(writer, "Remote Server", offset)?; + } + + if let Some(tunnel) = &self.tunnel { + tunnel.write(writer, "Tunnel", offset)?; + } + + Ok(()) + } +} + impl LocalServer { async fn load() -> Result { let local_server_client = LocalServerClient::new(&local_server::url()); @@ -275,6 +352,7 @@ impl Health { cli: Cli::load()?, system: System::load()?, states: States::load()?, + linkup_services: LinkupServices::load()?, local_server: LocalServer::load().await?, cloudflared: Cloudflared::load()?, }) @@ -290,6 +368,9 @@ impl Health { write!(writer, "{}", "States:".bold())?; self.states.write(writer, 2)?; + writeln!(writer, "{}", "Linkup Services:".bold())?; + self.linkup_services.write(writer, 2)?; + write!(writer, "{}", "Local Server:".bold())?; self.local_server.write(writer, 2)?; diff --git a/linkup-cli/src/commands/status/health.rs b/linkup-cli/src/commands/status/health.rs index 12a601d5..e63a831f 100644 --- a/linkup-cli/src/commands/status/health.rs +++ b/linkup-cli/src/commands/status/health.rs @@ -13,7 +13,7 @@ pub enum ServerStatus { } impl ServerStatus { - pub(super) fn colored(&self) -> ColoredString { + pub fn colored(&self) -> ColoredString { match self { ServerStatus::Ok => "ok".blue(), ServerStatus::Error => "error".yellow(), diff --git a/linkup-cli/src/commands/status/mod.rs b/linkup-cli/src/commands/status/mod.rs index cb51630d..8b7602a4 100644 --- a/linkup-cli/src/commands/status/mod.rs +++ b/linkup-cli/src/commands/status/mod.rs @@ -78,13 +78,10 @@ pub async fn status(args: &Args) -> anyhow::Result<()> { let config = load_config(&config_path).ok(); - let user_services = build_user_services(session_detail.as_ref(), config.as_ref()); - let internal_services = build_internal_services(&state); - let all_services: Vec = - user_services.into_iter().chain(internal_services).collect(); + let services = build_user_services(session_detail.as_ref(), config.as_ref()); let (mut service_statuses, status_receiver) = - prepare_service_statuses(&target_session, all_services); + prepare_service_statuses(&target_session, services); service_statuses.sort_by(|a, b| { a.priority @@ -261,41 +258,6 @@ fn build_user_services( .collect() } -fn build_internal_services(state: &State) -> Vec { - let local_url = services::local_server::url(); - - vec![ - ServiceToCheck { - name: "linkup_local_server".to_string(), - url: local_url.join("/linkup/check").unwrap(), - component_kind: "local".to_string(), - health: None, - priority: 1, - }, - ServiceToCheck { - name: "linkup_remote_server".to_string(), - url: state - .linkup - .worker_url - .join("/linkup/check") - .unwrap_or_else(|_| state.linkup.worker_url.clone()), - component_kind: "remote".to_string(), - health: None, - priority: 1, - }, - ServiceToCheck { - name: "tunnel".to_string(), - url: state - .get_tunnel_url() - .join("/linkup/check") - .unwrap_or_else(|_| state.get_tunnel_url()), - component_kind: "remote".to_string(), - health: None, - priority: 1, - }, - ] -} - fn prepare_service_statuses( session_name: &str, services: Vec,