Files
bincio-activity/docs/deployment/vps.md
T
2026-04-10 12:53:35 +02:00

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


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