diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..91d4acf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release + +on: + workflow_dispatch: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release-windows: + environment: release + runs-on: windows-latest + + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable + with: + targets: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + with: + workspaces: ". -> target" + + - name: Validate release ref and version + shell: pwsh + run: | + $manifest = Get-Content Cargo.toml -Raw + if ($manifest -notmatch '(?ms)^\[workspace\.package\]\s*.*?^version\s*=\s*"([^"]+)"') { + throw "Unable to read the workspace version from Cargo.toml." + } + $version = $Matches[1] + $tauriVersion = (Get-Content src-tauri\tauri.conf.json -Raw | ConvertFrom-Json).version + if ($version -ne $tauriVersion) { + throw "Cargo.toml version '$version' does not match tauri.conf.json version '$tauriVersion'." + } + + if ($env:GITHUB_EVENT_NAME -eq "push") { + $expectedTag = "v$version" + if ($env:GITHUB_REF_NAME -ne $expectedTag) { + throw "Release tag '$env:GITHUB_REF_NAME' must match application version '$expectedTag'." + } + git merge-base --is-ancestor $env:GITHUB_SHA origin/master + if ($LASTEXITCODE -ne 0) { + throw "Release tag '$env:GITHUB_REF_NAME' must point to a commit on master." + } + } elseif ($env:GITHUB_REF_NAME -ne "master") { + throw "Manual releases must run from the master branch." + } + + - name: Validate updater signing configuration + shell: pwsh + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_UPDATER_PUBLIC_KEY }} + run: | + if (-not $env:TAURI_SIGNING_PRIVATE_KEY) { + throw "Release environment secret TAURI_SIGNING_PRIVATE_KEY is required." + } + if (-not $env:TAURI_UPDATER_PUBLIC_KEY) { + throw "Repository variable TAURI_UPDATER_PUBLIC_KEY is required." + } + + - name: Prepare toolchain and bundled Everything runtime + shell: pwsh + run: .\scripts\setup.ps1 -SkipFormat + + - name: Build and publish GitHub release + uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # action-v1.0.0 + 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 }} + TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_UPDATER_PUBLIC_KEY }} + with: + tagName: v__VERSION__ + releaseName: "Everything Next v__VERSION__" + generateReleaseNotes: true + releaseDraft: false + prerelease: false + uploadUpdaterJson: true + updaterJsonPreferNsis: true + uploadUpdaterSignatures: true + args: --config src-tauri/tauri.release.conf.json diff --git a/Cargo.lock b/Cargo.lock index 78c8fe9..8e5dc6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,15 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[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" @@ -849,6 +858,17 @@ dependencies = [ "syn 2.0.119", ] +[[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.119", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1167,6 +1187,7 @@ dependencies = [ "tauri-build", "tauri-plugin-dialog", "tauri-plugin-single-instance", + "tauri-plugin-updater", "tauri-plugin-window-state", "tokio", "windows-shell", @@ -1214,6 +1235,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" @@ -1848,6 +1879,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -2144,6 +2190,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.19", + "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.119", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -2511,6 +2587,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" @@ -2775,6 +2857,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.13.1", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2858,6 +2952,12 @@ dependencies = [ "libc", ] +[[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" @@ -2880,6 +2980,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.19", +] + [[package]] name = "pango" version = "0.18.3" @@ -3441,15 +3555,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", @@ -3485,6 +3604,20 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rstml" version = "0.12.1" @@ -3528,6 +3661,79 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +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", + "core-foundation-sys", + "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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -3543,6 +3749,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" @@ -3600,6 +3815,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.36.1" @@ -3909,6 +4147,22 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +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" @@ -4030,6 +4284,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -4168,7 +4428,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -4201,6 +4461,17 @@ dependencies = [ "syn 2.0.119", ] +[[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" @@ -4224,7 +4495,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -4394,6 +4665,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", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.19", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-plugin-window-state" version = "2.4.1" @@ -4419,7 +4723,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -4442,7 +4746,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -4649,6 +4953,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -5006,6 +5320,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -5277,6 +5597,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.2" @@ -5617,6 +5946,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5921,7 +6259,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -5968,6 +6306,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 = "xxhash-rust" version = "0.8.18" @@ -6085,6 +6433,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" @@ -6118,6 +6472,18 @@ dependencies = [ "syn 2.0.119", ] +[[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.23" diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..83faf4d --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,40 @@ +# Releasing Everything Next + +GitHub Releases are also the update feed used by installed release builds. + +## One-time updater signing setup + +Generate a Tauri updater signing key pair on a trusted machine: + +```powershell +cargo tauri signer generate -w "$HOME\.tauri\everything-next.key" +``` + +Keep the private key out of the repository. + +Create a GitHub Actions environment named `release` with: + +- required approval before deployment; +- deployment restricted to the `master` branch and `v*` tags. + +Configure: + +- Environment secret `TAURI_SIGNING_PRIVATE_KEY`: the private key contents (or a value accepted by the Tauri CLI). +- Environment secret `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`: the key password, if one was configured. +- Repository variable `TAURI_UPDATER_PUBLIC_KEY`: the complete public key printed/generated by the signer command. + +The public key is compiled into release builds through `TAURI_UPDATER_PUBLIC_KEY`. Local builds that do not define it simply skip update checks. + +## Publishing + +1. Update the application version in both `Cargo.toml` workspace metadata and `src-tauri/tauri.conf.json`. +2. Run `./scripts/check.ps1`. +3. Run the **Release** GitHub Actions workflow from `master`, or push the matching `v` tag from a commit on `master`. + +The workflow builds the Windows NSIS installer, signs the updater artifact with the Tauri private key, creates the GitHub Release, uploads the `.sig` file, and generates `latest.json` for the updater endpoint. + +Installed release builds query: + +`https://github.com/StephanOrgiazzi/EverythingNext/releases/latest/download/latest.json` + +When a newer version is available, Everything Next offers **Install** or **Later**. The updater uses the NSIS installer; on Windows the app exits before installation, and the existing installer hooks stop/recreate the private Everything service during the upgrade. diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9040579..aedb3c9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,6 +17,7 @@ everything-engine = { path = "../crates/everything-engine" } tauri = { version = "2.11.5", features = ["tray-icon"] } tauri-plugin-dialog = "2" tauri-plugin-single-instance = "2.4.3" +tauri-plugin-updater = "2.10.1" tauri-plugin-window-state = "2.4.1" tokio = { version = "1", features = ["sync"] } windows-shell = { path = "../crates/windows-shell" } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d4fd057..5a494f1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,6 +5,7 @@ mod desktop; mod search; mod shell_commands; mod trash; +mod updates; use desktop::{take_pending_search_query, LaunchState}; use search::{begin_search_generation, engine_status, search_everything, SearchState}; @@ -33,11 +34,13 @@ fn main() -> tauri::Result<()> { } if !desktop::string_args_include_autostart(&args) { desktop::show_main_window(app); + updates::check_on_user_launch(app.clone()); } })); builder .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .plugin( tauri_plugin_window_state::Builder::default() .with_state_flags(StateFlags::all().difference(StateFlags::VISIBLE)) @@ -57,6 +60,7 @@ fn main() -> tauri::Result<()> { if !autostart_launch { desktop::show_main_window(app.handle()); + updates::check_on_user_launch(app.handle().clone()); } Ok(()) }) diff --git a/src-tauri/src/updates.rs b/src-tauri/src/updates.rs new file mode 100644 index 0000000..25bf245 --- /dev/null +++ b/src-tauri/src/updates.rs @@ -0,0 +1,72 @@ +#![cfg(windows)] + +use std::sync::atomic::{AtomicBool, Ordering}; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind}; +use tauri_plugin_updater::UpdaterExt; + +const UPDATE_ENDPOINT: &str = + "https://github.com/StephanOrgiazzi/EverythingNext/releases/latest/download/latest.json"; +const UPDATER_PUBLIC_KEY: Option<&str> = option_env!("TAURI_UPDATER_PUBLIC_KEY"); +static UPDATE_CHECK_STARTED: AtomicBool = AtomicBool::new(false); + +pub fn check_on_user_launch(app: tauri::AppHandle) { + if UPDATER_PUBLIC_KEY.is_none() || UPDATE_CHECK_STARTED.swap(true, Ordering::AcqRel) { + return; + } + + tauri::async_runtime::spawn(async move { + if let Err(error) = check_and_offer_update(app).await { + eprintln!("Unable to check for Everything Next updates: {error}"); + } + }); +} + +async fn check_and_offer_update( + app: tauri::AppHandle, +) -> Result<(), Box> { + let Some(public_key) = UPDATER_PUBLIC_KEY else { + return Ok(()); + }; + + let endpoint = UPDATE_ENDPOINT.parse()?; + let updater = app + .updater_builder() + .pubkey(public_key) + .endpoints(vec![endpoint])? + .build()?; + + let Some(update) = updater.check().await? else { + return Ok(()); + }; + + let version = update.version.clone(); + let should_install = app + .dialog() + .message(format!( + "Everything Next {version} is available. Install it now?" + )) + .title("Everything Next update") + .buttons(MessageDialogButtons::OkCancelCustom( + "Install".into(), + "Later".into(), + )) + .blocking_show(); + + if !should_install { + return Ok(()); + } + + if let Err(error) = update.download_and_install(|_, _| {}, || {}).await { + eprintln!("Unable to install the Everything Next update: {error}"); + app.dialog() + .message(format!( + "The update could not be installed. Please try again later.\n\n{error}" + )) + .title("Everything Next update failed") + .kind(MessageDialogKind::Error) + .buttons(MessageDialogButtons::Ok) + .blocking_show(); + } + + Ok(()) +} diff --git a/src-tauri/tauri.release.conf.json b/src-tauri/tauri.release.conf.json new file mode 100644 index 0000000..d50663d --- /dev/null +++ b/src-tauri/tauri.release.conf.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "bundle": { + "createUpdaterArtifacts": true + } +}