The deploy README described how to start the container but not how to get into it, which left the first-run experience at a login page nobody can get past. Adds the actual command, both the interactive and the piped form, and says why there is no --password flag. D18 records the three decisions worth arguing with later rather than rediscovering: why this is a CLI instead of a bootstrap HTTP endpoint or an env var (both rejected, with reasons), why it refuses an existing email, why it is not restricted to the first user, and why the admin role is recorded but not yet enforced. Numbered D18 because D17 was taken by the registry-TLS decision that merged while this branch was in flight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2ZKeWkZV7ehf7fivrAkkG
bike-app
A self-hosted cycling app: syncs rides from a Bryton Rider 650, tracks mileage like Strava, and adds a spare-parts inventory and a maintenance record with mileage-milestone reminders.
Status: planning complete, no code written yet.
Why
Today the Rider 650 syncs over Bluetooth to the Bryton Active phone app, which forwards to Strava — but Active has no background sync, so you have to remember to open the app. Research found a better path that removes the phone entirely:
ride ends -> Rider 650 joins home Wi-Fi (Main Menu -> Data Sync)
-> uploads to Bryton cloud
-> this app's poller fetches the ORIGINAL FIT file
-> rides, wear tracking, and push reminders
That's also higher fidelity than the current route — Strava's API can only ever return smoothed streams, never the original file.
Docs
| File | What's in it |
|---|---|
docs/PLAN.md |
The full implementation plan: stack, schema, ingestion pipeline, auth, notifications, phased roadmap, CI/CD, risks, verification |
docs/RESEARCH.md |
Raw findings: the Bryton cloud protocol (endpoints, headers, auth), FIT library comparisons, maintenance interval tables, self-hostable geo services, Gitea Actions gotchas |
docs/DECISIONS.md |
Every decision taken, what was rejected, and why |
Planned stack
Python 3.12 / FastAPI / SQLAlchemy async / PostgreSQL 16 + PostGIS, procrastinate for jobs,
SvelteKit static SPA as an installable PWA, MapLibre GL, all behind Caddy in Docker Compose.
Source control and CI in self-hosted Gitea with an act_runner on the same box.
Four containers in v1, under 2GB RAM.
Next steps
- Verify on the Rider 650: does
Main Menu -> Data Syncupload automatically on joining Wi-Fi, or only on manual trigger? This determines how completely the phone leaves the loop. - Plug the 650 in over USB and
ls -Rthe mounted volume to confirm the real.fitpath (documented asBryton/Activities/, but worth confirming). - Grab a real
.fitfile from it and run it throughfitdecode— Bryton's encoder is not Garmin's, and the schema should be checked against reality before it's written. - Then Phase 0: scaffolding and CI (see the roadmap in
docs/PLAN.md).