summaryrefslogtreecommitdiff
path: root/makima/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server')
-rw-r--r--makima/src/server/handlers/daemon_download.rs163
-rw-r--r--makima/src/server/handlers/mod.rs1
-rw-r--r--makima/src/server/mod.rs5
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",