Files
inbuxa-server/crates/common/src/manager/boot.rs
T
jcoffey-dev 6a53d47106 Mark the files this fork changed (AGPL section 5(a))
The AGPL asks a modified version to carry prominent notices saying it was
modified, and giving a date. Publishing the source is the conveyance that
asks for it, so it wants doing before the repository is public rather than
at the release.

Every upstream file the fork changed now says so in its header, beneath the
notice it came with: 164 files, found by diffing against the upstream
snapshot branch rather than by guessing, so the list is what actually
differs. Files the fork wrote itself already carry their own copyright and
need nothing. Upstream's notices are untouched, which its licence requires
and which was already true.

The README says the same thing in prose, since the obligation is on the
work as a whole and not only its Rust files.

Builds unchanged: the server and the test binary both compile.
2026-09-19 23:48:35 -07:00

303 lines
10 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/
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<Inner>,
pub servers: Listeners,
pub ipc_rxs: IpcReceivers,
}
pub struct IpcReceivers {
pub push_rx: Option<mpsc::Receiver<PushEvent>>,
pub queue_rx: Option<mpsc::Receiver<QueueEvent>>,
pub report_rx: Option<mpsc::Receiver<ReportingEvent>>,
pub broadcast_rx: Option<mpsc::Receiver<BroadcastEvent>>,
}
const HELP: &str = concat!(
types::brand_server!(),
" ",
types::brand_version_full!(),
r#"
Usage: inbuxa [OPTIONS]
Options:
-c, --config <PATH> Start server with the specified configuration file
-e, --export <PATH> Export all store data to a specific path
-i, --import <PATH> 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<Core> =
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
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),
},
)
}