unify single user and multi user behaviour

This commit is contained in:
Davide Scaini
2026-04-09 08:58:35 +02:00
parent 2007f53580
commit 98c42dc443
25 changed files with 678 additions and 232 deletions
+20 -15
View File
@@ -96,9 +96,27 @@ index.json
## Deployment modes
Single-user and multi-user share the same data layout. The only difference is whether `instance.db` exists (which enables auth).
### Data layout (always)
```
{data-root}/
index.json ← shard manifest (always; one shard for single-user)
instance.db ← SQLite auth (only in multi-user, created by bincio init)
{handle}/
index.json ← user's BAS feed
_merged/ ← sidecar-merged output
activities/
edits/
athlete.json
```
### Single-user (static)
No server process required. Run `bincio render`, drop `site/dist/` anywhere. The edit drawer requires `bincio edit` running locally and `PUBLIC_EDIT_URL` set in `site/.env`.
No login, no server. Run `bincio dev --data-dir {root}` or `bincio render`, drop `site/dist/` anywhere. The site opens directly at `/u/{handle}/`. The "Feed" tab (combined feed) is hidden — there's only one user.
The edit drawer requires `bincio edit` running locally and `PUBLIC_EDIT_URL` set in `site/.env`.
### Multi-user (VPS)
@@ -113,20 +131,7 @@ nginx / caddy
`bincio serve` is a FastAPI application that owns auth, user management, and write operations. It never serves static files. nginx handles TLS and static file serving.
Data is partitioned per user:
```
/data/
instance.db ← SQLite: users, sessions, invites
index.json ← root shard manifest (no activity data)
{handle}/
index.json ← user's BAS feed
_merged/ ← sidecar-merged output
activities/
edits/
```
The root `index.json` is a shard manifest that lists user shard URLs. The browser resolves all shards concurrently and merges them into a single feed.
The root `index.json` shard manifest lists all user shard URLs. The browser resolves them concurrently and merges activities into a combined feed at `/`.
### Instance privacy
+44 -50
View File
@@ -1,6 +1,6 @@
# Multi-user deployment
Multiple users share one bincio instance. Activities are public within the instance by default. The `private` flag hides individual activities. The whole instance requires login to view (private by default).
Multiple users share one bincio instance. The whole instance requires login to view (private by default). Activities are visible to all logged-in users; the `private` flag hides individual activities.
## Architecture
@@ -15,12 +15,12 @@ internet
`bincio serve` owns all dynamic behaviour — auth, user management, write operations. nginx serves static files and proxies API routes. `bincio serve` never handles static files.
Sessions are httpOnly cookies (`bincio_session`), stored in SQLite. The Astro site calls `GET /api/me` on page load to detect the logged-in user.
Sessions are httpOnly cookies (`bincio_session`), stored in SQLite. The Astro site calls `GET /api/me` on page load to detect the logged-in user and update nav links.
## Data layout
```
/data/ ← BINCIO_DATA_DIR
/data/ ← instance root
instance.db ← SQLite: users, sessions, invites
index.json ← shard manifest (no activity data)
{handle}/
@@ -28,10 +28,13 @@ Sessions are httpOnly cookies (`bincio_session`), stored in SQLite. The Astro si
_merged/ ← sidecar-merged output (served to browser)
activities/
edits/
athlete.json
strava_token.json
```
The root `index.json` is a shard manifest — it lists user shard URLs but contains no activity data. Each user's `{handle}/index.json` is a valid standalone BAS feed (usable for federation). The browser resolves shards concurrently and merges them.
The root `index.json` is a shard manifest — it lists user shard URLs but contains no activity data. Each user's `{handle}/index.json` is a valid standalone BAS feed. The browser resolves all shards concurrently and merges them into a combined feed.
This is the same layout used for single-user deployments — the only addition is `instance.db`.
## Step 1 — Initialise the instance
@@ -41,9 +44,9 @@ uv sync --extra serve
uv run bincio init \
--data-dir /var/bincio \
--handle dave \
--password 'your-password' \
--display-name "Dave" \
--name "My Bincio"
# prompted for password
```
This creates:
@@ -56,10 +59,11 @@ This creates:
## Step 2 — Extract activities
Pass the **instance root** to `--output`. The handle is appended automatically:
```bash
uv run bincio extract \
--input ~/activity-files \
--output /var/bincio/dave
uv run bincio extract --output /var/bincio
# → writes to /var/bincio/dave/
```
## Step 3 — Build the site
@@ -74,17 +78,16 @@ uv run bincio render \
# Output: site/dist/
```
In multi-user mode, `bincio render`:
- Runs `merge_all()` for each user's directory
- Rewrites the root `index.json` shard manifest
- Symlinks `site/public/data → /var/bincio`
- Builds the Astro site
`bincio render` always:
1. Runs `merge_all()` for each user's directory
2. Rewrites the root `index.json` shard manifest
3. Symlinks `site/public/data → /var/bincio`
4. Builds the Astro site
Incremental rebuild (one user only):
Incremental rebuild (one user only, no full site rebuild):
```bash
uv run bincio render --data-dir /var/bincio --handle dave
# Re-merges dave's shard, rewrites root manifest — does not rebuild the site
```
## Step 4 — Configure nginx
@@ -136,14 +139,29 @@ Restart=on-failure
WantedBy=multi-user.target
```
## Inviting users
After initialising, `bincio init` prints an invite code. To generate more:
## Local testing (before deploying)
```bash
# From the admin account, via the browser at /invites/
# Or directly in the database:
python3 -c "
# 1. Initialise the instance
uv run bincio init --data-dir /tmp/bincio_test --handle dave
# 2. Extract activities (pass instance root, not user dir)
uv run bincio extract --output /tmp/bincio_test
# → writes to /tmp/bincio_test/dave/
# 3. Start everything with one command
uv run bincio dev --data-dir /tmp/bincio_test
# → http://localhost:4321
```
`bincio dev` detects `instance.db`, starts `bincio serve` (port 4041) in the background and `astro dev` (port 4321) in the foreground. No `.env` file needed. Ctrl+C stops both.
## Inviting users
After initialising, `bincio init` prints a first invite code. Generate more from the browser at `/invites/`, or directly:
```bash
uv run python -c "
from pathlib import Path
from bincio.serve.db import open_db, create_invite
db = open_db(Path('/var/bincio'))
@@ -153,42 +171,17 @@ print(create_invite(db, 'dave'))
Share the invite link: `https://example.com/register/?code=XXXXXXXX`
Invite limits: admins — unlimited. Regular users — 3 invites each (configurable in `bincio/serve/db.py`, `_MAX_USER_INVITES`).
Invite limits: admins — unlimited. Regular users — 3 each (configurable via `_MAX_USER_INVITES` in `bincio/serve/db.py`).
## Instance privacy
By default, `bincio init` sets `"private": true` in the root `index.json`. This means every page (except `/login/` and `/register/`) redirects unauthenticated visitors to `/login/`.
`bincio init` sets `"private": true` in the root `index.json` by default. This means every page (except `/login/` and `/register/`) redirects unauthenticated visitors to `/login/`.
To make the instance public, edit `/var/bincio/index.json` and set `"private": false`. The next `bincio render` will preserve this setting.
## Local testing (before deploying)
```bash
# 1. Initialise a test instance
uv run bincio init --data-dir /tmp/bincio_test --handle dave --password test
# 2. Extract activities into the user's dir
uv run bincio extract --input ~/activity-files --output /tmp/bincio_test/dave
# 3. Build + start the dev server (terminal 1)
uv run bincio render --data-dir /tmp/bincio_test --site-dir site --serve
# 4. Start bincio serve (terminal 2)
uv run bincio serve --data-dir /tmp/bincio_test
```
The Astro dev server proxies `/api/*` to `localhost:4041` (configured in `astro.config.mjs`), so cookies work same-origin. Set `site/.env`:
```
BINCIO_DATA_DIR=/tmp/bincio_test
PUBLIC_EDIT_URL=
```
`PUBLIC_EDIT_URL` empty = edit UI enabled via proxy. The edit/upload button appears when `bincio serve` is running. In production nginx plays the same proxy role.
To make the instance public, edit the root `index.json` and set `"private": false`. The next `bincio render` preserves this setting.
## Per-user Strava sync
Each user connects their own Strava account. The OAuth token is stored in `/var/bincio/{handle}/strava_token.json`. The "Connect Strava" and "Sync" buttons in the upload modal work per-session — each user syncs only their own activities.
Each user connects their own Strava account. The OAuth token is stored in `{handle}/strava_token.json`. The "Sync from Strava" button in the upload modal works per-session — each user syncs only their own activities.
## Federation
@@ -209,5 +202,6 @@ The browser fetches and merges remote shards concurrently. Remote activities app
- [CLI reference — bincio init](../reference/cli.md#bincio-init)
- [CLI reference — bincio serve](../reference/cli.md#bincio-serve)
- [CLI reference — bincio dev](../reference/cli.md#bincio-dev)
- [API reference](../reference/api.md)
- [BAS schema — instance manifest](../../SCHEMA.md#instance-manifest)
+45 -14
View File
@@ -1,32 +1,65 @@
# Single-user deployment
One person, one machine, all your data stays with you. This is the default and simplest mode.
One person, one machine, all your data stays with you. No login, no server process.
## Data layout
All data lives under your instance root in a per-user subdirectory:
```
~/bincio_data/ ← instance root (output.dir in config)
index.json ← shard manifest (generated by bincio render/dev)
yourname/
index.json ← your BAS feed
_merged/ ← sidecar-merged output (served to browser)
activities/
edits/
athlete.json
```
`bincio extract` writes into `yourname/` automatically — pass the instance root to `--output`, not the user directory.
## Local development
```bash
uv run bincio dev --data-dir ~/bincio_data
# → http://localhost:4321/u/yourname/
```
`bincio dev` without an `instance.db` runs in single-user mode: no login, no API server, just `astro dev`.
## GitHub Pages (free, automated)
```bash
uv run bincio render --deploy github
uv run bincio render --data-dir ~/bincio_data --deploy github
```
This builds `site/dist/` and pushes it to the `gh-pages` branch. Requires `npx gh-pages` (`npm install -g gh-pages`).
Builds `site/dist/` and pushes it to the `gh-pages` branch. Requires `npx gh-pages` (`npm install -g gh-pages`).
Set the repository to serve from the `gh-pages` branch in GitHub → Settings → Pages.
## Static hosting (Netlify, Vercel, Cloudflare Pages, etc.)
Build locally and deploy the `site/dist/` directory. Or set up CI:
Build locally and deploy `site/dist/`:
```bash
uv run bincio render --data-dir ~/bincio_data
# upload site/dist/ to your host
```
Or set up CI:
```yaml
# .github/workflows/deploy.yml (example)
- run: uv run bincio render
- run: uv run bincio render --data-dir ~/bincio_data
- uses: actions/upload-pages-artifact@v3
with:
path: site/dist
```
## VPS with nginx
## VPS with nginx (read-only)
Serve `site/dist/` as a static directory. No server process needed for read-only access.
Serve `site/dist/` as a static directory:
```nginx
server {
@@ -40,7 +73,7 @@ server {
### Enable the edit UI on a VPS
If you want to edit activities from the browser while on your VPS:
To edit activities from the browser on your VPS, run `bincio edit` and proxy `/api/*` to it:
```nginx
server {
@@ -53,7 +86,6 @@ server {
try_files $uri $uri/ $uri.html =404;
}
# Proxy /api/* to bincio edit (local-only, never exposed directly)
location /api/ {
proxy_pass http://127.0.0.1:4041;
proxy_set_header Host $host;
@@ -61,21 +93,20 @@ server {
}
```
Then run `bincio edit` as a background service:
```bash
uv sync --extra edit
uv run bincio edit --data-dir ~/bincio_data
```
And set `PUBLIC_EDIT_URL=` (empty — the proxy makes /api/ same-origin) in your environment before building.
Set `PUBLIC_EDIT_URL=` (empty — the proxy makes `/api/` same-origin) in your environment before building.
## Keeping the site up to date
After extracting new activities or editing sidecars:
```bash
uv run bincio extract # process new files
uv run bincio render # rebuild site/dist/
uv run bincio extract --data-dir ~/bincio_data # process new files
uv run bincio render --data-dir ~/bincio_data # rebuild site/dist/
rsync -av site/dist/ user@server:/var/www/bincio/dist/
```
+50 -18
View File
@@ -23,11 +23,11 @@ cp extract_config.example.yaml extract_config.yaml
$EDITOR extract_config.yaml
```
Minimum configuration:
Set your handle and input directory at minimum:
```yaml
owner:
handle: yourname # used in URLs and federation
handle: yourname # used in URLs: /u/yourname/
display_name: Your Name
input:
@@ -35,54 +35,86 @@ input:
- ~/your-activity-data/activities
output:
dir: ~/bincio_data
dir: ~/bincio_data # instance root; activities go into ~/bincio_data/yourname/
```
The config file is gitignored — safe to store Strava credentials here.
---
## Extract
```bash
uv run bincio extract
```
This reads all GPX/FIT/TCX files (including `.gz` variants), deduplicates them, and writes a BAS data store to `~/bincio_data/`.
Reads all GPX/FIT/TCX files and writes a BAS data store to `~/bincio_data/yourname/`. Re-running is safe — unchanged files are skipped (hash-based).
Re-running is safe — unchanged files are skipped (hash-based). To force a full re-extract: `rm -rf ~/bincio_data && uv run bincio extract`.
> `--output` overrides `output.dir` from the config and is the **instance root**,
> not the user directory. The handle is always appended automatically:
> `bincio extract --output ~/bincio_data` → writes to `~/bincio_data/yourname/`.
## Build the site
---
## Single-user — no login, static site
```bash
# Build and preview
cd site && npm install && cd ..
cp site/.env.example site/.env
# Edit site/.env: set BINCIO_DATA_DIR=~/bincio_data
uv run bincio render
uv run bincio dev --data-dir ~/bincio_data
# → http://localhost:4321
```
Output is in `site/dist/` — a folder of static files. Drop it anywhere: GitHub Pages, Netlify, a Raspberry Pi, a USB stick.
`bincio dev` merges edits, builds the shard manifest, and starts `astro dev`. No login required — the site opens directly at `/u/yourname/`.
## Dev mode
To build for deployment (no live server):
```bash
uv run bincio render --serve # → http://localhost:4321
uv run bincio render --data-dir ~/bincio_data
# output: site/dist/
```
## Enable the edit UI
See [Single-user deployment](deployment/single-user.md).
The edit UI lets you rename activities, add descriptions, upload photos, and sync from Strava — all from the browser.
---
## Multi-user — shared instance, login required
```bash
uv sync --extra serve
# One-time: create the instance database and admin account
uv run bincio init --data-dir ~/bincio_data --handle yourname
# Start everything
uv run bincio dev --data-dir ~/bincio_data
# → http://localhost:4321 (login with the password set during init)
```
`bincio dev` detects the `instance.db` and automatically starts `bincio serve` alongside `astro dev`. Ctrl+C stops both.
See [Multi-user deployment](deployment/multi-user.md).
---
## Enable the edit UI (single-user)
The edit UI lets you rename activities, add descriptions, upload photos, and sync from Strava — from the browser.
```bash
uv sync --extra edit
uv run bincio edit # starts on http://localhost:4041
uv run bincio edit --data-dir ~/bincio_data
# Add to site/.env:
# PUBLIC_EDIT_URL=http://localhost:4041
```
An Edit button and an Upload ↑ button appear in the nav.
In multi-user mode the edit UI is always available via `bincio serve` — no extra step needed.
---
## Next steps
- [Single-user deployment](deployment/single-user.md) — serve your site on a VPS or GitHub Pages
- [Multi-user deployment](deployment/multi-user.md) — invite friends, shared feed
- [Single-user deployment](deployment/single-user.md) — GitHub Pages, Netlify, VPS
- [Multi-user deployment](deployment/multi-user.md) — VPS with nginx, inviting users
- [CLI reference](reference/cli.md) — all commands and options
- [BAS schema](../SCHEMA.md) — the data format and federation protocol
+45 -3
View File
@@ -4,6 +4,39 @@ All commands are run via `uv run bincio <command>` from the project root.
---
## bincio dev
Start the full local development environment. One command replaces the two-terminal setup.
```bash
uv sync --extra serve
uv run bincio dev [OPTIONS]
```
| Option | Default | Description |
|---|---|---|
| `--data-dir DIR` | auto-detected | BAS data directory (must contain `instance.db`) |
| `--site-dir DIR` | `./site` | Astro project directory |
| `--port PORT` | `4321` | Astro dev server port |
| `--api-port PORT` | `4041` | bincio serve API port |
`bincio dev` runs the following steps automatically:
1. Merges sidecar edits for all users (`merge_all()`)
2. Rewrites the root `index.json` shard manifest
3. Symlinks `site/public/data` → data directory
4. Starts `bincio serve` on `--api-port` in a background thread (**only if `instance.db` exists**)
5. Starts `astro dev` on `--port` in the foreground
No `.env` file needed — `BINCIO_DATA_DIR` and `PUBLIC_EDIT_URL` are set automatically.
Works in both modes:
- **Single-user** (no `instance.db`): no login, no API server, just `astro dev`
- **Multi-user** (`instance.db` present): starts `bincio serve` alongside `astro dev`
Ctrl+C stops everything.
---
## bincio extract
Extract GPX/FIT/TCX files into a BAS data store.
@@ -16,12 +49,21 @@ uv run bincio extract [OPTIONS]
|---|---|---|
| `--config PATH` | `extract_config.yaml` | Path to config file |
| `--input DIR` | from config | Input directory (scanned recursively) |
| `--output DIR` | from config | Output BAS data store directory |
| `--output DIR` | from config | Instance root directory |
| `--file PATH` | — | Extract a single file, print JSON to stdout |
| `--since DATE` | — | Only process files newer than this date (YYYY-MM-DD) |
| `--dev N` | — | Dev mode: sample N files evenly, output to `/tmp/bincio_dev/` |
Extraction is incremental by default — unchanged files (same hash) are skipped. To force a full re-extract: `rm -rf <output_dir>`.
`--output` (and `output.dir` in config) is the **instance root**, not the user directory. The handle from `owner.handle` in `extract_config.yaml` is always appended automatically:
```
bincio extract --output ~/bincio_data
# → writes to ~/bincio_data/{handle}/
```
This applies to both single-user and multi-user setups — the data layout is always the same.
Extraction is incremental by default — unchanged files (same hash) are skipped. To force a full re-extract, delete the user directory: `rm -rf ~/bincio_data/{handle}`.
Supported formats: GPX, FIT, TCX — all with optional `.gz` compression.
@@ -46,7 +88,7 @@ uv run bincio render [OPTIONS]
`bincio render` always:
1. Runs `merge_all()` — applies sidecar edits, produces `_merged/`
2. (Multi-user) Rewrites the root `index.json` shard manifest
2. Rewrites the root `index.json` shard manifest
3. Symlinks `site/public/data` → data directory
4. Runs `astro build` (or `astro dev` with `--serve`)