diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index c33c1a5..82616a1 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -6,6 +6,15 @@ on: tag: required: true type: string + secrets: + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + GPG_PRIVATE_KEY: + required: false + GPG_PASSPHRASE: + required: false workflow_dispatch: inputs: tag: @@ -19,6 +28,9 @@ permissions: jobs: build: runs-on: macos-latest + env: + HAS_TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY != '' }} + HAS_GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY != '' }} steps: - name: Checkout @@ -55,14 +67,54 @@ jobs: - name: Lint run: pnpm lint + - name: Check updater signing key + if: env.HAS_TAURI_SIGNING_PRIVATE_KEY != 'true' + run: | + echo "TAURI_SIGNING_PRIVATE_KEY is required because bundle.createUpdaterArtifacts is enabled." + exit 1 + - name: Build and upload unsigned DMG 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 }} with: tagName: ${{ inputs.tag }} releaseName: 'AISync ${{ inputs.tag }}' - releaseBody: 'Unsigned macOS DMG. macOS Gatekeeper may warn on first open.' + releaseBody: 'Unsigned macOS DMG with Tauri updater signatures. macOS Gatekeeper may warn on first open.' releaseDraft: false prerelease: false args: --target universal-apple-darwin + + - name: Create SHA256 checksums + run: | + find src-tauri/target/universal-apple-darwin/release/bundle -type f \( -name '*.dmg' -o -name '*.tar.gz' -o -name '*.sig' -o -name 'latest.json' \) -print0 \ + | sort -z \ + | xargs -0 shasum -a 256 \ + > SHASUMS256.txt + + - name: Import GPG key + if: env.HAS_GPG_PRIVATE_KEY == 'true' + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + run: | + printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import + + - name: Sign SHA256 checksums + if: env.HAS_GPG_PRIVATE_KEY == 'true' + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \ + --armor --detach-sign --output SHASUMS256.txt.asc SHASUMS256.txt + + - name: Upload verification assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + assets=(SHASUMS256.txt) + if [ -f SHASUMS256.txt.asc ]; then + assets+=(SHASUMS256.txt.asc) + fi + gh release upload "${{ inputs.tag }}" "${assets[@]}" --clobber diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 746b8cc..022d214 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,3 +67,4 @@ jobs: uses: ./.github/workflows/build-macos.yml with: tag: ${{ inputs.tag || needs.release-please.outputs.tag_name }} + secrets: inherit diff --git a/Caskfile b/Caskfile new file mode 100644 index 0000000..5dbbf07 --- /dev/null +++ b/Caskfile @@ -0,0 +1,12 @@ +cask "aisync" do + version "0.3.0" # x-release-please-version + sha256 :no_check + + url "https://github.com/mohammedhammoud/aisync/releases/download/aisync-v#{version}/AISync_#{version}_universal.dmg", + verified: "github.com/mohammedhammoud/aisync/" + name "AISync" + desc "Local desktop utility for AI provider, skill, instruction, and sync management" + homepage "https://github.com/mohammedhammoud/aisync" + + app "AISync.app" +end diff --git a/README.md b/README.md index 03b1fc0..1a7aa25 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ If that still does not work, remove the quarantine flag manually. Only do this f xattr -dr com.apple.quarantine /Applications/AISync.app ``` -The app checks the latest GitHub Release on startup and shows an in-app notification when a newer stable version is available. +The app checks for updates on startup and can install signed Tauri updater releases in-app. If updater metadata is unavailable, the notice falls back to the GitHub Release page. ## Quick start @@ -139,13 +139,15 @@ pnpm tauri build ## Release -The `Release` workflow runs Release Please from Conventional Commits on `main`. It opens a release PR; merging that PR updates versions and changelog, creates the `v*.*.*` tag, then builds an unsigned universal macOS DMG. +The `Release` workflow runs Release Please from Conventional Commits on `main`. It opens a release PR; merging that PR updates versions and changelog, creates the `aisync-v*.*.*` tag, then builds an unsigned universal macOS DMG plus Tauri updater artifacts. The same workflow can also be run manually with an existing tag to rebuild/upload the DMG. No Apple Developer Program is required for this workflow. macOS Gatekeeper may warn users on first open because the app is not signed or notarized. -The release build uploads the DMG to the GitHub Release. +The release build uploads the DMG, updater metadata, SHA256 checksums, and a detached GPG signature for the checksum file when `GPG_PRIVATE_KEY` is configured. + +Updater signing uses the public key in `src-tauri/tauri.conf.json`. Keep the private key outside the repo and configure `TAURI_SIGNING_PRIVATE_KEY` in GitHub Actions secrets. ## Quality checks diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 0874a83..fa91c34 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -12,6 +12,8 @@ Open the DMG, drag `AISync.app` to `Applications`, then launch it. AISync is currently unsigned. If macOS blocks the first launch, right-click `AISync.app`, choose `Open`, then confirm. +After installation, AISync can install signed in-app updates when release updater metadata is available. + ## 2. Choose the AISync folder By default, AISync stores its source files in: diff --git a/package.json b/package.json index 0f0d890..9ac6436 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "@tanstack/react-router": "^1.169.2", "@tauri-apps/api": "^2", "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-process": "~2.3.1", + "@tauri-apps/plugin-updater": "~2.10.1", "date-fns": "^4.1.0", "diff": "^9.0.0", "i18next": "^26.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4128a94..3228d13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,12 @@ importers: '@tauri-apps/plugin-opener': specifier: ^2 version: 2.5.4 + '@tauri-apps/plugin-process': + specifier: ~2.3.1 + version: 2.3.1 + '@tauri-apps/plugin-updater': + specifier: ~2.10.1 + version: 2.10.1 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -1334,6 +1340,12 @@ packages: '@tauri-apps/plugin-opener@2.5.4': resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==} + '@tauri-apps/plugin-process@2.3.1': + resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==} + + '@tauri-apps/plugin-updater@2.10.1': + resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -4383,6 +4395,14 @@ snapshots: dependencies: '@tauri-apps/api': 2.11.0 + '@tauri-apps/plugin-process@2.3.1': + dependencies: + '@tauri-apps/api': 2.11.0 + + '@tauri-apps/plugin-updater@2.10.1': + dependencies: + '@tauri-apps/api': 2.11.0 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 diff --git a/release-please-config.json b/release-please-config.json index 2591e8a..a2aabd3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -14,6 +14,10 @@ "type": "toml", "path": "src-tauri/Cargo.toml", "jsonpath": "$.package.version" + }, + { + "type": "generic", + "path": "Caskfile" } ] } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 604c22a..f57b964 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -38,6 +38,8 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-opener", + "tauri-plugin-process", + "tauri-plugin-updater", "tauri-specta", "uuid", ] @@ -72,6 +74,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 = "async-broadcast" version = "0.7.2" @@ -705,6 +716,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 = "2.1.1" @@ -989,6 +1011,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1845,6 +1877,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" @@ -2059,6 +2121,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[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" @@ -2291,6 +2359,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.1", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2306,6 +2375,18 @@ dependencies = [ "objc2-core-foundation", ] +[[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", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2381,6 +2462,12 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" @@ -2397,6 +2484,20 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "pango" version = "0.18.3" @@ -2761,7 +2862,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2943,15 +3044,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", @@ -3019,6 +3125,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -3029,6 +3147,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", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "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" @@ -3061,6 +3206,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3385,6 +3539,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 = "siphasher" version = "1.0.3" @@ -3637,7 +3807,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -3670,6 +3840,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -3693,7 +3874,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -3828,6 +4009,49 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[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.11.1" @@ -3838,7 +4062,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -3861,7 +4085,7 @@ checksum = "a3989df2ae1c476404fe0a2e8ffc4cfbde97e51efd613c2bb5355fbc9ab52cf0" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -4715,6 +4939,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" @@ -4972,6 +5205,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -5005,13 +5247,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -5042,6 +5301,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5054,6 +5319,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -5066,12 +5337,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -5084,6 +5367,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -5096,6 +5385,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -5108,6 +5403,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -5120,6 +5421,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -5272,7 +5579,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -5319,6 +5626,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.2" @@ -5483,6 +5800,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/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b4189b0..ac09ade 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -31,3 +31,7 @@ uuid = { version = "1", features = ["v4", "serde"] } specta = { version = "2.0.0-rc.25", features = ["derive"] } tauri-specta = { version = "2.0.0-rc.25", features = ["javascript", "typescript"] } specta-typescript = "0.0.12" +tauri-plugin-process = "2" + +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-updater = "2" diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json new file mode 100644 index 0000000..47424fa --- /dev/null +++ b/src-tauri/capabilities/desktop.json @@ -0,0 +1,6 @@ +{ + "identifier": "desktop-capability", + "platforms": ["macOS", "windows", "linux"], + "windows": ["main"], + "permissions": ["updater:default"] +} diff --git a/src-tauri/src/core/github/auth/keychain.rs b/src-tauri/src/core/github/auth/keychain.rs index ce3cc08..ae8e02e 100644 --- a/src-tauri/src/core/github/auth/keychain.rs +++ b/src-tauri/src/core/github/auth/keychain.rs @@ -34,9 +34,9 @@ fn token_entry() -> AppResult { } pub fn save_token(token: &str) -> AppResult<()> { - token_entry()? - .set_password(token) - .map_err(|_error| AppError::new(GithubErrorCode::KeychainFailed, "Keychain access failed"))?; + token_entry()?.set_password(token).map_err(|_error| { + AppError::new(GithubErrorCode::KeychainFailed, "Keychain access failed") + })?; cache_token(Some(token.to_string())) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index abc340b..5b87302 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -58,6 +58,8 @@ pub fn run() { } tauri::Builder::default() + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_opener::init()) .setup(|app| { core::github::events::init(app.handle().clone()); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7efd2b3..ef055b3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -31,10 +31,8 @@ }, "bundle": { "active": true, - "targets": [ - "app", - "dmg" - ], + "createUpdaterArtifacts": true, + "targets": ["app", "dmg"], "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -42,5 +40,13 @@ "icons/icon.icns", "icons/icon.ico" ] + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDU2MzhCMEY3REYyMTYzMjQKUldRa1l5SGY5N0E0Vm9tQmd3MVlEY3JHVi9UbUZ2MTh0SktUdEE4Y3NjUDFnRVR2djBnbE9wQUgK", + "endpoints": [ + "https://github.com/mohammedhammoud/aisync/releases/latest/download/latest.json" + ] + } } } diff --git a/src/base/i18n/locales/en.json b/src/base/i18n/locales/en.json index ea6e129..25898cd 100644 --- a/src/base/i18n/locales/en.json +++ b/src/base/i18n/locales/en.json @@ -125,8 +125,8 @@ "updates": { "available": "AISync {{version}} is available.", "availableShort": "Update available", - "download": "Download", - "downloadHint": "Download the latest DMG from GitHub Releases." + "download": "Install", + "downloadHint": "Install the signed update. Falls back to GitHub Releases if needed." }, "dirty": { "unsaved": "Unsaved, save or discard.", diff --git a/src/base/i18n/locales/sv.json b/src/base/i18n/locales/sv.json index 8ad6a94..85e4fb2 100644 --- a/src/base/i18n/locales/sv.json +++ b/src/base/i18n/locales/sv.json @@ -125,8 +125,8 @@ "updates": { "available": "AISync {{version}} finns tillgänglig.", "availableShort": "Uppdatering finns", - "download": "Ladda ner", - "downloadHint": "Ladda ner senaste DMG från GitHub Releases." + "download": "Installera", + "downloadHint": "Installerar den signerade uppdateringen. Faller tillbaka till GitHub Releases vid behov." }, "dirty": { "unsaved": "Osparat, spara eller förkasta.", diff --git a/src/features/settings/views/SettingsView.tsx b/src/features/settings/views/SettingsView.tsx index 83d5fca..20805dd 100644 --- a/src/features/settings/views/SettingsView.tsx +++ b/src/features/settings/views/SettingsView.tsx @@ -1,5 +1,7 @@ import { createLazyRoute } from "@tanstack/react-router"; import { openUrl } from "@tauri-apps/plugin-opener"; +import { relaunch } from "@tauri-apps/plugin-process"; +import { check } from "@tauri-apps/plugin-updater"; import { useLanguage } from "@/base/i18n/hooks/useLanguage"; import { useGlobalsStore } from "@/base/store/globalsStore"; import { useTheme } from "@/ui/theme/useTheme"; @@ -16,11 +18,26 @@ function SettingsView() { const { changeLanguage, language } = useLanguage(); const { colorScheme, setColorScheme } = useTheme(); + async function installUpdate(downloadUrl: string) { + try { + const update = await check(); + if (!update) { + await openUrl(downloadUrl); + return; + } + + await update.downloadAndInstall(); + await relaunch(); + } catch { + await openUrl(downloadUrl); + } + } + return (
{availableUpdate ? ( void openUrl(availableUpdate.downloadUrl)} + onDownload={() => void installUpdate(availableUpdate.downloadUrl)} version={availableUpdate.version} /> ) : null}