3 Commits
Author SHA1 Message Date
samiandClaude Opus 5.5 608d85c297 Linux: browser helper for snap Firefox; packages register it
ci / build (ubuntu-22.04) (push) Waiting to run
ci / build (windows-2025) (push) Waiting to run
ci / deny (push) Waiting to run
Snap Firefox starts the helper through the desktop portal, which reads the
manifest from ~/.mozilla or /usr/lib/mozilla. The .deb and .rpm now install the
manifests system-wide, and install.sh detects snap Firefox.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 01:12:45 +04:00
samiandClaude Opus 5.5 32eadc4d03 Windows updates come from the Kestrove website; version 1.2.1
ci / build (ubuntu-22.04) (push) Canceled after 0s
ci / build (windows-2025) (push) Canceled after 0s
ci / deny (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 18:57:46 +04:00
samiandClaude Opus 5.5 57b728c7e3 gitea-release.sh: work with any subset of bundles
ci / build (ubuntu-22.04) (push) Canceled after 0s
ci / build (windows-2025) (push) Canceled after 0s
ci / deny (push) Canceled after 0s
A release with only the Windows installer aborted: shasum failed on the
unmatched *.deb and *.rpm globs under set -e. Checksum the files that exist.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 18:29:32 +04:00
13 changed files with 102 additions and 47 deletions
+12 -6
View File
@@ -63,15 +63,21 @@ XWIN_ACCEPT_LICENSE=1 scripts/windows.sh
`src-tauri/Cargo.toml`, tag the commit (`git tag v<version> && git push origin v<version>`), then:
```bash
GITEA_TOKEN=<token with write:repository> TAURI_SIGNING_PRIVATE_KEY_PATH=<updater key> scripts/gitea-release.sh
GITEA_TOKEN=<token with write:repository> 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=<updater key> 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.
+4
View File
@@ -27,5 +27,9 @@ native-messaging/install.sh --uninstall
| Firefox | `~/.mozilla/native-messaging-hosts/app.kestrove.nmh.json` | `HKCU\Software\Mozilla\NativeMessagingHosts\app.kestrove.nmh` |
| Brave | `~/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts/` | same key as Chrome |
The Linux `.deb` and `.rpm` install these manifests system-wide (`src-tauri/linux/`), so they need no script. That
covers snap Firefox too: it can't start the host itself, so the desktop portal does, after asking the user once.
AppImage users run `install.sh`.
`allowed_origins` / `allowed_extensions` pin the host to **your** extension id.
That is the first line of defense against other extensions (docs/04 A8).
+4
View File
@@ -84,6 +84,10 @@ esac
eval "d=\$dir_$b"
[ -d "$d" ] && BROWSERS="$BROWSERS $b"
done
# Snap Firefox keeps its profile in ~/snap, but its portal reads the host from ~/.mozilla like any other Firefox.
case " $BROWSERS " in *" firefox "*) ;; *)
[ -n "$USER_DATA_DIR" ] || { [ -d /snap/firefox ] && BROWSERS="$BROWSERS firefox"; } || true ;;
esac
render() { # template → stdout
sed -e "s|__NMH_PATH__|$NMH|" -e "s|chrome-extension://__CHROME_EXTENSION_ID__/|$CHROME_ORIGINS|" -e "s|__FIREFOX_EXTENSION_ID__|$FIREFOX_ID|" "$HERE/$HOST.$1.json"
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kestrove-desktop",
"version": "1.2.0",
"version": "1.2.1",
"private": true,
"license": "Apache-2.0",
"type": "module",
+4 -32
View File
@@ -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<version> must already be pushed. Files: *.AppImage, *.deb, *.rpm, *-setup.exe, kestrove-nmh-*, SHA256SUMS.
#
# Updates: the Windows installer needs an updater signature (<installer>.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,17 +15,8 @@ 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 && shasum -a 256 -- *.AppImage *.deb *.rpm *-setup.exe kestrove-nmh-* 2>/dev/null > SHA256SUMS; cat SHA256SUMS)
(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)
ID=$(curl -fsS -H "$AUTH" "$API/repos/$REPO/releases/tags/$TAG" 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).id||"")}catch{console.log("")}})')
if [ -z "$ID" ]; then
@@ -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
+37
View File
@@ -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."
+1 -1
View File
@@ -3064,7 +3064,7 @@ dependencies = [
[[package]]
name = "kestrove-desktop"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"arboard",
"base64 0.22.1",
+2 -2
View File
@@ -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"] }
@@ -0,0 +1,7 @@
{
"name": "app.kestrove.nmh",
"description": "Kestrove native messaging host",
"path": "/usr/bin/kestrove-nmh",
"type": "stdio",
"allowed_origins": ["chrome-extension://pmhfoafjoogobhfadaicklhkldgnafmk/", "chrome-extension://kogapfibnnpeapfcjkkpgjddbhnjfein/"]
}
@@ -0,0 +1,7 @@
{
"name": "app.kestrove.nmh",
"description": "Kestrove native messaging host",
"path": "/usr/bin/kestrove-nmh",
"type": "stdio",
"allowed_extensions": ["[email protected]"]
}
+1 -1
View File
@@ -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.
+20 -2
View File
@@ -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",
@@ -41,6 +41,24 @@
"icons/icon.ico",
"icons/icon.png"
],
"linux": {
"deb": {
"files": {
"/usr/lib/mozilla/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.firefox.json",
"/etc/opt/chrome/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json",
"/etc/chromium/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json",
"/etc/opt/edge/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json"
}
},
"rpm": {
"files": {
"/usr/lib/mozilla/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.firefox.json",
"/etc/opt/chrome/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json",
"/etc/chromium/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json",
"/etc/opt/edge/native-messaging-hosts/app.kestrove.nmh.json": "linux/app.kestrove.nmh.chrome.json"
}
}
},
"windows": {
"nsis": {
"installerHooks": "windows/hooks.nsh",
@@ -53,7 +71,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"