# Kin — personal relationships app A self-hosted "relational wealth" tracker: who matters, when you last talked, and who's due for a catch-up. Monica-style personal CRM, but thin: all data lives in one Postgres database on your own server; the mobile app and web UI are both clients of the same tiny API. - **`server/`** — Bun + Hono API and vanilla-JS web UI, deployed as the `crm` container at https://crm.rehbock.xyz (data in the shared `personal-db` Postgres, database `personal`). - **`app/`** — Expo / React Native app ("Kin"), Android APK built by Gitea Actions and installed via Obtainium. iOS builds from the same code. - **`docs/SCOPE.md`** — product scope and architecture decisions. ## How it works Each person can have a **cadence** (contact every N days). The server computes `urgency = days_since_last_contact / cadence` for everyone; the app's home screen sorts by it (overdue → coming up → on track) and schedules a local daily notification ("3 people are due for a catch-up"). Logging an interaction takes two taps and resets the clock. Snooze pushes someone off the list for a week without pretending you talked. ## Auth Two paths into the same API, split by Caddy (`~/personal/proxy/Caddyfile` on the VPS): - **Browser** → Caddy `basic_auth` (user `admin`), then proxied to the app. - **Mobile app** → sends `Authorization: Bearer `; Caddy lets Bearer requests through and the server validates the token itself (`API_TOKEN` in `server/.env` on the VPS; local copy in `server/.env.token.local`, gitignored). Anything on the Docker networks can hit the API unauthenticated — the port is never published on the host. Don't change that property. ## Server deploy The server lives at `~/personal/crm` on the VPS. Restart is NOT enough — the image bakes the source in: ```sh rsync -a --delete --exclude .env --exclude .env.token.local --exclude node_modules \ server/ rehbock.xyz:personal/crm/ ssh rehbock.xyz 'cd ~/personal/crm; and docker compose up -d --build' ``` Migrations in `server/migrations/*.sql` run automatically at container boot (recorded in `schema_migrations`). Add a new numbered file; never edit an applied one. ## App development ```sh cd app npm install npx expo start # dev server; press a for Android npx tsc --noEmit # typecheck ``` `app/android/` is committed (CI builds it directly; `expo prebuild` only when native config changes). Release signing: `~/.android-keys/kin-release.jks` (alias `kin`, password in `kin-release.password` next to it), or `KEYSTORE_FILE`/`KEYSTORE_PASSWORD` env in CI. ## Release pipeline Push to `main` → Gitea Actions (`.gitea/workflows/release.yml`) on the **desktop runner** (label `desktop` — never the VPS) → signed APK → Gitea release `v1.` with asset `kin-v1..apk` → Obtainium picks it up from the repo URL. Repo secrets: `KEYSTORE_B64`, `KEYSTORE_PASSWORD`. Version code/name come from the CI run number; `app.json`'s version is unused on Android.