wraith/src-tauri/src/commands/updater.rs
Vantz Stockwell 037c76384b
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m59s
feat: migrate all artifacts to SeaweedFS — single source of truth
All build artifacts now upload to files.command.vigilcyber.com/wraith/:
- Installer: /wraith/{ver}/Wraith_{ver}_x64-setup.exe + /wraith/latest/
- MCP bridge: /wraith/{ver}/wraith-mcp-bridge.exe + /wraith/latest/
- Update bundle: /wraith/{ver}/*.nsis.zip
- Update manifest: /wraith/update.json (Tauri updater endpoint)
- Version metadata: /wraith/{ver}/version.json + /wraith/latest/

Removed: Gitea package uploads, Gitea release creation/attachment.
Updated: tauri.conf.json updater endpoint, bridge auto-download URL,
manual update checker download URL.

CI is now: build -> sign -> upload to SeaweedFS. Done.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:52:10 -04:00

95 lines
2.9 KiB
Rust

//! Version check against Gitea releases API.
use serde::Serialize;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateInfo {
pub current_version: String,
pub latest_version: String,
pub update_available: bool,
pub download_url: String,
pub release_notes: String,
}
/// Check Gitea for the latest release and compare with current version.
#[tauri::command]
pub async fn check_for_updates(app_handle: tauri::AppHandle) -> Result<UpdateInfo, String> {
// Read version from tauri.conf.json (patched by CI from git tag)
// rather than CARGO_PKG_VERSION which is always 0.1.0
let current = app_handle.config().version.clone().unwrap_or_else(|| "0.0.0".to_string());
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| format!("HTTP client error: {}", e))?;
let resp = client
.get("https://git.command.vigilcyber.com/api/v1/repos/vstockwell/wraith/releases?limit=1")
.header("Accept", "application/json")
.send()
.await
.map_err(|e| format!("Failed to check for updates: {}", e))?;
let releases: Vec<serde_json::Value> = resp.json().await
.map_err(|e| format!("Failed to parse releases: {}", e))?;
let latest = releases.first()
.ok_or_else(|| "No releases found".to_string())?;
let tag = latest.get("tag_name")
.and_then(|v| v.as_str())
.unwrap_or("v0.0.0")
.trim_start_matches('v')
.to_string();
let notes = latest.get("body")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
// Direct download from SeaweedFS
let html_url = format!("https://files.command.vigilcyber.com/wraith/{}/", tag);
let update_available = version_is_newer(&tag, &current);
Ok(UpdateInfo {
current_version: current,
latest_version: tag,
update_available,
download_url: html_url,
release_notes: notes,
})
}
/// Compare semver strings. Returns true if `latest` is newer than `current`.
fn version_is_newer(latest: &str, current: &str) -> bool {
let parse = |v: &str| -> Vec<u32> {
v.split('.').filter_map(|s| s.parse().ok()).collect()
};
let l = parse(latest);
let c = parse(current);
for i in 0..3 {
let lv = l.get(i).copied().unwrap_or(0);
let cv = c.get(i).copied().unwrap_or(0);
if lv > cv { return true; }
if lv < cv { return false; }
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_comparison() {
assert!(version_is_newer("1.5.7", "1.5.6"));
assert!(version_is_newer("1.6.0", "1.5.9"));
assert!(version_is_newer("2.0.0", "1.9.9"));
assert!(!version_is_newer("1.5.6", "1.5.6"));
assert!(!version_is_newer("1.5.5", "1.5.6"));
assert!(!version_is_newer("1.4.0", "1.5.0"));
}
}