feat: add update command to CLI, relay, and client

This commit is contained in:
Helios Agent 2026-03-06 12:16:10 +01:00
parent 835d20f734
commit 6345209538
No known key found for this signature in database
GPG key ID: C8259547CD8309B5
9 changed files with 302 additions and 0 deletions

View file

@ -401,6 +401,91 @@ pub async fn clipboard_set(
}
}
/// POST /relay/update — self-update the relay binary and restart the service
pub async fn relay_update() -> impl IntoResponse {
tokio::spawn(async {
// Give the HTTP response time to be sent before we restart
tokio::time::sleep(Duration::from_millis(800)).await;
let url = "https://agent-helios.me/downloads/helios-remote/helios-remote-relay-linux";
let bytes = match reqwest::get(url).await {
Ok(r) => match r.bytes().await {
Ok(b) => b,
Err(e) => {
error!("relay update: failed to read response body: {e}");
return;
}
},
Err(e) => {
error!("relay update: download failed: {e}");
return;
}
};
let exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => {
error!("relay update: current_exe: {e}");
return;
}
};
let tmp = exe.with_extension("new");
if let Err(e) = std::fs::write(&tmp, &bytes) {
error!("relay update: write tmp failed: {e}");
return;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o755));
}
if let Err(e) = std::fs::rename(&tmp, &exe) {
error!("relay update: rename failed: {e}");
return;
}
let _ = std::process::Command::new("systemctl")
.args(["restart", "helios-remote"])
.spawn();
});
(
axum::http::StatusCode::OK,
Json(serde_json::json!({ "ok": true, "message": "update triggered, relay restarting..." })),
)
}
/// POST /devices/:label/update — trigger client self-update
pub async fn client_update(
Path(label): Path<String>,
State(state): State<AppState>,
) -> impl IntoResponse {
match dispatch_with_timeout(&state, &label, "update", |rid| {
ServerMessage::UpdateRequest { request_id: rid }
}, Duration::from_secs(60)).await {
Ok(ClientMessage::UpdateResponse { success, message, .. }) => (
StatusCode::OK,
Json(serde_json::json!({ "success": success, "message": message })),
).into_response(),
Ok(ClientMessage::Ack { .. }) => (
StatusCode::OK,
Json(serde_json::json!({ "success": true, "message": "update acknowledged" })),
).into_response(),
Ok(ClientMessage::Error { message, .. }) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "success": false, "message": message })),
).into_response(),
Ok(_) => (
StatusCode::OK,
Json(serde_json::json!({ "success": true, "message": "acknowledged" })),
).into_response(),
Err(e) => e.into_response(),
}
}
/// POST /devices/:label/inform
pub async fn inform_user(
Path(label): Path<String>,