So verteilen Sie Desktop-Apps als Solo-Entwickler ohne Sicherheitswarnungen
TuBrief 편집팀
2026년 7월 15일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Wenn Sie eine Desktop-App fertiggestellt haben, die lokal einwandfrei läuft, ist die Entwicklung erst zur Hälfte abgeschlossen. Die wirkliche Hürde beginnt in dem Moment, in dem der Benutzer auf den Download-Link klickt und auf seinem Bildschirm rote Warnmeldungen wie "Datei beschädigt" oder "Der Computer wurde geschützt" erscheinen.
Die Überwindung von OS-Sicherheitsbarrieren, die Kontrolle der Build-Geschwindigkeit und der Aufbau eines automatischen Updatesystems, das bei jedem Start sicherstellt, dass die neueste Version geladen wird, ist anstrengender als gedacht. Auch wenn Sie sich aufgrund der im Vergleich zu Electron geringeren Größe für Tauri v2 entschieden haben, bleiben die praktischen Probleme des Verteilungsprozesses bestehen. Ich habe zusammengefasst, wie Solo-Entwickler oder kleine Teams ihr Produkt sauber verteilen können, ohne unnötige Zeit und Geld zu verschwenden.
Eine Desktop-App ohne Codesignierung wird vom Betriebssystem als Malware eingestuft. Um Sicherheitswarnungen auf dem PC des Benutzers zu vermeiden, sind Geld und Papierkram erforderlich.
Für die Verteilung unter macOS ist eine Mitgliedschaft im Apple Developer Program für 99 US-Dollar pro Jahr zwingend erforderlich. Sobald Sie ein Konto haben, müssen Sie eine src-tauri/Entitlements.plist erstellen, die Ausnahmen für die Speichersicherheit definiert, damit die Tauri-Webview ordnungsgemäß funktioniert. Wenn diese Einstellung fehlt, stürzt die App sofort beim Start ab.
`xml
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
`
Diese Datei wird in den Bundle-Optionen der src-tauri/tauri.conf.json angegeben.
json { "bundle": { "macOS": { "signingIdentity": "Developer ID Application: Your Name (TEAMID)", "entitlements": "./Entitlements.plist", "minimumSystemVersion": "11.0", "dmg": { "appPosition": { "x": 180, "y": 170 }, "applicationFolderPosition": { "x": 480, "y": 170 } } } } }
Um den SmartScreen-Filter unter Windows zu passieren, musste man früher ein EV-Zertifikat (Extended Validation) für 400 bis 700 US-Dollar pro Jahr als physisches USB-Token erwerben. Das ist nicht nur teuer, sondern für eine Einzelperson auch extrem mühsam zu verwalten.
Die Alternative ist Azure Trusted Signing (ATS), der Cloud-basierte Signaturdienst von Microsoft. Wenn Sie die Abonnementgebühr von etwa 9,99 US-Dollar pro Monat zahlen, wird die Signierung innerhalb der von Microsoft verwalteten HSM-Cloud abgewickelt, sodass keine physischen Schlüssel aufbewahrt werden müssen.
AZURE_TENANT_ID, CLIENT_ID, CLIENT_SECRET) sowie die ATS-Informationen in die GitHub Actions-Umgebungsvariablen (Secrets) ein.sign-tool aus, um die von Tauri kompilierten MSI- oder EXE-Dateien digital zu signieren.Eine so signierte App umgeht von Beginn des Downloads an die Windows SmartScreen-Warnung, sodass Sie keine Benutzer während des Installationsschritts verlieren.
Tauri ist zwar leichtgewichtig, aber beim Bauen müssen der Rust-Compiler und die nativen Toolchains für jedes Betriebssystem komplett durchlaufen werden. Selbst wenn es auf dem eigenen Computer funktioniert, kommt es häufig vor, dass auf den Computern anderer Teammitglieder Linker-Fehler auftreten oder das Build aufgrund von Abhängigkeitskonflikten in der lokalen Umgebung beschädigt wird. Verteilungs-Builds müssen zur Sicherheit in einer isolierten CI/CD-Pipeline erstellt werden.
Das Problem ist, dass die Standard-Hosting-Runner von GitHub für das Kompilieren von Rust unterdimensioniert sind. Wenn bei jedem Durchlauf die Abhängigkeiten neu geladen und das gesamte Projekt gebaut wird, dauert ein Release-Build schnell mehr als 10 Minuten.
Anstatt nur Dateien mit actions/cache in die Cloud zu schieben, verbessert die Kombination mit einem dedizierten Cache-Plugin (swatinem/rust-cache), das auf NVMe-Hochleistungsspeicher optimiert ist, oder dedizierten Hosting-Runnern (Namespace, Depot usw.) die Geschwindigkeit erheblich.
Basierend auf den Build-Logs des Open-Source-Musik-Players spotify-player sehen die Ergebnisse des Leistungsvergleichs zwischen einem normalen GitHub-Runner und einem dedizierten Runner mit lokalem Volume-Cache wie folgt aus:
| Plattform & Cache-Konfiguration | Zeitaufwand (normaler GitHub-Runner) | Zeitaufwand (mit Cache-Optimierung) | Zeitersparnis |
|---|---|---|---|
| Ubuntu Linux | 9 Min. 31 Sek. | 34 Sek. | 94,0 % |
| macOS Darwin | 9 Min. 31 Sek. | 27 Sek. | 95,2 % |
| Windows MSVC | 9 Min. 31 Sek. | 44 Sek. | 92,2 % |
| Workflow-Kosten | 0,44 pro Lauf | 83,1 % |
Die Einbindung einer Infrastruktur mit dauerhaftem Volume-Cache reduziert die Wartezeit des Entwicklungsteams auf Builds um mindestens 40 %.
Die Konfiguration der Verteilungsautomatisierung wird in .github/workflows/publish.yml wie folgt festgelegt:
yaml jobs: build-binaries: strategy: matrix: platform: [macos-latest, windows-latest] runs-on: ${{ matrix.platform }} # ... Nach den Build-Schritten tauri-action aufrufen
Wenn Sie tauri-apps/tauri-action am Ende des Workflows platzieren, wird bei jedem Pushing eines neuen Tags automatisch ein signierter Installer für beide Betriebssysteme im GitHub Release Draft registriert.
Beim Verpacken des Installers für Windows müssen Sie entscheiden, wie die Webview-Engine (WebView2) installiert werden soll. Wenn eine Internetverbindung garantiert ist und die Downloadgröße extrem klein sein muss, ist der downloadBootstrapper-Ansatz, bei dem die Bundle-Größe nicht zunimmt, die beste Wahl. Wenn Sie hingegen in geschlossenen Netzwerken oder Offline-Umgebungen arbeiten, ist es sicherer, den offlineInstaller beizufügen, auch wenn dies die Installationsdatei um ca. 127 MB vergrößert.
Auch der Umgang mit Daten ist bei Tauri-basierten Apps wichtig. Daten einfach in Browserspeichern wie IndexedDB oder LocalStorage abzulegen, ist riskant.
Beim Übergang von Tauri v1 zu v2 gab es tatsächlich eine interne Änderung am Webview-Domain-Schema unter Windows von [https://tauri.localhost](https://tauri.localhost) zu [http://tauri.localhost](http://tauri.localhost). Dies führte oft dazu, dass der Browser-Cache-Pfad zwangsweise umgestellt wurde und bestehende Daten verloren gingen.
Um ein Desaster durch das Zurücksetzen von Daten nach der Veröffentlichung zu vermeiden, sollten wichtige Informationen nicht im Webview-Speicher, sondern direkt als SQLite-Datei im nativen Dateisystem gespeichert werden. Die appDataDir-API von Tauri v2 sucht automatisch den sicheren Sandbox-Pfad gemäß den Betriebssystemspezifikationen.
C:\Users\<UserName>\AppData\Roaming\<BundleIdentifier>/Users/<UserName>/Library/Application Support/<BundleIdentifier>Ein Beispiel dafür, wie der Rust-Code (src-tauri/src/lib.rs), das Backend von Tauri v2, den App-Lebenszyklus nutzt, um die SQLite-Datenbank an einen sicheren Bereich zu binden und Schema-Migrationen durchzuführen:
`rust
use std::fs;
use tauri::Manager;
use tauri_plugin_sql::{Migration, MigrationKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let database_migrations = vec![
Migration {
version: 1,
description: "initialize_user_profiles_table",
sql: "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);",
kind: MigrationKind::Up,
}
];
tauri::Builder::default()
.setup(|app| {
let local_app_dir = app.path().app_data_dir()
.expect("Critical: Could not resolve target operating system app data path.");
if !local_app_dir.exists() {
fs::create_dir_all(&local_app_dir)
.expect("Critical: Failed to establish persistent storage directory structure.");
}
Ok(())
})
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:users.db", database_migrations)
.build()
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
`
Wenn Sie es so konfigurieren, bleibt die Datenbank des tatsächlichen Benutzers sicher erhalten, selbst wenn der interne Cache von Electron oder der Chromium-Webview durch automatische Updates oder Neuinstallationen gelöscht wird.
Den Benutzer jedes Mal auf die Website zu schicken, damit er eine neue Version herunterlädt, erhöht die Abbruchrate. Sie sollten eine Struktur aufbauen, in der Updates über Cloud-Objektspeicher und CDN im Hintergrund bereitgestellt werden.
Als Distributionsserver ist die Kombination aus Cloudflare R2 und AWS CloudFront effizient. Cloudflare R2 erhebt keine Gebühren für den Datentransfer (Egress Fees), wodurch die Netzwerktraffic-Kosten bei der Veröffentlichung großer Update-Dateien bei null bleiben.
Die Metadatendatei (latest.json), die der Client abruft, um zu prüfen, ob eine neue Version verfügbar ist, darf nicht vom CDN oder Browser zwischengespeichert werden. Der Antwort-Header muss die folgende Richtlinie enthalten:
http Cache-Control: no-cache, no-store, must-revalidate
Im Gegensatz dazu sind die eigentlichen Installations-Binärdateien unveränderlich (Immutable) und enthalten einen eindeutigen Hash. Diese sollten so lange wie möglich vom CDN gespeichert werden, um den Ursprungsserver zu entlasten.
http Cache-Control: public, max-age=31536000, immutable
In Tauri v2 wurden die Optionen für Updates unter den plugins.updater-Block verschoben. Hier ist die Spezifikation für tauri.conf.json:
json { "bundle": { "createUpdaterArtifacts": true }, "plugins": { "updater": { "active": true, "endpoints": [ "https://cdn.myapp.com/releases/latest.json" ], "dialog": false, "pubkey": "dW5zaWduZWQgYm91bmRmaXg...", "windows": { "installMode": "passive" } } } }
Um unter Windows zu vermeiden, dass der Benutzer störende Bestätigungsfenster wegklicken muss, sollte installMode auf passive oder quiet gesetzt werden. Der passive-Modus zeigt anstelle des Installationsassistenten nur einen dezenten Fortschrittsbalken und schließt das Update im Hintergrund ab.
Sobald die Einrichtung abgeschlossen ist, können Sie im Frontend @tauri-apps/plugin-updater und @tauri-apps/plugin-process verknüpfen, um beim App-Start nach neuen Patches zu suchen und einen Neustart anzuregen.
`typescript
import { check } from "@tauri-apps/plugin-updater";
import { ask } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
export async function runBackgroundUpdater(): Promise {
try {
const updatePayload = await check();
if (updatePayload && updatePayload.available) {
const userResponse = await ask(
`Eine neue Version [v${updatePayload.version}] ist verfügbar. Möchten Sie jetzt aktualisieren und die App neu starten?`,
{
title: "Automatische Software-Update-Benachrichtigung",
kind: "info",
okLabel: "Update installieren und neu starten",
cancelLabel: "Später"
}
);
if (userResponse) {
await updatePayload.downloadAndInstall();
await relaunch();
}
}
} catch (error) {
console.error("Fehler bei der automatischen Update-Prüfung:", error);
}
}
`
Allein durch das Einbetten dieser Funktion in die oberste React-Komponente oder in die initiale Mount-Phase Ihrer View nutzen die Benutzer stets die aktuellste Version der Software, ohne jemals manuell auf Ihrer Website suchen zu müssen.
appDataDir-Pfad, der vom Betriebssystem verwaltet wird, und führen Sie langfristige Schema-Migrationen durch, damit die Daten bei App-Updates nicht korrumpiert werden.