Parent
[>]
1//! Binary entry point. Parses CLI arguments, initialises global configuration, 2//! builds the CSS theme, constructs the axum router, and starts the HTTP server. 3 4mod cli; 5 6use clap::Parser; 7use teak::config; 8use teak::theme::{BASE_CSS, MARKDOWN_CSS}; 9use teak::{error, routes}; 10use tower_http::trace::TraceLayer; 11use tracing_subscriber::EnvFilter; 12 13async fn shutdown_signal() { 14 let ctrl_c = tokio::signal::ctrl_c(); 15 #[cfg(unix)] 16 let sigterm = async { 17 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) 18 .expect("install SIGTERM handler") 19 .recv() 20 .await; 21 }; 22 #[cfg(not(unix))] 23 let sigterm = std::future::pending::<()>(); 24 25 tokio::select! { 26 _ = ctrl_c => {}, 27 _ = sigterm => {}, 28 } 29 tracing::info!("shutting down"); 30} 31 32#[tokio::main] 33async fn main() -> error::Result<()> { 34 tracing_subscriber::fmt() 35 .with_env_filter( 36 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), 37 ) 38 .init(); 39 40 let args = cli::Args::parse(); 41 42 config::set_hostname(args.hostname); 43 config::set_ssh_prefix(args.ssh_prefix); 44 config::set_base_url(args.base_url); 45 46 let (favicon_data, favicon_mime) = match args.favicon { 47 Some(path) => match std::fs::read(&path) { 48 Ok(data) => { 49 let mime = match path.extension().and_then(|e| e.to_str()) { 50 Some("ico") => "image/x-icon", 51 Some("png") => "image/png", 52 Some("gif") => "image/gif", 53 _ => "image/svg+xml", 54 }; 55 (data, mime) 56 } 57 Err(e) => { 58 tracing::warn!("failed to read favicon {path:?}: {e}; using default"); 59 (include_bytes!("../static/favicon.svg").to_vec(), "image/svg+xml") 60 } 61 }, 62 None => (include_bytes!("../static/favicon.svg").to_vec(), "image/svg+xml"), 63 }; 64 config::set_favicon(favicon_data, favicon_mime); 65 66 let theme_vars = match args.theme_file { 67 Some(path) => std::fs::read_to_string(path)?, 68 None => args.theme.css().to_owned(), 69 }; 70 let css = format!("{theme_vars}\n{BASE_CSS}\n{MARKDOWN_CSS}"); 71 72 let state = routes::AppState { 73 root: args.root, 74 css, 75 }; 76 let router = routes::router(state).layer(TraceLayer::new_for_http()); 77 let addr = format!("{}:{}", args.bind, args.port); 78 79 tracing::info!("listening on {addr}"); 80 81 let socket = tokio::net::TcpListener::bind(&addr).await?; 82 axum::serve(socket, router) 83 .with_graceful_shutdown(shutdown_signal()) 84 .await?; 85 86 Ok(()) 87}