summaryrefslogblamecommitdiff
path: root/makima/src/server/handlers/daemon_download.rs
blob: 1575d8b35e1ee4dd778d7bf5d4717da945621688 (plain) (tree)


































































































































































                                                                                             
//! 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<PlatformInfo>,
}

/// 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<String>,
) -> 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()
}