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