TS Pro Backup is a self-hosted server deployed off-site from your Trusted Servants Pro portals. Portals connect via API key to push backup archives, which the server stores with optional end-to-end encryption, enforces grandfather-father-son retention on, and manages through a web console styled to match TS Pro. It handles both whole-site (full) and frontend-only (frontend) backups.
Features
- End-to-end encryption (default on) — each site gets an X25519 keypair; the portal encrypts to the public key before upload, so the server holds only ciphertext and rejects unencrypted uploads
- Encryption at rest — streaming AES-256-GCM, server-wide or per-site
- GFS retention — keep N recent days / weeks / months / years
- Hardened console — brute-force lockout, forced first-login password change, non-root container, CSP/HSTS headers,
0600data files, upload-size and per-site quota caps - Admin / user roles — users manage sites and backups; only admins rotate keys, delete sites or change encryption policy
- HTTP API mirroring TS Pro's backup interface (
put/list/delete/fetch), with single-shot and chunked uploads - Web console — dashboard, per-site API keys, and a backup browser with download/delete
Requirements
Docker with the Compose plugin, and a TLS reverse proxy in front for production.
Install with Docker Compose
Create docker-compose.yml with the published image:
services:
tspro-backup:
image: hyprlab/tspro-backup:latest
container_name: tspro-backup
ports:
# Host port is configurable; the container always listens on 8000.
# SECURITY: with TSPB_TRUST_PROXY=1 (default) the app trusts the
# X-Forwarded-For header, which is only safe when a trusted reverse
# proxy is the SOLE ingress. If your proxy runs on this same host,
# bind to loopback so the container isn't reachable directly:
# - "127.0.0.1:${TSPB_PORT:-8095}:8000"
# If the proxy is on another host, keep the bind below AND set
# TSPB_TRUST_PROXY=0 unless that proxy overwrites X-Forwarded-For.
- "${TSPB_PORT:-8095}:8000"
volumes:
- ./data:/data
# Defence in depth: the entrypoint already drops to a non-root user;
# these stop a compromised process from regaining privileges.
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- SETGID
- SETUID
# Cap memory so a runaway worker can't OOM the host. Transfers stream in
# 1 MiB blocks, so even multi-GB backups fit comfortably.
mem_limit: ${TSPB_MEM_LIMIT:-2g}
environment:
- TSPB_SECRET_KEY=${TSPB_SECRET_KEY:?TSPB_SECRET_KEY must be set in .env}
- TSPB_ADMIN_USERNAME=${TSPB_ADMIN_USERNAME:-admin}
- TSPB_ADMIN_PASSWORD=${TSPB_ADMIN_PASSWORD:-admin}
# Optional at-rest passphrase so a rebuilt host can decrypt existing
# archives. If blank, a random rest.key is generated in ./data.
- TSPB_REST_PASSPHRASE=${TSPB_REST_PASSPHRASE:-}
# Max single upload (MiB). Whole-site bundles can be large.
- TSPB_MAX_UPLOAD_MB=${TSPB_MAX_UPLOAD_MB:-8192}
# Optional per-site storage quota in MiB (0 = unlimited).
- TSPB_SITE_QUOTA_MB=${TSPB_SITE_QUOTA_MB:-0}
# Sign-in lockout thresholds (0 failures disables the lockout).
- TSPB_LOGIN_MAX_FAILURES=${TSPB_LOGIN_MAX_FAILURES:-5}
- TSPB_LOGIN_WINDOW_MINUTES=${TSPB_LOGIN_WINDOW_MINUTES:-15}
# How long the "remember me" cookie stays valid, in days.
- TSPB_REMEMBER_DAYS=${TSPB_REMEMBER_DAYS:-14}
# Trust X-Forwarded-* from the proxy in front (see the ports note).
# 1 = behind a trusted proxy (default); 0 = use the real socket peer.
- TSPB_TRUST_PROXY=${TSPB_TRUST_PROXY:-1}
# Local dev only: serve over plain HTTP (no Secure cookie flag).
- TSPB_DEBUG=${TSPB_DEBUG:-0}
restart: unless-stopped
Configure .env and start it, then open http://localhost:8095:
cp .env.example .env
python -c "import secrets; print(secrets.token_urlsafe(48))" # -> TSPB_SECRET_KEY in .env
docker compose up -d
Sign in, change the admin password, configure encryption/retention under Settings, then add a site to get its API key and private key (shown once — store it safely). Point the TSP portal's backup target at https://<host>/api/v1 with that key.
Configuration
| Variable | Default | Purpose |
|---|---|---|
TSPB_SECRET_KEY |
required | Flask session secret |
TSPB_PORT |
8095 |
Host port for the console |
TSPB_REST_PASSPHRASE |
auto | At-rest encryption passphrase (stored at data/rest.key if unset) |
TSPB_MAX_UPLOAD_MB |
8192 |
Max backup size |
TSPB_SITE_QUOTA_MB |
0 (off) |
Optional per-site storage quota |
TSPB_TRUST_PROXY |
1 |
Trust X-Forwarded-* headers (set 0 if reachable without a proxy) |
Example .env
Copy this to .env next to your docker-compose.yml and adjust:
# Copy to .env and fill in. Generate a strong secret:
# python -c "import secrets; print(secrets.token_urlsafe(48))"
TSPB_SECRET_KEY=change-me-to-a-long-random-string
# Seed admin (used only on first boot, when the DB is empty).
TSPB_ADMIN_USERNAME=admin
TSPB_ADMIN_PASSWORD=admin
# Host port to expose the console on (the container always listens on
# 8000 internally).
TSPB_PORT=8095
# Optional at-rest encryption passphrase. If set, reproducible across
# rebuilds; if blank, a random key is generated in ./data/rest.key.
TSPB_REST_PASSPHRASE=
# Max single upload in MiB (whole-site bundles can be large).
TSPB_MAX_UPLOAD_MB=8192
# Console sign-in lockout. After this many failed attempts for one
# username OR one client IP within the window (minutes), further sign-ins
# are refused until the oldest failures age out. Set FAILURES to 0 to
# disable the lockout entirely.
TSPB_LOGIN_MAX_FAILURES=5
TSPB_LOGIN_WINDOW_MINUTES=15
# Set to 1 only for local HTTP dev (disables Secure cookie flag).
TSPB_DEBUG=0
# Trust X-Forwarded-* headers (client IP / scheme / host) from the proxy in
# front. Leave at 1 ONLY when a trusted reverse proxy (Caddy/Cloudflare) is
# the SOLE ingress and overwrites X-Forwarded-For with the real client IP.
# If the container port is reachable directly, set this to 0 — otherwise an
# attacker can spoof X-Forwarded-For to defeat the per-IP login lockout and
# poison the audit/last-seen IP. When 0, the real socket peer is used.
TSPB_TRUST_PROXY=1
# Console sign-in is also protected by Cloudflare Turnstile if configured in
# the settings panel — strongly recommended for an internet-facing console.
# Per-site storage quota in MiB (sum of stored bytes; 0 = unlimited).
# Strongly recommended if any site uses an all-zero (keep-everything)
# retention policy — it is the backstop that keeps one site from filling
# the volume.
TSPB_SITE_QUOTA_MB=0
# How long the console "remember me" cookie stays valid, in days.
TSPB_REMEMBER_DAYS=14
# Container memory cap used by docker-compose (a runaway worker can't OOM
# the host; transfers stream in 1 MiB blocks so multi-GB backups still fit).
TSPB_MEM_LIMIT=2g
Updating
docker compose pull
docker compose up -d
Data in ./data persists and schema migrations apply automatically at boot.
Note: the site's private key is the only way to decrypt its archives, and the server never retains it. Lose the key and those backups are unrecoverable by design — store it in a password manager.
AI notice
TS Pro Backup is built by a human maintainer working with generative AI as a development tool:
- Code — the large majority of the code in the repository was written with Anthropic's Claude (via Claude Code), working from the maintainer's direction. The maintainer decides what gets built, reviews the results, tests every release, and signs off on everything that ships.
- Text — documentation and release notes are largely AI-drafted and human-edited.
- The software itself contains no AI. TS Pro Backup has no AI features and makes no requests to AI services — AI was used to build it, not to run it.
Bug reports and pull requests are welcome from humans and their AI tools alike; everything merged gets the same human review.
License
Free and open source under the GNU AGPL-3.0-or-later. Companion to Trusted Servants Pro · Source on GitHub.