Files
bincio-activity/docs/deployment/vps.md
T
Davide Scaini 4593478863 feedback page
2026-04-10 14:23:31 +02:00

6.7 KiB

VPS deployment guide

Concrete setup for a Debian VPS running a private multi-user bincio instance. Code is deployed directly from your laptop via git push — no GitHub required.

Assumptions

  • Bare Debian 12 VPS with root SSH access
  • You own a domain pointed at the VPS
  • You have Strava API credentials
  • Up to ~30 users

1. Install system dependencies

apt update && apt upgrade -y
apt install -y git curl nginx certbot python3-certbot-nginx sqlite3

Node.js 20 LTS (the Debian package is too old):

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs

uv (manages Python and all Python deps):

curl -LsSf https://astral.sh/uv/install.sh | sh
# add to PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

2. Set up the code directory

mkdir -p /opt/bincio
git init --bare /opt/bincio-repo.git

Create the post-receive hook at /opt/bincio-repo.git/hooks/post-receive:

#!/bin/bash
set -e

REPO=/opt/bincio-repo.git
DEPLOY=/opt/bincio
DATA=/var/bincio/data

echo "--- Checking out code ---"
git --work-tree=$DEPLOY --git-dir=$REPO checkout -f

echo "--- Syncing Python deps ---"
cd $DEPLOY
~/.local/bin/uv sync

echo "--- Syncing JS deps ---"
cd $DEPLOY/site
npm install --silent

echo "--- Building site ---"
cd $DEPLOY
~/.local/bin/uv run bincio render --data-dir $DATA --site-dir $DEPLOY/site

echo "--- Copying dist to webroot ---"
rsync -a --delete $DEPLOY/site/dist/ /var/www/bincio/

echo "--- Restarting API ---"
systemctl restart bincio

echo "--- Done ---"
chmod +x /opt/bincio-repo.git/hooks/post-receive
mkdir -p /var/www/bincio

3. First deploy from your laptop

Add the VPS as a git remote (run this locally, once):

git remote add vps root@<your-vps-ip>:/opt/bincio-repo.git

Push your code:

git push vps main

The hook checks out the code, installs deps, and builds the site. Subsequent pushes (including unpublished branches) work the same way:

git push vps mobile_app   # deploy any branch directly

4. Initialise the instance

cd /opt/bincio

uv run bincio init \
  --data-dir /var/bincio/data \
  --handle dave \
  --display-name "Dave" \
  --name "My Bincio"
# prompted for password; prints a first invite code

Set the user cap:

sqlite3 /var/bincio/data/instance.db \
  "INSERT INTO settings VALUES ('max_users', '30');"

5. Prepare your own activities

Source files (raw GPX/FIT) live separately from the BAS output:

/var/bincio/sources/dave/    ← raw activity files, rsync'd from laptop
/var/bincio/data/dave/       ← BAS JSON output (bincio extract writes here)

Configure /opt/bincio/extract_config.yaml on the server to point to your source dir:

sources:
  - path: /var/bincio/sources/dave/activities
    type: strava_export
  - path: /var/bincio/sources/dave/activities.csv
    type: strava_csv

output:
  dir: /var/bincio/data

Sync and extract (run from your laptop or SSH in):

# push raw files from laptop
rsync -avz ~/your-activity-data/ root@<vps>:/var/bincio/sources/dave/

# extract on server
ssh root@<vps> "cd /opt/bincio && uv run bincio extract"

# rebuild site
ssh root@<vps> "cd /opt/bincio && \
  uv run bincio render --data-dir /var/bincio/data --site-dir site && \
  rsync -a --delete site/dist/ /var/www/bincio/"

6. systemd service

Create /etc/systemd/system/bincio.service:

[Unit]
Description=BincioActivity API
After=network.target

[Service]
WorkingDirectory=/opt/bincio
ExecStart=/root/.local/bin/uv run bincio serve \
    --data-dir /var/bincio/data \
    --site-dir /opt/bincio/site \
    --host 127.0.0.1 \
    --port 4041
EnvironmentFile=/etc/bincio/secrets.env
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Create /etc/bincio/secrets.env:

mkdir -p /etc/bincio
chmod 700 /etc/bincio
cat > /etc/bincio/secrets.env <<EOF
STRAVA_CLIENT_ID=your_client_id
STRAVA_CLIENT_SECRET=your_client_secret
EOF
chmod 600 /etc/bincio/secrets.env

Enable and start:

systemctl daemon-reload
systemctl enable --now bincio
systemctl status bincio

7. nginx

Create /etc/nginx/sites-available/bincio:

server {
    listen 80;
    server_name yourdomain.com;

    root /var/www/bincio;
    index index.html;

    # API → bincio serve
    location /api/ {
        proxy_pass http://127.0.0.1:4041;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 120s;   # Strava sync can be slow
    }

    # Static files
    location / {
        try_files $uri $uri/ $uri.html =404;
    }
}
ln -s /etc/nginx/sites-available/bincio /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

8. SSL

certbot --nginx -d yourdomain.com
# certbot edits the nginx config and sets up automatic renewal

9. Invite users

After bincio init prints the first invite code, you can generate more from the browser at /u/{handle}/athlete/Invites button (visible only to the page owner), or directly via the CLI:

sqlite3 /var/bincio/data/instance.db \
  "INSERT INTO invites (code, created_by, created_at) \
   VALUES (upper(hex(randomblob(4))), 'dave', unixepoch());"

Share the link: https://yourdomain.com/register/?code=XXXXXXXX

Each new user uploads their activities via the + button in the top nav (supports bulk GPX/FIT/TCX drop). They can later connect Strava for incremental sync from the same modal.


Reading user feedback

Users can submit feedback from the Feedback link in the nav (visible when logged in). Submissions are stored as JSON on the server:

/var/bincio/data/_feedback/
  {handle}.json       ← one file per user, array of submissions
  {handle}/           ← attached images

To read all feedback:

cat /var/bincio/data/_feedback/*.json | python3 -m json.tool

Per-user only:

cat /var/bincio/data/_feedback/pres.json | python3 -m json.tool

Day-to-day operations

Task Command
Deploy code update git push vps main (from laptop)
Sync your raw files rsync -avz ~/your-activity-data/ root@<vps>:/var/bincio/sources/dave/
Re-extract after sync ssh root@<vps> "cd /opt/bincio && uv run bincio extract" then push again to rebuild
View API logs journalctl -u bincio -f
Restart API systemctl restart bincio
Check nginx logs tail -f /var/log/nginx/error.log
Renew SSL (auto) certbot renew --dry-run

See also