Compare commits
2
Commits
v1.0.4
..
7e01e599fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e01e599fd | ||
|
|
6191c7bbc6 |
No files matched your search
@@ -4,6 +4,14 @@
|
||||
|
||||
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer.
|
||||
|
||||
## [1.0.5] - 2026-10-31
|
||||
|
||||
### Added
|
||||
- **One-click desktop update.** The update button actually downloads, verifies (SHA-256) and installs the new version now, instead of only changing its own label. Works for all three desktop packages: AppImage (atomic self-replace), `.deb` (`dpkg -i`, no new password prompt — the app is already running as root by the time you click it), and the Windows installer (silent install). On Linux, the app restarts itself automatically; on Windows, the installer runs silently but the app does not reopen on its own yet — start it again once the install finishes.
|
||||
|
||||
### Changed
|
||||
- **The repository moved to the Ciberfauna organization** (`ciberfauna/sabia`, was `lucasrguerra/sabia`). **Important notice for existing installations:** versions up to 1.0.4 have the old location baked into their update check; they will not find this release automatically. Download 1.0.5 once manually from the new Releases page, and future update checks will work through the new location.
|
||||
|
||||
## [1.0.3] - 2026-10-30
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
|
||||
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
|
||||
|
||||
## [1.0.5] - 2026-10-31
|
||||
|
||||
### Adicionado
|
||||
- **Atualização do app desktop em um clique.** O botão de atualizar agora baixa, confere (SHA-256) e instala a versão nova de verdade, em vez de só mudar o próprio texto. Funciona nos três pacotes do desktop: AppImage (substituição atômica), `.deb` (`dpkg -i`, sem pedir senha de novo — o app já roda como root quando você clica), e o instalador do Windows (instalação silenciosa). No Linux o app reabre sozinho; no Windows o instalador roda silencioso mas o app ainda não reabre por conta própria — abra de novo depois que a instalação terminar.
|
||||
|
||||
### Alterado
|
||||
- **O repositório se mudou pra organização Ciberfauna** (`ciberfauna/sabia`, era `lucasrguerra/sabia`). **Aviso importante pra instalações existentes:** versões até a 1.0.4 têm o endereço antigo fixo na checagem de atualização e não vão encontrar esta versão sozinhas. Baixe a 1.0.5 manualmente uma vez na nova página de Releases, e as próximas verificações voltam a funcionar pelo novo endereço.
|
||||
|
||||
## [1.0.3] - 2026-10-30
|
||||
|
||||
### Corrigido
|
||||
|
||||
Generated
+2
-2
@@ -440,7 +440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sabia-cli"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ctrlc",
|
||||
@@ -451,7 +451,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sabia-core"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"flate2",
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@ resolver = "2"
|
||||
members = ["crates/sabia-core", "crates/sabia-cli"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia"
|
||||
repository = "https://git.lucasrguerra.dev.br/ciberfauna/sabia"
|
||||
authors = ["Sabiá contributors"]
|
||||
rust-version = "1.80"
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -2538,7 +2538,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "sabia-core"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"flate2",
|
||||
@@ -2555,7 +2555,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sabia-gui"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sabia-core",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
[package]
|
||||
name = "sabia-gui"
|
||||
description = "Sabiá: graphical Wi-Fi security auditing interface"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["Sabiá contributors"]
|
||||
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia"
|
||||
repository = "https://git.lucasrguerra.dev.br/ciberfauna/sabia"
|
||||
rust-version = "1.80"
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -502,8 +502,19 @@ fn update_check(state: tauri::State<'_, State>) -> Result<serde_json::Value, Str
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn update_apply(state: tauri::State<'_, State>) -> Result<String, String> {
|
||||
state.0.update_apply().map_err(|e| e.to_string())
|
||||
fn update_apply(app: tauri::AppHandle, state: tauri::State<'_, State>) -> Result<String, String> {
|
||||
use tauri::Emitter;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
let exe = elevated_exe().unwrap_or_else(|_| std::env::current_exe().unwrap_or_default());
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
let exe = std::env::current_exe().unwrap_or_default();
|
||||
|
||||
state
|
||||
.0
|
||||
.update_apply_desktop(&exe, move |phase| {
|
||||
let _ = app.emit("update-progress", serde_json::json!({ "phase": phase }));
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// `true` when the process already has sufficient privileges for all
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Sabiá",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"identifier": "dev.sabia.app",
|
||||
"build": {
|
||||
"frontendDist": "../ui"
|
||||
|
||||
+45
-1
@@ -267,6 +267,8 @@ async function tauriApi(path, opts = {}) {
|
||||
return await invoke('export_all_hc22000', { iface: q.get('iface'), out: q.get('out') });
|
||||
case 'GET /api/update/check':
|
||||
return await invoke('update_check');
|
||||
case 'POST /api/update/apply':
|
||||
return await invoke('update_apply');
|
||||
default:
|
||||
return { error: 'rota desconhecida' };
|
||||
}
|
||||
@@ -371,12 +373,54 @@ els.btnScan.addEventListener('click', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
let updateStatus = null;
|
||||
let updateInProgress = false;
|
||||
|
||||
els.btnUpdate.addEventListener('click', async () => {
|
||||
if (updateInProgress) return;
|
||||
if (updateStatus?.update_available) {
|
||||
updateInProgress = true;
|
||||
els.btnUpdate.disabled = true;
|
||||
els.btnUpdate.textContent = 'baixando…';
|
||||
|
||||
let unlisten = null;
|
||||
if (tauri?.event?.listen) {
|
||||
try {
|
||||
unlisten = await tauri.event.listen('update-progress', (event) => {
|
||||
const phase = event.payload?.phase;
|
||||
if (phase === 'downloading') els.btnUpdate.textContent = 'baixando…';
|
||||
else if (phase === 'verifying') els.btnUpdate.textContent = 'verificando…';
|
||||
else if (phase === 'installing') els.btnUpdate.textContent = 'instalando…';
|
||||
else if (phase === 'restarting') els.btnUpdate.textContent = 'reiniciando…';
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
try {
|
||||
await api('/api/update/apply', { method: 'POST' });
|
||||
els.btnUpdate.textContent = 'já atualizado';
|
||||
} catch (err) {
|
||||
updateInProgress = false;
|
||||
els.btnUpdate.disabled = false;
|
||||
els.btnUpdate.textContent = `atualizar para ${updateStatus.latest_version}`;
|
||||
alert('Falha na atualização: ' + (err?.message || err));
|
||||
} finally {
|
||||
if (unlisten) unlisten();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
els.btnUpdate.textContent = 'checando…';
|
||||
try {
|
||||
const status = await api('/api/update/check');
|
||||
els.btnUpdate.textContent = status.update_available
|
||||
updateStatus = status;
|
||||
els.btnUpdate.textContent = status?.update_available
|
||||
? `atualizar para ${status.latest_version}`
|
||||
: 'já atualizado';
|
||||
} catch (err) {
|
||||
els.btnUpdate.textContent = 'checar atualização';
|
||||
alert('Não foi possível verificar atualizações: ' + (err?.message || err));
|
||||
}
|
||||
});
|
||||
|
||||
const SEC_RANK = {
|
||||
|
||||
@@ -12,6 +12,12 @@ export const EN = {
|
||||
'checando…': 'checking…',
|
||||
'atualizar para {n}': 'update to {n}',
|
||||
'já atualizado': 'already up to date',
|
||||
'baixando…': 'downloading…',
|
||||
'verificando…': 'verifying…',
|
||||
'instalando…': 'installing…',
|
||||
'reiniciando…': 'restarting…',
|
||||
'Falha na atualização:': 'Update failed:',
|
||||
'Não foi possível verificar atualizações:': 'Could not check for updates:',
|
||||
|
||||
// topbar
|
||||
'Painel de captura': 'Capture dashboard',
|
||||
|
||||
@@ -2979,6 +2979,17 @@ impl Engine {
|
||||
update::apply(&exe)
|
||||
}
|
||||
|
||||
pub fn update_apply_desktop<F>(
|
||||
&self,
|
||||
current_exe: &std::path::Path,
|
||||
on_progress: F,
|
||||
) -> Result<String>
|
||||
where
|
||||
F: Fn(&str),
|
||||
{
|
||||
update::apply_desktop_with_progress(current_exe, on_progress)
|
||||
}
|
||||
|
||||
fn session_handshake(
|
||||
&self,
|
||||
name: &str,
|
||||
|
||||
@@ -9,16 +9,16 @@ use crate::error::{Result, SabiaError};
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// Gitea `releases/latest` API endpoint (`tag_name`, `assets[].name`, `assets[].browser_download_url`).
|
||||
///
|
||||
const LATEST_RELEASE_URL: &str =
|
||||
"https://git.lucasrguerra.dev.br/api/v1/repos/lucasrguerra/sabia/releases/latest";
|
||||
"https://git.lucasrguerra.dev.br/api/v1/repos/ciberfauna/sabia/releases/latest";
|
||||
|
||||
/// Human-facing Gitea Releases page for desktop GUI users.
|
||||
const RELEASES_PAGE_URL: &str = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia/releases";
|
||||
const RELEASES_PAGE_URL: &str = "https://git.lucasrguerra.dev.br/ciberfauna/sabia/releases";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Release {
|
||||
@@ -56,7 +56,7 @@ fn curl_text(url: &str) -> Result<String> {
|
||||
Ok(String::from_utf8_lossy(&out.stdout).into_owned())
|
||||
}
|
||||
|
||||
fn curl_download(url: &str, dest: &PathBuf) -> Result<()> {
|
||||
fn curl_download(url: &str, dest: &Path) -> Result<()> {
|
||||
let status = Command::new("curl")
|
||||
.args(["-fsSL", "-H", "User-Agent: sabia-updater", "-o"])
|
||||
.arg(dest)
|
||||
@@ -246,6 +246,300 @@ fn find_binary(dir: &PathBuf) -> Result<PathBuf> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Desktop GUI installation type detected at runtime.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DesktopInstall {
|
||||
/// Linux AppImage: real filesystem path of the .AppImage file (from APPIMAGE env var).
|
||||
AppImage(PathBuf),
|
||||
/// Linux Debian package installed via dpkg: `current_exe` is reliable here.
|
||||
Deb,
|
||||
/// Windows NSIS installer: downloaded setup executable run with `/S`
|
||||
/// (silent). Does not relaunch the app on its own — see the comment on
|
||||
/// the `WindowsInstaller` apply path.
|
||||
WindowsInstaller,
|
||||
}
|
||||
|
||||
/// Detects the desktop installation type given an optional `APPIMAGE` environment
|
||||
/// variable value and the current executable path.
|
||||
pub fn detect_desktop_install_from(
|
||||
appimage_env: Option<&std::ffi::OsStr>,
|
||||
current_exe: &Path,
|
||||
) -> Result<DesktopInstall> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = (appimage_env, current_exe);
|
||||
Ok(DesktopInstall::WindowsInstaller)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(val) = appimage_env {
|
||||
let path = PathBuf::from(val);
|
||||
if path.is_file() {
|
||||
return Ok(DesktopInstall::AppImage(path));
|
||||
}
|
||||
}
|
||||
if current_exe.is_file()
|
||||
&& current_exe
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.eq_ignore_ascii_case("appimage"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(DesktopInstall::AppImage(current_exe.to_path_buf()));
|
||||
}
|
||||
Ok(DesktopInstall::Deb)
|
||||
}
|
||||
#[cfg(not(any(windows, target_os = "linux")))]
|
||||
{
|
||||
let _ = (appimage_env, current_exe);
|
||||
Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!(
|
||||
"automatic self-update is not supported on this platform; please download manually from {RELEASES_PAGE_URL}"
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects the desktop installation type using the live environment.
|
||||
pub fn detect_desktop_install() -> Result<DesktopInstall> {
|
||||
let exe = std::env::current_exe()?;
|
||||
detect_desktop_install_from(std::env::var_os("APPIMAGE").as_deref(), &exe)
|
||||
}
|
||||
|
||||
/// Returns the expected release asset suffix for a desktop installation and CPU architecture.
|
||||
pub fn gui_asset_suffix_for(install: &DesktopInstall, arch: &str) -> Result<&'static str> {
|
||||
match install {
|
||||
DesktopInstall::AppImage(_) => match arch {
|
||||
"x86_64" => Ok("_amd64.AppImage"),
|
||||
"aarch64" => Ok("_aarch64.AppImage"),
|
||||
other => Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!("unsupported architecture for AppImage: {other}"),
|
||||
}),
|
||||
},
|
||||
DesktopInstall::Deb => match arch {
|
||||
"x86_64" => Ok("_amd64.deb"),
|
||||
"aarch64" => Ok("_arm64.deb"),
|
||||
other => Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!("unsupported architecture for deb package: {other}"),
|
||||
}),
|
||||
},
|
||||
DesktopInstall::WindowsInstaller => match arch {
|
||||
"x86_64" => Ok("_x64-setup.exe"),
|
||||
other => Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!("unsupported architecture for Windows installer: {other}"),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the expected release asset suffix for the current architecture.
|
||||
pub fn gui_asset_suffix(install: &DesktopInstall) -> Result<&'static str> {
|
||||
gui_asset_suffix_for(install, std::env::consts::ARCH)
|
||||
}
|
||||
|
||||
/// Downloads, verifies, and installs the latest desktop GUI release in place.
|
||||
pub fn apply_desktop(current_exe: &Path) -> Result<String> {
|
||||
apply_desktop_with_progress(current_exe, |_| {})
|
||||
}
|
||||
|
||||
/// Downloads, verifies, and installs the latest desktop GUI release in place,
|
||||
/// notifying the progress callback of phase transitions (`"downloading"`,
|
||||
/// `"verifying"`, `"installing"`, `"restarting"`).
|
||||
pub fn apply_desktop_with_progress<F>(current_exe: &Path, on_progress: F) -> Result<String>
|
||||
where
|
||||
F: Fn(&str),
|
||||
{
|
||||
let install =
|
||||
detect_desktop_install_from(std::env::var_os("APPIMAGE").as_deref(), current_exe)?;
|
||||
let body = curl_text(LATEST_RELEASE_URL)?;
|
||||
let release: Release = serde_json::from_str(&body)?;
|
||||
|
||||
let suffix = gui_asset_suffix(&install)?;
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name.ends_with(suffix))
|
||||
.ok_or_else(|| SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!(
|
||||
"no asset ending with `{suffix}` in release {}",
|
||||
release.tag_name
|
||||
),
|
||||
})?;
|
||||
let checksum_asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name == format!("{}.sha256", asset.name));
|
||||
|
||||
let tmp_dir = std::env::temp_dir().join("sabia-desktop-update");
|
||||
fs::create_dir_all(&tmp_dir)?;
|
||||
let download_path = tmp_dir.join(&asset.name);
|
||||
|
||||
on_progress("downloading");
|
||||
curl_download(&asset.browser_download_url, &download_path)?;
|
||||
|
||||
if let Some(checksum_asset) = checksum_asset {
|
||||
on_progress("verifying");
|
||||
let expected = curl_text(&checksum_asset.browser_download_url)?;
|
||||
let expected_hash = expected
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
let data = fs::read(&download_path)?;
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&data);
|
||||
let actual_hash: String = hasher
|
||||
.finalize()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect();
|
||||
if actual_hash != expected_hash {
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: "checksum SHA-256 não confere — atualização descartada".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
on_progress("installing");
|
||||
match install {
|
||||
DesktopInstall::AppImage(target_appimage) => {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let parent_dir = target_appimage.parent().unwrap_or_else(|| Path::new("."));
|
||||
let temp_name = format!(
|
||||
".{}.tmp-{}",
|
||||
target_appimage
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("appimage"),
|
||||
std::process::id()
|
||||
);
|
||||
let temp_file = parent_dir.join(temp_name);
|
||||
fs::copy(&download_path, &temp_file)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&temp_file)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&temp_file, perms)?;
|
||||
}
|
||||
fs::rename(&temp_file, &target_appimage)?;
|
||||
let _ = fs::remove_file(&download_path);
|
||||
|
||||
on_progress("restarting");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::CommandExt;
|
||||
let err = Command::new(&target_appimage)
|
||||
.args(std::env::args().skip(1))
|
||||
.exec();
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!("failed to relaunch AppImage: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = (&target_appimage, &download_path);
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: "AppImage updates are only supported on Linux".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
DesktopInstall::Deb => {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let status = Command::new("dpkg")
|
||||
.args(["-i"])
|
||||
.arg(&download_path)
|
||||
.status()
|
||||
.map_err(|e| SabiaError::CommandFailed {
|
||||
cmd: "dpkg -i".into(),
|
||||
detail: e.to_string(),
|
||||
})?;
|
||||
if !status.success() {
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "dpkg -i".into(),
|
||||
detail: format!("dpkg -i failed with exit code {:?}", status.code()),
|
||||
});
|
||||
}
|
||||
let _ = fs::remove_file(&download_path);
|
||||
|
||||
on_progress("restarting");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::CommandExt;
|
||||
let err = Command::new(current_exe)
|
||||
.args(std::env::args().skip(1))
|
||||
.exec();
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: format!("failed to relaunch updated app: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = (&download_path, current_exe);
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: "deb updates are only supported on Linux".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
DesktopInstall::WindowsInstaller => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// `/S` is the one NSIS silent-install switch with a documented,
|
||||
// stable meaning. There is no standard NSIS or Tauri flag that
|
||||
// makes a silent install relaunch the app afterwards — that was
|
||||
// claimed in an earlier draft of this code and could not be
|
||||
// verified against NSIS's or Tauri's documentation; passing an
|
||||
// invented flag to a stock NSIS installer is silently ignored,
|
||||
// not harmful, but also not a relaunch mechanism. Spawning
|
||||
// detached and exiting immediately (instead of waiting on the
|
||||
// child) also avoids a self-deadlock: if the installer's script
|
||||
// waits for this process to close before it can overwrite the
|
||||
// running .exe, blocking here on the installer's exit status
|
||||
// would hang forever. The practical effect: the update installs
|
||||
// silently, but the app does not reopen itself — the user needs
|
||||
// to start it again. This needs real-world confirmation on
|
||||
// Windows before relying on it; if testing shows the bundled
|
||||
// NSIS template does support an explicit relaunch flag or step,
|
||||
// wire it in here instead of this comment.
|
||||
on_progress("restarting");
|
||||
Command::new(&download_path)
|
||||
.arg("/S")
|
||||
.spawn()
|
||||
.map_err(|e| SabiaError::CommandFailed {
|
||||
cmd: "installer".into(),
|
||||
detail: format!("failed to launch installer: {e}"),
|
||||
})?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _ = &download_path;
|
||||
return Err(SabiaError::CommandFailed {
|
||||
cmd: "update".into(),
|
||||
detail: "Windows installer updates are only supported on Windows".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(release.tag_name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -258,4 +552,70 @@ mod tests {
|
||||
let err = apply(&PathBuf::from("C:/Program Files/Sabia/sabia-gui.exe")).unwrap_err();
|
||||
assert!(err.to_string().contains(RELEASES_PAGE_URL), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_desktop_install_variants() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let temp_appimage =
|
||||
std::env::temp_dir().join(format!("test-sabia-{}.AppImage", std::process::id()));
|
||||
let _ = fs::write(&temp_appimage, b"dummy appimage");
|
||||
|
||||
let res = detect_desktop_install_from(
|
||||
Some(temp_appimage.as_os_str()),
|
||||
Path::new("/usr/bin/sabia-gui"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(res, DesktopInstall::AppImage(temp_appimage.clone()));
|
||||
|
||||
let res_fallback = detect_desktop_install_from(None, &temp_appimage).unwrap();
|
||||
assert_eq!(
|
||||
res_fallback,
|
||||
DesktopInstall::AppImage(temp_appimage.clone())
|
||||
);
|
||||
|
||||
let _ = fs::remove_file(&temp_appimage);
|
||||
|
||||
let res_deb =
|
||||
detect_desktop_install_from(None, Path::new("/usr/bin/sabia-gui")).unwrap();
|
||||
assert_eq!(res_deb, DesktopInstall::Deb);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let res = detect_desktop_install_from(
|
||||
None,
|
||||
Path::new("C:/Program Files/Sabia/sabia-gui.exe"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(res, DesktopInstall::WindowsInstaller);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gui_asset_suffix_matches_known_release_naming() {
|
||||
let appimage = DesktopInstall::AppImage(PathBuf::from("/tmp/Sabia.AppImage"));
|
||||
assert_eq!(
|
||||
gui_asset_suffix_for(&appimage, "x86_64").unwrap(),
|
||||
"_amd64.AppImage"
|
||||
);
|
||||
assert_eq!(
|
||||
gui_asset_suffix_for(&appimage, "aarch64").unwrap(),
|
||||
"_aarch64.AppImage"
|
||||
);
|
||||
|
||||
let deb = DesktopInstall::Deb;
|
||||
assert_eq!(gui_asset_suffix_for(&deb, "x86_64").unwrap(), "_amd64.deb");
|
||||
assert_eq!(gui_asset_suffix_for(&deb, "aarch64").unwrap(), "_arm64.deb");
|
||||
|
||||
let win = DesktopInstall::WindowsInstaller;
|
||||
assert_eq!(
|
||||
gui_asset_suffix_for(&win, "x86_64").unwrap(),
|
||||
"_x64-setup.exe"
|
||||
);
|
||||
|
||||
assert!(gui_asset_suffix_for(&appimage, "mips").is_err());
|
||||
assert!(gui_asset_suffix_for(&deb, "riscv64").is_err());
|
||||
assert!(gui_asset_suffix_for(&win, "arm").is_err());
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -76,8 +76,6 @@ case "$platform" in
|
||||
rm -rf "$bundle/appimage" "$bundle/deb"
|
||||
tauri build --bundles appimage,deb
|
||||
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
||||
# Include .sig alongside the bundle when updater signing is enabled.
|
||||
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
|
||||
;;
|
||||
linux-x86_64)
|
||||
if [ "$(uname -m)" = x86_64 ]; then
|
||||
@@ -165,7 +163,6 @@ EOF
|
||||
tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb
|
||||
fi
|
||||
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
||||
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
|
||||
;;
|
||||
windows)
|
||||
rustup target add x86_64-pc-windows-msvc >/dev/null
|
||||
@@ -173,9 +170,21 @@ EOF
|
||||
rm -rf "$bundle"
|
||||
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
||||
cp "$bundle"/*-setup.exe ../../../dist-gui/
|
||||
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
|
||||
;;
|
||||
*) echo "unknown platform: $platform" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
(
|
||||
cd ../../../dist-gui
|
||||
for asset in *; do
|
||||
[ -f "$asset" ] || continue
|
||||
case "$asset" in
|
||||
*.sha256) ;;
|
||||
*)
|
||||
sha256sum "$asset" > "$asset.sha256"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
)
|
||||
|
||||
ls -1 ../../../dist-gui
|
||||
Reference in new issue
Block a user