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
+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)