update readme
This commit is contained in:
@@ -1,68 +1,110 @@
|
||||
# BincioActivity
|
||||
|
||||
A federated, open-source, self-hosted activity stats platform.
|
||||
Own your data. Share what you want. Follow friends by URL.
|
||||
> **Your data. Your server. Your rules.**
|
||||
> No cloud. No subscriptions. No lock-in.
|
||||
|
||||
## What it is
|
||||
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.
|
||||
|
||||
BincioActivity turns a folder of GPX/FIT/TCX files into a beautiful, modern
|
||||
static website — no database, no server required. It can run from the local
|
||||
filesystem, GitHub Pages, or any static host.
|
||||
**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.
|
||||
|
||||
**Federation**: anyone can "follow" a friend's data by adding a URL to their
|
||||
config. Friends' activities appear in your site, attributed to them.
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
```bash
|
||||
# Install
|
||||
# 1. Install Python package
|
||||
pip install bincio # or: uv add bincio
|
||||
|
||||
# Extract your activities
|
||||
# 2. Configure
|
||||
cp extract_config.example.yaml extract_config.yaml
|
||||
# edit extract_config.yaml with your paths
|
||||
$EDITOR extract_config.yaml # set input dirs, output dir, your name
|
||||
|
||||
# 3. Extract activities → BAS JSON
|
||||
bincio extract
|
||||
|
||||
# Build the site (requires Node ≥ 20)
|
||||
# 4. Build the site (requires Node >= 20)
|
||||
cd site && npm install
|
||||
BINCIO_DATA_DIR=~/bincio_data npm run build
|
||||
# open site/dist/index.html
|
||||
ln -sf ~/bincio_data public/data
|
||||
npm run build
|
||||
# → open site/dist/index.html
|
||||
```
|
||||
|
||||
## Two stages
|
||||
|
||||
### Stage 1 — Extract (`bincio extract`)
|
||||
|
||||
Reads GPX, FIT, TCX files (including `.gz` compressed) and writes a
|
||||
BincioActivity Schema (BAS) data store: plain JSON + GeoJSON files.
|
||||
|
||||
For live development with hot reload:
|
||||
```bash
|
||||
cd site
|
||||
ln -sf ~/bincio_data public/data
|
||||
npm run dev
|
||||
# → http://localhost:4321
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cheatsheet
|
||||
|
||||
### Extract
|
||||
|
||||
```bash
|
||||
bincio extract # uses extract_config.yaml
|
||||
bincio extract --input ~/rides --output ~/bincio_data
|
||||
bincio extract --file ride.gpx # single file → stdout
|
||||
bincio extract --since 2025-01-01 # incremental
|
||||
bincio extract --file ride.gpx # single file, prints JSON to stdout
|
||||
bincio extract --since 2025-01-01 # only files newer than date
|
||||
```
|
||||
|
||||
Supported sources:
|
||||
- GPX (generic, Garmin extensions)
|
||||
- FIT (Garmin, Hammerhead Karoo)
|
||||
- TCX (including Garmin's https:// namespace variant)
|
||||
- All of the above gzip-compressed (`.gz`)
|
||||
- Strava bulk export (`activities.csv` carries titles and descriptions)
|
||||
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.
|
||||
|
||||
### Stage 2 — Render (`bincio render`)
|
||||
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`.
|
||||
|
||||
Generates a static site from the BAS data store using Astro.
|
||||
### Site dev
|
||||
|
||||
```
|
||||
```bash
|
||||
cd site
|
||||
BINCIO_DATA_DIR=~/bincio_data npm run dev # development
|
||||
BINCIO_DATA_DIR=~/bincio_data npm run build # production build → site/dist/
|
||||
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:
|
||||
```bash
|
||||
ln -sf ~/bincio_data site/public/data
|
||||
```
|
||||
|
||||
### Python / tests
|
||||
|
||||
```bash
|
||||
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
|
||||
### `extract_config.yaml`
|
||||
|
||||
```yaml
|
||||
owner:
|
||||
@@ -73,7 +115,7 @@ input:
|
||||
dirs:
|
||||
- ~/Activities/gpx
|
||||
- ~/Activities/fit
|
||||
metadata_csv: ~/strava_export/activities.csv # optional Strava metadata
|
||||
metadata_csv: ~/strava_export/activities.csv # optional — Strava titles/descriptions
|
||||
|
||||
output:
|
||||
dir: ~/bincio_data
|
||||
@@ -81,36 +123,46 @@ output:
|
||||
default_privacy: public # public | blur_start | no_gps | private
|
||||
|
||||
track:
|
||||
rdp_epsilon: 0.0001 # GPS simplification (~11m at equator)
|
||||
rdp_epsilon: 0.0001 # GPS track simplification (~11 m at equator)
|
||||
timeseries_hz: 1 # data samples per second stored in JSON
|
||||
|
||||
incremental: true # skip already-processed files
|
||||
incremental: true # skip files whose hash hasn't changed
|
||||
```
|
||||
|
||||
### site/.env
|
||||
### 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_DIR=/path/to/bincio_data
|
||||
```
|
||||
|
||||
## The BincioActivity Schema (BAS)
|
||||
|
||||
The data store is a directory of plain JSON files:
|
||||
|
||||
```
|
||||
bincio_data/
|
||||
index.json ← activity feed + owner manifest
|
||||
~/bincio_data/
|
||||
index.json ← summary of all activities + owner info
|
||||
activities/
|
||||
{id}.json ← full activity with timeseries
|
||||
{id}.geojson ← simplified GPS track
|
||||
2024-05-15T08:30:00Z.json ← full activity: stats, laps, timeseries
|
||||
2024-05-15T08:30:00Z.geojson ← simplified GPS track (RDP)
|
||||
```
|
||||
|
||||
See `SCHEMA.md` for the full specification. The schema is versioned and
|
||||
published as a standalone document so anyone can write importers in any
|
||||
language.
|
||||
`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.
|
||||
|
||||
## Federation
|
||||
See `SCHEMA.md` for the full specification.
|
||||
|
||||
Add a friend's `index.json` URL to your `site_config.yaml`:
|
||||
---
|
||||
|
||||
## Federation (work in progress)
|
||||
|
||||
Add a friend's published `index.json` URL to your `site_config.yaml`:
|
||||
|
||||
```yaml
|
||||
data_sources:
|
||||
@@ -118,45 +170,70 @@ data_sources:
|
||||
path: ~/bincio_data
|
||||
- type: remote
|
||||
handle: alice
|
||||
url: https://alice.github.io/bincio/index.json
|
||||
url: https://alice.example.com/bincio/index.json
|
||||
```
|
||||
|
||||
At build time the renderer fetches their public data and renders it under
|
||||
`/friends/alice/`.
|
||||
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.
|
||||
|
||||
## Privacy
|
||||
|
||||
Privacy is enforced at the data layer — activities never leave your control:
|
||||
|
||||
| Level | GPS track | Stats visible |
|
||||
|---|---|---|
|
||||
| `public` | Full track | Yes |
|
||||
| `blur_start` | First/last 200 m removed | Yes |
|
||||
| `no_gps` | Not published | Yes |
|
||||
| `private` | Not published | Not in index |
|
||||
---
|
||||
|
||||
## Tech stack
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Extract | Python 3.12, click, fitdecode, gpxpy, lxml, rdp |
|
||||
| Site framework | Astro (static generation) |
|
||||
| Extract | Python 3.12, click, fitdecode, gpxpy, lxml |
|
||||
| Site framework | Astro 4 (static output) |
|
||||
| UI components | Svelte 5 |
|
||||
| Styling | Tailwind CSS |
|
||||
| Styling | Tailwind CSS v3 |
|
||||
| Charts | Observable Plot |
|
||||
| Maps | MapLibre GL + OpenFreeMap tiles |
|
||||
| Package manager (Python) | uv |
|
||||
| Package manager (Node) | npm |
|
||||
| Maps | MapLibre GL v5 + OpenFreeMap tiles |
|
||||
| Python packages | uv |
|
||||
| Node packages | npm |
|
||||
|
||||
## Development
|
||||
---
|
||||
|
||||
```bash
|
||||
# Python
|
||||
uv sync
|
||||
uv run pytest
|
||||
uv run bincio --help
|
||||
## Project layout
|
||||
|
||||
# Site
|
||||
cd site && npm install
|
||||
BINCIO_DATA_DIR=/tmp/bincio_test npm run dev
|
||||
```
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user