mod cli;
use crate::cli::parse_args;
use lazydns::config::Config;
#[cfg(feature = "log")]
use lazydns::config::LogConfig;
#[cfg(feature = "log")]
use lazydns::init_logging;
use lazydns::plugin::PluginBuilder;
use lazydns::server::ServerLauncher;
use std::sync::Arc;
#[cfg(unix)]
use tokio::signal::unix::{SignalKind, signal};
use tokio::sync::RwLock;
use tokio::time::{Duration, timeout};
use tracing::{debug, error, info};
async fn wait_for_shutdown_signal() -> anyhow::Result<()> {
#[cfg(unix)]
{
let mut sigterm = signal(SignalKind::terminate())?;
let mut sighup = signal(SignalKind::hangup())?;
tokio::select! {
res = tokio::signal::ctrl_c() => {
info!("Received Ctrl-C signal");
res?;
},
_ = sigterm.recv() => {
info!("Received SIGTERM signal");
},
_ = sighup.recv() => {
info!("Received SIGHUP signal");
},
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c().await?;
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = match parse_args() {
Some(a) => a,
None => return Ok(()),
};
if let Some(dir) = &args.dir {
if let Err(e) = std::env::set_current_dir(dir) {
error!("Failed to change working directory to {}: {}", dir, e);
return Err(anyhow::anyhow!("Failed to change working directory"));
}
info!("Working directory changed to: {}", dir);
}
let config = match Config::from_file(&args.config) {
Ok(config) => {
println!("Configuration loaded successfully");
config
}
Err(e) => {
eprintln!("Failed to load configuration: {}", e);
eprintln!("Using default configuration");
Config::default()
}
};
if let Err(e) = config.validate() {
eprintln!("Configuration validation warning: {}", e);
}
#[cfg(feature = "log")]
{
let log_spec = effective_log_spec(&config.log, args.verbose);
let lazy_config = config.log.to_lazylog(log_spec);
if let Err(e) = init_logging(&lazy_config) {
eprintln!("Failed to initialize logging: {}", e);
}
}
info!("lazydns starting...");
info!("Version: {}", env!("CARGO_PKG_VERSION"));
info!("Configuration file: {}", args.config);
#[cfg(feature = "log")]
debug!("Logging configuration: {}", config.log.summary());
#[cfg(feature = "rustls")]
let _ = rustls::crypto::ring::default_provider().install_default();
let mut builder = PluginBuilder::new();
let mut plugin_count = 0;
for plugin_config in &config.plugins {
match builder.build(plugin_config) {
Ok(_plugin) => {
info!(
"Loaded plugin: {} (type: {})",
plugin_config.effective_name(),
plugin_config.plugin_type
);
plugin_count += 1;
}
Err(e) => {
error!(
"Failed to load plugin {}: {}",
plugin_config.effective_name(),
e
);
}
}
}
info!("Loaded {} plugins", plugin_count);
if let Err(e) = builder.resolve_references(&config.plugins) {
error!("Failed to resolve plugin references: {}", e);
}
let registry = Arc::new(builder.get_registry());
debug!(plugins = ?registry.plugin_names(), "Plugin registry contents");
let launcher = ServerLauncher::new(Arc::clone(®istry));
let mut startup_receivers = launcher.launch_all(&config.plugins).await;
let config_arc = Arc::new(RwLock::new(config.clone()));
if let Some(rx) = launcher.launch_admin_server(Arc::clone(&config_arc)).await {
startup_receivers.push(rx);
}
if let Some(rx) = launcher
.launch_monitoring_server(Arc::clone(&config_arc))
.await
{
startup_receivers.push(rx);
}
for rx in startup_receivers {
let _ = rx.await; }
tokio::time::sleep(Duration::from_millis(50)).await;
let _background_tasks = builder.start_background_tasks();
info!("lazydns initialized successfully");
if let Err(e) = wait_for_shutdown_signal().await {
error!("Error waiting for shutdown signal: {}", e);
}
info!("Shutdown signal received, beginning graceful shutdown...");
match timeout(Duration::from_secs(30), builder.shutdown_all()).await {
Ok(Ok(())) => info!("Shutdown finished successfully"),
Ok(Err(e)) => error!("Error during plugin shutdown: {}", e),
Err(_) => error!("Shutdown timed out after 30s"),
}
info!("lazydns exited normally");
Ok(())
}
#[cfg(feature = "log")]
fn effective_log_spec(config: &LogConfig, cli_verbose: u8) -> String {
if let Ok(rust_log) = std::env::var("RUST_LOG")
&& !rust_log.is_empty()
{
return rust_log;
}
if cli_verbose > 0 {
return match cli_verbose {
1 => format!("{},lazydns=debug", config.level),
2 => format!("{},lazydns=trace", config.level),
_ => "trace".to_string(),
};
}
if config.level.is_empty() {
"info,lazydns=info".to_string()
} else {
format!("{},lazydns={}", config.level, config.level)
}
}