use actix_cors::Cors; use actix_files::Files; use actix_web::{http::header, middleware::Logger, web, App, HttpResponse, HttpServer}; use std::path::PathBuf; /// Static file server for avatar, blob, and other static files /// Serves files from /data/{type} directories #[derive(Clone)] struct StaticConfig { root: PathBuf, cors_enabled: bool, } impl StaticConfig { fn from_env() -> Self { let root = std::env::var("STATIC_ROOT").unwrap_or_else(|_| "/data".to_string()); let cors = std::env::var("STATIC_CORS").unwrap_or_else(|_| "true".to_string()); Self { root: PathBuf::from(root), cors_enabled: cors == "true" || cors == "1", } } fn ensure_dir(&self, name: &str) -> PathBuf { let dir = self.root.join(name); if !dir.exists() { std::fs::create_dir_all(&dir).ok(); } dir } } async fn health() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "status": "ok", "service": "static-server" })) } #[actix_web::main] async fn main() -> anyhow::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let cfg = StaticConfig::from_env(); let bind = std::env::var("STATIC_BIND").unwrap_or_else(|_| "0.0.0.0:8081".to_string()); println!("Static file server starting..."); println!(" Root: {:?}", cfg.root); println!(" Bind: {}", bind); println!(" CORS: {}", if cfg.cors_enabled { "enabled" } else { "disabled" }); // Ensure all directories exist for name in ["avatar", "blob", "media", "static"] { let dir = cfg.ensure_dir(name); println!(" {} dir: {:?}", name, dir); } let root = cfg.root.clone(); let cors_enabled = cfg.cors_enabled; HttpServer::new(move || { let root = root.clone(); let cors = if cors_enabled { Cors::default() .allow_any_origin() .allowed_methods(vec!["GET", "HEAD", "OPTIONS"]) .allowed_headers(vec![ header::AUTHORIZATION, header::ACCEPT, header::CONTENT_TYPE, ]) .max_age(3600) } else { Cors::permissive() }; App::new() .wrap(cors) .wrap(Logger::default()) .route("/health", web::get().to(health)) .service( Files::new("/avatar", root.join("avatar")) .prefer_utf8(true) .index_file("index.html"), ) .service( Files::new("/blob", root.join("blob")) .prefer_utf8(true) .index_file("index.html"), ) .service( Files::new("/media", root.join("media")) .prefer_utf8(true) .index_file("index.html"), ) .service( Files::new("/static", root.join("static")) .prefer_utf8(true) .index_file("index.html"), ) }) .bind(&bind)? .run() .await?; Ok(()) }