Files
bincio-activity/docs/deployment/multi-user.md
T
2026-04-08 19:37:33 +02:00

6.0 KiB

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

Architecture

internet
    │
    ▼
 nginx / caddy
    ├── /*      → static files (site/dist/)
    └── /api/*  → proxy → bincio serve (127.0.0.1:4041)

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.

Data layout

/data/                          ← BINCIO_DATA_DIR
  instance.db                   ← SQLite: users, sessions, invites
  index.json                    ← shard manifest (no activity data)
  {handle}/
    index.json                  ← user's BAS feed (activities)
    _merged/                    ← sidecar-merged output (served to browser)
    activities/
    edits/
    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.

Step 1 — Initialise the instance

uv sync --extra serve

uv run bincio init \
  --data-dir /var/bincio \
  --handle dave \
  --password 'your-password' \
  --display-name "Dave" \
  --name "My Bincio"

This creates:

  • /var/bincio/instance.db — SQLite database
  • /var/bincio/dave/ — admin user data directory
  • /var/bincio/index.json — root shard manifest (with "private": true)
  • Prints a first invite code

bincio init is idempotent — safe to re-run.

Step 2 — Extract activities

uv run bincio extract \
  --input ~/activity-files \
  --output /var/bincio/dave

Step 3 — Build the site

cd site && npm install && cd ..

uv run bincio render \
  --data-dir /var/bincio \
  --site-dir site

# 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

Incremental rebuild (one user only):

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

server {
    listen 443 ssl;
    server_name example.com;

    root /var/www/bincio;   # → site/dist/

    location / {
        try_files $uri $uri/ $uri.html =404;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:4041;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Copy site/dist/ to /var/www/bincio after each build.

Step 5 — Start bincio serve

uv run bincio serve \
  --data-dir /var/bincio \
  --site-dir /path/to/site

As a systemd service:

[Unit]
Description=bincio serve
After=network.target

[Service]
Type=simple
User=bincio
WorkingDirectory=/home/bincio/bincio-activity
ExecStart=uv run bincio serve --data-dir /var/bincio --site-dir site
Restart=on-failure

[Install]
WantedBy=multi-user.target

Inviting users

After initialising, bincio init prints an invite code. To generate more:

# From the admin account, via the browser at /invites/
# Or directly in the database:
python3 -c "
from pathlib import Path
from bincio.serve.db import open_db, create_invite
db = open_db(Path('/var/bincio'))
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).

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/.

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)

# 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.

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.

Federation

To follow another bincio instance, add a shard entry to the root index.json:

{
  "shards": [
    { "handle": "dave",  "url": "dave/_merged/index.json" },
    { "handle": "alice", "url": "https://alice.example.com/index.json" }
  ]
}

The browser fetches and merges remote shards concurrently. Remote activities appear in the combined feed with @alice attribution.

See also