use crate::cli::commands::{DiagCmd, NginxSubCommand};
use crate::commands::load_xbp_config;
use crate::commands::nginx::run_nginx;
use crate::commands::system_diag::{
print_diagnostic_report, run_full_diagnostics, DiagnosticRunOptions,
};
#[cfg(feature = "docker")]
use crate::commands::{docker::validate_compose, docker::validate_docker_env};
use crate::logging::{log_error, log_info, log_success, log_warn};
use crate::strategies::get_all_services;
use anyhow::Result;
use regex::Regex;
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
fn parse_diag_ports(input: &str) -> (Vec<u16>, usize) {
let mut ports = Vec::new();
let mut invalid = 0usize;
for token in input.split(',') {
let trimmed = token.trim();
if trimmed.is_empty() {
continue;
}
match trimmed.parse::<u16>() {
Ok(port) => {
if !ports.contains(&port) {
ports.push(port);
}
}
Err(_) => invalid += 1,
}
}
(ports, invalid)
}
pub async fn run_diag(cmd: DiagCmd, debug: bool) -> Result<()> {
if cmd.nginx {
check_nginx_interactive(debug).await?;
} else {
let config = load_xbp_config().await.ok();
let mut ports_to_check = Vec::new();
if let Some(raw_ports) = cmd.ports.as_deref() {
let (parsed_ports, invalid_count) = parse_diag_ports(raw_ports);
if invalid_count > 0 {
let details = format!(
"Ignored {} invalid port token(s) from --ports={}",
invalid_count, raw_ports
);
let _ = log_warn(
"diag",
"Invalid --ports entries ignored",
Some(details.as_str()),
)
.await;
}
if !parsed_ports.is_empty() {
ports_to_check = parsed_ports;
}
}
if ports_to_check.is_empty() {
if let Some(ref cfg) = config {
let services = get_all_services(cfg);
for service in services {
ports_to_check.push(service.port);
}
}
}
if ports_to_check.is_empty() {
ports_to_check = vec![80, 443, 3000, 8080, 9090];
}
let run_options = DiagnosticRunOptions {
skip_speed_test: cmd.no_speed_test,
};
match run_full_diagnostics(ports_to_check, run_options).await {
Ok(report) => {
print_diagnostic_report(&report).await;
}
Err(e) => {
let _ = log_error("diag", "Failed to run diagnostics", Some(&e.to_string())).await;
}
}
#[cfg(feature = "docker")]
{
if let Err(e) = validate_docker_env(debug).await {
let _ = log_warn("docker", "Docker not healthy", Some(&e)).await;
}
if let Err(e) = validate_compose(cmd.compose_file.as_deref(), debug).await {
let _ = log_warn("docker", "Compose file invalid", Some(&e)).await;
}
}
check_nginx_interactive(debug).await?;
}
Ok(())
}
pub async fn check_nginx_mismatches() -> Result<Vec<(String, u16, u16)>> {
let config = match load_xbp_config().await {
Ok(c) => c,
Err(_) => return Ok(vec![]), };
let services = get_all_services(&config);
let mut mismatches = Vec::new();
let sites_enabled = PathBuf::from("/etc/nginx/sites-enabled");
if !sites_enabled.exists() {
return Ok(vec![]);
}
let proxy_pass_regex = Regex::new(r"proxy_pass\s+http://127\.0\.0\.1:(\d+);").unwrap();
for service in services {
let expected_port = service.port;
let mut potential_names = vec![service.name.clone()];
if let Some(url) = &service.url {
let domain = url
.trim_start_matches("http://")
.trim_start_matches("https://")
.split('/')
.next()
.unwrap_or(url);
potential_names.push(domain.to_string());
}
for name in potential_names {
let config_path = sites_enabled.join(&name);
if config_path.exists() {
if let Ok(content) = fs::read_to_string(&config_path) {
if let Some(cap) = proxy_pass_regex.captures(&content) {
if let Ok(port) = cap[1].parse::<u16>() {
if port != expected_port {
mismatches.push((name.clone(), expected_port, port));
break; }
}
}
}
}
}
}
Ok(mismatches)
}
async fn check_nginx_interactive(debug: bool) -> Result<()> {
let _ = log_info("diag", "Checking Nginx configuration...", None).await;
let mismatches = check_nginx_mismatches().await?;
if mismatches.is_empty() {
let _ = log_success("diag", "Nginx configuration looks correct.", None).await;
return Ok(());
}
for (name, expected, actual) in mismatches {
let _ = log_warn(
"diag",
&format!(
"Port mismatch for {}: Nginx uses {}, XBP config uses {}",
name, actual, expected
),
None,
)
.await;
print!(
"Do you want to update Nginx to use port {}? [y/N]: ",
expected
);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if input.trim().to_lowercase() == "y" {
let cmd = NginxSubCommand::Update {
domain: name.clone(),
port: expected,
};
if let Err(e) = run_nginx(cmd, debug).await {
let _ = log_error(
"diag",
&format!("Failed to update Nginx for {}", name),
Some(&e.to_string()),
)
.await;
}
}
}
Ok(())
}