Some checks failed
Build & Release APK / build (push) Failing after 13s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
2.9 KiB
Markdown
75 lines
2.9 KiB
Markdown
# 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:
|
|
|
|
```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.<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.
|