diff --git a/linkup-cli/src/commands/health.rs b/linkup-cli/src/commands/health.rs index 55071f53..03d3c318 100644 --- a/linkup-cli/src/commands/health.rs +++ b/linkup-cli/src/commands/health.rs @@ -90,20 +90,18 @@ struct OrphanProcess { pub struct BackgroundServices { pub linkup_server: BackgroundServiceHealth, cloudflared: BackgroundServiceHealth, - dns_server: BackgroundServiceHealth, possible_orphan_processes: Vec, } #[derive(Debug, Serialize)] pub enum BackgroundServiceHealth { - Unknown, NotInstalled, Stopped, Running(u32), } impl BackgroundServices { - pub fn load(state: Option<&State>) -> Self { + pub fn load(_state: Option<&State>) -> Self { let mut managed_pids: Vec = Vec::with_capacity(4); let linkup_server = match services::LocalServer::find_pid() { @@ -128,30 +126,9 @@ impl BackgroundServices { BackgroundServiceHealth::NotInstalled }; - let dns_server = match services::LocalDnsServer::find_pid() { - Some(pid) => { - managed_pids.push(pid); - - BackgroundServiceHealth::Running(pid.as_u32()) - } - None => match state { - // If there is no state, we cannot know if local-dns is installed since we depend on - // the domains listed on it. - Some(state) => { - if local_dns::is_installed(&crate::state::managed_domains(Some(state), &None)) { - BackgroundServiceHealth::Stopped - } else { - BackgroundServiceHealth::NotInstalled - } - } - None => BackgroundServiceHealth::Unknown, - }, - }; - Self { linkup_server, cloudflared, - dns_server, possible_orphan_processes: find_potential_orphan_processes(managed_pids), } } @@ -357,15 +334,6 @@ impl Display for Health { BackgroundServiceHealth::NotInstalled => writeln!(f, "{}", "NOT INSTALLED".yellow())?, BackgroundServiceHealth::Stopped => writeln!(f, "{}", "NOT RUNNING".yellow())?, BackgroundServiceHealth::Running(pid) => writeln!(f, "{} ({})", "RUNNING".blue(), pid)?, - BackgroundServiceHealth::Unknown => writeln!(f, "{}", "UNKNOWN".yellow())?, - } - - write!(f, " - DNS Server ")?; - match &self.background_services.dns_server { - BackgroundServiceHealth::NotInstalled => writeln!(f, "{}", "NOT INSTALLED".yellow())?, - BackgroundServiceHealth::Stopped => writeln!(f, "{}", "NOT RUNNING".yellow())?, - BackgroundServiceHealth::Running(pid) => writeln!(f, "{} ({})", "RUNNING".blue(), pid)?, - BackgroundServiceHealth::Unknown => writeln!(f, "{}", "UNKNOWN".yellow())?, } write!(f, " - Cloudflared ")?; @@ -373,7 +341,6 @@ impl Display for Health { BackgroundServiceHealth::NotInstalled => writeln!(f, "{}", "NOT INSTALLED".yellow())?, BackgroundServiceHealth::Stopped => writeln!(f, "{}", "NOT RUNNING".yellow())?, BackgroundServiceHealth::Running(pid) => writeln!(f, "{} ({})", "RUNNING".blue(), pid)?, - BackgroundServiceHealth::Unknown => writeln!(f, "{}", "UNKNOWN".yellow())?, } writeln!(f, "{}", "Linkup:".bold().italic())?; diff --git a/linkup-cli/src/commands/server.rs b/linkup-cli/src/commands/server.rs index fa488143..75213b47 100644 --- a/linkup-cli/src/commands/server.rs +++ b/linkup-cli/src/commands/server.rs @@ -1,78 +1,31 @@ +use std::path::PathBuf; + use crate::Result; use linkup::MemoryStringStore; -use tokio::select; #[derive(clap::Args)] pub struct Args { - #[command(subcommand)] - server_kind: ServerKind, -} + #[arg(long)] + session_name: String, -#[derive(clap::Subcommand)] -pub enum ServerKind { - LocalWorker { - #[arg(long)] - certs_dir: String, - }, + #[arg(long, value_parser, num_args = 1.., value_delimiter = ',')] + domains: Vec, - Dns { - #[arg(long)] - session_name: String, - #[arg(long, value_parser, num_args = 1.., value_delimiter = ',')] - domains: Vec, - }, + #[arg(long)] + certs_dir: String, } pub async fn server(args: &Args) -> Result<()> { - match &args.server_kind { - ServerKind::LocalWorker { certs_dir } => { - let config_store = MemoryStringStore::default(); - - let http_config_store = config_store.clone(); - let handler_http = tokio::spawn(async move { - linkup_local_server::start_server_http(http_config_store) - .await - .unwrap(); - }); - - let handler_https = { - use std::path::PathBuf; - - let https_config_store = config_store.clone(); - let https_certs_dir = PathBuf::from(certs_dir); - - Some(tokio::spawn(async move { - linkup_local_server::start_server_https(https_config_store, &https_certs_dir) - .await; - })) - }; - - match handler_https { - Some(handler_https) => { - select! { - _ = handler_http => (), - _ = handler_https => (), - } - } - None => { - handler_http.await.unwrap(); - } - } - } - ServerKind::Dns { - session_name, - domains, - } => { - let session_name = session_name.clone(); - let domains = domains.clone(); - - let handler_dns = tokio::spawn(async move { - linkup_local_server::start_dns_server(session_name, domains).await; - }); - - handler_dns.await.unwrap(); - } - } + let config_store = MemoryStringStore::default(); + let https_certs_dir = PathBuf::from(&args.certs_dir); + + linkup_local_server::start( + config_store, + &https_certs_dir, + args.session_name.clone(), + args.domains.clone(), + ) + .await; Ok(()) } diff --git a/linkup-cli/src/commands/start.rs b/linkup-cli/src/commands/start.rs index 624f2bbb..82bc72ea 100644 --- a/linkup-cli/src/commands/start.rs +++ b/linkup-cli/src/commands/start.rs @@ -46,7 +46,6 @@ pub async fn start(args: &Args, fresh_state: bool, config_arg: &Option) let local_server = services::LocalServer::new(); let cloudflare_tunnel = services::CloudflareTunnel::new(); - let local_dns_server = services::LocalDnsServer::new(); let mut display_thread: Option> = None; let display_channel = sync::mpsc::channel::(); @@ -59,7 +58,6 @@ pub async fn start(args: &Args, fresh_state: bool, config_arg: &Option) &[ services::LocalServer::NAME, services::CloudflareTunnel::NAME, - services::LocalDnsServer::NAME, ], status_update_channel.1, display_channel.1, @@ -89,16 +87,6 @@ pub async fn start(args: &Args, fresh_state: bool, config_arg: &Option) } } - if exit_error.is_none() { - match local_dns_server - .run_with_progress(&mut state, status_update_channel.0.clone()) - .await - { - Ok(_) => (), - Err(err) => exit_error = Some(err), - } - } - if let Some(display_thread) = display_thread { display_channel.0.send(true).unwrap(); display_thread.join().unwrap(); diff --git a/linkup-cli/src/commands/stop.rs b/linkup-cli/src/commands/stop.rs index 306dbf93..9b0b8e0b 100644 --- a/linkup-cli/src/commands/stop.rs +++ b/linkup-cli/src/commands/stop.rs @@ -37,7 +37,6 @@ pub fn stop(_args: &Args, clear_env: bool) -> Result<()> { services::LocalServer::stop(); services::CloudflareTunnel::stop(); - services::LocalDnsServer::stop(); println!("Stopped linkup"); diff --git a/linkup-cli/src/services/local_dns_server.rs b/linkup-cli/src/services/local_dns_server.rs deleted file mode 100644 index 6686cc6a..00000000 --- a/linkup-cli/src/services/local_dns_server.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::{ - env, - fs::File, - os::unix::process::CommandExt, - path::PathBuf, - process::{self, Stdio}, -}; - -use anyhow::Context; - -use crate::{commands::local_dns, linkup_file_path, state::State, Result}; - -use super::BackgroundService; - -pub struct LocalDnsServer { - stdout_file_path: PathBuf, - stderr_file_path: PathBuf, -} - -impl LocalDnsServer { - pub fn new() -> Self { - Self { - stdout_file_path: linkup_file_path("localdns-stdout"), - stderr_file_path: linkup_file_path("localdns-stderr"), - } - } - - fn start(&self, session_name: &str, domains: &[String]) -> Result<()> { - log::debug!("Starting {}", Self::NAME); - - let stdout_file = File::create(&self.stdout_file_path)?; - let stderr_file = File::create(&self.stderr_file_path)?; - - let mut command = process::Command::new( - env::current_exe().context("Failed to get the current executable")?, - ); - command.env("RUST_LOG", "debug"); - command.env("LINKUP_SERVICE_ID", Self::ID); - command.args([ - "server", - "dns", - "--session-name", - session_name, - "--domains", - &domains.join(","), - ]); - - command - .process_group(0) - .stdout(stdout_file) - .stderr(stderr_file) - .stdin(Stdio::null()) - .spawn()?; - - Ok(()) - } -} - -impl BackgroundService for LocalDnsServer { - const ID: &str = "linkup-local-dns-server"; - const NAME: &str = "Local DNS server"; - - async fn run_with_progress( - &self, - state: &mut State, - status_sender: std::sync::mpsc::Sender, - ) -> Result<()> { - self.notify_update(&status_sender, super::RunStatus::Starting); - - let session_name = state.linkup.session_name.clone(); - let domains = state.domain_strings(); - - if !local_dns::is_installed(&domains) { - self.notify_update_with_details( - &status_sender, - super::RunStatus::Skipped, - "Not installed", - ); - - return Ok(()); - } - - if let Err(e) = self.start(&session_name, &domains) { - self.notify_update_with_details( - &status_sender, - super::RunStatus::Error, - "Failed to start", - ); - - return Err(e); - } - - self.notify_update(&status_sender, super::RunStatus::Started); - - Ok(()) - } -} diff --git a/linkup-cli/src/services/local_server.rs b/linkup-cli/src/services/local_server.rs index ccf59d3b..ec6222bd 100644 --- a/linkup-cli/src/services/local_server.rs +++ b/linkup-cli/src/services/local_server.rs @@ -50,7 +50,7 @@ impl LocalServer { Url::parse("http://localhost:80").expect("linkup url invalid") } - fn start(&self) -> Result<()> { + fn start(&self, session_name: String, domains: Vec) -> Result<()> { log::debug!("Starting {}", Self::NAME); let stdout_file = File::create(&self.stdout_file_path)?; @@ -66,7 +66,10 @@ impl LocalServer { command.env("LINKUP_SERVICE_ID", Self::ID); command.args([ "server", - "local-worker", + "--session-name", + &session_name, + "--domains", + &domains.join(","), "--certs-dir", linkup_certs_dir_path().to_str().unwrap(), ]); @@ -116,6 +119,9 @@ impl BackgroundService for LocalServer { ) -> Result<()> { self.notify_update(&status_sender, super::RunStatus::Starting); + let session_name = state.linkup.session_name.clone(); + let domains = state.domain_strings(); + if self.reachable().await { self.notify_update_with_details( &status_sender, @@ -126,7 +132,7 @@ impl BackgroundService for LocalServer { return Ok(()); } - if let Err(e) = self.start() { + if let Err(e) = self.start(session_name, domains) { self.notify_update_with_details( &status_sender, super::RunStatus::Error, diff --git a/linkup-cli/src/services/mod.rs b/linkup-cli/src/services/mod.rs index 374ea50a..8d099fd3 100644 --- a/linkup-cli/src/services/mod.rs +++ b/linkup-cli/src/services/mod.rs @@ -4,10 +4,8 @@ use sysinfo::{ProcessRefreshKind, RefreshKind, System}; use thiserror::Error; mod cloudflare_tunnel; -mod local_dns_server; mod local_server; -pub use local_dns_server::LocalDnsServer; pub use local_server::LocalServer; pub use sysinfo::{Pid, Signal}; pub use { diff --git a/local-server/src/lib.rs b/local-server/src/lib.rs index 0a726d88..bf199038 100644 --- a/local-server/src/lib.rs +++ b/local-server/src/lib.rs @@ -36,10 +36,11 @@ use linkup::{ use rustls::ServerConfig; use std::{ net::{Ipv4Addr, SocketAddr}, + path::PathBuf, str::FromStr, }; use std::{path::Path, sync::Arc}; -use tokio::{net::UdpSocket, signal}; +use tokio::{net::UdpSocket, select, signal}; use tokio_tungstenite::tungstenite::client::IntoClientRequest; use tower::ServiceBuilder; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; @@ -99,7 +100,33 @@ pub fn linkup_router(config_store: MemoryStringStore) -> Router { ) } -pub async fn start_server_https(config_store: MemoryStringStore, certs_dir: &Path) { +pub async fn start( + config_store: MemoryStringStore, + certs_dir: &Path, + session_name: String, + domains: Vec, +) { + let http_config_store = config_store.clone(); + let https_config_store = config_store.clone(); + let https_certs_dir = PathBuf::from(certs_dir); + + select! { + () = start_server_http(http_config_store) => { + println!("HTTP server shut down"); + }, + () = start_server_https(https_config_store, &https_certs_dir) => { + println!("HTTPS server shut down"); + }, + () = start_dns_server(session_name, domains) => { + println!("DNS server shut down"); + }, + () = shutdown_signal() => { + println!("Shutdown signal received, stopping all servers"); + } + } +} + +async fn start_server_https(config_store: MemoryStringStore, certs_dir: &Path) { let _ = rustls::crypto::ring::default_provider().install_default(); let sni = match certificates::WildcardSniResolver::load_dir(certs_dir) { @@ -121,7 +148,7 @@ pub async fn start_server_https(config_store: MemoryStringStore, certs_dir: &Pat let app = linkup_router(config_store); let addr = SocketAddr::from(([0, 0, 0, 0], 443)); - println!("listening on {}", &addr); + println!("HTTPS listening on {}", &addr); axum_server::bind_rustls(addr, RustlsConfig::from_config(Arc::new(server_config))) .serve(app.into_make_service()) @@ -129,21 +156,22 @@ pub async fn start_server_https(config_store: MemoryStringStore, certs_dir: &Pat .expect("failed to start HTTPS server"); } -pub async fn start_server_http(config_store: MemoryStringStore) -> std::io::Result<()> { +async fn start_server_http(config_store: MemoryStringStore) { let app = linkup_router(config_store); let addr = SocketAddr::from(([0, 0, 0, 0], 80)); - println!("listening on {}", &addr); + println!("HTTP listening on {}", &addr); - let listener = tokio::net::TcpListener::bind(addr).await?; - axum::serve(listener, app) - .with_graceful_shutdown(shutdown_signal()) - .await?; + let listener = tokio::net::TcpListener::bind(addr) + .await + .expect("failed to bind to address"); - Ok(()) + axum::serve(listener, app) + .await + .expect("failed to start HTTP server"); } -pub async fn start_dns_server(linkup_session_name: String, domains: Vec) { +async fn start_dns_server(linkup_session_name: String, domains: Vec) { let mut catalog = Catalog::new(); for domain in &domains { @@ -399,11 +427,6 @@ async fn always_ok() -> &'static str { "OK" } -async fn shutdown_signal() { - let _ = signal::ctrl_c().await; - println!("signal received, starting graceful shutdown"); -} - fn https_client() -> HttpsClient { let _ = rustls::crypto::ring::default_provider().install_default(); @@ -425,3 +448,27 @@ fn https_client() -> HttpsClient { Client::builder(TokioExecutor::new()).build(https) } + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to start SIGINT handler"); + }; + + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to start SIGTERM handler") + .recv() + .await; + }; + + tokio::select! { + () = ctrl_c => { + println!("Received SIGINT signal"); + }, + () = terminate => { + println!("Received SIGTERM signal"); + }, + } +}