Add auto-update via tauri-plugin-updater + GitHub Releases

- tauri-plugin-updater + tauri-plugin-process for self-update
- check_app_update / install_app_update Rust commands (UpdaterExt)
- tauri.conf.json: createUpdaterArtifacts + updater endpoint
- Capabilities: updater:default, process:allow-restart, dialog:allow-ask
- Frontend: checkForAppUpdate() with native dialog prompt
- CI/CD: replaced manual build with tauri-apps/tauri-action@v0
  - includeUpdaterJson generates latest.json automatically
  - TAURI_SIGNING_PRIVATE_KEY for update signature verification
  - Rust cache via swatinem/rust-cache@v2
- i18n: app_update_title/text/updating in all 4 locales

NOTE: pubkey in tauri.conf.json is placeholder — replace after
running `cargo tauri signer generate` and adding secrets to GitHub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Douglas Teles
2026-03-02 01:31:39 -03:00
parent a5e5ec7e79
commit 24568f90af
10 changed files with 125 additions and 66 deletions
+23 -61
View File
@@ -15,13 +15,13 @@ jobs:
matrix:
include:
- platform: ubuntu-22.04
target: x86_64-unknown-linux-gnu
args: ''
- platform: macos-latest
target: aarch64-apple-darwin
args: '--target aarch64-apple-darwin'
- platform: macos-latest
target: x86_64-apple-darwin
args: '--target x86_64-apple-darwin'
- platform: windows-latest
target: x86_64-pc-windows-msvc
args: ''
runs-on: ${{ matrix.platform }}
@@ -31,7 +31,12 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
@@ -47,9 +52,6 @@ jobs:
libglib2.0-dev \
libgtk-3-dev
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^2"
# macOS: import signing certificate from GitHub Secrets
- name: Import Apple certificate
if: runner.os == 'macOS'
@@ -72,65 +74,25 @@ jobs:
-k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# macOS: build with signing + notarization
- name: Build (macOS signed)
if: runner.os == 'macOS'
- name: Build and release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: cargo tauri build --target ${{ matrix.target }}
# Linux/Windows: build without signing
- name: Build
if: runner.os != 'macOS'
run: cargo tauri build --target ${{ matrix.target }}
with:
tagName: v__VERSION__
releaseName: 'Arch R Flasher v__VERSION__'
releaseBody: 'See the assets to download and install.'
releaseDraft: true
prerelease: false
includeUpdaterJson: true
args: ${{ matrix.args }}
# macOS: cleanup keychain
- name: Cleanup keychain
if: runner.os == 'macOS' && always()
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true
- name: Upload artifacts (Linux)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: archr-flasher-linux-${{ matrix.target }}
path: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage
- name: Upload artifacts (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
with:
name: archr-flasher-macos-${{ matrix.target }}
path: |
src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
- name: Upload artifacts (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: archr-flasher-windows-${{ matrix.target }}
path: |
src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
release:
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create Release
uses: softprops/action-gh-release@v2
with:
draft: true
generate_release_notes: true
files: artifacts/**/*
+2
View File
@@ -20,6 +20,8 @@ xz2 = "0.1"
sys-locale = "0.3"
sha2 = "0.10"
fs2 = "0.4"
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
[features]
default = ["custom-protocol"]
+4 -1
View File
@@ -7,6 +7,9 @@
"core:default",
"shell:allow-open",
"dialog:allow-open",
"dialog:default"
"dialog:default",
"dialog:allow-ask",
"updater:default",
"process:allow-restart"
]
}
+48
View File
@@ -10,6 +10,7 @@ use disk::DiskInfo;
use github::{DownloadResult, ReleaseInfo};
use panels::Panel;
use tauri::Manager;
use tauri_plugin_updater::UpdaterExt;
/// Returns the OS locale (e.g. "pt-BR", "en-US") for i18n.
#[tauri::command]
@@ -78,10 +79,55 @@ async fn flash_image(
Ok("Flash complete".into())
}
/// Check if a new version of the Flasher app is available.
/// Returns "version|body" string if update available, null if up to date.
#[tauri::command]
async fn check_app_update(app: tauri::AppHandle) -> Result<Option<String>, String> {
let update = app.updater_builder()
.build()
.map_err(|e| format!("{}", e))?
.check()
.await
.map_err(|e| format!("{}", e))?;
match update {
Some(u) => Ok(Some(format!("{}|{}", u.version, u.body.unwrap_or_default()))),
None => Ok(None),
}
}
/// Download and install the app update, then restart.
#[tauri::command]
async fn install_app_update(app: tauri::AppHandle) -> Result<(), String> {
let update = app.updater_builder()
.build()
.map_err(|e| format!("{}", e))?
.check()
.await
.map_err(|e| format!("{}", e))?;
if let Some(update) = update {
update.download_and_install(|_, _| {}, || {})
.await
.map_err(|e| format!("{}", e))?;
app.restart();
}
Ok(())
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
.setup(|app| {
#[cfg(desktop)]
app.handle().plugin(
tauri_plugin_updater::Builder::new().build()
)?;
Ok(())
})
.invoke_handler(tauri::generate_handler![
get_locale,
get_panels,
@@ -89,6 +135,8 @@ fn main() {
check_latest_release,
download_image,
flash_image,
check_app_update,
install_app_update,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+12
View File
@@ -27,6 +27,7 @@
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -39,5 +40,16 @@
"signingIdentity": null,
"entitlements": null
}
},
"plugins": {
"updater": {
"pubkey": "PLACEHOLDER_GERAR_COM_CARGO_TAURI_SIGNER",
"endpoints": [
"https://github.com/archr-linux/archr-flasher/releases/latest/download/latest.json"
],
"windows": {
"installMode": "passive"
}
}
}
}
+4 -1
View File
@@ -42,5 +42,8 @@
"error_checksum_failed": "Checksum verification failed. Re-download the image.",
"verifying_checksum": "Verifying checksum...",
"latest_version": "Latest: {version}",
"offline": "Offline"
"offline": "Offline",
"app_update_title": "Update Available",
"app_update_text": "Version {version} available. Update now?",
"app_updating": "Updating..."
}
+4 -1
View File
@@ -42,5 +42,8 @@
"error_checksum_failed": "Verificacion de checksum fallo. Descargue la imagen nuevamente.",
"verifying_checksum": "Verificando checksum...",
"latest_version": "Ultima: {version}",
"offline": "Sin conexion"
"offline": "Sin conexion",
"app_update_title": "Actualizacion disponible",
"app_update_text": "Version {version} disponible. Actualizar ahora?",
"app_updating": "Actualizando..."
}
+4 -1
View File
@@ -42,5 +42,8 @@
"error_checksum_failed": "Verificacao de checksum falhou. Baixe a imagem novamente.",
"verifying_checksum": "Verificando checksum...",
"latest_version": "Ultima: {version}",
"offline": "Offline"
"offline": "Offline",
"app_update_title": "Atualizacao disponivel",
"app_update_text": "Versao {version} disponivel. Atualizar agora?",
"app_updating": "Atualizando..."
}
+4 -1
View File
@@ -42,5 +42,8 @@
"error_checksum_failed": "校验和验证失败。请重新下载镜像。",
"verifying_checksum": "正在验证校验和...",
"latest_version": "最新:{version}",
"offline": "离线"
"offline": "离线",
"app_update_title": "有更新可用",
"app_update_text": "版本 {version} 可用。立即更新?",
"app_updating": "正在更新..."
}
+20
View File
@@ -381,6 +381,7 @@ function translateError(msg) {
async function init() {
await initI18n();
checkLatestVersion(); // fire-and-forget (does not block UI)
checkForAppUpdate(); // fire-and-forget (check for flasher app update)
}
async function checkLatestVersion() {
@@ -393,4 +394,23 @@ async function checkLatestVersion() {
}
}
async function checkForAppUpdate() {
try {
const result = await window.__TAURI__.core.invoke('check_app_update');
if (!result) return;
const [version, ...rest] = result.split('|');
const yes = await window.__TAURI__.dialog.ask(
t('app_update_text', { version }),
{ title: t('app_update_title'), kind: 'info' }
);
if (!yes) return;
setStatus(t('app_updating'), '');
await window.__TAURI__.core.invoke('install_app_update');
} catch (_) {
// offline or error — ignore silently
}
}
init();