commit
bc5fbd6ba9
180 files changed
+63750
No files matched your search
+149
@@ -0,0 +1,149 @@
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user