Rebuild Runbook

How to stand BackupBloc Control back up on a fresh server — from bare Debian to a fully-licensed, TLS-terminated, admin-UI-serving host at license.backupbloc.com.

Read this first. These steps assume you are rebuilding from scratch on a new server. If you just want to push a code change to the existing server, jump to Redeploy code.

account_treeOverview

BackupBloc Control is a single Flask app + SQLite DB + static admin UI, fronted by nginx with Let's Encrypt TLS. It runs as one systemd service (backupbloc-control.service) on port 5100, and nginx terminates HTTPS on 443.

The server has exactly one job: it is the license & update authority that every customer panel phones home to. Customer panels identify themselves by their public outbound IP — no tokens, no keys. If the control server is offline, panels keep working for 12 hours on the last cached validation, then lock down.

What you need before you start

buildRebuild steps

1

Provision a fresh Debian box

Any hosting provider will do — we've used standard VPS boxes. Minimum: 1 vCPU, 1 GB RAM, 20 GB disk. The app is tiny; most of the disk goes to uploaded release tarballs.

Log in as root and confirm the base system is up-to-date:

apt-get update -q && apt-get dist-upgrade -yq
2

Point DNS at the new server

Create an A record for license.backupbloc.com → your new server's public IP. Wait for it to resolve globally before the next step — certbot will fail otherwise.

Verify from your laptop:

dig +short license.backupbloc.com
# should return the new server IP

Let's Encrypt issues the cert by making an HTTP-01 callback to http://license.backupbloc.com/. If DNS isn't propagated yet, the installer's certbot step fails and you'll have to re-run it manually.

3

Upload the code to the new server

From your dev machine, either clone from git or SCP the repo over. The fastest way is to use the deployment helpers already in the repo, but for a fresh box you need the whole repo not just the app files. Simplest:

# On the new server, as root:
apt-get install -yq git
git clone <your-repo-url> /root/backupbloc-control
cd /root/backupbloc-control

Or, if you don't keep a remote git mirror, from your laptop:

scp -r ./backupbloc-control root@<new-ip>:/root/
4

Run the installer

One command does everything — installs packages, copies the app to /opt/backupbloc-control/, writes /etc/backupbloc-control/config.env, registers the systemd unit, configures nginx, and requests a TLS certificate:

cd /root/backupbloc-control
bash install-server.sh --hostname license.backupbloc.com

The installer is idempotent — re-running it on a working box is safe.

The installer already patches the known Debian 12 certbot crash (pyOpenSSL 23.0.0 breaks against modern cryptography). It upgrades pyopenssl to a matching version automatically.

Useful flags

FlagPurpose
--hostnamePublic FQDN. Certbot issues a cert for this name. Omit for a private/LAN install.
--api-portOverride the internal Flask port (default 5100).
--skip-certSkip Let's Encrypt — useful when DNS isn't ready yet. Re-run certbot later.
5

Grab the initial admin password

The installer prints it at the end. If you missed it:

journalctl -u backupbloc-control.service | grep -i "initial admin password"

Log in at https://license.backupbloc.com/ (user admin), then go to Settings → Security and change the password immediately.

Smoke test

# From anywhere:
curl -s https://license.backupbloc.com/api/health
# → {"ok":true,"service":"backupbloc-control"}

# Unlicensed IPs always get 403:
curl -s -X POST https://license.backupbloc.com/api/validate \
  -H "Content-Type: application/json" -d "{}"
# → {"valid":false,"reason":"No license for this IP","ip":"..."}

Issue yourself a license immediately. Go to Licenses → New and create one for the customer panel's public IP (e.g. your own panel at 119.148.82.102). If you don't, the panel locks itself out once Phase B is deployed.

restoreRestoring from a backup

If you're rebuilding because the old server died, run steps 1–4 on the new box (this creates a fresh empty DB), then stop the service, overwrite the data files, and start it again:

systemctl stop backupbloc-control.service

# Restore from wherever you keep the backup (rsync, S3, restic, etc.)
cp /path/to/backup/licenses.db   /etc/backupbloc-control/licenses.db
cp /path/to/backup/users.json    /etc/backupbloc-control/users.json
cp /path/to/backup/sessions.json /etc/backupbloc-control/sessions.json
rsync -a /path/to/backup/releases/ /var/lib/backupbloc-control/releases/

