Add auto-update support (tauri-plugin-updater) and Gitea release pipeline

Ed25519-signed updates, checked via a fixed-tag latest.json manifest on
Gitea Releases; scripts/release.sh automates build+sign+publish. Adds
a manual "Nach Updates suchen" trigger plus a silent startup check.
This commit is contained in:
2026-07-28 21:38:49 +02:00
parent 20bfd2cf6b
commit 4b2248655b
11 changed files with 540 additions and 2 deletions
+26
View File
@@ -45,6 +45,7 @@ import { api } from './lib/api'
import { parseAddressList } from './lib/addresses'
import { escapeHtml } from './lib/richtext'
import { loadAppSettings, saveAppSettings, type AppSettings } from './lib/settings'
import { checkForUpdate, installUpdate } from './lib/updater'
import {
applyTheme,
loadConversationsEnabled,
@@ -253,6 +254,11 @@ export default function App() {
setTimeout(() => setToast(null), 3500)
}, [])
useEffect(() => {
handleCheckForUpdates(true)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
applyTheme(theme)
if (theme !== 'system') return
@@ -443,6 +449,25 @@ export default function App() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [view, selectedMessage])
async function handleCheckForUpdates(silent = false) {
const update = await checkForUpdate()
if (!update) {
if (!silent) showToast('Du hast schon die neueste Version')
return
}
if (!window.confirm(`Version ${update.version} ist verfügbar (aktuell: ${update.currentVersion}).\n\n${update.body ?? ''}\n\nJetzt installieren? Die App startet danach neu.`)) {
return
}
showToast('Update wird heruntergeladen…')
try {
await installUpdate(update, (pct) => {
if (pct !== null) showToast(`Update wird installiert… ${pct}%`)
})
} catch (e) {
showToast(`Update fehlgeschlagen: ${e}`)
}
}
function handleRefresh() {
if (offlineMode) {
showToast('Offline-Modus aktiv')
@@ -1818,6 +1843,7 @@ export default function App() {
onSyncGoogleCalendar={handleSyncGoogleCalendar}
onReconnectGoogleAccount={handleReconnectGoogleAccount}
onOpenCaldav={() => setCaldavModalOpen(true)}
onCheckForUpdates={handleCheckForUpdates}
theme={theme}
onThemeChange={handleThemeChange}
onOpenSettings={() => setSettingsModalOpen(true)}
+3
View File
@@ -273,6 +273,7 @@ export function Ribbon({
onSyncGoogleCalendar,
onReconnectGoogleAccount,
onOpenCaldav,
onCheckForUpdates,
theme,
onThemeChange,
onOpenSettings,
@@ -337,6 +338,7 @@ export function Ribbon({
onSyncGoogleCalendar?: (accountId: string) => Promise<void>
onReconnectGoogleAccount?: (accountId: string) => Promise<void>
onOpenCaldav?: () => void
onCheckForUpdates: () => void
theme: Theme
onThemeChange: (t: Theme) => void
onOpenSettings: () => void
@@ -556,6 +558,7 @@ export function Ribbon({
<ClassicRibbonGroup caption="Support">
<HelpMenu onShowShortcuts={onShowShortcuts} />
<AboutMenu />
<ClassicButton icon={RefreshCw} label="Nach Updates suchen" onClick={onCheckForUpdates} />
</ClassicRibbonGroup>
)}
</ClassicRibbonBody>
+27
View File
@@ -0,0 +1,27 @@
import { check, type Update } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process'
export async function checkForUpdate(): Promise<Update | null> {
try {
return await check()
} catch {
return null
}
}
export async function installUpdate(update: Update, onProgress: (pct: number | null) => void): Promise<void> {
let total = 0
let downloaded = 0
await update.downloadAndInstall((event) => {
if (event.event === 'Started') {
total = event.data.contentLength ?? 0
onProgress(total > 0 ? 0 : null)
} else if (event.event === 'Progress') {
downloaded += event.data.chunkLength
onProgress(total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : null)
} else if (event.event === 'Finished') {
onProgress(100)
}
})
await relaunch()
}