Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 thecrmcontainer at https://crm.rehbock.xyz (data in the sharedpersonal-dbPostgres, databasepersonal).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(useradmin), then proxied to the app. - Mobile app → sends
Authorization: Bearer <API_TOKEN>; Caddy lets Bearer requests through and the server validates the token itself (API_TOKENinserver/.envon the VPS; local copy inserver/.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:
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
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.<run> with asset kin-v1.<run>.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.