Skip to content

Commit 3114d2d

Browse files
committed
feat(updater): add auto-update with background check and progress UI
- tauri-plugin-updater checks for updates 10 s after startup, then every 6 h so updates are detected after sleep/wake cycles - Update badge appears in the dashboard header when a new version is found; clicking it (or the new tray menu item) opens the updater dialog showing version diff, release notes and download progress - NSIS installer handles restart; About window opens automatically on first launch after an upgrade to show the changelog - CI generates latest.json (Tauri-signed) and uploads it as a release asset alongside the installer - fix(ui): set decorations=false to prevent title bar appearing on some Windows configurations
1 parent e2cb4fd commit 3114d2d

18 files changed

Lines changed: 806 additions & 33 deletions

File tree

.github/workflows/release.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,46 @@ jobs:
7070
timestamp-rfc3161: http://timestamp.acs.microsoft.com
7171
timestamp-digest: SHA256
7272

73+
- name: Sign update bundle (Tauri updater signature)
74+
shell: pwsh
75+
env:
76+
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
77+
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
78+
run: |
79+
$exePath = (Get-ChildItem src-tauri/target/release/bundle/nsis/*.exe | Select-Object -First 1).FullName
80+
npx @tauri-apps/cli signer sign -k "$env:TAURI_SIGNING_PRIVATE_KEY" -p "$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD" $exePath
81+
82+
- name: Generate latest.json update manifest
83+
shell: pwsh
84+
run: |
85+
$exePath = (Get-ChildItem src-tauri/target/release/bundle/nsis/*.exe | Select-Object -First 1).FullName
86+
$sigPath = "$exePath.sig"
87+
$sig = (Get-Content $sigPath -Raw).Trim()
88+
$version = ($env:RELEASE_TAG -replace '^v', '')
89+
$basename = Split-Path $exePath -Leaf
90+
$pubDate = (Get-Date -AsUTC -Format "yyyy-MM-ddTHH:mm:ssZ")
91+
$repoUrl = "https://github.com/dvalfrid/rigstats"
92+
93+
$manifest = [ordered]@{
94+
version = $version
95+
notes = "See the full changelog at ${repoUrl}/releases/tag/$env:RELEASE_TAG"
96+
pub_date = $pubDate
97+
platforms = [ordered]@{
98+
"windows-x86_64" = [ordered]@{
99+
signature = $sig
100+
url = "${repoUrl}/releases/download/$env:RELEASE_TAG/${basename}"
101+
}
102+
}
103+
} | ConvertTo-Json -Depth 5
104+
105+
$manifest | Out-File "latest.json" -Encoding UTF8NoBOM
106+
Write-Host "Generated latest.json for v$version"
107+
73108
- name: Publish GitHub release
74109
uses: softprops/action-gh-release@v2
75110
with:
76111
tag_name: ${{ env.RELEASE_TAG }}
77112
fail_on_unmatched_files: true
78113
files: |
79114
src-tauri/target/release/bundle/nsis/*.exe
115+
latest.json

ROADMAP.md

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,35 @@ Planned features in rough priority order. Each item is scoped as a self-containe
44

55
---
66

7-
## v1.6 — NVMe / SSD temperatures
7+
## v1.6 — Auto-update ✓
8+
9+
**Plugin:** `tauri-plugin-updater`
10+
**Distribution:** GitHub Releases (existing pipeline)
11+
12+
**Implemented.** On startup the app silently checks for updates after a 10-second
13+
delay. If a newer version is available a badge appears in the dashboard header.
14+
Clicking the badge (or "Check for Updates" in the tray menu) opens an update
15+
dialog showing the new version, release notes, and a download progress bar.
16+
After installation the NSIS installer restarts the app; the About window
17+
opens automatically on the first launch following an upgrade.
18+
19+
**Setup required (one-time, not yet done):**
20+
21+
1. Generate a signing keypair:
22+
23+
```bash
24+
npx @tauri-apps/cli signer generate -w ./rigstats-update.key
25+
```
26+
27+
Copy the printed **public key** to `tauri.conf.json``plugins.updater.pubkey`.
28+
29+
2. Add two GitHub Actions secrets:
30+
- `TAURI_SIGNING_PRIVATE_KEY` — the base64-encoded private key content
31+
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` — the key password (empty string if none)
32+
33+
---
34+
35+
## v1.7 — NVMe / SSD temperatures
836

937
**Panel:** Disk
1038
**Data source:** LHM `Temperatures` section per storage device
@@ -21,7 +49,7 @@ temperatures, making this a high-value, low-effort addition.
2149

2250
---
2351

24-
## v1.7 — Temperature threshold alerts
52+
## v1.8 — Temperature threshold alerts
2553

2654
**Panel:** Settings (new threshold fields) + tray notifications
2755
**Data source:** Existing CPU / GPU / disk temp fields
@@ -38,7 +66,7 @@ genuinely useful during gaming or overclocking sessions.
3866

3967
---
4068

41-
## v1.8 — CPU fan speed
69+
## v1.9 — CPU fan speed
4270

4371
**Panel:** CPU
4472
**Data source:** LHM `Fans` section on the CPU device node
@@ -54,7 +82,7 @@ GPU fan RPM is already displayed. Adding CPU fan speed is a trivial backend chan
5482

5583
---
5684

57-
## v1.9 — Battery panel (laptop support)
85+
## v2.0 — Battery panel (laptop support)
5886

5987
**Panel:** New `battery` panel
6088
**Data source:** `sysinfo` battery API
@@ -71,24 +99,3 @@ with no battery detected.
7199
- Add `battery` to the valid panel keys list in `monitor.rs` and settings
72100

73101
---
74-
75-
## v2.0 — Auto-update
76-
77-
**Plugin:** `tauri-plugin-updater`
78-
**Distribution:** GitHub Releases (existing pipeline)
79-
80-
Users currently have to manually download and run a new installer for each release.
81-
The Tauri v2 updater plugin checks for a new version on launch, prompts the user,
82-
and installs silently — integrating directly with the existing release-please +
83-
`release.yml` workflow.
84-
85-
**Blocker:** Windows requires a code-signing certificate to avoid SmartScreen warnings
86-
on each update. Worth resolving before implementation starts.
87-
88-
**Scope:**
89-
90-
- Add `tauri-plugin-updater` and configure the update endpoint in `tauri.conf.json`
91-
- Publish a `latest.json` update manifest as a GitHub Release asset in `release.yml`
92-
- Sign the installer in CI using the code-signing certificate
93-
- Add an in-app update check on startup with a user prompt (not silent forced updates)
94-
- Consider Winget publishing as a complementary distribution channel

frontend/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@
117117
.dot{width:7px;height:7px;border-radius:50%;background:var(--grn);display:inline-block;margin-right:6px;animation:blink 2s ease-in-out infinite;box-shadow:0 0 6px var(--grn);}
118118
@keyframes blink{0%,100%{opacity:1;}50%{opacity:.3;box-shadow:none;}}
119119
.uptime{font-family:var(--mono);font-size:11px;color:var(--grn);margin-top:10px;}
120+
.update-badge{margin-top:8px;padding:3px 8px;border:1px solid rgba(57,255,136,.45);background:rgba(57,255,136,.10);font-family:var(--mono);font-size:9px;letter-spacing:2px;color:#39ff88;text-transform:uppercase;width:max-content;cursor:pointer;line-height:1.4;}
121+
.update-badge:hover{background:rgba(57,255,136,.2);}
120122
#modelName{color:var(--accent);font-size:var(--model-name-size);}
121123
#cpuModel,#gpuModel{font-size:var(--panel-model-size);}
122124

@@ -205,6 +207,7 @@
205207
<div id="rigBrandPreview" class="rig-brand-preview"></div>
206208
<div class="rig-name"><span id="rigName">--</span><br><span id="modelName">--</span></div>
207209
<div class="uptime"><span class="dot"></span><span id="uptime">UP 00:00:00</span></div>
210+
<div id="updateBadge" class="update-badge" style="display:none"></div>
208211
</div>
209212
<div class="rig-brand-mark" aria-hidden="true">
210213
<img id="rigLogo" class="rig-logo" src="./assets/ROG_logo_red.png" alt="ROG">

frontend/renderer/app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ function start() {
383383
backend.on('apply-model-name', (_event, name) => applyModelName(name)),
384384
backend.on('apply-profile', (_event, profile) => applyProfile(profile)),
385385
backend.on('apply-visible-panels', (_event, panels) => applyVisiblePanels(panels)),
386+
backend.on('update-available', (_event, version) => {
387+
const badge = document.getElementById('updateBadge');
388+
if (badge) {
389+
badge.textContent = `↑ UPDATE v${version}`;
390+
badge.style.display = '';
391+
badge.addEventListener('click', () => backend.invoke('open-updater-window').catch(() => {}), { once: true });
392+
}
393+
}),
386394
]).then((unlisteners) => {
387395
window.addEventListener('beforeunload', () => unlisteners.forEach((fn) => fn()));
388396
});

frontend/renderer/updater.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { backend, IS_DESKTOP } from './environment.js';
2+
3+
const ids = {
4+
heroTitle: document.getElementById('heroTitle'),
5+
currentVersion: document.getElementById('currentVersion'),
6+
newVersion: document.getElementById('newVersion'),
7+
notesContent: document.getElementById('notesContent'),
8+
progressWrap: document.getElementById('progressWrap'),
9+
progressLabel: document.getElementById('progressLabel'),
10+
progressFill: document.getElementById('progressFill'),
11+
statusMsg: document.getElementById('statusMsg'),
12+
updateBtn: document.getElementById('updateBtn'),
13+
laterBtn: document.getElementById('laterBtn'),
14+
};
15+
16+
let isInstalling = false;
17+
18+
function setStatus(msg) {
19+
ids.statusMsg.textContent = msg;
20+
}
21+
22+
function showProgress(pct, label) {
23+
ids.progressWrap.classList.add('visible');
24+
ids.progressFill.style.width = `${pct}%`;
25+
if (label) ids.progressLabel.textContent = label;
26+
}
27+
28+
async function loadUpdateInfo() {
29+
if (!IS_DESKTOP) {
30+
ids.currentVersion.textContent = '1.5.1';
31+
ids.newVersion.textContent = '2.0.0';
32+
ids.notesContent.textContent = 'Auto-update support.\nDashboard improvements.';
33+
return;
34+
}
35+
36+
try {
37+
const info = await backend.invoke('check-for-update');
38+
if (!info) {
39+
ids.heroTitle.textContent = 'Up to Date';
40+
ids.notesContent.textContent = 'No update available.';
41+
ids.updateBtn.disabled = true;
42+
setStatus('You are running the latest version.');
43+
return;
44+
}
45+
ids.currentVersion.textContent = `v${info.currentVersion}`;
46+
ids.newVersion.textContent = `v${info.version}`;
47+
ids.notesContent.textContent = info.body || 'See GitHub releases for details.';
48+
} catch (err) {
49+
ids.notesContent.textContent = 'Could not check for updates.';
50+
ids.updateBtn.disabled = true;
51+
setStatus(`Error: ${err}`);
52+
}
53+
}
54+
55+
async function startUpdate() {
56+
if (isInstalling) return;
57+
isInstalling = true;
58+
ids.updateBtn.disabled = true;
59+
ids.laterBtn.disabled = true;
60+
ids.heroTitle.textContent = 'Downloading Update…';
61+
showProgress(0, 'DOWNLOADING...');
62+
setStatus('Downloading update — do not close this window.');
63+
64+
if (!IS_DESKTOP) return;
65+
66+
const unlistenProgress = await backend.on('update-progress', (_event, data) => {
67+
if (data.total) {
68+
const pct = Math.round((data.downloaded / data.total) * 100);
69+
showProgress(pct, `DOWNLOADING ${pct}%`);
70+
} else {
71+
showProgress(100, 'DOWNLOADING...');
72+
}
73+
});
74+
75+
try {
76+
await backend.invoke('install-update');
77+
// If we reach here the installer is running — app may close at any moment.
78+
showProgress(100, 'INSTALLING...');
79+
ids.heroTitle.textContent = 'Installing…';
80+
setStatus('Installing update. The app will restart automatically.');
81+
} catch (err) {
82+
isInstalling = false;
83+
ids.updateBtn.disabled = false;
84+
ids.laterBtn.disabled = false;
85+
ids.heroTitle.textContent = 'Update Failed';
86+
showProgress(0);
87+
setStatus(`Install failed: ${err}`);
88+
} finally {
89+
unlistenProgress();
90+
}
91+
}
92+
93+
ids.updateBtn.addEventListener('click', startUpdate);
94+
ids.laterBtn.addEventListener('click', async () => {
95+
if (!IS_DESKTOP) return;
96+
await backend.invoke('close-window');
97+
});
98+
99+
document.addEventListener('keydown', async (event) => {
100+
if (event.key === 'Escape' && IS_DESKTOP && !isInstalling) {
101+
await backend.invoke('close-window');
102+
}
103+
});
104+
105+
loadUpdateInfo();

0 commit comments

Comments
 (0)