2 Commits
Author SHA1 Message Date
Lucas RayanandClaude Sonnet 5 7e01e599fd chore(release): bump version to 1.0.5, move repository to the ciberfauna org
Release / Create release (push) Successful in 7s
CI / Format and clippy (push) Successful in 1m25s
CI / Dependency audit (push) Successful in 11s
CI / Tests (Linux) (push) Successful in 1m57s
CI / Check desktop app (Linux) (push) Successful in 3m43s
Release / CLI (linux-aarch64) (push) Successful in 45s
Release / Desktop app (linux-aarch64) (push) Successful in 4m22s
Release / CLI (linux-x86_64) (push) Successful in 51s
Release / Desktop app (linux-x86_64) (push) Successful in 4m20s
Release / Desktop app (windows) (push) Successful in 3m12s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-31 10:19:32 -03:00
Lucas RayanandClaude Sonnet 5 6191c7bbc6 feat(update): one-click desktop self-update (AppImage, .deb, Windows)
Same change as Guará: update_apply used to flatly refuse to run for
the GUI binary and point the user to a manual download instead. Since
the desktop app already elevates to root before showing its window,
the process applying the update already has everything it needs — no
second authentication round is required.

- New sabia_core::update::apply_desktop[_with_progress], alongside the
  existing CLI-only apply() (left untouched): detects the install kind
  at runtime (AppImage via the APPIMAGE env var, same FUSE-mount issue
  elevated_exe() already works around and this reuses; Deb otherwise
  on Linux; WindowsInstaller on Windows), matches the right release
  asset by the GUI bundle naming (_amd64/_aarch64.AppImage,
  _amd64/_arm64.deb, _x64-setup.exe), verifies SHA-256 when a sidecar
  is published, and installs:
  - AppImage: downloads to a temp file in the same directory, renames
    over the running file, execs it in place (same PID, already root).
  - .deb: runs dpkg -i directly (already root), execs current_exe()
    again now that dpkg replaced it on disk.
  - Windows: runs the installer silently and detached, exits
    immediately to release the file lock. Does not claim to relaunch
    the app afterwards — no documented NSIS or Tauri flag does that;
    needs a real Windows machine to confirm actual post-install
    behavior.
- Engine::update_apply_desktop wires it into the Tauri update_apply
  command (now takes AppHandle, emits update-progress events).
- Fixed a real gap: the existing update button handler changed its own
  label when an update was available but never actually called apply
  — wired it for real, with per-phase progress feedback in the
  interface's existing language (download/verify/install/restart).
- scripts/build-gui.sh: generates a checksum file next to every file
  in dist-gui/, matching the CLI release archive convention.
- 3 new unit tests: install-kind detection, GUI asset suffix matching
  against real release naming, existing CLI-refusal test unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-31 10:18:31 -03:00
13 changed files with 477 additions and 20 deletions

No files matched your search

