A minimal desktop application demonstrating Tauri 2's self-update plugin with a SvelteKit frontend — designed in DAI Studio.
This project was designed in DAI Studio before a single line of code was written. The app.dai file at the root of this repo is the workflow. Open it in DAI Studio to explore or remix the design.
See
session.mdfor the complete AI session that built this project.
| Type | Name | Description |
|---|---|---|
| Context | Purpose | Build a desktop application with self-update capability |
| Dependency | Rust | Systems language powering the Tauri backend — native OS integration, IPC commands, update logic |
| Dependency | Tauri 2 | Desktop shell — native window, file system, IPC |
| Dependency | SvelteKit | SPA-mode frontend (adapter-static, ssr=false required) |
| Dependency | self-update | tauri-plugin-updater v2 — update check, download, install via HTTPS |
| Rule | No Committed Keys | The private signing key must never be committed to version control |
| Rule | SPA Mode Required | SvelteKit must use adapter-static with ssr=false — Tauri has no Node.js server |
| Rule | HTTPS Only | Update endpoints must be served over HTTPS in production |
| Step | Update Lifecycle | Check → download with progress streaming → verify signature → install → restart |
The app provides a clean reference implementation of the full update lifecycle: checking for updates, downloading with live progress, and installing with an automatic restart.
| Layer | Technology |
|---|---|
| Desktop shell | Tauri 2 |
| Frontend | SvelteKit 2 + Svelte 5 (SPA mode) |
| Language | TypeScript (frontend), Rust (backend) |
| Build tool | Vite 6 |
| Self-update | tauri-plugin-updater v2 |
Before you start, install the following:
xcode-select --install)git clone <repo-url>
cd tauri_app_self_update
npm install
Rust dependencies are resolved automatically by Cargo during the first build — no separate step needed.
Tauri requires a minisign key pair to sign update artifacts and verify them on the client. Generate one with:
npm run tauri signer generate -- -w ~/.tauri/blank.key
This creates two files:
~/.tauri/blank.key — private key (keep this secret, never commit it)~/.tauri/blank.key.pub — public keyCopy the contents of ~/.tauri/blank.key.pub into the plugins.updater.pubkey field in src-tauri/tauri.conf.json:
"plugins": {
"updater": {
"pubkey": "<paste your public key here>",
...
}
}
Important: The private key at
~/.tauri/blank.keymust never be committed to version control. If it is lost, you will be unable to release future updates to existing installations.
No signing key is required for development.
# Recommended
make dev
# Alternatives
./run.sh dev
npm run tauri dev
Tauri will start the Vite dev server on port 1420 and open the app window. The frontend supports hot-module replacement.
The build requires your private signing key. The Makefile and run.sh automatically load it from ~/.tauri/blank.key if the TAURI_SIGNING_PRIVATE_KEY environment variable is not already set.
# Recommended (auto-loads the key)
make build
# Alternative
./run.sh build
# Manual (set the key yourself first)
export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/blank.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
npm run tauri build
Build output is placed in src-tauri/target/release/bundle/. Each platform produces:
.app on macOS, .AppImage / .deb on Linux, .msi / .exe on Windows).tar.gz archive of the bundle.sig signature file — required for the update server manifestThe app polls an HTTPS endpoint at runtime to check for updates. You must host this endpoint yourself.
Open src-tauri/tauri.conf.json and replace the placeholder URL with your actual server:
"plugins": {
"updater": {
"endpoints": [
"https://your-server.example.com/blank/{{target}}/{{arch}}/latest.json"
]
}
}
Note: The current value (
releases.example.com) is a placeholder. The app will not be able to check for updates until this is replaced with a real HTTPS URL.
The {{target}} and {{arch}} placeholders are substituted at runtime:
{{target}} — darwin, linux, or windows{{arch}} — aarch64 or x86_64After running make build, upload the generated bundles and .tar.gz files to your server.
Use latest.json.template as a guide to create a latest.json file for each target/arch combination:
{
"version": "0.2.0",
"pub_date": "2026-03-25T00:00:00Z",
"notes": "What's new in this release",
"platforms": {
"darwin-aarch64": {
"signature": "<contents of the .sig file>",
"url": "https://your-server.example.com/blank/darwin/aarch64/blank.app.tar.gz"
},
"darwin-x86_64": { "signature": "...", "url": "..." },
"linux-x86_64": { "signature": "...", "url": "..." },
"windows-x86_64": { "signature": "...", "url": "..." }
}
}
The signature field is the raw text content of the corresponding .sig file. Tauri verifies this cryptographically before installing any update.
Serve each latest.json at the endpoint URL matching its target and architecture.
The update flow is split across three layers:
src-tauri/tauri.conf.json)bundle.createUpdaterArtifacts: true — instructs tauri build to generate .tar.gz + .sig artifactsplugins.updater.pubkey — the public key embedded in the binary for signature verificationplugins.updater.endpoints — the HTTPS URL the app polls to check for a newer versionsrc-tauri/src/lib.rs)check_update command — calls the updater plugin to fetch the endpoint and compare versions. If an update is available, stores the pending Update object in a Mutex and returns metadata (new version, current version, release notes) to the frontend.apply_update command — retrieves the stored Update, downloads the artifact, and streams DownloadEvent progress events (Started, Progress, Finished) back to the frontend via a Tauri IPC Channel. After installation completes, calls app.restart().src/routes/+page.svelte)Status state (idle → checking → available / up-to-date → downloading → installing).@tauri-apps/api invoke and streams progress updates through an IPC channel.| Variable | Required | Description |
|---|---|---|
TAURI_SIGNING_PRIVATE_KEY |
Required for builds | Raw text content of the private key file. Makefile/run.sh auto-load from ~/.tauri/blank.key if unset. |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Optional | Password for the private key. Defaults to empty (key generated without a password). |
SDKROOT |
macOS only | Set automatically by Makefile/run.sh via xcrun --show-sdk-path. |
Tauri does not support loading signing keys from
.envfiles. These must be real environment variables set in the shell.
tauri_app_self_update/
├── src/ # SvelteKit frontend
│ ├── app.html # HTML shell
│ └── routes/
│ ├── +layout.ts # Disables SSR (SPA mode)
│ └── +page.svelte # Update UI (check, download, install)
│
├── src-tauri/ # Tauri / Rust backend
│ ├── Cargo.toml # Rust package manifest
│ ├── build.rs # Calls tauri_build::build()
│ ├── tauri.conf.json # App config, bundle settings, updater config
│ ├── src/
│ │ ├── main.rs # Binary entry point
│ │ └── lib.rs # Plugin setup, check_update / apply_update commands
│ └── capabilities/
│ └── default.json # IPC capability grants (includes updater:default)
│
├── app.dai # DAI Studio workflow diagram
├── latest.json.template # Template for the update server manifest
├── session.md # The AI session that built this project
├── Makefile # dev, build, check targets (handles key loading)
├── run.sh # Shell script equivalent of Makefile targets
├── package.json # JS dependencies and scripts
├── svelte.config.js # SvelteKit config (static adapter)
└── vite.config.js # Vite config (port 1420)
VS Code with the following extensions: