//! moqbox — Shared media rooms with HTTP media + WebSocket state. #![deny(rust_2018_idioms, unsafe_code)] #![cfg_attr(not(test), deny(clippy::unwrap_used))] mod ingest; mod media; mod names; mod room; mod state; mod store; mod transport; mod types; mod util; mod web; use clap::Parser; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use tracing_subscriber::EnvFilter; #[derive(Parser)] #[command(name = "moqbox", version, about = "Shared media rooms over MoQ")] struct Args { /// HTTP listen address. #[arg(long, default_value = "0.0.0.0:3000", env = "MOQBOX_HTTP_ADDR")] http_addr: SocketAddr, /// Directory for downloaded media files. #[arg(long, default_value = "/tmp/moqbox", env = "MOQBOX_CACHE_DIR")] cache_dir: PathBuf, /// Path to SQLite database file. #[arg(long, default_value = "moqbox.db", env = "MOQBOX_DB_PATH")] db_path: PathBuf, } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); let args = Args::parse(); tokio::fs::create_dir_all(&args.cache_dir).await?; // Ensure database parent directory exists. if let Some(parent) = args.db_path.parent() { if !parent.as_os_str().is_empty() { tokio::fs::create_dir_all(parent).await?; } } let store: Arc = Arc::new(crate::store::SqliteStore::connect(&args.db_path).await?); tracing::info!("database ready at {}", args.db_path.display()); let rooms = room::Registry::new(store, args.cache_dir.clone()); // Idle room sweeper: every 60s, remove rooms idle for 600s (10 min). let registry_clone = rooms.clone(); tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(60)); loop { interval.tick().await; let swept = registry_clone.sweep_idle(Duration::from_secs(600)).await; if !swept.is_empty() { tracing::info!("swept {} idle rooms", swept.len()); } } }); let app = web::router(rooms, args.cache_dir); let listener = tokio::net::TcpListener::bind(args.http_addr).await?; tracing::info!("HTTP server listening on {}", args.http_addr); axum::serve( listener, app.into_make_service_with_connect_info::(), ) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } async fn shutdown_signal() { let ctrl_c = async { tokio::signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) .expect("failed to install SIGTERM handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => {}, _ = terminate => {}, } tracing::info!("shutting down"); }