bindizr 0.1.0-beta.4

DNS Synchronization Service for BIND9
Documentation
mod commands;
mod output;

use std::sync::Arc;

use async_trait::async_trait;
use clap::{Parser, Subcommand};

use crate::{
    api,
    cli::commands::{notify::NotifyCommand, token::TokenCommand},
    config, database, dns, log_error, log_info, logger, service, socket,
};

struct DnsNotifySender;

#[async_trait]
impl service::notify::NotifySender for DnsNotifySender {
    async fn send_notify(&self, zone_name: Option<&str>) -> Result<(), String> {
        dns::xfr::notify::send_notify(zone_name, false)
            .await
            .map_err(|e| e.to_string())
    }
}

#[derive(Parser, Debug)]
#[command(name = "bindizr", version, about)]
pub(crate) struct Args {
    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand, Debug)]
pub(crate) enum Command {
    /// Start bindizr on foreground
    Start {
        /// Path to the configuration file (default: /etc/bindizr/bindizr.conf.toml)
        #[arg(short, long, value_name = "FILE")]
        config: Option<String>,
    },
    /// Show the status of the bindizr service
    Status,
    /// Manage API tokens
    Token {
        #[command(subcommand)]
        subcommand: TokenCommand,
    },
    /// Get resources
    Get {
        #[command(subcommand)]
        subcommand: commands::get::GetCommand,
    },
    /// Create resources
    Create {
        #[command(subcommand)]
        subcommand: commands::create::CreateCommand,
    },
    /// Delete resources
    Delete {
        #[command(subcommand)]
        subcommand: commands::delete::DeleteCommand,
    },
    /// Send NOTIFY to secondary servers
    Notify {
        #[command(subcommand)]
        subcommand: NotifyCommand,
    },
}

pub(crate) async fn bootstrap(config_file: Option<&str>) -> Result<(), String> {
    // Initialize Configuration
    if let Some(file) = config_file {
        // Load configuration from the specified file
        config::initialize(Some(file));
    } else {
        // Use default configuration file
        config::initialize(None);
    }

    logger::initialize();
    service::notify::set_notify_sender(Arc::new(DnsNotifySender)).map_err(String::from)?;
    database::initialize().await;
    dns::initialize().await;

    if config::get_bindizr_config().dns.notify_on_startup {
        match dns::xfr::notify::send_notify(None, false).await {
            Ok(()) => log_info!("Startup DNS NOTIFY completed."),
            Err(e) => log_error!("Startup DNS NOTIFY failed: {}", e),
        }
    }

    log_info!("Bindizr is running in foreground mode.");
    log_info!("For production use, please run bindizr as a systemd service:");
    log_info!("# systemctl start bindizr");

    socket::server::initialize().await?;
    api::initialize().await?;

    // Wait only for Ctrl+C signal, ignore all stdin input
    tokio::signal::ctrl_c()
        .await
        .map_err(|e| format!("Failed to listen for shutdown signal: {}", e))?;

    log_info!("Shutdown signal received, exiting gracefully...");

    Ok(())
}

pub async fn execute() {
    let args = Args::parse();

    // Execute command
    if let Err(e) = match args.command {
        Command::Start { config } => commands::start::handle_command(config).await,
        Command::Status => commands::status::handle_command().await,
        Command::Token { subcommand } => commands::token::handle_command(subcommand).await,
        Command::Get { subcommand } => commands::get::handle_command(subcommand).await,
        Command::Create { subcommand } => commands::create::handle_command(subcommand).await,
        Command::Delete { subcommand } => commands::delete::handle_command(subcommand).await,
        Command::Notify { subcommand } => commands::notify::handle_notify(&subcommand).await,
    } {
        eprintln!("Error: {}", e);
        std::process::exit(1);
    }
}