use std::net::SocketAddr;
pub(crate) async fn run_daemon(addr: SocketAddr, tailscale: bool, mcp: bool) -> anyhow::Result<()> {
use std::io::ErrorKind;
let state = trusty_mpm::daemon::DaemonState::shared();
if mcp {
return trusty_mpm::daemon::run_mcp(state).await;
}
let recorded_url = trusty_mpm::core::resolve_daemon_url(None);
if recorded_url != trusty_mpm::core::DEFAULT_DAEMON_URL
&& trusty_common::probe_health(&recorded_url, "/health").await
{
eprintln!("trusty-mpm daemon is already running at {recorded_url}");
return Ok(());
}
let listener = match tokio::net::TcpListener::bind(addr).await {
Ok(l) => l,
Err(e) if e.kind() == ErrorKind::AddrInUse => {
tracing::warn!("port {} is busy — selecting an ephemeral port", addr.port());
tokio::net::TcpListener::bind("127.0.0.1:0").await?
}
Err(e) => return Err(e.into()),
};
let actual_addr = listener.local_addr()?;
let base_url = format!("http://{actual_addr}");
tracing::info!("daemon listening on {base_url}");
let tailscale_url = if tailscale {
match get_tailscale_ip() {
Some(ts_ip) => {
let ts_addr = format!("{ts_ip}:{}", actual_addr.port());
match tokio::net::TcpListener::bind(&ts_addr).await {
Ok(ts_listener) => {
let ts_url = format!("http://{ts_addr}");
tracing::info!("Tailscale listener on {ts_url}");
trusty_mpm::daemon::spawn_secondary_listener(
trusty_mpm::daemon::DaemonState::shared(),
ts_listener,
);
Some(ts_url)
}
Err(e) => {
tracing::warn!("failed to bind Tailscale address {ts_addr}: {e}");
None
}
}
}
None => {
tracing::warn!("--tailscale requested but no Tailscale IP found");
None
}
}
} else {
None
};
trusty_mpm::daemon::lock::write_lock(&base_url, tailscale_url.as_deref());
tokio::spawn(async {
wait_for_shutdown_signal().await;
trusty_mpm::daemon::lock::remove_lock();
std::process::exit(0);
});
spawn_telegram_bot(&base_url);
trusty_mpm::daemon::serve_http(state, listener).await
}
async fn wait_for_shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut term = match signal(SignalKind::terminate()) {
Ok(s) => s,
Err(e) => {
tracing::warn!("failed to register SIGTERM handler: {e}");
let _ = tokio::signal::ctrl_c().await;
return;
}
};
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
fn spawn_telegram_bot(base_url: &str) {
match trusty_mpm::telegram::resolve_token("TELEGRAM_BOT_TOKEN") {
Some(token) => {
tracing::info!("TELEGRAM_BOT_TOKEN found — starting Telegram bot");
let url = base_url.to_string();
tokio::spawn(async move {
let options = trusty_mpm::telegram::BotOptions::default();
if let Err(e) = trusty_mpm::telegram::run(url, Some(token), false, options).await {
tracing::warn!("Telegram bot exited: {e}");
}
});
}
None => {
tracing::warn!(
"TELEGRAM_BOT_TOKEN not set — Telegram bot not started \
(set it in .env.local to enable)"
);
}
}
}
fn get_tailscale_ip() -> Option<String> {
let output = std::process::Command::new("tailscale")
.args(["ip", "-4"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let ip = String::from_utf8(output.stdout).ok()?;
let ip = ip.trim().to_string();
if ip.contains('.') && !ip.is_empty() {
Some(ip)
} else {
None
}
}