Implement robust auto-update system for GuruConnect agent

Features:
- Agent checks for updates periodically (hourly) during idle
- Admin can trigger immediate updates via dashboard "Update Agent" button
- Silent updates with in-place binary replacement (no reboot required)
- SHA-256 checksum verification before installation
- Semantic version comparison

Server changes:
- New releases table for tracking available versions
- GET /api/version endpoint for agent polling (unauthenticated)
- POST /api/machines/:id/update endpoint for admin push updates
- Release management API (/api/releases CRUD)
- Track agent_version in machine status

Agent changes:
- New update.rs module with download/verify/install/restart logic
- Handle ADMIN_UPDATE WebSocket command for push updates
- --post-update flag for cleanup after successful update
- Periodic update check in idle loop (persistent agents only)
- agent_version included in AgentStatus messages

Dashboard changes:
- Version display in machine detail panel
- "Update Agent" button for each connected machine

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 09:31:23 -07:00
parent 7df824c2ca
commit 4e5328fe4a
15 changed files with 1399 additions and 18 deletions

View File

@@ -308,6 +308,11 @@ async fn handle_agent_connection(
}
Some(proto::message::Payload::AgentStatus(status)) => {
// Update session with agent status
let agent_version = if status.agent_version.is_empty() {
None
} else {
Some(status.agent_version.clone())
};
sessions_status.update_agent_status(
session_id,
Some(status.os_version.clone()),
@@ -315,9 +320,16 @@ async fn handle_agent_connection(
status.uptime_secs,
status.display_count,
status.is_streaming,
agent_version.clone(),
).await;
info!("Agent status update: {} - streaming={}, uptime={}s",
status.hostname, status.is_streaming, status.uptime_secs);
// Update version in database if present
if let (Some(ref db), Some(ref version)) = (&db, &agent_version) {
let _ = crate::db::releases::update_machine_version(db.pool(), &agent_id, version).await;
}
info!("Agent status update: {} - streaming={}, uptime={}s, version={:?}",
status.hostname, status.is_streaming, status.uptime_secs, agent_version);
}
Some(proto::message::Payload::Heartbeat(_)) => {
// Update heartbeat timestamp