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)