first commit
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-29 15:24:59 +01:00
commit bc5fbd6ba9
180 files changed
+63750

No files matched your search

+149
View File
@@ -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();
}