use std::net::TcpStream;
use std::process::{Command, Stdio};
use std::time::Duration;
use anyhow::{Context, Result};
use mi6_core::OtelMode;
pub const DEFAULT_PORT: u16 = 4318;
pub fn is_server_running(port: u16) -> bool {
TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_millis(100),
)
.is_ok()
}
pub fn is_mi6_server(port: u16) -> bool {
use std::io::{Read, Write};
let Ok(mut stream) = TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_millis(500),
) else {
return false;
};
let _ = stream.set_read_timeout(Some(Duration::from_millis(500)));
let request = "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
if stream.write_all(request.as_bytes()).is_err() {
return false;
}
let mut response = [0u8; 256];
if stream.read(&mut response).is_err() {
return false;
}
let response_str = String::from_utf8_lossy(&response);
response_str.contains("mi6-otel")
}
#[cfg(unix)]
fn kill_mi6_server(port: u16) -> Result<bool> {
let output = Command::new("lsof")
.args(["-ti", &format!(":{}", port)])
.output()
.context("failed to run lsof")?;
if output.status.success() {
let pids = String::from_utf8_lossy(&output.stdout);
let mut killed = false;
for pid in pids.lines() {
let pid = pid.trim();
if !pid.is_empty() {
let _ = Command::new("kill").args(["-TERM", pid]).output();
killed = true;
}
}
if killed {
std::thread::sleep(Duration::from_millis(500));
return Ok(true);
}
}
Ok(false)
}
#[cfg(not(unix))]
fn kill_mi6_server(_port: u16) -> Result<bool> {
anyhow::bail!("stop is not supported on this platform")
}
pub fn stop_server(port: u16) -> Result<()> {
if !is_server_running(port) {
eprintln!("No server running on port {}", port);
return Ok(());
}
if !is_mi6_server(port) {
anyhow::bail!(
"port {} is in use by another service, not a mi6 OTel server",
port
);
}
if kill_mi6_server(port)? {
eprintln!("Stopped OTel server on port {}", port);
}
Ok(())
}
#[derive(Debug)]
pub struct OtelServerStatus {
pub running: bool,
pub is_mi6: bool,
pub port: u16,
pub cli_binary: Option<String>,
pub cli_version: Option<String>,
pub mode: OtelMode,
}
impl OtelServerStatus {
pub fn get(port: u16) -> Self {
let running = is_server_running(port);
let is_mi6 = running && is_mi6_server(port);
let config = mi6_core::Config::load().unwrap_or_default();
let mode = config.otel.mode;
let cli_binary = which::which("mi6").ok().map(|p| p.display().to_string());
let cli_version = get_cli_version();
Self {
running,
is_mi6,
port,
cli_binary,
cli_version,
mode,
}
}
}
fn get_cli_version() -> Option<String> {
let output = Command::new("mi6").arg("--version").output().ok()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.trim().strip_prefix("mi6 ").map(String::from)
} else {
None
}
}
pub fn get_status(port: u16) -> Result<()> {
let status = OtelServerStatus::get(port);
if !status.running {
eprintln!("OTel server is not running on port {}", port);
return Ok(());
}
if !status.is_mi6 {
eprintln!(
"Port {} is in use by another service (not a mi6 OTel server)",
port
);
return Ok(());
}
eprintln!("OTel Server Status");
eprintln!(" Running: yes");
eprintln!(" Port: {}", status.port);
eprintln!(" Mode: {} (from config)", status.mode);
if let Some(ref path) = status.cli_binary {
eprintln!(" CLI Binary: {}", path);
if let Some(ref v) = status.cli_version {
eprintln!(" CLI Version: {}", v);
}
} else {
eprintln!(" CLI Binary: not found on PATH");
}
Ok(())
}
pub fn ensure_running(port: u16, restart: bool, mode: Option<OtelMode>) -> Result<bool> {
if is_server_running(port) {
if is_mi6_server(port) {
if restart {
kill_mi6_server(port)?;
} else {
return Ok(true); }
} else {
anyhow::bail!(
"port {} is in use by another service; \
cannot start mi6 OTel server; \
consider changing the port in your settings",
port
);
}
}
let binary_path = std::env::current_exe().context("failed to determine binary path")?;
let port_str = port.to_string();
let mode_str = mode.map(|m| m.to_string());
#[cfg(unix)]
{
let mut cmd = Command::new("nohup");
cmd.arg(&binary_path)
.args(["otel", "run", "--port", &port_str]);
if let Some(ref m) = mode_str {
cmd.args(["--mode", m]);
}
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
use std::os::unix::process::CommandExt;
cmd.process_group(0);
cmd.spawn().context("failed to spawn otel server")?;
}
#[cfg(not(unix))]
{
let mut cmd = Command::new(&binary_path);
cmd.args(["otel", "run", "--port", &port_str]);
if let Some(ref m) = mode_str {
cmd.args(["--mode", m]);
}
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.context("failed to spawn otel server")?;
}
for _ in 0..10 {
std::thread::sleep(Duration::from_millis(50));
if is_server_running(port) {
return Ok(true);
}
}
eprintln!(
"Warning: OTel server not yet responding on port {}; it may still be starting",
port
);
Ok(false)
}
pub fn default_port() -> u16 {
DEFAULT_PORT
}
pub fn find_available_port(start_port: u16) -> u16 {
for port in start_port..start_port + 100 {
if !is_server_running(port) || is_mi6_server(port) {
return port;
}
}
start_port
}