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() {