diff --git a/agent/src/collectors/service.rs b/agent/src/collectors/service.rs index 655a220..b7caa66 100644 --- a/agent/src/collectors/service.rs +++ b/agent/src/collectors/service.rs @@ -433,11 +433,17 @@ impl ServiceCollector { async fn get_service_description(&self, service: &str) -> Option> { let result = match service { "sshd" | "ssh" => self.get_ssh_active_users().await.map(|s| vec![s]), - "nginx" => self.get_nginx_sites().await, + "nginx" => self.get_nginx_description().await.map(|s| vec![s]), "apache2" | "httpd" => self.get_web_server_connections().await.map(|s| vec![s]), - "docker" => self.get_docker_containers().await.map(|s| vec![s]), + "docker" | "docker-registry" => self.get_docker_containers().await.map(|s| vec![s]), "postgresql" | "postgres" => self.get_postgres_connections().await.map(|s| vec![s]), "mysql" | "mariadb" => self.get_mysql_connections().await.map(|s| vec![s]), + "redis" | "redis-immich" => self.get_redis_info().await.map(|s| vec![s]), + "gitea" => self.get_gitea_info().await.map(|s| vec![s]), + "immich-server" | "immich" => self.get_immich_info().await.map(|s| vec![s]), + "vaultwarden" => self.get_vaultwarden_info().await.map(|s| vec![s]), + "unifi" => self.get_unifi_info().await.map(|s| vec![s]), + "mosquitto" => self.get_mosquitto_info().await.map(|s| vec![s]), _ => None, }; @@ -791,6 +797,167 @@ impl ServiceCollector { false } + + async fn get_nginx_description(&self) -> Option { + // Get site count and active connections + let sites = self.get_nginx_sites().await?; + let site_count = sites.len(); + + // Get active connections + let connections = self.get_web_server_connections().await; + + if let Some(conn_info) = connections { + Some(format!("{} sites, {}", site_count, conn_info)) + } else { + Some(format!("{} sites", site_count)) + } + } + + async fn get_redis_info(&self) -> Option { + let output = Command::new("redis-cli") + .args(["info", "clients"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.starts_with("connected_clients:") { + if let Some(count) = line.split(':').nth(1) { + if let Ok(client_count) = count.trim().parse::() { + return Some(format!("{} connected clients", client_count)); + } + } + } + } + None + } + + async fn get_gitea_info(&self) -> Option { + // Try to get gitea stats from API (if accessible) + let output = Command::new("curl") + .args(["-s", "-f", "http://localhost:3000/api/v1/repos/search?limit=1"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + if let Ok(json) = serde_json::from_str::(&stdout) { + if let Some(total_count) = json["total_count"].as_u64() { + return Some(format!("{} repositories", total_count)); + } + } + } + + // Fallback: check data directory + if let Ok(metadata) = tokio::fs::metadata("/var/lib/gitea/data/gitea.db").await { + let size_mb = metadata.len() as f32 / (1024.0 * 1024.0); + return Some(format!("DB: {:.1} MB", size_mb)); + } + + None + } + + async fn get_immich_info(&self) -> Option { + // Check upload directory for photo count estimate + let output = Command::new("find") + .args(["/var/lib/immich/upload", "-type", "f", "-name", "*.jpg", "-o", "-name", "*.png", "-o", "-name", "*.mp4", "-o", "-name", "*.mov"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let file_count = stdout.lines().count(); + if file_count > 0 { + return Some(format!("~{} media files", file_count)); + } + } + + // Fallback: check storage usage + let output = Command::new("sudo") + .args(["/run/current-system/sw/bin/du", "-sh", "/var/lib/immich/upload"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + if let Some(size) = stdout.split_whitespace().next() { + return Some(format!("Storage: {}", size)); + } + } + + None + } + + async fn get_vaultwarden_info(&self) -> Option { + // Check database for basic stats (SQLite) + if let Ok(metadata) = tokio::fs::metadata("/var/lib/bitwarden_rs/db.sqlite3").await { + let size_mb = metadata.len() as f32 / (1024.0 * 1024.0); + return Some(format!("DB: {:.1} MB", size_mb)); + } + + None + } + + async fn get_unifi_info(&self) -> Option { + // Check for device count via UniFi API (if accessible) + let output = Command::new("curl") + .args(["-s", "-f", "--insecure", "https://localhost:8443/api/self/sites"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if output.status.success() { + return Some("Controller active".to_string()); + } + + // Fallback: check data directory + if tokio::fs::metadata("/var/lib/unifi/data").await.is_ok() { + return Some("Data directory exists".to_string()); + } + + None + } + + async fn get_mosquitto_info(&self) -> Option { + // Check for active connections using netstat on MQTT ports + let output = Command::new("/run/current-system/sw/bin/ss") + .args(["-tn", "state", "established", "sport", "= :1883", "or", "sport", "= :8883"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .ok()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let connection_count = stdout.lines().count().saturating_sub(1); + if connection_count > 0 { + return Some(format!("{} MQTT connections", connection_count)); + } else { + return Some("No active connections".to_string()); + } + } + + None + } } #[async_trait] diff --git a/agent/src/discovery.rs b/agent/src/discovery.rs index 0a6fe29..b089aa9 100644 --- a/agent/src/discovery.rs +++ b/agent/src/discovery.rs @@ -202,6 +202,20 @@ impl AutoDiscovery { } fn is_monitorable_service(service_name: &str) -> bool { + // Skip setup/certificate services that don't need monitoring + let excluded_services = [ + "mosquitto-certs", + "immich-setup", + "phpfpm-kryddorten", + "phpfpm-mariehall2", + ]; + + for excluded in &excluded_services { + if service_name.contains(excluded) { + return false; + } + } + // Define patterns for services we want to monitor let interesting_services = [ // Web applications @@ -235,8 +249,6 @@ impl AutoDiscovery { "haasp", // Backup services "backup", - // Status web services - "mqtt-status", ]; // Check if service name contains any of our interesting patterns