150 lines
4.4 KiB
Rust
150 lines
4.4 KiB
Rust
mod auth;
|
|
mod calendar;
|
|
mod config;
|
|
mod db;
|
|
mod email_theme;
|
|
mod error;
|
|
mod imap;
|
|
mod pgp;
|
|
mod realtime;
|
|
mod routes;
|
|
mod search;
|
|
mod sieve;
|
|
mod smtp;
|
|
mod ui;
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use config::AppConfig;
|
|
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
|
|
|
use crate::auth::session::SessionStore;
|
|
use crate::imap::client::RealImapClient;
|
|
use crate::realtime::idle::IdleManager;
|
|
use crate::smtp::client::RealSmtpClient;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
if args.iter().any(|arg| arg == "--version" || arg == "-V") {
|
|
println!("rustbox {}", env!("CARGO_PKG_VERSION"));
|
|
return Ok(());
|
|
}
|
|
|
|
// Initialize structured JSON logging with env filter.
|
|
// Default to INFO level; override with RUST_LOG env var.
|
|
tracing_subscriber::registry()
|
|
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
|
|
.with(fmt::layer().json())
|
|
.init();
|
|
|
|
// Load configuration via figment (serde defaults + env vars).
|
|
let loaded_config = AppConfig::load()?;
|
|
loaded_config.validate().map_err(|message| {
|
|
std::io::Error::new(
|
|
std::io::ErrorKind::InvalidInput,
|
|
format!("invalid RustBox configuration: {message}"),
|
|
)
|
|
})?;
|
|
if args.iter().any(|arg| arg == "--check-config") {
|
|
println!("RustBox configuration OK");
|
|
return Ok(());
|
|
}
|
|
let config = Arc::new(loaded_config);
|
|
|
|
// Create the in-memory session store with the configured timeout.
|
|
let store = Arc::new(SessionStore::new(Duration::from_secs(
|
|
config.session_timeout_hours * 3600,
|
|
)));
|
|
|
|
// Spawn a background task that periodically purges expired sessions.
|
|
{
|
|
let store = Arc::clone(&store);
|
|
tokio::spawn(async move {
|
|
let mut interval = tokio::time::interval(Duration::from_secs(300));
|
|
loop {
|
|
interval.tick().await;
|
|
store.purge_expired();
|
|
tracing::debug!("Purged expired sessions");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Create the IMAP and SMTP clients for production use.
|
|
let imap_client: Arc<dyn imap::client::ImapClient> = Arc::new(RealImapClient::default());
|
|
let smtp_client: Arc<dyn smtp::client::SmtpClient> = Arc::new(RealSmtpClient);
|
|
|
|
// Create the Tantivy search engine for full-text indexing.
|
|
let search_engine = Arc::new(search::engine::SearchEngine::new(std::path::PathBuf::from(
|
|
&config.data_dir,
|
|
)));
|
|
|
|
// Create the real-time event bus and IDLE manager.
|
|
let event_bus = Arc::new(realtime::events::EventBus::new());
|
|
let idle_manager = Arc::new(realtime::idle::IdleManager::new());
|
|
|
|
// Build the application router with auth, session, and static file serving.
|
|
let app = routes::create_router(
|
|
config.clone(),
|
|
store,
|
|
imap_client,
|
|
smtp_client,
|
|
search_engine,
|
|
event_bus,
|
|
idle_manager.clone(),
|
|
);
|
|
|
|
// Bind to the configured host and port.
|
|
let bind_addr = format!("{}:{}", config.host, config.port);
|
|
let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
|
|
|
|
tracing::info!(
|
|
host = %config.host,
|
|
port = %config.port,
|
|
"RustBox server starting"
|
|
);
|
|
|
|
axum::serve(
|
|
listener,
|
|
app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
|
|
)
|
|
.with_graceful_shutdown(shutdown_signal(idle_manager))
|
|
.await?;
|
|
|
|
tracing::info!("RustBox server stopped");
|
|
Ok(())
|
|
}
|
|
|
|
async fn shutdown_signal(idle_manager: Arc<IdleManager>) {
|
|
let ctrl_c = async {
|
|
if let Err(error) = tokio::signal::ctrl_c().await {
|
|
tracing::error!(error = %error, "Failed to install Ctrl+C handler");
|
|
}
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
let terminate = async {
|
|
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
|
|
Ok(mut signal) => {
|
|
signal.recv().await;
|
|
}
|
|
Err(error) => {
|
|
tracing::error!(error = %error, "Failed to install SIGTERM handler");
|
|
std::future::pending::<()>().await;
|
|
}
|
|
}
|
|
};
|
|
|
|
#[cfg(not(unix))]
|
|
let terminate = std::future::pending::<()>();
|
|
|
|
tokio::select! {
|
|
_ = ctrl_c => {},
|
|
_ = terminate => {},
|
|
}
|
|
|
|
tracing::info!("Shutdown signal received; stopping IDLE tasks");
|
|
idle_manager.stop_all();
|
|
}
|