diff --git a/.github/workflows/desktop-beta.yml b/.github/workflows/desktop-beta.yml deleted file mode 100644 index 8fc0a1b8c..000000000 --- a/.github/workflows/desktop-beta.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Desktop beta -on: - push: - branches: 'dev' - workflow_dispatch: - -concurrency: - group: desktop-beta-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - strategy: - fail-fast: false - matrix: - include: - - platform: macos-latest - rust_targets: aarch64-apple-darwin,x86_64-apple-darwin,wasm32-unknown-unknown - args: --target universal-apple-darwin - bundles: target/universal-apple-darwin/release/bundle/dmg/*.dmg - # the oldest supported Ubuntu, so that the AppImage runs on older systems too - - platform: ubuntu-22.04 - rust_targets: wasm32-unknown-unknown - args: '' - bundles: | - target/release/bundle/deb/*.deb - target/release/bundle/rpm/*.rpm - target/release/bundle/appimage/*.AppImage - - platform: windows-latest - rust_targets: wasm32-unknown-unknown - args: '' - bundles: | - target/release/bundle/msi/*.msi - target/release/bundle/nsis/*.exe - - runs-on: ${{ matrix.platform }} - - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - - - name: Install Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: npm - cache-dependency-path: website/package-lock.json - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.rust_targets }} - - - name: Cache Rust build - uses: Swatinem/rust-cache@v2 - - - name: Install wasm-pack - uses: taiki-e/install-action@v2 - with: - tool: wasm-pack - - # the desktop app does not use it, but the website depends on its package - - name: Build wasm - run: wasm-pack build --target bundler - working-directory: wasm - - - name: Install dependencies for website - run: npm install --prefix website - - # the key would be readable in the app: it is left empty, so only the MapTiler layer fails - - name: Create env file - run: echo "PUBLIC_MAPTILER_KEY=" >> website/.env - - - name: Build app - run: npx --yes @tauri-apps/cli@2 build ${{ matrix.args }} - working-directory: tauri - - # after the build, which gives the app the website it embeds - - name: Test engine and app - run: cargo test -p gpx-engine -p gpx-studio-desktop - - - name: Upload bundles - uses: actions/upload-artifact@v4 - with: - name: bundles-${{ matrix.platform }} - path: ${{ matrix.bundles }} - if-no-files-found: error - - # replaces the beta release only once every platform is built - release: - needs: build - runs-on: ubuntu-latest - - permissions: - contents: write - - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - - steps: - - name: Download bundles - uses: actions/download-artifact@v5 - with: - pattern: bundles-* - path: bundles - merge-multiple: true - - - name: Publish beta release - # the bundles of a platform are in a folder per format (deb, msi…) - run: | - mapfile -t files < <(find bundles -type f) - gh release delete desktop-beta --cleanup-tag --yes || true - gh release create desktop-beta "${files[@]}" \ - --prerelease \ - --target ${{ github.sha }} \ - --title "Desktop beta" \ - --notes "Beta of the desktop app, built from ${{ github.sha }} on the dev branch. The builds are not signed yet: macOS and Windows will warn before opening them." diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml new file mode 100644 index 000000000..b94b539d3 --- /dev/null +++ b/.github/workflows/desktop.yml @@ -0,0 +1,214 @@ +name: Desktop release +# run by hand: from `main` for a release, from `dev` for a pre-release +on: + workflow_dispatch: + +concurrency: + group: desktop-${{ github.ref }} + +jobs: + # the release stays a draft until every platform is built and tested + prepare: + runs-on: ubuntu-latest + + permissions: + contents: write + + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + outputs: + version: ${{ steps.version.outputs.version }} + draft_tag: ${{ steps.version.outputs.draft_tag }} + release_id: ${{ steps.draft.outputs.release_id }} + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Choose version + id: version + run: | + base=$(jq -r .version tauri/tauri.conf.json) + if [[ ! "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::The version in tauri.conf.json should be x.y.z, not $base" + exit 1 + fi + case "$GITHUB_REF_NAME" in + main) + version=$base + draft_tag=desktop-v$base + if git ls-remote --exit-code --tags origin "refs/tags/$draft_tag"; then + echo "::error::$draft_tag is already released: bump the version in tauri.conf.json" + exit 1 + fi + if [ -z "$(jq -r '.plugins.updater.pubkey' tauri/tauri.conf.json)" ]; then + echo "::error::The public key of the updater is missing from tauri.conf.json" + exit 1 + fi + ;; + dev) + version=$base-beta.$GITHUB_RUN_NUMBER + draft_tag=desktop-beta-$GITHUB_RUN_NUMBER + ;; + *) + echo "::error::Desktop builds are made from main or dev, not $GITHUB_REF_NAME" + exit 1 + ;; + esac + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "draft_tag=$draft_tag" >> "$GITHUB_OUTPUT" + + - name: Create draft release + id: draft + env: + VERSION: ${{ steps.version.outputs.version }} + DRAFT_TAG: ${{ steps.version.outputs.draft_tag }} + run: | + # what a previous attempt left: this tag is not released, so it can only be a draft + gh release delete "$DRAFT_TAG" --yes || true + if [ "$GITHUB_REF_NAME" = main ]; then + gh release create "$DRAFT_TAG" --draft --target "$GITHUB_SHA" \ + --title "Desktop $VERSION" --generate-notes + else + gh release create "$DRAFT_TAG" --draft --prerelease --target "$GITHUB_SHA" \ + --title "Desktop beta $VERSION" \ + --notes "Beta of the desktop app, built from $GITHUB_SHA on the dev branch. The builds are not signed yet: macOS and Windows will warn before opening them." + fi + echo "release_id=$(gh release view "$DRAFT_TAG" --json databaseId --jq .databaseId)" >> "$GITHUB_OUTPUT" + + build: + needs: prepare + + strategy: + fail-fast: false + matrix: + include: + - platform: macos-latest + rust_targets: aarch64-apple-darwin,x86_64-apple-darwin,wasm32-unknown-unknown + args: --target universal-apple-darwin + # the oldest supported Ubuntu, so that the AppImage runs on older systems too + - platform: ubuntu-22.04 + rust_targets: wasm32-unknown-unknown + args: '' + - platform: windows-latest + rust_targets: wasm32-unknown-unknown + args: '' + + runs-on: ${{ matrix.platform }} + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + cache-dependency-path: website/package-lock.json + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust_targets }} + + - name: Cache Rust build + uses: Swatinem/rust-cache@v2 + + - name: Install wasm-pack + uses: taiki-e/install-action@v2 + with: + tool: wasm-pack + + # the desktop app does not use it, but the website depends on its package + - name: Build wasm + run: wasm-pack build --target bundler + working-directory: wasm + + - name: Install dependencies for website + run: npm install --prefix website + + # the key would be readable in the app: it is left empty, so only the MapTiler layer fails + - name: Create env file + run: echo "PUBLIC_MAPTILER_KEY=" >> website/.env + + # only the releases update themselves: they need the updater files, and the signing key + - name: Set version and updater files + shell: bash + env: + VERSION: ${{ needs.prepare.outputs.version }} + UPDATER: ${{ github.ref_name == 'main' }} + run: | + node -e " + const fs = require('fs'); + const path = 'tauri/tauri.conf.json'; + const config = JSON.parse(fs.readFileSync(path)); + config.version = process.env.VERSION; + config.bundle.createUpdaterArtifacts = process.env.UPDATER === 'true'; + fs.writeFileSync(path, JSON.stringify(config, null, 4)); + " + + - name: Build app and upload it to the draft + uses: tauri-apps/tauri-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ github.ref_name == 'main' && secrets.TAURI_SIGNING_PRIVATE_KEY || '' }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ github.ref_name == 'main' && secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || '' }} + with: + projectPath: tauri + tauriScript: npx --yes @tauri-apps/cli@2 + args: ${{ matrix.args }} + releaseId: ${{ needs.prepare.outputs.release_id }} + # given with the id for the addresses in `latest.json` + tagName: ${{ needs.prepare.outputs.draft_tag }} + uploadUpdaterJson: ${{ github.ref_name == 'main' }} + + # after the build, which gives the app the website it embeds + - name: Test engine and app + run: cargo test -p gpx-engine -p gpx-studio-desktop + + # `releases/latest`, which the updater reads, only changes here + publish: + needs: [prepare, build] + runs-on: ubuntu-latest + + permissions: + contents: write + + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + DRAFT_TAG: ${{ needs.prepare.outputs.draft_tag }} + + steps: + # the builds of the platforms add themselves to it in parallel, and could overwrite each other + - name: Check updater file + if: github.ref_name == 'main' + run: | + gh release download "$DRAFT_TAG" --pattern latest.json + for platform in darwin-aarch64 darwin-x86_64 linux-x86_64 windows-x86_64; do + if ! jq -e --arg platform "$platform" '.platforms[$platform].signature | length > 0' latest.json; then + echo "::error::latest.json has no signed update for $platform" + exit 1 + fi + done + + - name: Publish release + if: github.ref_name == 'main' + run: gh release edit "$DRAFT_TAG" --draft=false --latest + + - name: Replace beta release + if: github.ref_name == 'dev' + run: | + gh release delete desktop-beta --cleanup-tag --yes || true + gh release edit "$DRAFT_TAG" --tag desktop-beta --draft=false --prerelease --latest=false diff --git a/Cargo.lock b/Cargo.lock index 162d5b9ed..d7e98ca50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chrono" version = "0.4.45" @@ -537,6 +543,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -560,7 +576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -573,7 +589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -1020,6 +1036,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.12" @@ -1287,6 +1313,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -1306,8 +1343,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", + "wasm-bindgen", ] [[package]] @@ -1431,7 +1470,9 @@ dependencies = [ "tauri-build", "tauri-plugin-dialog", "tauri-plugin-opener", + "tauri-plugin-process", "tauri-plugin-single-instance", + "tauri-plugin-updater", "trash", "uuid", ] @@ -1631,6 +1672,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.21" @@ -1650,9 +1706,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1990,6 +2048,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.21", + "walkdir", + "windows-link", +] + +[[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.118", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -2214,6 +2302,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "minisign-verify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2303,6 +2397,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "notify" version = "8.2.0" @@ -2529,6 +2635,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.2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2609,6 +2727,12 @@ dependencies = [ "libc", ] +[[package]] +name = "openssl-probe" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1" + [[package]] name = "option-ext" version = "0.2.0" @@ -2625,6 +2749,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.21", +] + [[package]] name = "pango" version = "0.18.3" @@ -3049,15 +3187,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", @@ -3093,6 +3236,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 = "rustc-hash" version = "2.1.3" @@ -3121,6 +3278,79 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +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", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -3136,6 +3366,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3193,6 +3432,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.2", + "core-foundation 0.10.1", + "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.38.0" @@ -3425,6 +3687,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.4" @@ -3546,6 +3824,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.9" @@ -3609,6 +3893,27 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3630,7 +3935,7 @@ checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400" dependencies = [ "bitflags 2.13.2", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -3640,7 +3945,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -3673,6 +3978,17 @@ dependencies = [ "syn 2.0.118", ] +[[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" @@ -3696,7 +4012,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -3872,6 +4188,16 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-process" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ae3d8549ae564c0397377373e262f340908eb23a5b9fbe03f2a00007dc0038" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-single-instance" version = "2.5.3" @@ -3888,6 +4214,40 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-updater" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d957abbcb3f9f6c76d22a3a0f012f30880640d0bccb5634d2b12a9937e4c872b" +dependencies = [ + "base64 0.23.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "libc", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.21", + "time", + "tokio", + "url", + "windows-sys 0.61.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.12.1" @@ -3898,7 +4258,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -3921,7 +4281,7 @@ checksum = "7f9e7e0c9f80a5130773cfabb6df4dc924518fb1b2c923726966804634b57d22" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -4110,6 +4470,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.20" @@ -4341,6 +4711,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + [[package]] name = "typeid" version = "1.0.3" @@ -4376,6 +4752,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[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" @@ -4675,6 +5057,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.39.1" @@ -4839,6 +5230,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4866,6 +5268,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.60.2" @@ -4899,6 +5310,22 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "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" @@ -4909,7 +5336,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm", + "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", @@ -4940,6 +5367,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +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" @@ -4952,6 +5385,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +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" @@ -4964,12 +5403,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +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" @@ -4982,6 +5433,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +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" @@ -4994,6 +5451,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +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" @@ -5006,6 +5469,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +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" @@ -5018,6 +5487,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +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" @@ -5082,7 +5557,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -5129,6 +5604,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.3" @@ -5243,6 +5728,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + [[package]] name = "zerotrie" version = "0.2.5" @@ -5276,6 +5767,22 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "zip" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426" +dependencies = [ + "crc32fast", + "getrandom 0.4.3", + "indexmap 2.14.2", + "libc", + "memchr", + "nix", + "typed-path", + "windows-sys 0.61.2", +] + [[package]] name = "zlib-rs" version = "0.6.8" diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index 2c7f91147..b94483790 100644 --- a/tauri/Cargo.toml +++ b/tauri/Cargo.toml @@ -37,3 +37,7 @@ tauri-plugin-single-instance = "2.5.3" tauri-plugin-opener = "2" trash = "5" +[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies] +tauri-plugin-process = "2.4.0" +tauri-plugin-updater = "2.14.1" + diff --git a/tauri/capabilities/default.json b/tauri/capabilities/default.json index b3c3d11bc..0d60cab4d 100644 --- a/tauri/capabilities/default.json +++ b/tauri/capabilities/default.json @@ -3,5 +3,5 @@ "identifier": "default", "description": "Permissions for the main window", "windows": ["main"], - "permissions": ["core:default"] + "permissions": ["core:default", "updater:default", "process:allow-restart"] } diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index 38791b5fe..743aa2fed 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -55,6 +55,12 @@ pub fn run() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .setup(|app| { + #[cfg(desktop)] + { + app.handle() + .plugin(tauri_plugin_updater::Builder::new().build())?; + app.handle().plugin(tauri_plugin_process::init())?; + } let handle = app.handle().clone(); let emit: state::Emit = std::sync::Arc::new(move |event, payload| { let _ = handle.emit(event, payload); diff --git a/tauri/tauri.conf.json b/tauri/tauri.conf.json index 95424c8d4..b5889b8fc 100644 --- a/tauri/tauri.conf.json +++ b/tauri/tauri.conf.json @@ -27,7 +27,8 @@ }, "bundle": { "active": true, - "targets": "all", + "targets": ["app", "dmg", "appimage", "deb", "rpm", "nsis"], + "macOS": { "signingIdentity": "-" }, "fileAssociations": [ { "ext": ["gpx"], @@ -44,5 +45,13 @@ "icons/icon.icns", "icons/icon.ico" ] + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5NTFDQzVDOUYwNEMyNEMKUldSTXdnU2ZYTXhSR1ZXRDV6aVErc3dUWXBaK0VGSitYRnFFR21hZ2s5UEJTT01aYkE5Unp5c0cKdW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEFFNTU3Q0Y1NzhFODkyMTEKUldRUmt1aDQ5WHhWcmdXQmx0UG83V29TenRxSVJjdkc4ckN1Zi93ajc3ZUhVUVp3eFlCOXphb1IK", + "endpoints": [ + "https://github.com/gpxstudio/gpx.studio/releases/latest/download/latest.json" + ] + } } } diff --git a/website/package-lock.json b/website/package-lock.json index 40a44a35b..709701346 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -32,6 +32,8 @@ "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.8", "@tauri-apps/api": "^2.12.3", + "@tauri-apps/plugin-process": "^2.4.0", + "@tauri-apps/plugin-updater": "^2.14.1", "@types/eslint": "^9.6.1", "@types/events": "^3.0.3", "@types/file-saver": "^2.0.7", @@ -2667,6 +2669,26 @@ "url": "https://opencollective.com/tauri" } }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.4.0.tgz", + "integrity": "sha512-GddrmsSLAYrLa/yTXlXK/YRDzUgYeeOnUXTbiyvJWkmI424RmDPD3FVLeqcon7rv6O6ZfhBilcrHWnSo7o6hIQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.14.1.tgz", + "integrity": "sha512-Hr7nlBm1wT6xbwzjZ/S36YULazu8MNlj5vvOwMl47fXI7HTa5Bjl1PbDEwVHaZTAKzE1VZsUy35UqnbrQxmYQw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "dev": true, diff --git a/website/package.json b/website/package.json index 26bbc7f1d..6f8bb7aca 100644 --- a/website/package.json +++ b/website/package.json @@ -27,6 +27,8 @@ "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.8", "@tauri-apps/api": "^2.12.3", + "@tauri-apps/plugin-process": "^2.4.0", + "@tauri-apps/plugin-updater": "^2.14.1", "@types/eslint": "^9.6.1", "@types/events": "^3.0.3", "@types/file-saver": "^2.0.7", diff --git a/website/src/lib/desktop/updates.ts b/website/src/lib/desktop/updates.ts new file mode 100644 index 000000000..68b310704 --- /dev/null +++ b/website/src/lib/desktop/updates.ts @@ -0,0 +1,58 @@ +import { BundleType, getBundleType, getVersion } from '@tauri-apps/api/app'; +import { relaunch } from '@tauri-apps/plugin-process'; +import { check, type Update } from '@tauri-apps/plugin-updater'; +import { toast } from 'svelte-sonner'; +import { engine } from '$lib/engine'; +import { i18n } from '$lib/i18n/i18n.svelte'; + +/** Where the `.deb` and `.rpm` packages can be downloaded, as they do not update themselves. */ +const releases = 'https://github.com/gpxstudio/gpx.studio/releases/latest'; + +/** + * Offers the update, if there is one. The app does not restart by itself, as it would interrupt + * the user. The beta versions are not updated, as they would be replaced by the stable release. + */ +export async function checkForUpdates() { + if (import.meta.env.DEV || (await getVersion()).includes('-')) { + return; + } + const update = await check().catch((error) => { + // most likely offline: it will be checked again the next time the app starts + console.warn('Could not check for updates', error); + return null; + }); + if (update === null) { + return; + } + + const message = i18n._('desktop.update_available').replace('{version}', update.version); + const bundle = await getBundleType(); + if (bundle === BundleType.Deb || bundle === BundleType.Rpm) { + toast.info(message, { + duration: Number.POSITIVE_INFINITY, + action: { + label: i18n._('desktop.update_download'), + onClick: () => engine.openUrl(releases), + }, + }); + } else { + toast.info(message, { + duration: Number.POSITIVE_INFINITY, + action: { + label: i18n._('desktop.update_restart'), + onClick: () => install(update), + }, + }); + } +} + +async function install(update: Update) { + const installing = toast.loading(i18n._('desktop.update_installing')); + try { + await update.downloadAndInstall(); + await relaunch(); + } catch (error) { + console.error('Could not install the update', error); + toast.error(i18n._('desktop.update_failed'), { id: installing }); + } +} diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 0e6849f10..92d3e62f8 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -598,5 +598,12 @@ "title": "This page is out of date", "description": "Your files were saved by a newer version of gpx.studio, which this page cannot read. Reload the page to get the latest version.", "reload": "Reload" + }, + "desktop": { + "update_available": "gpx.studio {version} is available", + "update_restart": "Update and restart", + "update_download": "Download", + "update_installing": "Installing the update...", + "update_failed": "The update could not be installed" } } diff --git a/website/src/routes/[[language]]/app/+page.svelte b/website/src/routes/[[language]]/app/+page.svelte index 77df06856..504e424d3 100644 --- a/website/src/routes/[[language]]/app/+page.svelte +++ b/website/src/routes/[[language]]/app/+page.svelte @@ -87,6 +87,8 @@ ); } languageRestored = true; + // in its own chunk, as the website does not need it + import('$lib/desktop/updates').then(({ checkForUpdates }) => checkForUpdates()); } let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');