From 32eadc4d03c255952e04ad9fc660445a6bc6a4b8 Mon Sep 17 00:00:00 2001 From: samialabri Date: Fri, 30 Oct 2026 18:57:46 +0400 Subject: [PATCH] Windows updates come from the Kestrove website; version 1.2.1 Co-Authored-By: Claude Opus 5.5 --- README.md | 18 ++++++++++++------ package-lock.json | 4 ++-- package.json | 2 +- scripts/gitea-release.sh | 34 +++------------------------------- scripts/publish-update.sh | 37 +++++++++++++++++++++++++++++++++++++ src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 4 ++-- src-tauri/src/updates.rs | 2 +- src-tauri/tauri.conf.json | 4 ++-- 9 files changed, 61 insertions(+), 46 deletions(-) create mode 100755 scripts/publish-update.sh diff --git a/README.md b/README.md index 58d9881..1cdb364 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,21 @@ XWIN_ACCEPT_LICENSE=1 scripts/windows.sh `src-tauri/Cargo.toml`, tag the commit (`git tag v && git push origin v`), then: ```bash -GITEA_TOKEN= TAURI_SIGNING_PRIVATE_KEY_PATH= scripts/gitea-release.sh +GITEA_TOKEN= scripts/gitea-release.sh ``` It creates the release for the tag and uploads the installers, the browser helpers and a `SHA256SUMS` file. -**Offer it as an update (Windows):** the script also signs the installer with the updater key, uploads the -signature, and writes `updater/latest.json`. Sign the installer with the code-signing certificate first, because -the updater signature covers the final file. Installed copies are offered the update once that file is committed -and pushed to main; the script prints the commands. Keep the updater key and its password offline and backed up: +**Put it on the website and offer it as an update (Windows):** + +```bash +TAURI_SIGNING_PRIVATE_KEY_PATH= scripts/publish-update.sh +``` + +This signs the installer with the updater key, copies it to `downloads/` in `kestrove-website` next to this repo, +and writes `updates/latest.json` there. Sign the installer with the code-signing certificate first, because the +updater signature covers the final file. Installed copies are offered the update once the website is deployed. +Keep the updater key and its password offline and backed up: the app only accepts updates signed with it, so losing it means existing installs can't update. ## How the browser connection works @@ -93,7 +99,7 @@ browser extension ─(native messaging)─▶ kestrove-nmh ─(local connection) ## Updates On Windows, Kestrove checks for a new version a minute after it starts and once a day. The check is a plain request -for `updater/latest.json` on the Kestrove Gitea: it reveals this computer's IP address and app version, and nothing +for `updates/latest.json` on the Kestrove website: it reveals this computer's IP address and app version, and nothing from your vault. A new version appears as a banner and installs only when you choose **Update and restart**. The app verifies the download's signature before running it. Turn checks off in **Settings → Updates**. On Linux, updates come through your package manager. diff --git a/package-lock.json b/package-lock.json index cf707f9..86a317d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kestrove-desktop", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kestrove-desktop", - "version": "1.2.0", + "version": "1.2.1", "license": "Apache-2.0", "dependencies": { "@tauri-apps/api": "2.12.3" diff --git a/package.json b/package.json index 9cd5382..5aa4cb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kestrove-desktop", - "version": "1.2.0", + "version": "1.2.1", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/scripts/gitea-release.sh b/scripts/gitea-release.sh index 4d5ef72..7b8e2b4 100755 --- a/scripts/gitea-release.sh +++ b/scripts/gitea-release.sh @@ -1,17 +1,11 @@ #!/bin/sh -# Creates (or reuses) the Gitea release for this version, uploads the desktop bundles to it, and writes -# updater/latest.json, which the Windows app checks for updates (src-tauri/src/updates.rs). +# Creates (or reuses) the Gitea release for this version and uploads the desktop bundles to it. The website's +# downloads and the Windows app's updates come from scripts/publish-update.sh instead. # -# GITEA_TOKEN=… TAURI_SIGNING_PRIVATE_KEY_PATH=… scripts/gitea-release.sh [dir-with-files] (default ../release/desktop) +# GITEA_TOKEN=… scripts/gitea-release.sh [dir-with-files] (default ../release/desktop) # # GITEA_TOKEN: a Gitea access token with the write:repository scope (Settings → Applications). The tag # v must already be pushed. Files: *.AppImage, *.deb, *.rpm, *-setup.exe, kestrove-nmh-*, SHA256SUMS. -# -# Updates: the Windows installer needs an updater signature (.sig) from the Kestrove updater key, whose -# public half is in src-tauri/tauri.conf.json. Sign after Authenticode signing: the signature covers the final -# file. With TAURI_SIGNING_PRIVATE_KEY_PATH set, a missing .sig is made here (the key's password is asked for, or -# read from TAURI_SIGNING_PRIVATE_KEY_PASSWORD). Optional NOTES: the text shown with the update. -# Users get the update only once updater/latest.json is committed and pushed to main (printed at the end). set -eu cd "$(dirname "$0")/.." : "${GITEA_TOKEN:?set GITEA_TOKEN (Gitea → Settings → Applications → token with write:repository)}" @@ -21,15 +15,6 @@ DIR=${1:-../release/desktop} V=$(node -p 'require("./package.json").version') TAG="v$V" AUTH="Authorization: token $GITEA_TOKEN" -SETUP="$DIR/Kestrove_${V}_x64-setup.exe" - -if [ -f "$SETUP" ] && [ ! -f "$SETUP.sig" ]; then - : "${TAURI_SIGNING_PRIVATE_KEY_PATH:?$SETUP.sig is missing: set TAURI_SIGNING_PRIVATE_KEY_PATH to the updater key to sign it}" - # --app-version binds the signature to this version (the app requires it), so an older signed installer can't - # be offered again under a newer version number. - npx tauri signer sign -f "$TAURI_SIGNING_PRIVATE_KEY_PATH" ${TAURI_SIGNING_PRIVATE_KEY_PASSWORD+-p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD"} \ - --app-version "$V" "$SETUP" -fi (cd "$DIR" && rm -f SHA256SUMS && for f in *.AppImage *.deb *.rpm *-setup.exe kestrove-nmh-*; do if [ -f "$f" ]; then shasum -a 256 -- "$f"; fi; done > SHA256SUMS; cat SHA256SUMS) @@ -45,16 +30,3 @@ for f in "$DIR"/*.AppImage "$DIR"/*.deb "$DIR"/*.rpm "$DIR"/*-setup.exe "$DIR"/* done echo "Release: ${API%/api/v1}/$REPO/releases/tag/$TAG" -# The update manifest, written only after the installer it points to is uploaded. -if [ -f "$SETUP.sig" ]; then - mkdir -p updater - node -e ' - const [v, url, sigFile, notes] = process.argv.slice(1); - const signature = require("fs").readFileSync(sigFile, "utf8").trim(); - const doc = { version: v, notes, pub_date: new Date().toISOString().replace(/\.\d+Z$/, "Z"), - platforms: { "windows-x86_64": { signature, url } } }; - require("fs").writeFileSync("updater/latest.json", JSON.stringify(doc, null, 2) + "\n"); - ' "$V" "${API%/api/v1}/$REPO/releases/download/$TAG/$(basename "$SETUP")" "$SETUP.sig" "${NOTES:-Kestrove $V}" - echo "Wrote updater/latest.json. To offer the update, commit it and push to main:" - echo " git add updater/latest.json && git commit -m \"Offer $V as an update\" && git push origin main" -fi diff --git a/scripts/publish-update.sh b/scripts/publish-update.sh new file mode 100755 index 0000000..819895a --- /dev/null +++ b/scripts/publish-update.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Puts the Windows installer on the Kestrove website and offers it as an update: signs it with the updater key if +# needed, copies it to the website's downloads/, and writes the website's updates/latest.json, which the app checks +# (src-tauri/src/updates.rs). +# +# TAURI_SIGNING_PRIVATE_KEY_PATH=… scripts/publish-update.sh [dir-with-installer] (default ../release/desktop) +# +# Sign after Authenticode signing: the updater signature covers the final file. The key's password is asked for, +# or read from TAURI_SIGNING_PRIVATE_KEY_PASSWORD. Optional NOTES: the text shown with the update. Optional +# WEBSITE_DIR: the website folder (default ../kestrove-website). Users get the update once the website is deployed. +set -eu +cd "$(dirname "$0")/.." +DIR=${1:-../release/desktop} +SITE=${WEBSITE_DIR:-../kestrove-website} +V=$(node -p 'require("./package.json").version') +NAME="Kestrove_${V}_x64-setup.exe" +SETUP="$DIR/$NAME" +[ -f "$SETUP" ] || { echo "$SETUP not found" >&2; exit 1; } + +if [ ! -f "$SETUP.sig" ]; then + : "${TAURI_SIGNING_PRIVATE_KEY_PATH:?$SETUP.sig is missing: set TAURI_SIGNING_PRIVATE_KEY_PATH to the updater key to sign it}" + # --app-version binds the signature to this version (the app requires it), so an older signed installer can't + # be offered again under a newer version number. + npx tauri signer sign -f "$TAURI_SIGNING_PRIVATE_KEY_PATH" ${TAURI_SIGNING_PRIVATE_KEY_PASSWORD+-p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD"} \ + --app-version "$V" "$SETUP" +fi + +mkdir -p "$SITE/downloads" "$SITE/updates" +cp "$SETUP" "$SITE/downloads/$NAME" +node -e ' + const [out, v, url, sigFile, notes] = process.argv.slice(1); + const signature = require("fs").readFileSync(sigFile, "utf8").trim(); + const doc = { version: v, notes, pub_date: new Date().toISOString().replace(/\.\d+Z$/, "Z"), + platforms: { "windows-x86_64": { signature, url } } }; + require("fs").writeFileSync(out, JSON.stringify(doc, null, 2) + "\n"); +' "$SITE/updates/latest.json" "$V" "https://kestrove.samialabri.com/downloads/$NAME" "$SETUP.sig" "${NOTES:-Kestrove $V}" +echo "Copied $NAME to $SITE/downloads and wrote $SITE/updates/latest.json. Deploy the website to offer the update." diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0374606..775e91a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3064,7 +3064,7 @@ dependencies = [ [[package]] name = "kestrove-desktop" -version = "1.2.0" +version = "1.2.1" dependencies = [ "arboard", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 70e33d3..e163bb9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kestrove-desktop" -version = "1.2.0" +version = "1.2.1" edition = "2021" rust-version = "1.82" license = "Apache-2.0" @@ -73,7 +73,7 @@ windows-sys = { version = "0.61", features = [ "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", ] } -# In-app updates (updates.rs): signed installers listed in latest.json on the Kestrove Gitea. +# In-app updates (updates.rs): signed installers listed in latest.json on the Kestrove website. tauri-plugin-updater = "2" # Windows Hello (KeyCredentialManager); the same version Tauri already pulls in. windows = { version = "0.62", features = ["Foundation", "Security_Credentials", "Security_Cryptography", "Storage_Streams"] } diff --git a/src-tauri/src/updates.rs b/src-tauri/src/updates.rs index 142db44..acf9635 100644 --- a/src-tauri/src/updates.rs +++ b/src-tauri/src/updates.rs @@ -1,7 +1,7 @@ //! In-app updates on Windows. The Microsoft Store doesn't update EXE installs, so the app does it itself. //! //! Tauri's updater fetches `latest.json` from the first address in tauri.conf.json → plugins.updater.endpoints -//! that answers (the Kestrove Gitea; a mirror can be added after it), and offers the newer version. Nothing is +//! that answers (the Kestrove website; a mirror can be added after it), and offers the newer version. Nothing is //! downloaded until the user chooses "Update and restart". The installer must carry a signature from the //! Kestrove updater key, whose public half is in tauri.conf.json, so a compromised download server can hold //! updates back but can't ship one. diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c3aa004..5afe974 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": "Kestrove", - "version": "1.2.0", + "version": "1.2.1", "identifier": "app.kestrove.desktop", "build": { "beforeDevCommand": "npm run dev", @@ -53,7 +53,7 @@ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDZFRUM4RDQ2Mzk0Rjg5Q0IKUldUTGlVODVSbzNzYml1ZTN3d0pBN1IwWkRoZXNROTRmbk5WOE5CR2VJQVYrTUJLN21PNzI4eHEK", "requireSignedVersion": true, "endpoints": [ - "https://gitea.samialabri.com/kestrove/kestrove-desktop/raw/branch/main/updater/latest.json" + "https://kestrove.samialabri.com/updates/latest.json" ], "windows": { "installMode": "passive"