/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use super::{backup::BackupParams, console::store_console}; use crate::{ BuildServer, Caches, Core, Data, IPC_CHANNEL_BUFFER, Inner, Ipc, config::{ network::AsnGeoLookupConfig, server::Listeners, storage::Storage, telemetry::Telemetry, }, ipc::{BroadcastEvent, PushEvent, QueueEvent, ReportingEvent, TrainTaskController}, manager::defaults::BootstrapDefaults, }; use arc_swap::ArcSwap; use std::{ net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::Arc, }; use store::{RegistryStore, registry::bootstrap::Bootstrap}; use tokio::sync::{Notify, mpsc}; use utils::{UnwrapFailure, failed}; pub struct BootManager { pub bootstrap: Bootstrap, pub inner: Arc, pub servers: Listeners, pub ipc_rxs: IpcReceivers, } pub struct IpcReceivers { pub push_rx: Option>, pub queue_rx: Option>, pub report_rx: Option>, pub broadcast_rx: Option>, } const HELP: &str = concat!( types::brand_server!(), " ", types::brand_version_full!(), r#" Usage: inbuxa [OPTIONS] Options: -c, --config Start server with the specified configuration file -e, --export Export all store data to a specific path -i, --import Import store data from a specific path -o, --console Open the store console -h, --help Print help -V, --version Print version "# ); #[derive(PartialEq, Eq)] enum StoreOp { Export(BackupParams), Import(PathBuf), Console, None, } impl BootManager { pub async fn init() -> Self { let mut config_path = std::env::var("CONFIG_PATH").ok(); let mut import_export = StoreOp::None; if config_path.is_none() { let mut args = std::env::args().skip(1); while let Some(arg) = args.next().and_then(|arg| { arg.strip_prefix("--") .or_else(|| arg.strip_prefix('-')) .map(|arg| arg.to_string()) }) { let (key, value) = if let Some((key, value)) = arg.split_once('=') { (key.to_string(), Some(value.trim().to_string())) } else { (arg, args.next()) }; match (key.as_str(), value) { ("help" | "h", _) => { eprintln!("{HELP}"); std::process::exit(0); } ("version" | "V", _) => { println!("{}", types::brand_version_full!()); std::process::exit(0); } ("config" | "c", Some(value)) => { config_path = Some(value); } ("export" | "e", Some(value)) => { import_export = StoreOp::Export(BackupParams::new(value.into())); } ("import" | "i", Some(value)) => { import_export = StoreOp::Import(value.into()); } ("console" | "o", None) => { import_export = StoreOp::Console; } (_, None) => { failed(&format!("Unrecognized command '{key}', try '--help'.")); } (_, Some(_)) => failed(&format!( "Missing value for argument '{key}', try '--help'." )), } } if config_path.is_none() { if import_export == StoreOp::None { eprintln!("{HELP}"); } else { eprintln!("Missing '--config' argument for import/export.") } std::process::exit(0); } } // Initialize registry let registry = RegistryStore::init( PathBuf::from(config_path.unwrap()), import_export == StoreOp::None, ) .await .failed("⚠️ Startup failed"); let mut bootstrap = Bootstrap::new(registry).await; // Add safe defaults if missing if import_export == StoreOp::None { bootstrap.insert_safe_defaults().await; } // Start listeners let mut servers = Listeners::parse(&mut bootstrap).await; servers.bind_and_drop_priv(&mut bootstrap); // Parse storage let storage = Storage::parse(&mut bootstrap).await; // Parse telemetry let telemetry = Telemetry::parse(&mut bootstrap, &storage).await; match import_export { StoreOp::None => { // Parse components let core: Box = Box::new(Box::pin(Core::parse(&mut bootstrap, storage)).await); let data = Data::parse(&mut bootstrap).await; let cache = Caches::parse(&mut bootstrap).await; // Enable telemetry #[cfg(not(feature = "enterprise"))] telemetry.enable(); if bootstrap.registry.is_bootstrap_mode() { trc::event!( Server(trc::ServerEvent::BootstrapMode), Hostname = bootstrap.registry.local_hostname().to_string(), Details = "No configuration file was found. Port 8080 is open for initial setup.", Version = types::brand_version_full!(), ); } else if bootstrap.registry.is_recovery_mode() { trc::event!( Server(trc::ServerEvent::RecoveryMode), Details = "Port 8080 is open for troubleshooting and recovery.", Hostname = bootstrap.registry.local_hostname().to_string(), Version = types::brand_version_full!(), ); } else { trc::event!( Server(trc::ServerEvent::Startup), Hostname = bootstrap.registry.local_hostname().to_string(), Version = types::brand_version_full!(), ); } if core.storage.coordinator.is_enabled() { trc::event!( Cluster(trc::ClusterEvent::Startup), Id = bootstrap.registry.node_id(), Type = bootstrap .registry .cluster_role() .unwrap_or("[default]") .to_string(), Details = bootstrap.registry.cluster_push_shard() ); } // Build shared inner let has_remote_asn = matches!( core.network.asn_geo_lookup, AsnGeoLookupConfig::Resource { .. } ); let (ipc, ipc_rxs) = build_ipc(!core.storage.coordinator.is_none()); let inner = Arc::new(Inner { shared_core: ArcSwap::new(Arc::from(core)), data, ipc, cache, }); if !bootstrap.registry.is_recovery_mode() { // Load spam model if let Err(err) = inner.build_server().spam_model_reload().await { trc::error!( err.details("Failed to load spam filter model") .caused_by(trc::location!()) ); } // Fetch ASN database if has_remote_asn { inner .build_server() .lookup_asn_country(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))) .await; } } // Parse TCP acceptors servers .parse_tcp_acceptors(&mut bootstrap, inner.clone()) .await; BootManager { inner, bootstrap, servers, ipc_rxs, } } StoreOp::Export(path) => { // Enable telemetry telemetry.enable(); // Parse settings and backup Box::pin(Core::parse(&mut bootstrap, storage)) .await .backup(path) .await; std::process::exit(0); } StoreOp::Import(path) => { // Enable telemetry telemetry.enable(); // Parse settings and restore Box::pin(Core::parse(&mut bootstrap, storage)) .await .restore(path) .await; std::process::exit(0); } StoreOp::Console => { // Store console store_console( Box::pin(Core::parse(&mut bootstrap, storage)) .await .storage .data, ) .await; std::process::exit(0); } } } } pub fn build_ipc(has_pubsub: bool) -> (Ipc, IpcReceivers) { // Build ipc receivers let (push_tx, push_rx) = mpsc::channel(IPC_CHANNEL_BUFFER); let (queue_tx, queue_rx) = mpsc::channel(IPC_CHANNEL_BUFFER); let (report_tx, report_rx) = mpsc::channel(IPC_CHANNEL_BUFFER); let (broadcast_tx, broadcast_rx) = mpsc::channel(IPC_CHANNEL_BUFFER); ( Ipc { push_tx, queue_tx, report_tx, broadcast_tx: has_pubsub.then_some(broadcast_tx), task_tx: Arc::new(Notify::new()), train_task_controller: Arc::new(TrainTaskController::default()), }, IpcReceivers { push_rx: Some(push_rx), queue_rx: Some(queue_rx), report_rx: Some(report_rx), broadcast_rx: has_pubsub.then_some(broadcast_rx), }, ) }