chown -R root:root /etc/backupbloc-control /var/lib/backupbloc-control
chmod 700 /etc/backupbloc-control
chmod 600 /etc/backupbloc-control/*.json /etc/backupbloc-control/licenses.db /etc/backupbloc-control/config.env

systemctl start backupbloc-control.service
systemctl status backupbloc-control.service

All admin sessions in sessions.json will still work — no need to re-login on your laptop unless sessions have expired by time.

settingsDay-2 operations

Redeploying code changes

From your dev machine, with BBC_PASS set:

export BBC_HOST=license.backupbloc.com BBC_PASS=<root password>

python _deploy_api.py     # pushes license-api.py + schema.sql, restarts service
python _deploy_html.py    # pushes admin/index.html

Both scripts use paramiko over SSH. They restart the systemd service automatically when needed.

Logs and debugging

# Live tail
journalctl -u backupbloc-control.service -f

# Last 200 lines
journalctl -u backupbloc-control.service -n 200 --no-pager

# Nginx access/error
tail -f /var/log/nginx/access.log /var/log/nginx/error.log

# Service state
systemctl status backupbloc-control.service

What to back up on this server

The only stateful paths you need to protect:

PathWhy
/etc/backupbloc-control/licenses.dbAll issued licenses, customers, check-in history, audit trail.
/etc/backupbloc-control/users.jsonAdmin accounts (hashed passwords).
/etc/backupbloc-control/sessions.jsonActive admin sessions. Safe to lose — you'll just re-login.
/etc/backupbloc-control/config.envPort, trust-proxy, grace-hours config.
/var/lib/backupbloc-control/releases/Uploaded signed panel release tarballs.

Everything else — the app code, systemd unit, nginx config, certbot state — is reproduced by re-running the installer.

TLS certificate renewal

Certbot installs a systemd timer that auto-renews. Check it:

systemctl list-timers | grep certbot
certbot certificates

menu_bookReference

File locations (on the control server)

PathContents
/opt/backupbloc-control/api/Flask app (license-api.py, schema.sql)
/opt/backupbloc-control/admin/Admin UI (nginx-served static files)
/etc/backupbloc-control/config.envRuntime config
/etc/backupbloc-control/licenses.dbSQLite database
/etc/backupbloc-control/users.jsonAdmin accounts
/etc/backupbloc-control/sessions.jsonActive admin bearer-token sessions
/var/lib/backupbloc-control/releases/Uploaded release tarballs
/etc/systemd/system/backupbloc-control.servicesystemd unit
/etc/nginx/sites-available/backupbloc-controlnginx site config
/etc/letsencrypt/live/license.backupbloc.com/TLS cert + key

config.env settings

KeyDefaultPurpose
API_PORT5100Flask listens here; nginx proxies to it
TRUST_PROXY1Honour X-Forwarded-For (needed behind nginx)
GRACE_HOURS12How long a customer panel keeps working if we're unreachable
CHECKIN_KEEP_DAYS90Telemetry retention window

Restart the service after editing: systemctl restart backupbloc-control.service.

Public API endpoints (customer panels call these)

EndpointPurposeFrequency
POST /api/validateLicense check (IP-gated)24h
GET /api/updates/manifestCheck for new version6h
GET /api/updates/bundle/<version>Download signed tarballOn apply
POST /api/checkinTelemetry (version, agent count)1h
POST /api/updates/eventReport update outcomePer update
GET /api/healthLiveness probe (no auth)

All public endpoints except /api/health are IP-gated: unlicensed IPs receive 403 {valid:false, reason, ip}.

Release signing keys

Release bundles are signed with an ed25519 keypair. The private key stays on your dev laptop and is never uploaded to the control server. The public key ships in the customer panel installer.

# One-time, on your dev laptop:
bash tools/keygen.sh
# private:  ~/.backupbloc/update-signing-key.pem  (keep offline)
# public :  ~/.backupbloc/update-pubkey.pem

# Each release:
bash tools/sign-bundle.sh dist/backupbloc-1.8.0.tar.gz
# prints the base64 signature — paste into Releases → Upload Release

If you lose the private key, you can never sign another release that existing panels will trust — they'll all reject new bundles. Back it up (and the passphrase if you added one) somewhere offline and redundant.

troubleshootTroubleshooting

Service won't start

journalctl -u backupbloc-control.service -n 50 --no-pager

Common causes: syntax error in config.env, missing Python dep (re-run pip3 install --break-system-packages -r /opt/backupbloc-control/api/requirements.txt), permission issue on /etc/backupbloc-control/ (chmod 700 that dir).

Certbot fails with AttributeError: module 'lib' has no attribute 'X509_V_FLAG_NOTIFY_POLICY'

The Debian 12 / pyOpenSSL / cryptography mismatch. The current installer fixes it automatically; if you hit it on an old install:

pip3 install --break-system-packages --upgrade pyopenssl
certbot --nginx -d license.backupbloc.com --redirect --non-interactive --agree-tos --register-unsafely-without-email

Customer panel says "no license for this IP" but a license exists

Admin login rejected but you're sure the password is right

Sessions live in /etc/backupbloc-control/sessions.json — if that file is corrupted, delete it (you'll just have to log in fresh). User accounts live in users.json. To reset the admin user entirely: stop the service, delete users.json, start the service — it will seed a new random password and log it.

Locked out of the admin UI entirely

SSH to the server and run:

systemctl stop backupbloc-control.service
rm /etc/backupbloc-control/users.json /etc/backupbloc-control/sessions.json
systemctl start backupbloc-control.service
journalctl -u backupbloc-control.service -n 20 | grep -i "initial admin password"