From 08b482cb8894c223554f1e4401542fb9f0800146 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Thu, 30 Apr 2026 16:36:13 +0800 Subject: [PATCH] Add explicit in-app updater flow Issue #33 requires users to control updates instead of being silently moved to a new version. The app now exposes a manual check beside the About version, prompts before installing, shows download/install state, and lets users choose whether to restart immediately. Release builds also publish the signed updater bundles and per-platform manifests needed by Tauri's updater while keeping unsigned manual CI runs usable for build validation. Constraint: Tauri updater requires signed artifacts and a public-key manifest Constraint: User requested no silent automatic updates Rejected: Startup background update check | violates the requested confirmation flow Rejected: Always requiring updater signing for workflow_dispatch | would break test builds without release secrets Confidence: high Scope-risk: moderate Directive: Do not reintroduce startup update checks without explicit product approval Tested: npm run build Tested: cargo check Tested: npm run tauri -- build --debug --no-bundle --ci Tested: git diff --check; node --check write-updater-manifest.mjs; bash -n build-mac.sh Not-tested: Live tagged GitHub Actions release run with updater signing secret Related: https://github.com/appergb/openless/issues/33 --- .github/workflows/release-tauri.yml | 93 ++++++- openless-all/app/package-lock.json | 14 +- openless-all/app/package.json | 1 + openless-all/app/scripts/build-mac.sh | 6 +- .../app/scripts/write-updater-manifest.mjs | 63 +++++ openless-all/app/src-tauri/Cargo.lock | 254 +++++++++++++++++- openless-all/app/src-tauri/Cargo.toml | 1 + .../app/src-tauri/capabilities/default.json | 3 +- openless-all/app/src-tauri/src/lib.rs | 7 + openless-all/app/src-tauri/tauri.conf.json | 8 + .../app/src/components/SettingsModal.tsx | 13 +- openless-all/app/src/i18n/en.ts | 30 +++ openless-all/app/src/i18n/zh-CN.ts | 30 +++ openless-all/app/src/lib/ipc.ts | 4 + openless-all/app/src/pages/Settings.tsx | 190 ++++++++++++- openless-all/app/src/pages/_atoms.tsx | 9 +- 16 files changed, 695 insertions(+), 31 deletions(-) create mode 100755 openless-all/app/scripts/write-updater-manifest.mjs diff --git a/.github/workflows/release-tauri.yml b/.github/workflows/release-tauri.yml index 28558a86..3db6d1d2 100644 --- a/.github/workflows/release-tauri.yml +++ b/.github/workflows/release-tauri.yml @@ -30,11 +30,20 @@ jobs: include: - platform: macos-latest rust-target: aarch64-apple-darwin + updater-target: darwin + updater-arch: aarch64 - platform: windows-latest rust-target: x86_64-pc-windows-msvc + updater-target: windows + updater-arch: x86_64 - platform: ubuntu-22.04 rust-target: x86_64-unknown-linux-gnu + updater-target: linux + updater-arch: x86_64 runs-on: ${{ matrix.platform }} + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} steps: - uses: actions/checkout@v4 @@ -76,6 +85,17 @@ jobs: working-directory: 'openless-all/app' run: npm ci + - name: Check updater signing availability + if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') + shell: bash + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: | + if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then + echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for signed auto-update artifacts." + exit 1 + fi + - name: Check Apple signing availability if: matrix.platform == 'macos-latest' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') shell: bash @@ -159,19 +179,50 @@ jobs: working-directory: 'openless-all/app' env: INSTALL: '0' # CI 不要装到 /Applications,也不要 reset TCC + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: bash scripts/build-mac.sh # ── Windows:仅 Windows 跑通用 tauri build(Linux 用下方专用 step,避免重复构建)── - name: Build (Windows) if: matrix.platform == 'windows-latest' + shell: bash working-directory: 'openless-all/app' - run: npm run tauri build + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then + npm run tauri -- build --config '{"bundle":{"createUpdaterArtifacts":true}}' + else + npm run tauri build + fi # ── Linux:产 deb / rpm / AppImage ── - name: Build (Linux) if: matrix.platform == 'ubuntu-22.04' + shell: bash + working-directory: 'openless-all/app' + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then + npm run tauri -- build --bundles deb,rpm,appimage --config '{"bundle":{"createUpdaterArtifacts":true}}' + else + npm run tauri -- build --bundles deb,rpm,appimage + fi + + - name: Write updater manifest + if: env.TAURI_SIGNING_PRIVATE_KEY != '' + shell: bash working-directory: 'openless-all/app' - run: npm run tauri -- build --bundles deb,rpm,appimage + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + OPENLESS_UPDATE_TARGET: ${{ matrix.updater-target }} + OPENLESS_UPDATE_ARCH: ${{ matrix.updater-arch }} + OPENLESS_UPDATE_REPO: appergb/openless + run: node scripts/write-updater-manifest.mjs # ── 收集产物 ── - name: List artifacts (debug) @@ -206,6 +257,17 @@ jobs: openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg if-no-files-found: error + - name: Upload macOS updater artifacts + if: matrix.platform == 'macos-latest' && env.TAURI_SIGNING_PRIVATE_KEY != '' + uses: actions/upload-artifact@v4 + with: + name: openless-macos-arm64-updater + path: | + openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz + openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig + openless-all/app/src-tauri/target/release/bundle/latest-darwin-aarch64.json + if-no-files-found: error + - name: Upload Windows artifacts if: matrix.platform == 'windows-latest' uses: actions/upload-artifact@v4 @@ -216,6 +278,17 @@ jobs: openless-all/app/src-tauri/target/release/bundle/msi/*.msi if-no-files-found: error + - name: Upload Windows updater artifacts + if: matrix.platform == 'windows-latest' && env.TAURI_SIGNING_PRIVATE_KEY != '' + uses: actions/upload-artifact@v4 + with: + name: openless-windows-x64-updater + path: | + openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig + openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig + openless-all/app/src-tauri/target/release/bundle/latest-windows-x86_64.json + if-no-files-found: error + - name: Upload Linux artifacts if: matrix.platform == 'ubuntu-22.04' uses: actions/upload-artifact@v4 @@ -227,6 +300,16 @@ jobs: openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage if-no-files-found: error + - name: Upload Linux updater artifacts + if: matrix.platform == 'ubuntu-22.04' && env.TAURI_SIGNING_PRIVATE_KEY != '' + uses: actions/upload-artifact@v4 + with: + name: openless-linux-x64-updater + path: | + openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig + openless-all/app/src-tauri/target/release/bundle/latest-linux-x86_64.json + if-no-files-found: error + # ── tag 推送时,同步上传到 GitHub Release ── - name: Create / update release if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') @@ -239,8 +322,14 @@ jobs: generate_release_notes: true files: | openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg + openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz + openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig openless-all/app/src-tauri/target/release/bundle/nsis/*.exe + openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig openless-all/app/src-tauri/target/release/bundle/msi/*.msi + openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig openless-all/app/src-tauri/target/release/bundle/deb/*.deb openless-all/app/src-tauri/target/release/bundle/rpm/*.rpm openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage + openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig + openless-all/app/src-tauri/target/release/bundle/latest-*.json diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index d0164033..14f3ac57 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -1,15 +1,16 @@ { "name": "openless-app", - "version": "1.1.4", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openless-app", - "version": "1.1.4", + "version": "1.2.2", "dependencies": { "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-shell": "^2.0.1", + "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1349,6 +1350,15 @@ "@tauri-apps/api": "^2.10.1" } }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.1.tgz", + "integrity": "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/openless-all/app/package.json b/openless-all/app/package.json index a0f60200..6bdf51ec 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-shell": "^2.0.1", + "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/openless-all/app/scripts/build-mac.sh b/openless-all/app/scripts/build-mac.sh index 454fb049..63b7c94e 100755 --- a/openless-all/app/scripts/build-mac.sh +++ b/openless-all/app/scripts/build-mac.sh @@ -25,7 +25,11 @@ else fi echo "▶ tauri build" -npm run tauri build +TAURI_BUILD_ARGS=(build) +if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ] || [ -n "${TAURI_SIGNING_PRIVATE_KEY_PATH:-}" ]; then + TAURI_BUILD_ARGS+=(--config '{"bundle":{"createUpdaterArtifacts":true}}') +fi +npm run tauri -- "${TAURI_BUILD_ARGS[@]}" echo "▶ 校验 Info.plist / 签名" /usr/libexec/PlistBuddy -c "Print :NSMicrophoneUsageDescription" "$INFO" >/dev/null diff --git a/openless-all/app/scripts/write-updater-manifest.mjs b/openless-all/app/scripts/write-updater-manifest.mjs new file mode 100755 index 00000000..4b4e8a40 --- /dev/null +++ b/openless-all/app/scripts/write-updater-manifest.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { basename, join } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const target = process.env.OPENLESS_UPDATE_TARGET; +const arch = process.env.OPENLESS_UPDATE_ARCH; +const repo = process.env.OPENLESS_UPDATE_REPO || 'appergb/openless'; + +if (!target || !arch) { + throw new Error('OPENLESS_UPDATE_TARGET and OPENLESS_UPDATE_ARCH are required'); +} + +const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); +const bundleDir = fileURLToPath(new URL('../src-tauri/target/release/bundle/', import.meta.url)); + +const candidatesByTarget = { + darwin: ['macos/OpenLess.app.tar.gz'], + windows: ['nsis/OpenLess_*_x64-setup.exe', 'nsis/OpenLess*_x64-setup.exe'], + linux: ['appimage/OpenLess_*.AppImage', 'appimage/OpenLess*.AppImage'], +}; + +function findFirst(patterns) { + for (const pattern of patterns) { + if (!pattern.includes('*')) { + const path = join(bundleDir, pattern); + if (existsSync(path)) return path; + continue; + } + const [dir, namePattern] = pattern.split('/'); + const dirPath = join(bundleDir, dir); + if (!existsSync(dirPath)) continue; + const prefix = namePattern.split('*')[0]; + const suffix = namePattern.split('*').at(-1); + const match = readdirSync(dirPath) + .filter(name => name.startsWith(prefix) && name.endsWith(suffix)) + .sort()[0]; + if (match) return join(dirPath, match); + } +} + +const artifact = findFirst(candidatesByTarget[target] || []); +if (!artifact) { + throw new Error(`No updater artifact found for ${target} in ${bundleDir}`); +} + +const signaturePath = `${artifact}.sig`; +if (!existsSync(signaturePath)) { + throw new Error(`Missing updater signature: ${signaturePath}`); +} + +const assetName = basename(artifact); +const manifestName = `latest-${target}-${arch}.json`; +const manifest = { + version: packageJson.version, + pub_date: new Date().toISOString(), + url: `https://github.com/${repo}/releases/latest/download/${assetName}`, + signature: readFileSync(signaturePath, 'utf8').trim(), +}; + +writeFileSync(join(bundleDir, manifestName), `${JSON.stringify(manifest, null, 2)}\n`); +console.log(`Wrote ${manifestName} for ${assetName}`); diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index ca7dcf74..321e00d9 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -119,6 +119,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.6.1" @@ -858,7 +867,7 @@ dependencies = [ "core-foundation-sys 0.8.7", "coreaudio-rs", "dasp_sample", - "jni", + "jni 0.21.1", "js-sys", "libc", "mach2", @@ -1026,6 +1035,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1375,6 +1395,17 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2397,6 +2428,36 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -2560,7 +2621,10 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ + "bitflags 2.11.1", "libc", + "plain", + "redox_syscall 0.7.4", ] [[package]] @@ -2708,6 +2772,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3040,6 +3110,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.1", "block2 0.6.2", + "libc", "objc2 0.6.4", "objc2-core-foundation", ] @@ -3067,6 +3138,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3124,7 +3207,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" dependencies = [ - "jni", + "jni 0.21.1", "ndk 0.8.0", "ndk-context", "num-derive", @@ -3196,6 +3279,7 @@ dependencies = [ "tauri-build", "tauri-plugin-shell", "tauri-plugin-single-instance", + "tauri-plugin-updater", "thiserror 1.0.69", "tokio", "tokio-tungstenite", @@ -3237,6 +3321,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "pango" version = "0.18.3" @@ -3286,7 +3384,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -3513,6 +3611,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plist" version = "1.9.0" @@ -3927,6 +4031,15 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -4041,15 +4154,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -4139,6 +4257,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys 0.8.7", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -4562,6 +4707,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simplelog" version = "0.12.2" @@ -4622,7 +4783,7 @@ dependencies = [ "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", "raw-window-handle", - "redox_syscall", + "redox_syscall 0.5.18", "tracing", "wasm-bindgen", "web-sys", @@ -4805,7 +4966,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk 0.9.0", @@ -4837,6 +4998,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4860,7 +5032,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -5010,6 +5182,39 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-updater" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest 0.13.3", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.18", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.10.1" @@ -5020,7 +5225,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2 0.6.4", "objc2-ui-kit", "objc2-web-kit", @@ -5043,7 +5248,7 @@ checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2 0.6.4", "objc2-app-kit 0.3.2", @@ -5984,6 +6189,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.7" @@ -6773,7 +6987,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk 0.9.0", "objc2 0.6.4", @@ -6837,6 +7051,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xkbcommon" version = "0.7.0" @@ -7018,6 +7242,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.14.0", + "memchr", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index ae8797f9..4ac05434 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = ["macos-private-api", "tray-icon"] } tauri-plugin-shell = "2" +tauri-plugin-updater = "2" tauri-plugin-single-instance = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/openless-all/app/src-tauri/capabilities/default.json b/openless-all/app/src-tauri/capabilities/default.json index 3bcc3167..49dc8e6c 100644 --- a/openless-all/app/src-tauri/capabilities/default.json +++ b/openless-all/app/src-tauri/capabilities/default.json @@ -15,6 +15,7 @@ "core:window:allow-close", "core:webview:default", "core:event:default", - "shell:allow-open" + "shell:allow-open", + "updater:default" ] } diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 5ab0d7ce..1121287f 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -46,6 +46,7 @@ pub fn run() { show_main_window(app); })) .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .manage(coordinator.clone()) .setup(move |app| { // Capsule 启动时定位到屏幕底部居中并隐藏;coordinator 按需显示。 @@ -165,6 +166,7 @@ pub fn run() { commands::read_credential, commands::set_active_asr_provider, commands::set_active_llm_provider, + restart_app, ]) .build(tauri::generate_context!()) .expect("error while building tauri application") @@ -187,6 +189,11 @@ pub fn run() { }); } +#[tauri::command] +fn restart_app(app: AppHandle) { + app.restart(); +} + /// 把日志同时写到 stderr + ~/Library/Logs/OpenLess/openless.log(match Swift `Log.swift`)。 fn init_file_logger() { use simplelog::{ diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index 6b32efa0..af421d31 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -65,5 +65,13 @@ "infoPlist": "Info.plist", "entitlements": "Entitlements.plist" } + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFERUFBODAzNTY0QzMyM0YKUldRL01reFdBNmpxSGE1K0JadlpONXNWTzhJcGZCRGxjUVdIWExNNFJpeUNsSGZwazdlQThhemkK", + "endpoints": [ + "https://github.com/appergb/openless/releases/latest/download/latest-{{target}}-{{arch}}.json" + ] + } } } diff --git a/openless-all/app/src/components/SettingsModal.tsx b/openless-all/app/src/components/SettingsModal.tsx index a5def4ef..fe7a0f68 100644 --- a/openless-all/app/src/components/SettingsModal.tsx +++ b/openless-all/app/src/components/SettingsModal.tsx @@ -7,8 +7,7 @@ import { useEffect, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { Icon } from './Icon'; -import { APP_VERSION_LABEL } from '../lib/appVersion'; -import { Settings as SettingsContent, type SettingsSectionId } from '../pages/Settings'; +import { AboutUpdateControl, Settings as SettingsContent, type SettingsSectionId } from '../pages/Settings'; import { Row } from './ui/Row'; import { openExternal } from '../lib/ipc'; import { @@ -219,17 +218,9 @@ function AboutMini() {
OpenLess
-
{t('modal.about.tagline')} · {APP_VERSION_LABEL}
+
- - -