7.4 KiB
BincioActivity
Your data. Your server. Your rules. No cloud. No subscriptions. No lock-in.
BincioActivity is a self-hosted, federated activity stats platform. You point it at a folder of GPX/FIT/TCX files, it produces a static website. The website runs anywhere — a Raspberry Pi, GitHub Pages, a USB stick. No database. No server process. No account required.
The philosophy in one sentence: your activity data is yours, it lives as plain files on your disk, and this tool turns those files into a beautiful site you control entirely.
How it works
GPX / FIT / TCX files
│
▼
bincio extract ← Python CLI. Reads files, writes plain JSON.
│
▼
~/bincio_data/ ← BAS data store. Human-readable JSON + GeoJSON.
│
▼
bincio render / npm ← Astro build. Reads JSON, writes static HTML/JS/CSS.
│
▼
site/dist/ ← Drop anywhere. Open index.html. Done.
Everything in ~/bincio_data/ is plain text you can read, edit, back up, or publish to a CDN. The site build is fully reproducible from those files.
Quick start
# 1. Install Python package
pip install bincio # or: uv add bincio
# 2. Configure
cp extract_config.example.yaml extract_config.yaml
$EDITOR extract_config.yaml # set input dirs, output dir, your name
# 3. Extract activities → BAS JSON
bincio extract
# 4. Build the site (requires Node >= 20)
cd site && npm install
ln -sf ~/bincio_data public/data
npm run build
# → open site/dist/index.html
For live development with hot reload:
cd site
ln -sf ~/bincio_data public/data
npm run dev
# → http://localhost:4321
Cheatsheet
Extract
bincio extract # uses extract_config.yaml
bincio extract --input ~/rides --output ~/bincio_data
bincio extract --file ride.gpx # single file, prints JSON to stdout
bincio extract --since 2025-01-01 # only files newer than date
Supported formats: GPX, FIT, TCX — all with optional .gz compression.
Strava bulk export: point metadata_csv at activities.csv to pull in titles, descriptions, and gear.
Extraction is incremental by default (incremental: true in config). Re-running only processes new or changed files. To force a full re-extract, delete ~/bincio_data/ or set incremental: false.
Site dev
cd site
npm run dev # http://localhost:4321 — live reload on data or code changes
npm run build # production build → site/dist/
npm run preview # serve site/dist/ locally to check the production build
The site reads data from site/public/data/. Symlink your BAS store there:
ln -sf ~/bincio_data site/public/data
Python / tests
uv run pytest # full test suite
uv run pytest tests/test_fit.py -x # single file, stop on first failure
uv run bincio --help # CLI help
uv sync # install / update dependencies
Configuration
extract_config.yaml
owner:
handle: yourname
display_name: Your Name
input:
dirs:
- ~/Activities/gpx
- ~/Activities/fit
metadata_csv: ~/strava_export/activities.csv # optional — Strava titles/descriptions
output:
dir: ~/bincio_data
default_privacy: public # public | blur_start | no_gps | private
track:
rdp_epsilon: 0.0001 # GPS track simplification (~11 m at equator)
timeseries_hz: 1 # data samples per second stored in JSON
incremental: true # skip files whose hash hasn't changed
Privacy levels
| Level | GPS track | Stats | Appears in index |
|---|---|---|---|
public |
Full | Yes | Yes |
blur_start |
First/last 200 m removed | Yes | Yes |
no_gps |
Not published | Yes | Yes |
private |
Not published | No | No |
Privacy is enforced at extract time. A private activity never enters index.json and is never served.
The BAS data store
bincio extract produces a directory of plain files — the BincioActivity Schema (BAS) store:
~/bincio_data/
index.json ← summary of all activities + owner info
activities/
2024-05-15T08:30:00Z.json ← full activity: stats, laps, timeseries
2024-05-15T08:30:00Z.geojson ← simplified GPS track (RDP)
index.json is everything the feed page needs — no extra fetches until you open an activity. {id}.json contains the full timeseries (elevation, speed, HR, cadence, power at 1 Hz) for charts and the detail map. Both are human-readable and editable with any text editor.
See SCHEMA.md for the full specification.
Federation (work in progress)
Add a friend's published index.json URL to your site_config.yaml:
data_sources:
- type: local
path: ~/bincio_data
- type: remote
handle: alice
url: https://alice.example.com/bincio/index.json
At build time the renderer fetches their public data and renders it under /friends/alice/. Your site, their data — with full attribution. They control what they publish; you control what you display.
Tech stack
| Layer | Technology |
|---|---|
| Extract | Python 3.12, click, fitdecode, gpxpy, lxml |
| Site framework | Astro 4 (static output) |
| UI components | Svelte 5 |
| Styling | Tailwind CSS v3 |
| Charts | Observable Plot |
| Maps | MapLibre GL v5 + OpenFreeMap tiles |
| Python packages | uv |
| Node packages | npm |
Project layout
bincio/ Python package
extract/
cli.py `bincio extract` entry point
parsers/ GPX, FIT, TCX parsers
sport.py sport name normalisation
metrics.py haversine stats (single-pass)
timeseries.py 1 Hz downsampling
simplify.py RDP track simplification
dedup.py hash-based + near-duplicate detection
strava_csv.py Strava activities.csv reader
writer.py BAS JSON + GeoJSON writer
render/
cli.py `bincio render` stub
schema/
bas-v1.schema.json JSON Schema for BAS format
SCHEMA.md Human-readable BAS specification
site/ Astro project
src/
pages/
index.astro Activity feed
activity/[id].astro Single activity detail
stats/index.astro Yearly heatmaps + totals
components/
ActivityFeed.svelte Card grid, sport filter, pagination
ActivityDetail.svelte Map + stats + charts
ActivityMap.svelte MapLibre GL map
ActivityCharts.svelte Observable Plot charts
StatsView.svelte Heatmap, percentile scaling, sport filter
lib/
types.ts BAS TypeScript types
format.ts Formatting helpers
Why no database?
Databases add operational complexity — backups, migrations, running processes, credentials. Activity data is append-only and read-heavy. Plain JSON files handle this perfectly, are trivially backed up with cp or rsync, can be diffed in git, and work offline. The site is a folder you can zip and email.
Why federation?
Strava, Garmin Connect, and similar platforms are silos. If the company shuts down or changes its terms, your data and your social graph go with it. BincioActivity's federation model is inspired by the open web: you host your own data at a URL, friends subscribe to that URL, and no central authority is involved.