mod actions;
mod catalog;
mod client;
mod handoff;
mod routes;
mod servers;
mod session;
mod ui;
pub(crate) use session::SharedSession;
use std::net::SocketAddr;
use glacier_ui::ExternalSender;
use std::sync::atomic::{AtomicBool, Ordering};
static STARTED: AtomicBool = AtomicBool::new(false);
const DEFAULT_ADDR: &str = "127.0.0.1:9800";
const ENV_VAR: &str = "RUSTPLOY_AGENT_API";
pub(crate) fn spawn(session: SharedSession, ui: ExternalSender) {
if STARTED.swap(true, Ordering::SeqCst) {
return;
}
let addr = match configured_addr() {
Some(addr) => addr,
None => {
eprintln!("[agent-api] desligada por {ENV_VAR}=off");
return;
}
};
std::thread::Builder::new()
.name("rustploy-agent-api".into())
.spawn(move || {
let rt = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(rt) => rt,
Err(e) => {
eprintln!("[agent-api] falha ao criar o runtime: {e}");
return;
}
};
if let Err(e) = rt.block_on(routes::serve(addr, session, ui)) {
eprintln!("[agent-api] encerrada: {e}");
}
})
.map(|_| ())
.unwrap_or_else(|e| eprintln!("[agent-api] falha ao criar a thread: {e}"));
}
pub(crate) fn cleanup() {
if STARTED.load(Ordering::SeqCst) {
handoff::remove();
}
}
fn configured_addr() -> Option<SocketAddr> {
resolve_addr(std::env::var(ENV_VAR).unwrap_or_default().trim())
}
fn resolve_addr(raw: &str) -> Option<SocketAddr> {
if raw.eq_ignore_ascii_case("off") || raw == "0" {
return None;
}
let escolhido = if raw.is_empty() { DEFAULT_ADDR } else { raw };
match escolhido.parse::<SocketAddr>() {
Ok(addr) if addr.ip().is_loopback() => Some(addr),
Ok(addr) => {
eprintln!(
"[agent-api] {ENV_VAR}={addr} não é loopback — ignorado, usando {DEFAULT_ADDR}"
);
DEFAULT_ADDR.parse().ok()
}
Err(e) => {
eprintln!("[agent-api] {ENV_VAR}={escolhido} inválido ({e}) — usando {DEFAULT_ADDR}");
DEFAULT_ADDR.parse().ok()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vazio_usa_o_default() {
assert_eq!(resolve_addr(""), DEFAULT_ADDR.parse().ok());
}
#[test]
fn off_desliga() {
assert_eq!(resolve_addr("off"), None);
assert_eq!(resolve_addr("OFF"), None);
assert_eq!(resolve_addr("0"), None);
}
#[test]
fn porta_alternativa_em_loopback_e_aceita() {
assert_eq!(
resolve_addr("127.0.0.1:9910"),
"127.0.0.1:9910".parse().ok()
);
assert_eq!(resolve_addr("[::1]:9910"), "[::1]:9910".parse().ok());
}
#[test]
fn endereco_publico_cai_no_default() {
assert_eq!(resolve_addr("0.0.0.0:9800"), DEFAULT_ADDR.parse().ok());
assert_eq!(resolve_addr("192.168.1.10:9800"), DEFAULT_ADDR.parse().ok());
}
#[test]
fn lixo_cai_no_default() {
assert_eq!(resolve_addr("nao-e-endereco"), DEFAULT_ADDR.parse().ok());
}
}