ADR-0004: Local persistence & credential storage

Status

Accepted

Area

Persistence

Context

The app needs to store known devices and their pairing tokens locally. These are LAN pairing tokens, not cloud credentials, so a file fallback is an acceptable degradation. Keyring access can fail in headless, CI, or flatpak sessions (including a panicking secret-service backend), and mobile platforms do not expose the desktop keyring at all.

Decision

Persist devices to devices.json in the app data dir using an atomic write (temp file + rename).

For tokens, use OS keyring first, file fallback. Keyring access runs inside spawn_blocking, and every error — including a panicking secret-service backend in a headless/CI/flatpak session — is caught and downgraded to file storage with a warn! log. When the keyring succeeds, the token is omitted from devices.json (empty string); when it fails, the token is written to the file as the documented fallback.

The keyring linux feature set is sync-secret-service + crypto-rust — pure-Rust crypto, no system OpenSSL, which keeps the future Android/flatpak build simpler.

Device identity uses the scheme:

DeviceId = "{vendor}:{mac-or-ip}"

The MAC is used when known (stable across DHCP lease changes), else the IP.

The keyring is desktop-only (cfg(not(android|ios))); on mobile the token lives in devices.json in the app’s private data dir (already per-app sandboxed by Android). A future improvement is an Android Keystore plugin.

Consequences

  • Devices persist safely without partial-write corruption thanks to the atomic temp-file-plus-rename.

  • Tokens are protected by the OS keyring when available, and degrade gracefully to file storage in headless/CI/flatpak or mobile environments without breaking pairing.

  • Pure-Rust crypto avoids a system OpenSSL dependency, simplifying future Android and flatpak builds.

  • DeviceId stays stable across DHCP lease changes when a MAC is known.

  • On mobile, tokens rely on the Android per-app sandbox rather than a dedicated secure store, pending a future Android Keystore plugin.