# firebase/ Deployable configuration for the Firebase project backing `crdt_sync` / `crdt_sync_dart` sync. Language-neutral, so it lives here rather than under either library. **This repository is public.** Nothing here names a concrete project, database URL, account or uid. Those live in a local, `0600` config file — see [Local configuration](#local-configuration). ## Why Realtime Database, over REST * **No SDK.** Official FlutterFire has no Linux desktop support, and one caller (`wake_alarm`'s PC side) is a dependency-free systemd job. The REST endpoints are plain HTTPS and behave identically on Linux, Android and headless. * **No per-operation quota on the Spark (no-cost) plan.** RTDB bills only storage and bandwidth, so a misbehaving sync loop cannot exhaust a daily budget and silently stop working mid-day. Firestore's 20k-writes/day cap would be a second way to hit a wall, for querying this workload never does. * **Path model matches the existing layout** — `//` — so no data remodelling. Deliberately **not** used: Cloud Storage (requires a linked billing account since 2026‑02‑03) and Cloud Functions. Avoiding both keeps the project structurally incapable of generating a bill. ## `database.rules.json` Scopes the whole database to a single Firebase Auth uid. `auth != null` alone would **not** be safe: email/password sign-up is open through the identitytoolkit REST API to anyone holding the Web API key, and that key ships inside the Android APKs. Pinning the uid means a stranger who registers an account still reads and writes nothing. The uid here is a **placeholder on purpose** — this repo is public, and the uid is the one value that must not appear in it. Publishing this file verbatim yields rules matching no account, which locks every app out of the project until someone notices; the failure looks like a permission error from every device at once. So do not hand-substitute. Render it: ```bash ./render_rules.sh # prints deployable rules, real uid filled in ./render_rules.sh --check # validates without printing the uid ``` It reads the uid from `~/.config/crdt-sync/firebase.json` and refuses to emit anything still containing the placeholder, missing `auth != null`, missing a uid pin, or not parsing as JSON. Paste its output into **Realtime Database → Rules → Publish**. Verify the rules are live — an unauthenticated request must be refused: ```bash curl -s "$DATABASE_URL/.json" # {"error" : "Permission denied"} <- expected ``` ## Local configuration Everything environment-specific lives in `~/.config/crdt-sync/`, mode `0600`, outside version control: | File | Contents | |---|---| | `firebase.json` | `apiKey`, `databaseUrl`, `projectId`, `uid`, `email` | | `password` | the sync account's password, no trailing newline | | `database.rules.json` | the rendered rules, real uid filled in (see `render_rules.sh`) | The Web API key is **not** a secret — it is a public project identifier that ships inside client apps. The password and refresh tokens are the real secrets. ## Credentials at runtime No service-account key is used anywhere: it would bypass the security rules entirely and is trivially extractable from an installed APK. Devices sign in as the single user and hold a refresh token: * Android — `flutter_secure_storage` * Linux — `~/.config//firebase.json`, mode `0600` ID tokens last about an hour, so the refresh exchange is mandatory rather than optional — `diet_guard`'s PC timer alone runs 96 times a day.