Coordination app for a private car friend group: set a meet, RSVP with your car, get auto-checked-in at the meetup point, and see everyone's live position on a group drive — so nobody types "otw", "reached", or "where r u" in WhatsApp again.
Scope is contract-driven: runs-v1-spec.md is the source of truth. Hard constraints: no speed is ever stored or transmitted, leaderless runs, invite-code auth only, location shared only during an active run, graceful degradation when the backend is down.
| Path | What it is |
|---|---|
server/ |
Node + TypeScript backend — REST + WebSocket, SQLite, invite-code auth. Ships as a Docker image. |
app/ |
React Native + Expo (Android) app — runs, RSVP, arrival board, live MapLibre map, Waze handoff. Ships as an APK. |
spike-m0/ |
M0 background-location spike (kill-switch milestone): bare tracker app + tiny WS relay + JB–SG drive test protocol. |
docs/CONTRACT.md |
The API contract both server/ and app/ are built against. Change it first, then the code. |
runs-v1-spec.md |
Product spec + guardrails. |
Backend — Node 22+:
cd server
npm install
npm run invite:create # prints an invite code, e.g. RUNS-DE94E937
npm run dev # http://localhost:4000, SQLite at data/runs.db
npm test # 26 integration tests (HTTP + WS)App — needs a dev build (Expo Go can't run MapLibre or background location). For iOS without a Mac, see docs/ios-build.md:
cd app
npm install
# set your server URL in src/config.ts (LAN IP or tunnel URL)
npx expo run:android # requires Android Studio / SDK + a deviceM0 spike — see spike-m0/README.md for the relay, the tracker app, and the JB–SG drive test protocol.
Every push to main that touches server/ publishes
ghcr.io/isaactan98/car-community-app/server:latest (linux/amd64 + arm64). Tags v*
additionally publish semver tags. On the homelab:
# docker-compose.yml
services:
runs-server:
image: ghcr.io/isaactan98/car-community-app/server:latest
restart: unless-stopped
ports:
# Bind to the tailnet IP only — never "4000:4000" (all interfaces).
# server/docker-compose.yml reads this from TAILNET_IP in server/.env.
- "100.x.y.z:4000:4000"
volumes:
- ./data:/app/data # SQLite lives here
environment:
INVITE_CODES: "RUNS-XXXX" # seeded at boot (idempotent, comma-separated)Point a Cloudflare Tunnel ingress at http://localhost:4000 (WebSockets are
proxied by default). Health probe: GET /healthz. All env vars (PORT,
DB_PATH, GEOFENCE_RADIUS_M, timers…) are documented in
server/README.md.
Getting at the live database — where runs.db actually sits on the
homelab, how to back it up, and how to browse or delete rows from a browser:
server/README.md → Getting at the database.
The snippet above is illustrative; the deployed stack is a hand-written compose
file on the homelab, and server/docker-compose.yml
is the reference build for a fresh host.
If the repo is private, the GHCR package is too: either make the package public (Package settings → Change visibility) or
docker login ghcr.ioon the server with a PAT that hasread:packages.
-
Beta builds: every push to
maintouchingapp/uploads aruns-apkartifact (Actions tab → run → Artifacts). -
Releases: pushing a tag like
v1.0.0builds the APK, signs it, and attaches it to a GitHub Release. Stable link for the group chat:https://github.com/isaactan98/car-community-app/releases/latest/download/runs.apk
APKs are signed with the release keystore when the three ANDROID_* secrets
are set — see credentials/README.md (local-only
directory, never committed). Without them, builds fall back to the shared RN
debug key: installable, but don't distribute those to the group.
| Workflow | Trigger | Does |
|---|---|---|
ci.yml |
every push / PR | server tests, app typecheck + tests, relay smoke test |
server-docker.yml |
push to main touching server/ |
build + push server:latest to GHCR |
app-apk.yml |
push to main touching app/ |
build APK, upload as artifact |
app-ios.yml |
manual (Actions tab) | build + sign iOS via EAS — needs no Mac, see docs/ios-build.md |
release.yml |
tag v* |
semver Docker image + signed APK attached to a GitHub Release |
git tag v1.0.0
git push origin v1.0.0- M0 drive test (the kill-switch): real phone, real JB–SG drive, battery numbers — protocol in spike-m0/README.md.
- OEM battery exemptions on the group's phones (Xiaomi/Samsung/Oppo/ Vivo/Huawei steps in the spike README) — background tracking dies without them on some devices.