/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
server/src/main.rs
122 строки
6 KB
mcmare
Bump version to 0.2.0, print ASCII-art banner with version on startup
18 июл 2026, 19:28
18 июл 2026, 19:28
06389ca
Код
Авторство
О чём код?
use arc_swap::ArcSwap; use engine::{ build_router, connect_all, docs_router, ensure_dirs, ensure_tables, hot_reload, load_config, resolve_jwt_secret, webui_router, EngineState, RouteTable, ScriptEngine, }; use rustapi_core::{AppState, AuthConfig, HealthCheck, HealthState}; use std::{path::PathBuf, pin::Pin, sync::Arc}; #[tokio::main] async fn main() -> anyhow::Result<()> { // Logs go to this terminal's stdout. Default level is "info"; override with e.g. // `RUST_LOG=debug` (or `RUST_LOG=engine=debug` for just this crate) for more detail. let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()); tracing_subscriber::fmt().with_env_filter(filter).init(); let config_dir = std::env::var("CONFIG_DIR").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("config")); let data_dir = std::env::var("DATA_DIR").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("data")); ensure_dirs(&config_dir, &data_dir)?; // Canonicalize purely for display in the startup banner — CONFIG_DIR/DATA_DIR stay // relative-to-CWD internally, but printing "config" with no hint of *which* CWD is // useless once this binary is installed system-wide and launched from anywhere. let config_dir_abs = display_path(&config_dir); let data_dir_abs = display_path(&data_dir); let jwt_secret = resolve_jwt_secret(&data_dir)?; let config = load_config(&config_dir)?; tracing::info!( endpoints = config.endpoints.len(), connections = config.connections.len(), "config loaded from {}", config_dir.display() ); let pools = Arc::new(connect_all(&config.connections).await?); ensure_tables(&config.endpoints, &pools).await?; let tokens = load_auth_tokens_or_warn(&config_dir)?; let auth = AuthConfig::new(tokens); let scripts = Arc::new(ScriptEngine::new(pools.clone(), jwt_secret.clone())); let routes = Arc::new(ArcSwap::from_pointee(RouteTable::build(&config)?)); // Keep the watcher alive for the process lifetime — dropping it stops file watching. let _watcher = hot_reload::spawn(config_dir.clone(), routes.clone(), scripts.clone(), pools.clone())?; let engine_state = EngineState { pools: pools.clone(), scripts, routes, }; let endpoints_router = build_router(engine_state.clone(), auth.clone()); let docs = docs_router(engine_state); let needs_setup = !data_dir.join("admin.yaml").exists(); let webui = webui_router(config_dir.clone(), data_dir.clone(), jwt_secret); let health = HealthState::new(health_checks(&pools)); let app_state = AppState { auth, health }; let router = rustapi_core::base_router(app_state).merge(docs).merge(webui).merge(endpoints_router); let router = rustapi_core::common_layers(router); let bind_addr = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".to_string()); print_banner(&bind_addr, &config_dir_abs, &data_dir_abs, needs_setup); rustapi_core::serve(router, bind_addr.parse()?).await } /// Printed once at startup, separate from the structured `tracing` log lines below it — /// this is the "what do I do now" summary, not a log entry. fn print_banner(bind_addr: &str, config_dir: &std::path::Path, data_dir: &std::path::Path, needs_setup: bool) { println!(); println!(r" ██████╗ ██╗ ██╗███████╗████████╗ █████╗ ██████╗ ██╗"); println!(r" ██╔══██╗██║ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║"); println!(r" ██████╔╝██║ ██║███████╗ ██║ ███████║██████╔╝██║"); println!(r" ██╔══██╗██║ ██║╚════██║ ██║ ██╔══██║██╔═══╝ ██║"); println!(r" ██║ ██║╚██████╔╝███████║ ██║ ██║ ██║██║ ██║"); println!(r" ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝"); println!(" declarative REST API framework · v{}", env!("CARGO_PKG_VERSION")); println!(); println!(" listening on http://{bind_addr}"); println!(" ├─ Admin UI: http://{bind_addr}/ui{}", if needs_setup { " (no admin account yet — set one up here)" } else { "" }); println!(" ├─ API docs: http://{bind_addr}/docs"); println!(" ├─ Health check: http://{bind_addr}/health"); println!(" ├─ Config dir: {}", config_dir.display()); println!(" └─ Data dir: {}", data_dir.display()); println!(); println!(" Logs print below, to this terminal (stdout). Default level is \"info\" —"); println!(" set RUST_LOG=debug (or e.g. RUST_LOG=engine=debug) for more detail."); println!(); } /// Absolute path for banner display, without Windows' `\\?\` verbatim prefix that /// `fs::canonicalize` adds — accurate but confusing to show a user. fn display_path(path: &std::path::Path) -> PathBuf { let abs = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()); match abs.to_str() { Some(s) => PathBuf::from(s.strip_prefix(r"\\?\").unwrap_or(s)), None => abs, } } fn load_auth_tokens_or_warn(config_dir: &std::path::Path) -> anyhow::Result<Vec<String>> { let tokens = engine::config::load_auth_tokens(&config_dir.join("auth.yaml"))?; if tokens.is_empty() { tracing::warn!("no tokens in auth.yaml — every declared endpoint will reject all requests with 401 (set them up at /ui)"); } Ok(tokens) } fn health_checks(pools: &Arc<std::collections::HashMap<String, engine::DbPool>>) -> Vec<HealthCheck> { pools .iter() .map(|(name, pool)| { let name = name.clone(); let pool = pool.clone(); Arc::new(move || { let name = name.clone(); let pool = pool.clone(); Box::pin(async move { (name, pool.ping().await) }) as Pin<Box<dyn std::future::Future<Output = (String, bool)> + Send>> }) as HealthCheck }) .collect() }