+8
View File
@@ -4,6 +4,14 @@
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer. 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 ## [1.0.3] - 2026-10-30
### Fixed ### Fixed
+8
View File
@@ -4,6 +4,14 @@
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer. 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 ## [1.0.3] - 2026-10-30
### Corrigido ### Corrigido
Generated
+2 -2
View File
@@ -440,7 +440,7 @@ dependencies = [
[[package]] [[package]]
name = "sabia-cli" name = "sabia-cli"
version = "1.0.4" version = "1.0.5"
dependencies = [ dependencies = [
"clap", "clap",
"ctrlc", "ctrlc",
@@ -451,7 +451,7 @@ dependencies = [
[[package]] [[package]]
name = "sabia-core" name = "sabia-core"
version = "1.0.4" version = "1.0.5"
dependencies = [ dependencies = [
"aes", "aes",
"flate2", "flate2",
+2 -2
View File
@@ -3,10 +3,10 @@ resolver = "2"
members = ["crates/sabia-core", "crates/sabia-cli"] members = ["crates/sabia-core", "crates/sabia-cli"]
[workspace.package] [workspace.package]
version = "1.0.4" version = "1.0.5"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" 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"] authors = ["Sabiá contributors"]
rust-version = "1.80" rust-version = "1.80"
+2 -2
View File
@@ -2538,7 +2538,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]] [[package]]
name = "sabia-core" name = "sabia-core"
version = "1.0.4" version = "1.0.5"
dependencies = [ dependencies = [
"aes", "aes",
"flate2", "flate2",
@@ -2555,7 +2555,7 @@ dependencies = [
[[package]] [[package]]
name = "sabia-gui" name = "sabia-gui"
version = "1.0.4" version = "1.0.5"
dependencies = [ dependencies = [
"libc", "libc",
"sabia-core", "sabia-core",
+2 -2
View File
@@ -3,11 +3,11 @@
[package] [package]
name = "sabia-gui" name = "sabia-gui"
description = "Sabiá: graphical Wi-Fi security auditing interface" description = "Sabiá: graphical Wi-Fi security auditing interface"
version = "1.0.4" version = "1.0.5"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
authors = ["Sabiá contributors"] authors = ["Sabiá contributors"]
repository = "https://git.lucasrguerra.dev.br/lucasrguerra/sabia" repository = "https://git.lucasrguerra.dev.br/ciberfauna/sabia"
rust-version = "1.80" rust-version = "1.80"
[workspace] [workspace]
+13 -2
View File
@@ -502,8 +502,19 @@ fn update_check(state: tauri::State<'_, State>) -> Result<serde_json::Value, Str
} }
#[tauri::command] #[tauri::command]
fn update_apply(state: tauri::State<'_, State>) -> Result<String, String> { fn update_apply(app: tauri::AppHandle, state: tauri::State<'_, State>) -> Result<String, String> {
state.0.update_apply().map_err(|e| e.to_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 /// `true` when the process already has sufficient privileges for all
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Sabiá", "productName": "Sabiá",
"version": "1.0.4", "version": "1.0.5",
"identifier": "dev.sabia.app", "identifier": "dev.sabia.app",
"build": { "build": {
"frontendDist": "../ui" "frontendDist": "../ui"
+45 -1
View File
@@ -267,6 +267,8 @@ async function tauriApi(path, opts = {}) {
return await invoke('export_all_hc22000', { iface: q.get('iface'), out: q.get('out') }); return await invoke('export_all_hc22000', { iface: q.get('iface'), out: q.get('out') });
case 'GET /api/update/check': case 'GET /api/update/check':
return await invoke('update_check'); return await invoke('update_check');
case 'POST /api/update/apply':
return await invoke('update_apply');
default: default:
return { error: 'rota desconhecida' }; return { error: 'rota desconhecida' };
} }
@@ -371,12 +373,54 @@ els.btnScan.addEventListener('click', async () => {
} }
}); });
let updateStatus = null;
let updateInProgress = false;
els.btnUpdate.addEventListener('click', async () => { 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…'; els.btnUpdate.textContent = 'checando…';
try {
const status = await api('/api/update/check'); 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}` ? `atualizar para ${status.latest_version}`
: 'já atualizado'; : '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 = { const SEC_RANK = {
+6
View File
@@ -12,6 +12,12 @@ export const EN = {
'checando…': 'checking…', 'checando…': 'checking…',
'atualizar para {n}': 'update to {n}', 'atualizar para {n}': 'update to {n}',
'já atualizado': 'already up to date', '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 // topbar
'Painel de captura': 'Capture dashboard', 'Painel de captura': 'Capture dashboard',
+11
View File
@@ -2979,6 +2979,17 @@ impl Engine {
update::apply(&exe) 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( fn session_handshake(
&self, &self,
name: &str, name: &str,
+364 -4
View File
@@ -9,16 +9,16 @@ use crate::error::{Result, SabiaError};
use serde::Deserialize; use serde::Deserialize;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
/// Gitea `releases/latest` API endpoint (`tag_name`, `assets[].name`, `assets[].browser_download_url`). /// Gitea `releases/latest` API endpoint (`tag_name`, `assets[].name`, `assets[].browser_download_url`).
/// ///
const LATEST_RELEASE_URL: &str = 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. /// 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)] #[derive(Debug, Deserialize)]
struct Release { struct Release {
@@ -56,7 +56,7 @@ fn curl_text(url: &str) -> Result<String> {
Ok(String::from_utf8_lossy(&out.stdout).into_owned()) 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") let status = Command::new("curl")
.args(["-fsSL", "-H", "User-Agent: sabia-updater", "-o"]) .args(["-fsSL", "-H", "User-Agent: sabia-updater", "-o"])
.arg(dest) .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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -258,4 +552,70 @@ mod tests {
let err = apply(&PathBuf::from("C:/Program Files/Sabia/sabia-gui.exe")).unwrap_err(); let err = apply(&PathBuf::from("C:/Program Files/Sabia/sabia-gui.exe")).unwrap_err();
assert!(err.to_string().contains(RELEASES_PAGE_URL), "{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
View File
@@ -76,8 +76,6 @@ case "$platform" in
rm -rf "$bundle/appimage" "$bundle/deb" rm -rf "$bundle/appimage" "$bundle/deb"
tauri build --bundles appimage,deb tauri build --bundles appimage,deb
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/ 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) linux-x86_64)
if [ "$(uname -m)" = x86_64 ]; then if [ "$(uname -m)" = x86_64 ]; then
@@ -165,7 +163,6 @@ EOF
tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb
fi fi
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/ cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
;; ;;
windows) windows)
rustup target add x86_64-pc-windows-msvc >/dev/null rustup target add x86_64-pc-windows-msvc >/dev/null
@@ -173,9 +170,21 @@ EOF
rm -rf "$bundle" rm -rf "$bundle"
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
cp "$bundle"/*-setup.exe ../../../dist-gui/ cp "$bundle"/*-setup.exe ../../../dist-gui/
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
;; ;;
*) echo "unknown platform: $platform" >&2; exit 2 ;; *) echo "unknown platform: $platform" >&2; exit 2 ;;
esac 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 ls -1 ../../../dist-gui