diff options
| author | soryu <soryu@soryu.co> | 2026-02-21 23:51:11 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-21 23:51:11 +0000 |
| commit | 0523765af84492640928d571f481e17b26008b13 (patch) | |
| tree | 644e0bac90c1945120df27dea36d18c81f4470e9 /makima/src | |
| parent | d670dcb72984cfa483063d161bb468704038895c (diff) | |
| download | soryu-0523765af84492640928d571f481e17b26008b13.tar.gz soryu-0523765af84492640928d571f481e17b26008b13.zip | |
feat: Add daemon health monitoring page, downloads & K8s support (#76)
* feat: soryu-co/soryu - makima: Add server-side daemon binary download endpoint
* feat: soryu-co/soryu - makima: Create Kubernetes daemon manifests and Dockerfile
* feat: soryu-co/soryu - makima: Create dedicated Daemons page with health monitoring UI
* WIP: heartbeat checkpoint
* feat: soryu-co/soryu - makima: Integrate daemon platform availability into frontend downloads
Diffstat (limited to 'makima/src')
| -rw-r--r-- | makima/src/server/handlers/daemon_download.rs | 163 | ||||
| -rw-r--r-- | makima/src/server/handlers/mod.rs | 1 | ||||
| -rw-r--r-- | makima/src/server/mod.rs | 5 |
3 files changed, 168 insertions, 1 deletions
diff --git a/makima/src/server/handlers/daemon_download.rs b/makima/src/server/handlers/daemon_download.rs new file mode 100644 index 0000000..1575d8b --- /dev/null +++ b/makima/src/server/handlers/daemon_download.rs @@ -0,0 +1,163 @@ +//! 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() +} diff --git a/makima/src/server/handlers/mod.rs b/makima/src/server/handlers/mod.rs index 8b06a28..4bdb424 100644 --- a/makima/src/server/handlers/mod.rs +++ b/makima/src/server/handlers/mod.rs @@ -5,6 +5,7 @@ pub mod chat; pub mod contract_chat; pub mod contract_daemon; pub mod contract_discuss; +pub mod daemon_download; pub mod contracts; pub mod directives; pub mod file_ws; diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs index 2310ba3..1ad3a8d 100644 --- a/makima/src/server/mod.rs +++ b/makima/src/server/mod.rs @@ -18,7 +18,7 @@ use tower_http::trace::TraceLayer; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; -use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, directives, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, orders, repository_history, speak, templates, transcript_analysis, users, versions}; +use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, daemon_download, directives, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, orders, repository_history, speak, templates, transcript_analysis, users, versions}; use crate::server::openapi::ApiDoc; use crate::server::state::SharedState; @@ -136,6 +136,9 @@ pub fn make_router(state: SharedState) -> Router { // Mesh WebSocket endpoints .route("/mesh/tasks/subscribe", get(mesh_ws::task_subscription_handler)) .route("/mesh/daemons/connect", get(mesh_daemon::daemon_handler)) + // Daemon binary download endpoints + .route("/daemon/download/platforms", get(daemon_download::list_daemon_platforms)) + .route("/daemon/download/{platform}", get(daemon_download::download_daemon)) // API key management endpoints .route( "/auth/api-keys", |
