// Bootstrap utility functions use crate::core::shared::utils::get_stack_path; use crate::security::command_guard::SafeCommand; use log::{debug, info, warn}; /// Get list of processes to kill (only used in dev with local botserver-stack) pub fn get_processes_to_kill() -> Vec<(String, Vec<&'static str>)> { let stack = get_stack_path(); vec![ (format!("{}/bin/vault", stack), vec!["-9", "-f"]), (format!("{}/bin/tables", stack), vec!["-9", "-f"]), (format!("{}/bin/drive", stack), vec!["-9", "-f"]), (format!("{}/bin/cache", stack), vec!["-9", "-f"]), (format!("{}/bin/directory", stack), vec!["-9", "-f"]), (format!("{}/bin/llm", stack), vec!["-9", "-f"]), (format!("{}/bin/email", stack), vec!["-9", "-f"]), (format!("{}/bin/proxy", stack), vec!["-9", "-f"]), (format!("{}/bin/dns", stack), vec!["-9", "-f"]), (format!("{}/bin/meeting", stack), vec!["-9", "-f"]), (format!("{}/bin/vector_db", stack), vec!["-9", "-f"]), (format!("{}/bin/zitadel", stack), vec!["-9", "-f"]), (format!("{}/bin/alm", stack), vec!["-9", "-f"]), ("forgejo".to_string(), vec!["-9", "-f"]), ("caddy".to_string(), vec!["-9", "-f"]), ("postgres".to_string(), vec!["-9", "-f"]), ("minio".to_string(), vec!["-9", "-f"]), ("redis-server".to_string(), vec!["-9", "-f"]), ("zitadel".to_string(), vec!["-9", "-f"]), ("llama-server".to_string(), vec!["-9", "-f"]), ("stalwart".to_string(), vec!["-9", "-f"]), ("vault server".to_string(), vec!["-9", "-f"]), ("watcher".to_string(), vec!["-9", "-f"]), ] } /// Kill processes by name safely pub fn safe_pkill(pattern: &[&str], extra_args: &[&str]) { let mut args: Vec<&str> = extra_args.to_vec(); args.extend(pattern); let result = SafeCommand::new("pkill") .and_then(|c| c.args(&args)) .and_then(|c| c.execute()); match result { Ok(output) => { debug!("Kill command output: {:?}", output); } Err(e) => { warn!("Failed to execute kill command: {}", e); } } } /// Grep for process safely pub fn safe_pgrep(pattern: &str) -> String { match SafeCommand::new("pgrep") .and_then(|c| c.arg("-a")) .and_then(|c| c.arg(pattern)) .and_then(|c| c.execute()) { Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), Err(e) => { warn!("Failed to execute pgrep: {}", e); String::new() } } } /// Execute curl command safely pub fn safe_curl(url: &str) -> String { format!("curl -f -s --connect-timeout 5 {}", url) } /// Execute shell command safely pub fn safe_sh_command(command: &str) -> String { match SafeCommand::new("sh") .and_then(|c| c.arg("-c")) .and_then(|c| c.arg(command)) .and_then(|c| c.execute()) { Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), Err(e) => { warn!("Failed to execute shell command: {}", e); String::new() } } } /// Check if vault is healthy pub fn vault_health_check() -> bool { let vault_addr = std::env::var("VAULT_ADDR").unwrap_or_else(|_| "https://localhost:8200".to_string()); let health_url = format!("{}/v1/sys/health", vault_addr); match SafeCommand::new("curl") .and_then(|c| c.args(&["-f", "-s", "--connect-timeout", "2", "-k", &health_url])) .and_then(|c| c.execute()) { Ok(output) => { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if let Ok(json) = serde_json::from_str::(&stdout) { let sealed = json.get("sealed").and_then(|v| v.as_bool()).unwrap_or(true); let initialized = json .get("initialized") .and_then(|v| v.as_bool()) .unwrap_or(false); return !sealed && initialized; } } let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); stdout.contains("\"initialized\":true") || stderr.contains("\"initialized\":true") } Err(_) => false, } } /// Check if Valkey/Redis cache is healthy pub fn cache_health_check() -> bool { if let Ok(output) = SafeCommand::new("valkey-cli") .and_then(|c| c.args(&["ping"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains("PONG") { return true; } } } let stack_path = get_stack_path(); if let Ok(output) = SafeCommand::new(&format!("{}/bin/cache/bin/valkey-cli", stack_path)) .and_then(|c| c.args(&["ping"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains("PONG") { return true; } } } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":6379") { return true; } } } false } /// Check if Qdrant vector database is healthy pub fn vector_db_health_check() -> bool { let qdrant_url = if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() { sm.get_vectordb_config_sync().0 } else { "http://localhost:6333".to_string() }; let urls = [ format!("{}/healthz", qdrant_url), qdrant_url.replace("http://", "https://") + "/healthz", ]; for url in &urls { if let Ok(output) = SafeCommand::new("curl") .and_then(|c| c.args(&["-sfk", "--connect-timeout", "2", "-m", "3", url])) .and_then(|c| c.execute()) { if output.status.success() { return true; } } } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":6333") { return true; } } } false } /// Get current user safely pub fn safe_fuser() -> String { "fuser -M '($USER)'".to_string() } /// Dump all component logs pub fn dump_all_component_logs(component: &str) { info!("Dumping logs for component: {}", component); } /// Result type for bot existence check #[derive(Debug)] pub enum BotExistsResult { BotExists, BotNotFound, } /// Check if Zitadel directory is healthy pub fn zitadel_health_check() -> bool { let output = SafeCommand::new("curl") .and_then(|c| { c.args(&[ "-sfk", "--connect-timeout", "2", "-m", "3", "http://localhost:8300/debug/healthz", ]) }) .and_then(|c| c.execute()); match output { Ok(result) => { if result.status.success() { let response = String::from_utf8_lossy(&result.stdout); debug!("Zitadel health check response: {}", response); return response.trim() == "ok"; } } Err(e) => { debug!("Zitadel health check error: {}", e); } } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":8300") { return true; } } } false } /// Check if PostgreSQL/Tables is healthy pub fn tables_health_check() -> bool { if let Ok(output) = SafeCommand::new("pg_isready") .and_then(|c| c.args(&["-h", "127.0.0.1", "-p", "5432"])) .and_then(|c| c.execute()) { return output.status.success(); } let stack_path = get_stack_path(); let pg_isready = format!("{}/bin/tables/bin/pg_isready", stack_path); if let Ok(output) = SafeCommand::new(&pg_isready) .and_then(|c| c.args(&["-h", "127.0.0.1", "-p", "5432"])) .and_then(|c| c.execute()) { return output.status.success(); } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":5432") { return true; } } } false } /// Check if MinIO/Drive is healthy pub fn drive_health_check() -> bool { let urls = [ "http://127.0.0.1:9100/minio/health/live", "https://127.0.0.1:9100/minio/health/live", ]; for url in &urls { if let Ok(output) = SafeCommand::new("curl") .and_then(|c| c.args(&["-sfk", "--connect-timeout", "2", "-m", "3", url])) .and_then(|c| c.execute()) { if output.status.success() { return true; } } } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":9100") { return true; } } } false } /// Check if ALM (Forgejo) is healthy pub fn alm_health_check() -> bool { let urls = ["http://localhost:3000", "https://localhost:3000"]; for url in &urls { if let Ok(output) = SafeCommand::new("curl") .and_then(|c| c.args(&["-sfk", "--connect-timeout", "2", "-m", "3", url])) .and_then(|c| c.execute()) { if output.status.success() { return true; } } } if let Ok(output) = SafeCommand::new("ss") .and_then(|c| c.args(&["-tln"])) .and_then(|c| c.execute()) { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); if stdout.contains(":3000") { return true; } } } false } /// Check if ALM CI (Forgejo Runner) is running pub fn alm_ci_health_check() -> bool { if let Ok(output) = SafeCommand::new("pgrep") .and_then(|c| c.args(&["-x", "forgejo-runner"])) .and_then(|c| c.execute()) { return output.status.success(); } match SafeCommand::new("ps") .and_then(|c| c.args(&["-ef"])) .and_then(|c| c.execute()) { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); stdout.contains("forgejo-runner") && stdout.contains("daemon") } Err(_) => false, } }