From 1da999a3d7016a14b4b2a47d69cd672cc70d84d7 Mon Sep 17 00:00:00 2001 From: Mathious6 <91007976+Mathious6@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:45:20 +0200 Subject: [PATCH 1/2] feat(updater): add signed in-app updates --- .github/workflows/release.yml | 58 ++++- AGENTS.md | 5 + README.md | 4 + SECURITY.md | 4 + bun.lock | 6 + package.json | 6 +- scripts/validate-release.sh | 41 ++++ src-tauri/Cargo.lock | 345 +++++++++++++++++++++++++++- src-tauri/Cargo.toml | 4 +- src-tauri/capabilities/default.json | 7 +- src-tauri/src/lib.rs | 2 + src-tauri/tauri.conf.json | 14 +- src/lib/UpdateControl.svelte | 89 +++++++ src/lib/update.ts | 34 +++ src/routes/+page.svelte | 9 +- 15 files changed, 614 insertions(+), 14 deletions(-) create mode 100755 scripts/validate-release.sh create mode 100644 src/lib/UpdateControl.svelte create mode 100644 src/lib/update.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5653f0..9cf4377 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,10 +7,15 @@ on: permissions: contents: write +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: release: strategy: fail-fast: false + max-parallel: 1 matrix: include: - os: macos-15 @@ -44,17 +49,31 @@ jobs: git fetch --no-tags --depth=1 origin main:refs/remotes/origin/main test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" + - name: verify updater signing configuration + shell: bash + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + test -n "$TAURI_SIGNING_PRIVATE_KEY" + test -n "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" + - name: build ad-hoc signed macOS release if: startsWith(matrix.os, 'macos') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_SIGNING_IDENTITY: "-" + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f with: tagName: v__VERSION__ releaseName: proxybench v__VERSION__ releaseBody: Preview builds made without certificates yet. macOS DMGs need right-click Open on first launch. The Windows NSIS setup wizard may trigger SmartScreen; choose More info, then Run anyway. - releaseDraft: false + releaseDraft: true + uploadUpdaterJson: true + uploadUpdaterSignatures: true + updaterJsonPreferNsis: true tauriScript: bun run tauri args: ${{ matrix.args }} @@ -63,10 +82,45 @@ jobs: uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f 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: v__VERSION__ releaseName: proxybench v__VERSION__ releaseBody: Preview builds made without certificates yet. macOS DMGs need right-click Open on first launch. The Windows NSIS setup wizard may trigger SmartScreen; choose More info, then Run anyway. - releaseDraft: false + releaseDraft: true + uploadUpdaterJson: true + uploadUpdaterSignatures: true + updaterJsonPreferNsis: true tauriScript: bun run tauri args: ${{ matrix.args }} + + publish: + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + + - name: install minisign + run: sudo apt-get update && sudo apt-get install -y minisign + + - name: validate updater release + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + RELEASE_ID=$(gh api --paginate "repos/$REPO/releases?per_page=100" --jq ".[] | select(.tag_name == \"$TAG\" and .draft == true) | .id" | head -n 1) + test -n "$RELEASE_ID" + export RELEASE_ID + sh scripts/validate-release.sh + + - name: publish release + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + RELEASE_ID=$(gh api --paginate "repos/$REPO/releases?per_page=100" --jq ".[] | select(.tag_name == \"$TAG\" and .draft == true) | .id" | head -n 1) + test -n "$RELEASE_ID" + gh api -X PATCH "repos/$REPO/releases/$RELEASE_ID" -F draft=false diff --git a/AGENTS.md b/AGENTS.md index ecc9ac0..91dfca4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -121,3 +121,8 @@ GitHub Actions (`.github/workflows/ci.yml` on `main` and pull requests, `.github/workflows/release.yml` on `vX.Y.Z`) builds macOS DMGs for Apple Silicon and Intel and a Windows NSIS setup wizard. macOS uses ad-hoc signing; Windows installers are unsigned. + +Updater releases require `TAURI_SIGNING_PRIVATE_KEY` and +`TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. The workflow keeps releases as drafts +until `latest.json` contains macOS arm64, macOS x64, and Windows x64. Never +replace published assets; ship a higher patch version. diff --git a/README.md b/README.md index 654fff3..d46ffff 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ usually already have it. macOS builds use ad-hoc signing and are not notarized. Windows installers are unsigned. +From the first updater-enabled release onward, use **Check for updates** in the +bottom bar to download and install newer versions. Existing `0.2.x` installs +need one final manual download before in-app updates become available. + ## Use 1. Drop a `.txt` file or a folder of `.txt` files onto the window, or click diff --git a/SECURITY.md b/SECURITY.md index 5555199..ec656a2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,3 +13,7 @@ without real proxy credentials. Please allow a reasonable amount of time for investigation before public disclosure. + +Application updates are signed and verified independently from operating +system code signing. Report suspected update or release-key compromise through +GitHub's private vulnerability reporting. diff --git a/bun.lock b/bun.lock index 1f0ca53..c2b613a 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,8 @@ "@tanstack/svelte-table": "^9.1.2", "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-process": "2", + "@tauri-apps/plugin-updater": "2.10.1", }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.6", @@ -221,6 +223,10 @@ "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.2", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg=="], + "@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="], + + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.1", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA=="], + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], diff --git a/package.json b/package.json index 92d5623..df1801f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxybench", - "version": "0.2.1", + "version": "0.3.0", "description": "Split HTTP proxy lists by subnet and measure Connect and TTFB", "type": "module", "scripts": { @@ -15,7 +15,9 @@ "dependencies": { "@tanstack/svelte-table": "^9.1.2", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2" + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-process": "2", + "@tauri-apps/plugin-updater": "2.10.1" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.6", diff --git a/scripts/validate-release.sh b/scripts/validate-release.sh new file mode 100755 index 0000000..54cb499 --- /dev/null +++ b/scripts/validate-release.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -eu + +repo="${REPO:?REPO is required}" +tag="${TAG:?TAG is required}" +release_id="${RELEASE_ID:?RELEASE_ID is required}" +release="$(gh api "repos/$repo/releases/$release_id")" + +test "$(printf '%s' "$release" | jq -r .draft)" = true +test "$(printf '%s' "$release" | jq -r .tag_name)" = "$tag" +assets="$(printf '%s' "$release" | jq -r '.assets[].name')" +test "$(printf '%s\n' "$assets" | grep -c '\.dmg$')" -eq 2 +test "$(printf '%s\n' "$assets" | grep -c '\.app\.tar\.gz$')" -eq 2 +test "$(printf '%s\n' "$assets" | grep -c '\.app\.tar\.gz\.sig$')" -eq 2 +test "$(printf '%s\n' "$assets" | grep -c -- '-setup\.exe$')" -eq 1 +test "$(printf '%s\n' "$assets" | grep -c -- '-setup\.exe\.sig$')" -eq 1 +test "$(printf '%s\n' "$assets" | grep -c '^latest\.json$')" -eq 1 + +latest_url="$(printf '%s' "$release" | jq -r '.assets[] | select(.name == "latest.json") | .url')" +gh api -H 'Accept: application/octet-stream' "$latest_url" > latest.json +test "$(jq -r .version latest.json)" = "${tag#v}" + +for platform in darwin-aarch64 darwin-x86_64 windows-x86_64; do + url="$(jq -er --arg platform "$platform" '.platforms[$platform].url' latest.json)" + signature="$(jq -er --arg platform "$platform" '.platforms[$platform].signature' latest.json)" + test -n "$signature" + asset="$(printf '%s' "$release" | jq -cer --arg url "$url" '.assets[] | select(.url == $url or .browser_download_url == $url)')" + name="$(printf '%s' "$asset" | jq -r .name)" + case "$platform:$name" in + darwin-aarch64:*_aarch64.app.tar.gz) ;; + darwin-x86_64:*_x64.app.tar.gz) ;; + windows-x86_64:*_x64-setup.exe) ;; + *) exit 1 ;; + esac + asset_url="$(printf '%s' "$asset" | jq -r .url)" + gh api -H 'Accept: application/octet-stream' "$asset_url" > "$name" + printf '%s' "$signature" | base64 --decode > "$name.sig" + public_key="$(jq -r '.plugins.updater.pubkey' src-tauri/tauri.conf.json)" + printf '%s' "$public_key" | base64 --decode > updater.pub + minisign -Vm "$name" -p updater.pub -x "$name.sig" +done diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 160a1b0..b3624b0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -47,6 +47,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 = "atk" version = "0.18.2" @@ -548,6 +557,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.119", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -749,6 +769,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "fastrand" version = "2.5.0" @@ -774,6 +804,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.11" @@ -1299,6 +1339,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" @@ -1602,6 +1657,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.20", + "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" @@ -1732,6 +1817,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.3" @@ -1785,6 +1876,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" @@ -2033,6 +2130,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" @@ -2096,12 +2205,32 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[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.20", +] + [[package]] name = "pango" version = "0.18.3" @@ -2370,7 +2499,7 @@ dependencies = [ [[package]] name = "proxybench" -version = "0.2.1" +version = "0.3.0" dependencies = [ "base64 0.22.1", "bytes", @@ -2385,6 +2514,8 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-dialog", + "tauri-plugin-process", + "tauri-plugin-updater", "tokio", "tokio-rustls", "ureq", @@ -2524,15 +2655,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", @@ -2597,6 +2733,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.43" @@ -2612,6 +2761,18 @@ dependencies = [ "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" @@ -2621,6 +2782,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", + "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.15" @@ -2647,6 +2835,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" @@ -2704,6 +2901,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" @@ -2915,6 +3135,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" @@ -3127,7 +3363,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -3160,6 +3396,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" @@ -3183,7 +3430,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -3337,6 +3584,48 @@ dependencies = [ "url", ] +[[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 = "git+https://github.com/tauri-apps/plugins-workspace?rev=71df1a095d007fb94f0eb07940b0a78e57ac984e#71df1a095d007fb94f0eb07940b0a78e57ac984e" +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.20", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.11.3" @@ -3347,7 +3636,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -3370,7 +3659,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -3437,6 +3726,19 @@ dependencies = [ "toml 1.1.4+spec-1.1.0", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "tendril" version = "0.5.1" @@ -4134,6 +4436,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 = "webpki-roots" version = "0.26.11" @@ -4686,7 +4997,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -4733,6 +5044,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 = "yasna" version = "0.5.2" @@ -4825,6 +5146,18 @@ dependencies = [ "syn 3.0.4", ] +[[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/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 07effca..01aaee2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proxybench" -version = "0.2.1" +version = "0.3.0" description = "Split HTTP proxy lists by subnet and measure Connect and TTFB" edition = "2021" license = "MIT" @@ -29,6 +29,8 @@ rustls = { version = "0.23", default-features = false, features = ["std", "ring" tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } webpki-roots = "1" rustls-pki-types = "1" +tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", rev = "71df1a095d007fb94f0eb07940b0a78e57ac984e", version = "2.10.1" } +tauri-plugin-process = "2" [dev-dependencies] rcgen = "0.13" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index cf20a06..2f0cd5a 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -3,5 +3,10 @@ "identifier": "default", "description": "Capability for the main window", "windows": ["main"], - "permissions": ["core:default", "dialog:default"] + "permissions": [ + "core:default", + "dialog:default", + "updater:default", + "process:allow-restart" + ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1a4b7d2..6964fb9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -22,6 +22,8 @@ use tauri::Manager; pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_process::init()) .setup(|app| { let store = import::open_store(app.handle())?; let inventory = import::open_inventory(app.handle())?; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index baa3040..975adc3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "proxybench", - "version": "0.2.1", + "version": "0.3.0", "identifier": "com.mathious.proxybench", "build": { "beforeDevCommand": "bun run dev", @@ -27,6 +27,7 @@ "bundle": { "active": true, "targets": "all", + "createUpdaterArtifacts": true, "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -34,5 +35,16 @@ "icons/icon.icns", "icons/icon.ico" ] + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU2NjE2NjEzQzM1QkVFQzQKUldURTdsdkRFMlpoNXJDQ2VicXNvUzdidi9IYWNKL2hmUkZrNEZ5OW5ESDJxb1h1QmFLYm5wdFYK", + "endpoints": [ + "https://github.com/Mathious6/proxybench/releases/latest/download/latest.json" + ], + "windows": { + "installMode": "passive" + } + } } } diff --git a/src/lib/UpdateControl.svelte b/src/lib/UpdateControl.svelte new file mode 100644 index 0000000..096c5de --- /dev/null +++ b/src/lib/UpdateControl.svelte @@ -0,0 +1,89 @@ + + +{#if update && !installing} + +{:else if installing} + {progressLabel} +{:else} + +{/if} diff --git a/src/lib/update.ts b/src/lib/update.ts new file mode 100644 index 0000000..645aaf3 --- /dev/null +++ b/src/lib/update.ts @@ -0,0 +1,34 @@ +import { relaunch } from "@tauri-apps/plugin-process"; +import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater"; + +export type UpdateProgress = { + downloaded: number; + total: number | null; +}; + +export async function checkForUpdate(): Promise { + return check({ timeout: 30_000 }); +} + +export async function installUpdate( + update: Update, + onProgress: (progress: UpdateProgress) => void, +): Promise { + let downloaded = 0; + let total: number | null = null; + await update.downloadAndInstall( + (event: DownloadEvent) => { + if (event.event === "Started") { + total = event.data.contentLength ?? null; + downloaded = 0; + } else if (event.event === "Progress") { + downloaded += event.data.chunkLength; + } else { + downloaded = total ?? downloaded; + } + onProgress({ downloaded, total }); + }, + { timeout: 10 * 60 * 1000 }, + ); + await relaunch(); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5c1f940..4a5bc70 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -8,6 +8,7 @@ import type { ImportResult, Progress, RunResult, SubnetRow } from "$lib/import"; import { emptyMetrics, withMetrics } from "$lib/import"; import SubnetTable from "$lib/SubnetTable.svelte"; + import UpdateControl from "$lib/UpdateControl.svelte"; let rows = $state([]); let hovering = $state(false); @@ -28,6 +29,7 @@ let probeCidr = $state(null); let pageReset = $state(0); let version = $state(""); + let updating = $state(false); let paging = $state({ label: "0 of 0", canPrevious: false, @@ -37,7 +39,7 @@ }); let noticeTimer: ReturnType | null = null; - const locked = $derived(busy || running); + const locked = $derived(busy || running || updating); function showNotice(message: string, isError: boolean) { if (noticeTimer) { @@ -509,6 +511,11 @@ {/if}