diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de9e988..3cf09df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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/**/* diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eb75e4c..9f5320e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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"] diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 47afb0c..d130915 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -7,6 +7,9 @@ "core:default", "shell:allow-open", "dialog:allow-open", - "dialog:default" + "dialog:default", + "dialog:allow-ask", + "updater:default", + "process:allow-restart" ] } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1d6cb61..03c68ee 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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, 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"); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4c45973..d99d06f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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" + } + } } } diff --git a/src/i18n/en.json b/src/i18n/en.json index 62e5404..b6e986c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -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..." } diff --git a/src/i18n/es.json b/src/i18n/es.json index 4a2c87e..6d80972 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -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..." } diff --git a/src/i18n/pt-BR.json b/src/i18n/pt-BR.json index 3ca661d..98536e6 100644 --- a/src/i18n/pt-BR.json +++ b/src/i18n/pt-BR.json @@ -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..." } diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 8ea7cb5..9503fd5 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -42,5 +42,8 @@ "error_checksum_failed": "校验和验证失败。请重新下载镜像。", "verifying_checksum": "正在验证校验和...", "latest_version": "最新:{version}", - "offline": "离线" + "offline": "离线", + "app_update_title": "有更新可用", + "app_update_text": "版本 {version} 可用。立即更新?", + "app_updating": "正在更新..." } diff --git a/src/main.js b/src/main.js index f0fd13d..0a0f25a 100644 --- a/src/main.js +++ b/src/main.js @@ -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();