From 0aced0012c50b1ec596d9896f7c7dada80fe845b Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 27 Jul 2026 16:44:47 +0800 Subject: [PATCH] fix(updater): separate Windows 7 offline update path --- .../scripts/prepare-webview2-win7-runtime.ps1 | 65 ++ .github/workflows/ci.yml | 66 ++ .github/workflows/release.yml | 108 ++- Cargo.lock | 2 - Cargo.toml | 4 + .../components/layout/UpdateDialog.spec.ts | 20 + .../src/components/layout/UpdateDialog.vue | 5 +- apps/desktop/src/composables/useAppUpdater.ts | 2 +- apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/es.ts | 1 + apps/desktop/src/i18n/locales/it.ts | 1 + apps/desktop/src/i18n/locales/ja.ts | 1 + apps/desktop/src/i18n/locales/pt-BR.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + apps/desktop/src/i18n/locales/zh-TW.ts | 1 + .../windowsInstallerTemplate.spec.ts | 65 ++ .../lib/app/windowsWebView2RuntimePolicy.ts | 9 + apps/desktop/src/lib/backend/tauri.ts | 1 + crates/dbx-core/src/update.rs | 2 + src-tauri/src/commands/update.rs | 22 +- .../tauri.webview2-win7-offline.conf.json | 11 + src-tauri/windows/nsis/installer.nsi | 69 +- vendor/ctor/Cargo.toml | 63 ++ vendor/ctor/LICENSE-APACHE | 201 ++++++ vendor/ctor/LICENSE-MIT | 5 + vendor/ctor/README.md | 144 ++++ vendor/ctor/src/example.rs | 111 +++ vendor/ctor/src/lib.rs | 251 +++++++ vendor/ctor/src/macros/mod.rs | 642 ++++++++++++++++++ 29 files changed, 1851 insertions(+), 24 deletions(-) create mode 100644 .github/scripts/prepare-webview2-win7-runtime.ps1 create mode 100644 apps/desktop/src/lib/__tests__/windowsInstallerTemplate.spec.ts create mode 100644 apps/desktop/src/lib/app/windowsWebView2RuntimePolicy.ts create mode 100644 src-tauri/tauri.webview2-win7-offline.conf.json create mode 100644 vendor/ctor/Cargo.toml create mode 100644 vendor/ctor/LICENSE-APACHE create mode 100644 vendor/ctor/LICENSE-MIT create mode 100644 vendor/ctor/README.md create mode 100644 vendor/ctor/src/example.rs create mode 100644 vendor/ctor/src/lib.rs create mode 100644 vendor/ctor/src/macros/mod.rs diff --git a/.github/scripts/prepare-webview2-win7-runtime.ps1 b/.github/scripts/prepare-webview2-win7-runtime.ps1 new file mode 100644 index 000000000..ed522bb49 --- /dev/null +++ b/.github/scripts/prepare-webview2-win7-runtime.ps1 @@ -0,0 +1,65 @@ +[CmdletBinding()] +param( + [string]$CacheRoot = (Join-Path $env:LOCALAPPDATA "tauri"), + [string]$DownloadDirectory = $env:RUNNER_TEMP +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$runtimeVersion = "109.0.1518.140" +$runtimeUrl = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/updt/2023/09/microsoftedgestandaloneinstallerx64_1c890b4b8dd6b7c93da98ebdc08ecdc5e30e50cb.exe" +$runtimeSha256 = "eac95c8095ec5f9971eade9827d8fb67fd251f5c16e702b5312d31067e39119b" +$evergreenUrl = "https://go.microsoft.com/fwlink/?linkid=2124701" + +if ([string]::IsNullOrWhiteSpace($CacheRoot)) { + throw "A Tauri cache root is required." +} +if ([string]::IsNullOrWhiteSpace($DownloadDirectory)) { + $DownloadDirectory = [System.IO.Path]::GetTempPath() +} + +New-Item -ItemType Directory -Force -Path $DownloadDirectory | Out-Null +$downloadPath = Join-Path $DownloadDirectory "MicrosoftEdgeWebView2Runtime-$runtimeVersion-x64.exe" + +if (Test-Path $downloadPath) { + $downloadHash = (Get-FileHash -LiteralPath $downloadPath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($downloadHash -ne $runtimeSha256) { + Remove-Item -LiteralPath $downloadPath -Force + } +} + +if (!(Test-Path $downloadPath)) { + Write-Host "Downloading WebView2 Runtime $runtimeVersion for Windows 7..." + Invoke-WebRequest -Uri $runtimeUrl -OutFile $downloadPath +} + +$actualHash = (Get-FileHash -LiteralPath $downloadPath -Algorithm SHA256).Hash.ToLowerInvariant() +if ($actualHash -ne $runtimeSha256) { + throw "WebView2 Runtime SHA-256 mismatch. Expected $runtimeSha256, got $actualHash." +} + +# Tauri 2.11 does not expose an offline-installer path override. It resolves the +# Evergreen URL and reuses a matching cache entry, so place the verified 109 +# installer at that exact location before bundling. +$response = Invoke-WebRequest -Uri $evergreenUrl -Method Head +$resolvedUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri +$match = [regex]::Match( + $resolvedUrl, + "/filestreamingservice/files/(?[^/]+)/(?[^/?]+)" +) +if (!$match.Success) { + throw "Unexpected Evergreen WebView2 URL: $resolvedUrl" +} + +$cacheDirectory = Join-Path $CacheRoot (Join-Path "x64" $match.Groups["guid"].Value) +$cachePath = Join-Path $cacheDirectory $match.Groups["filename"].Value +New-Item -ItemType Directory -Force -Path $cacheDirectory | Out-Null +Copy-Item -LiteralPath $downloadPath -Destination $cachePath -Force + +$cacheHash = (Get-FileHash -LiteralPath $cachePath -Algorithm SHA256).Hash.ToLowerInvariant() +if ($cacheHash -ne $runtimeSha256) { + throw "Cached WebView2 Runtime SHA-256 mismatch. Expected $runtimeSha256, got $cacheHash." +} + +Write-Host "Prepared WebView2 Runtime $runtimeVersion at $cachePath" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a0913db2..e4415723b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,60 @@ jobs: - name: Node package publish dry run run: pnpm publish:dry-run + windows-win7-bundle: + needs: changes + if: needs.changes.outputs.windows_win7_bundle == 'true' + runs-on: windows-2022 + timeout-minutes: 90 + env: + CARGO_INCREMENTAL: "0" + steps: + - uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10.27.0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.13.0 + cache: pnpm + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Rust for Windows 7 + uses: dtolnay/rust-toolchain@nightly + with: + toolchain: nightly-2026-07-22 + components: rust-src + + - name: Build frontend + run: pnpm build + + - name: Build DBX for Windows 7 + run: cargo build --locked --package dbx --release --target x86_64-win7-windows-msvc -Z build-std=std,panic_abort + + - name: Prepare WebView2 109 offline runtime + shell: pwsh + run: ./.github/scripts/prepare-webview2-win7-runtime.ps1 + + - name: Bundle Windows 7 offline installer + shell: pwsh + run: | + $bundleDir = "target/x86_64-win7-windows-msvc/release/bundle/nsis" + pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc --config src-tauri/tauri.webview2-win7-offline.conf.json + $installer = Get-ChildItem $bundleDir -Filter "*.exe" | + Sort-Object LastWriteTimeUtc -Descending | + Select-Object -First 1 + if (!$installer) { + Write-Error "Missing Windows 7 WebView2 offline installer in ${bundleDir}" + exit 1 + } + Get-FileHash -LiteralPath $installer.FullName -Algorithm SHA256 + rust-fmt-clippy: needs: changes if: needs.changes.outputs.rust == 'true' @@ -299,6 +353,7 @@ jobs: jdbc: ${{ steps.filter.outputs.jdbc }} agents: ${{ steps.filter.outputs.agents }} nix: ${{ steps.filter.outputs.nix }} + windows_win7_bundle: ${{ steps.filter.outputs.windows_win7_bundle }} steps: - uses: actions/checkout@v5 with: @@ -350,6 +405,17 @@ jobs: - 'flake.lock' - '.github/workflows/ci.yml' - '.github/workflows/update-nix-pnpm-hash.yml' + windows_win7_bundle: + - '.github/scripts/prepare-webview2-win7-runtime.ps1' + - '.github/workflows/ci.yml' + - '.github/workflows/release.yml' + - 'src-tauri/tauri.webview2-win7-offline.conf.json' + - 'src-tauri/windows/nsis/**' + - 'src-tauri/src/commands/update.rs' + - 'crates/dbx-core/src/update.rs' + - 'Cargo.toml' + - 'Cargo.lock' + - 'vendor/ctor/**' - name: Select Rust feature coverage id: rust-mode diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bded38a75..2603f1cee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -335,6 +335,112 @@ jobs: Copy-Item $installer.FullName $offlineName -Force gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber + build-windows-7-offline: + runs-on: windows-2022 + env: + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: sccache + SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }} + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Install frontend dependencies + run: pnpm install + + - name: Setup Rust for Windows 7 + uses: dtolnay/rust-toolchain@nightly + with: + toolchain: nightly-2026-07-22 + components: rust-src + + - name: Setup sccache + uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 + with: + version: "v0.10.0" + + - name: Configure S3 sccache + if: env.SCCACHE_GHA_ENABLED != 'true' + shell: bash + env: + CACHE_BUCKET: ${{ secrets.SCCACHE_S3_BUCKET }} + CACHE_ENDPOINT: ${{ secrets.SCCACHE_S3_ENDPOINT }} + CACHE_REGION: ${{ secrets.SCCACHE_S3_REGION }} + CACHE_KEY_PREFIX: ${{ secrets.SCCACHE_S3_KEY_PREFIX }} + CACHE_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }} + CACHE_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }} + run: | + { + echo "SCCACHE_BUCKET=${CACHE_BUCKET}" + echo "SCCACHE_ENDPOINT=${CACHE_ENDPOINT}" + echo "SCCACHE_REGION=${CACHE_REGION}" + echo "SCCACHE_S3_KEY_PREFIX=${CACHE_KEY_PREFIX}" + echo "SCCACHE_S3_USE_SSL=true" + echo "AWS_ACCESS_KEY_ID=${CACHE_ACCESS_KEY_ID}" + echo "AWS_SECRET_ACCESS_KEY=${CACHE_SECRET_ACCESS_KEY}" + } >> "$GITHUB_ENV" + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "./ -> target" + shared-key: release-x86_64-win7-windows-msvc + add-rust-environment-hash-key: true + cache-targets: false + cache-on-failure: true + + - name: Build frontend + run: pnpm build + + - name: Build DBX for Windows 7 + run: cargo build --locked --package dbx --release --target x86_64-win7-windows-msvc -Z build-std=std,panic_abort + + - name: Prepare WebView2 109 offline runtime + shell: pwsh + run: ./.github/scripts/prepare-webview2-win7-runtime.ps1 + + - name: Bundle and upload Windows 7 offline installer + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + $version = "${env:GITHUB_REF_NAME}".TrimStart("v") + $bundleDir = "target/x86_64-win7-windows-msvc/release/bundle/nsis" + $offlineName = "DBX_${version}_x64-win7-webview2-109-offline-setup.exe" + $cargoMetadata = cargo metadata --no-deps --format-version 1 | ConvertFrom-Json + $appVersion = ($cargoMetadata.packages | Where-Object { $_.name -eq "dbx" } | Select-Object -First 1).version + + if (!$appVersion -or $appVersion -ne $version) { + Write-Error "Release tag version $version does not match the built DBX package version $appVersion" + exit 1 + } + + pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc --config src-tauri/tauri.webview2-win7-offline.conf.json + + $installer = Get-ChildItem $bundleDir -Filter "*.exe" | + Sort-Object LastWriteTimeUtc -Descending | + Select-Object -First 1 + if (!$installer) { + Write-Error "Missing Windows 7 WebView2 offline installer in ${bundleDir}" + exit 1 + } + + Copy-Item $installer.FullName $offlineName -Force + gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber + + - name: Show sccache stats + if: always() + continue-on-error: true + shell: bash + run: ${SCCACHE_PATH} --show-stats + static-browser: # Fully static musl builds of the browser (dbx-web) variant. No glibc # dependency, so the tarball runs on any Linux distribution (verified in @@ -438,7 +544,7 @@ jobs: --repo "${GITHUB_REPOSITORY}" --clobber cleanup-release-signatures: - needs: build + needs: [build, build-windows-7-offline] runs-on: ubuntu-latest steps: - name: Remove standalone updater signature assets diff --git a/Cargo.lock b/Cargo.lock index 995dc3025..72d489045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1674,8 +1674,6 @@ dependencies = [ [[package]] name = "ctor" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ "ctor-proc-macro", "dtor", diff --git a/Cargo.toml b/Cargo.toml index 3d96f4490..716cfc927 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,12 @@ [workspace] resolver = "2" members = ["src-tauri", "crates/dbx-core", "crates/dbx-web", "crates/dbx-mcp", "crates/dbx-cli"] +exclude = ["vendor/ctor"] [patch.crates-io] +# Tauri 2.11 uses ctor 0.8, which excludes Rust's win7 vendor. This vendors +# upstream rust-ctor#443 until tauri-utils moves to ctor 1.x. +ctor = { path = "vendor/ctor" } tokio-postgres = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" } postgres-types = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" } postgres-protocol = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" } diff --git a/apps/desktop/src/components/layout/UpdateDialog.spec.ts b/apps/desktop/src/components/layout/UpdateDialog.spec.ts index 3a53913b8..00b7c7083 100644 --- a/apps/desktop/src/components/layout/UpdateDialog.spec.ts +++ b/apps/desktop/src/components/layout/UpdateDialog.spec.ts @@ -14,6 +14,7 @@ const mountedApps: App[] = []; interface DialogState { open: boolean; portableMode: boolean; + manualUpdateOnly: boolean; isDownloadingUpdate: boolean; downloadProgress: number; updateDownloaded: boolean; @@ -30,6 +31,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial({ open: true, portableMode: false, + manualUpdateOnly: false, isDownloadingUpdate: false, downloadProgress: 0, updateDownloaded: false, @@ -67,6 +69,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial { expect(downloadButton()?.disabled).toBe(false); }); + it("routes Windows 7 builds to the dedicated installer", async () => { + await mountDialog(0, { manualUpdateOnly: true }); + + expect(document.body.textContent).toContain("WebView2 109 offline installer"); + expect(downloadButton()).toBeUndefined(); + expect(buttonWithText("Open Release")).toBeDefined(); + }); + + it("prevents Windows 7 portable builds from installing the regular x64 portable update", async () => { + await mountDialog(0, { portableMode: true, manualUpdateOnly: true }); + + expect(document.body.textContent).toContain("WebView2 109 offline installer"); + expect(document.body.textContent).not.toContain("signed portable ZIP"); + expect(downloadButton()).toBeUndefined(); + expect(buttonWithText("Open Release")).toBeDefined(); + }); + it("retains the downloaded update and enables installation only after tasks finish", async () => { await mountDialog(1, { updateDownloaded: true, downloadProgress: 100 }); diff --git a/apps/desktop/src/components/layout/UpdateDialog.vue b/apps/desktop/src/components/layout/UpdateDialog.vue index fd37a6073..df1726453 100644 --- a/apps/desktop/src/components/layout/UpdateDialog.vue +++ b/apps/desktop/src/components/layout/UpdateDialog.vue @@ -106,9 +106,12 @@ watch( docker compose pull && docker compose up -d {{ t("updates.toUpdate") }}

-

+

{{ t("updates.portableAutomaticUpdate") }}

+

+ {{ t("updates.windows7ManualUpdate") }} +