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 <API_TOKEN>; 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:

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.

Description
Kin — personal relationships app (relational wealth CRM): Expo Android app + Bun/Hono server
Readme 384 KiB
v1.3 Latest
2026-08-09 18:29:56 +10:00
Languages
TypeScript 73.9%
JavaScript 11.9%
Kotlin 4.7%
CSS 4.5%
PLpgSQL 3.5%
Other 1.5%