diff --git a/ai-hub/app/api/routes/nodes.py b/ai-hub/app/api/routes/nodes.py index a98b2a9..b256ddd 100644 --- a/ai-hub/app/api/routes/nodes.py +++ b/ai-hub/app/api/routes/nodes.py @@ -460,6 +460,57 @@ script = services.mesh_service.generate_provisioning_sh(node, config_yaml, base_url) return PlainTextResponse(script) + @router.get("/provision/binary/{node_id}/{arch}", summary="Download Self-Contained Binary ZIP Bundle") + def provision_node_binary_bundle(node_id: str, arch: str, token: str, db: Session = Depends(get_db)): + """ + Dynamically zips the specified architecture's cortex-agent binary + with the autogenerated agent_config.yaml for secure, direct GUI downloads. + """ + import io + import zipfile + + node = db.query(models.AgentNode).filter(models.AgentNode.node_id == node_id).first() + if not node or node.invite_token != token: + raise HTTPException(status_code=403, detail="Invalid node or token.") + + # 1. Locate the requested architecture binary + from app.api.routes.agent_update import _AGENT_NODE_DIR + binary_path = os.path.join(_AGENT_NODE_DIR, "dist", arch, "cortex-agent") + + # If binary wasn't built for this arch natively, fallback to notifying user + if not os.path.exists(binary_path): + raise HTTPException(status_code=404, detail=f"Binary for {arch} is not compiled on hub.") + + # 2. Generate config + config_yaml = _generate_node_config_yaml(node) + + # 3. Create ZIP in memory + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file: + + # Add executable binary with execution permissions + binary_info = zipfile.ZipInfo("cortex-agent") + binary_info.external_attr = 0o100755 << 16 # -rwxr-xr-x + binary_info.compress_type = zipfile.ZIP_DEFLATED + with open(binary_path, "rb") as f: + zip_file.writestr(binary_info, f.read()) + + # Add pre-configured yaml + zip_file.writestr("agent_config.yaml", config_yaml) + + # Add a quick start script for comfort + quick_start = f"#!/bin/bash\n# Cortex Agent Quick Start\n./cortex-agent\n" + script_info = zipfile.ZipInfo("start.sh") + script_info.external_attr = 0o100755 << 16 + zip_file.writestr(script_info, quick_start) + + zip_buffer.seek(0) + return StreamingResponse( + zip_buffer, + media_type="application/x-zip-compressed", + headers={"Content-Disposition": f"attachment; filename=cortex-node-{node_id}-{arch}.zip"} + ) + @router.get("/admin/{node_id}/download", summary="[Admin] Download Agent Node Bundle (ZIP)") def admin_download_bundle( node_id: str, diff --git a/frontend/src/features/nodes/pages/NodesPage.js b/frontend/src/features/nodes/pages/NodesPage.js index 4d59d76..4d66a8b 100644 --- a/frontend/src/features/nodes/pages/NodesPage.js +++ b/frontend/src/features/nodes/pages/NodesPage.js @@ -717,28 +717,28 @@ Source (.tar.gz) Linux (AMD64) Linux (ARM64) macOS (ARM64)