feat(desktop): Tauri v2 kiosk shell — maximized window, prod right-click block, auto-update + code-signing
Add apps/desktop, a thin Tauri v2 shell wrapping the SAME @parking/web SPA so the desktop and browser UIs never drift: dev loads the Vite dev server (HMR), prod bundles the web app's dist/. No business logic in the shell (device/auth/ ledger stay in @parking/server); deny-by-default capabilities. apps/web (single UI source of truth): - lib/origin.ts: centralize the backend origin (API_BASE/apiUrl/wsUrl from VITE_API_BASE); no-op in the browser, lets the desktop build target Fastify. - lib/kiosk.ts: block the right-click context menu in PROD only (dev keeps it + devtools). - lib/desktop-updater.ts: prompt-on-update auto-update (no-op in browser/offline) → downloadAndInstall + relaunch; i18n update.* keys (sq+en). - .env.production: VITE_API_BASE wired to the Fastify origin for the bundle. Desktop: - window starts maximized (not fullscreen — operator keeps OS access). - auto-update via tauri-plugin-updater + -process; self-hosted endpoint is a PLACEHOLDER to fill in. Updater keypair: pubkey embedded in tauri.conf.json; private key + password kept OUTSIDE the repo (~/.parking-updater-keys) and as TAURI_SIGNING_* build secrets. - Turbo build is a no-op; the real signed bundle is `pnpm --filter @parking/desktop bundle` (verified → .deb/.rpm/.AppImage + .sig signatures). Verified: cargo check clean; turbo run build lint 14/14 green; i18n parity holds; no key/sig/bundle artifacts in the repo. Wiki (security + desktop analysis recorded alongside): - new concepts/tpm.md (TPM 2.0: how it works, sealed-LUKS auto-unlock + non- extractable signing key, limits — live-root, bus-sniff — TPM-vs-ATECC608 by platform). - new decisions/desktop-shell-tauri.md (Tauri v2 over Electron; best-case Ubuntu 26.04 LTS, worst-case Windows+WSL → kiosk browser; full as-built). - pull-the-disk attack trace on append-only-event-chain; ATECC608 not-in-a-PC caveat; cross-links from disk-os-hardening / threat-model. - open-questions #11 (appliance WebKitGTK), #12 (TPM hardening impl), #13 (startup verifyChain self-check); index/overview/log/standing-decisions. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// Desktop auto-update — prompt-on-update flow.
|
||||
//
|
||||
// Runs ONLY inside the Tauri desktop shell; a plain browser has no updater, so
|
||||
// this is a guarded no-op there. On launch it checks the configured update
|
||||
// endpoint (tauri.conf.json → plugins.updater); if a signed newer version is
|
||||
// available it asks the operator, then downloads + installs and relaunches.
|
||||
//
|
||||
// The plugins are imported dynamically so the browser build never bundles them
|
||||
// and never tries to resolve the Tauri APIs. Offline-first: a failed check (no
|
||||
// network — the appliance is usually offline) is swallowed; updates only happen
|
||||
// when someone has brought the box online (e.g. a phone hotspot) on purpose.
|
||||
|
||||
/** True when running inside the Tauri webview (not a normal browser). */
|
||||
function inTauri(): boolean {
|
||||
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
||||
}
|
||||
|
||||
export interface UpdatePrompt {
|
||||
/** Newer version string offered by the server. */
|
||||
version: string;
|
||||
/** Release notes, if the server provided them. */
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an update. If one is available, calls `confirm` (your UI) with the
|
||||
* version/notes; when it resolves true, downloads + installs and relaunches.
|
||||
* No-op (resolves silently) in the browser or when no update / offline.
|
||||
*/
|
||||
export async function checkForDesktopUpdate(
|
||||
confirm: (info: UpdatePrompt) => Promise<boolean>,
|
||||
): Promise<void> {
|
||||
if (!inTauri()) return;
|
||||
try {
|
||||
const { check } = await import("@tauri-apps/plugin-updater");
|
||||
const update = await check();
|
||||
if (!update) return; // up to date
|
||||
|
||||
const accepted = await confirm({ version: update.version, notes: update.body });
|
||||
if (!accepted) return;
|
||||
|
||||
// Download + install the signed update (signature verified against the
|
||||
// pubkey in tauri.conf.json), then relaunch into the new version.
|
||||
await update.downloadAndInstall();
|
||||
const { relaunch } = await import("@tauri-apps/plugin-process");
|
||||
await relaunch();
|
||||
} catch {
|
||||
// Offline / endpoint unreachable / no update server yet → ignore. The app
|
||||
// keeps running on the current version; checking again next launch.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user