use super::MhoConfig;
use super::core::Core;
use super::get_config_dir;
use crate::error::{MhoError, Result};
use crate::utils::check_connectivity;
use crate::utils::command::execute;
use crate::utils::current_timestamp;
use crate::utils::process::{get_pid, start, stop};
use crate::utils::tun::{ensure_tun_device, mihomo_tun_enabled, singbox_tun_enabled};
use crate::{log_debug, log_info};
impl MhoConfig {
pub async fn start(&mut self, force: bool) -> Result<()> {
log_info!("Starting proxy core: {}", self.core.name());
if self.stop_force {
if !force {
return Err(MhoError::Process(
"Skip starting proxy core: run 'mho start -f' instead.".to_string(),
));
} else {
self.stop_force = false;
self.save()?;
}
}
if get_pid(&self.core.exe_name()).is_ok() {
let current_time = current_timestamp();
let runtime_seconds = current_time.saturating_sub(self.start_time);
let max_runtime_seconds = self.max_runtime_hours * 3600;
let exceeds_runtime = self.max_runtime_hours > 0
&& self.start_time > 0
&& runtime_seconds >= max_runtime_seconds;
let check_url = self
.check_url
.as_deref()
.unwrap_or("https://www.google.com");
let connectivity_ok = check_connectivity(check_url).await;
let needs_restart = force || exceeds_runtime || !connectivity_ok;
if needs_restart {
let reason = if force {
"manual force stop"
} else if exceeds_runtime {
"maximum runtime exceeded"
} else {
"connectivity check failed"
};
log_info!("Stopping process, reason: {}", reason);
self.stop(false)?;
} else {
return Ok(());
}
}
let tun_enabled = self.check_core_config()?;
if tun_enabled {
ensure_tun_device();
} else {
log_info!("TUN not enabled in core config, skipping TUN device setup");
}
self.start_core()?;
self.start_time = current_timestamp();
self.save()?;
log_info!("Proxy core started successfully");
Ok(())
}
pub fn check_core_config(&self) -> Result<bool> {
let path = self.core_config_path();
let config = std::fs::read_to_string(&path).map_err(|e| {
MhoError::Config(format!(
"Failed to read core config {}: {}",
path.display(),
e
))
})?;
let tun_enabled = match self.core {
Core::Mihomo | Core::Clash => mihomo_tun_enabled(&config).map_err(|e| {
MhoError::Config(format!("Invalid config {}: {}", path.display(), e))
})?,
Core::Singbox => singbox_tun_enabled(&config).map_err(|e| {
MhoError::Config(format!("Invalid config {}: {}", path.display(), e))
})?,
};
log_debug!(
"Core config {} is valid, TUN enabled: {}",
path.display(),
tun_enabled
);
Ok(tun_enabled)
}
pub fn start_core(&self) -> Result<()> {
let exe_path = self.core.exe_path(&get_config_dir());
if !exe_path.exists() {
return Err(MhoError::Process(format!(
"Core executable not found: {}. Please run 'install' first.",
exe_path.display()
)));
}
let args = match self.core {
Core::Mihomo | Core::Clash => vec![
"-f".to_string(),
self.core_config_path().to_string_lossy().to_string(),
"-ext-ctl".to_string(),
self.web.host.clone(),
"-ext-ui".to_string(),
self.web.ui_name().to_string(),
"-d".to_string(),
get_config_dir().to_string_lossy().to_string(),
],
Core::Singbox => vec![
"run".to_string(),
"-c".to_string(),
self.core_config_path().to_string_lossy().to_string(),
"-D".to_string(),
get_config_dir().to_string_lossy().to_string(),
],
};
start(&exe_path, args, self.core.envs())?;
Ok(())
}
pub fn stop(&mut self, force: bool) -> Result<()> {
log_info!("Stopping proxy core: {}", self.core.name());
self.stop_force = force;
let exe_name = self.core.exe_name();
stop(&exe_name)?;
self.start_time = 0;
self.save()?;
log_info!("Proxy core stopped successfully");
Ok(())
}
pub fn get_version(&self) -> Result<String> {
log_debug!("Getting version for core: {}", self.core.name());
let exe_path = self.core.exe_path(&get_config_dir());
if !exe_path.exists() {
log_debug!("Core executable not found: {}", exe_path.display());
return Err(MhoError::Config("Core executable not found".to_string()));
}
let args = match self.core {
Core::Mihomo | Core::Clash => &["-v"],
Core::Singbox => &["version"],
};
let output = execute(exe_path.to_string_lossy().as_ref(), args)?;
let Some(version) = output.split_whitespace().nth(2).map(|s| s.to_string()) else {
return Err(MhoError::Config("Core version not found".to_string()));
};
log_debug!("Core version: {:?}", version);
Ok(version)
}
}