//! HTTP handlers for daemon binary downloads. //! //! Serves pre-compiled daemon binaries for download. Binaries are read from //! disk at a configurable path (default: `/app/daemon-binaries`), overridable //! via the `DAEMON_BINARIES_DIR` environment variable. use axum::{ extract::Path, http::{header, StatusCode}, response::IntoResponse, Json, }; use serde::Serialize; /// Default directory where daemon binaries are stored. const DEFAULT_BINARIES_DIR: &str = "/app/daemon-binaries"; /// Supported platforms for daemon binary downloads. const SUPPORTED_PLATFORMS: &[&str] = &[ "linux-x86_64", "linux-arm64", "macos-x86_64", "macos-arm64", ]; /// Response for listing available daemon platforms. #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct PlatformInfo { /// Platform identifier (e.g., "linux-x86_64") pub platform: String, /// Whether a binary is available for this platform pub available: bool, /// Download URL path for this platform pub download_url: String, } /// Response for the list platforms endpoint. #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ListPlatformsResponse { /// List of supported platforms and their availability pub platforms: Vec, } /// Get the binaries directory from environment or use default. fn get_binaries_dir() -> String { std::env::var("DAEMON_BINARIES_DIR").unwrap_or_else(|_| DEFAULT_BINARIES_DIR.to_string()) } /// Get the binary file path for a given platform. fn get_binary_path(platform: &str) -> std::path::PathBuf { let dir = get_binaries_dir(); std::path::PathBuf::from(dir).join(format!("makima-{}", platform)) } /// List available daemon platforms and their download availability. /// /// Returns a list of all supported platforms with availability status /// based on whether the binary file exists on disk. pub async fn list_daemon_platforms() -> impl IntoResponse { let mut platforms = Vec::with_capacity(SUPPORTED_PLATFORMS.len()); for &platform in SUPPORTED_PLATFORMS { let path = get_binary_path(platform); let available = path.exists(); platforms.push(PlatformInfo { platform: platform.to_string(), available, download_url: format!("/api/v1/daemon/download/{}", platform), }); } ( StatusCode::OK, Json(ListPlatformsResponse { platforms }), ) .into_response() } /// Download a daemon binary for the specified platform. /// /// Reads the binary from disk and returns it with appropriate headers /// for file download. Returns 404 if the binary is not available. pub async fn download_daemon( Path(platform): Path, ) -> impl IntoResponse { // Validate platform if !SUPPORTED_PLATFORMS.contains(&platform.as_str()) { return ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "code": "INVALID_PLATFORM", "message": format!( "Unsupported platform '{}'. Supported platforms: {}", platform, SUPPORTED_PLATFORMS.join(", ") ) })), ) .into_response(); } let binary_path = get_binary_path(&platform); // Read binary from disk let binary_data = match tokio::fs::read(&binary_path).await { Ok(data) => data, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { return ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "code": "BINARY_NOT_FOUND", "message": format!( "Daemon binary for platform '{}' is not available for download", platform ) })), ) .into_response(); } Err(e) => { tracing::error!( platform = %platform, path = %binary_path.display(), error = %e, "Failed to read daemon binary" ); return ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "code": "READ_ERROR", "message": "Failed to read daemon binary" })), ) .into_response(); } }; let filename = format!("makima-{}", platform); // Return binary with download headers ( StatusCode::OK, [ ( header::CONTENT_TYPE, "application/octet-stream".to_string(), ), ( header::CONTENT_DISPOSITION, format!("attachment; filename=\"{}\"", filename), ), ( header::CONTENT_LENGTH, binary_data.len().to_string(), ), ], binary_data, ) .into_response() }