{"id":51457025,"url":"https://github.com/hyprlab/tspro-backup","last_synced_at":"2026-07-06T01:02:58.475Z","repository":{"id":361955294,"uuid":"1256596546","full_name":"hyprlab/tspro-backup","owner":"hyprlab","description":"A companion app for Trusted Servants Pro to manage and store backups. Built with Claude Code.","archived":false,"fork":false,"pushed_at":"2026-07-04T13:34:48.000Z","size":710,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T15:20:13.593Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://gettspro.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyprlab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T23:38:09.000Z","updated_at":"2026-07-04T13:34:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hyprlab/tspro-backup","commit_stats":null,"previous_names":["viibeware/tspro-backup","hyprlab/tspro-backup"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/hyprlab/tspro-backup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftspro-backup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftspro-backup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftspro-backup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftspro-backup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyprlab","download_url":"https://codeload.github.com/hyprlab/tspro-backup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftspro-backup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35174071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-06T01:02:56.908Z","updated_at":"2026-07-06T01:02:58.456Z","avatar_url":"https://github.com/hyprlab.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"app/static/img/logo_tspro_white.svg\" alt=\"TS Pro Backup\" width=\"320\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eTS Pro Backup\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eOff-site, encrypted backup storage for Trusted Servants Pro.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://hub.docker.com/r/hyprlab/tspro-backup\"\u003e\u003cimg alt=\"Docker Image\" src=\"https://img.shields.io/docker/v/hyprlab/tspro-backup?label=docker\u0026sort=semver\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://hub.docker.com/r/hyprlab/tspro-backup\"\u003e\u003cimg alt=\"Docker Pulls\" src=\"https://img.shields.io/docker/pulls/hyprlab/tspro-backup\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"License: AGPL-3.0\" src=\"https://img.shields.io/badge/license-AGPL--3.0-blue\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nTS Pro Backup is a small, self-hostable server you deploy **off-site** from\nyour Trusted Servants Pro portal. Each portal (\"site\") connects to this\nserver's API with its own key and pushes backup archives here. The server\nstores them — optionally **AES-256 encrypted at rest** — enforces a\n**grandfather-father-son retention policy**, and gives you a clean web\nconsole (matching the TS Pro look) to manage sites and browse backups.\n\nIt receives two kinds of archive from TS Pro:\n\n- **Whole-site backups** (`full`) — the complete portal export: SQLite DB\n  + uploads + encryption key seed.\n- **Frontend-only backups** (`frontend`) — just the public web frontend.\n\nRetention runs independently per scope, so a burst of frontend snapshots\nnever evicts your whole-site backups.\n\n## Features\n\n- 🔑 **End-to-end encrypted** (default on) — every site gets its own\n  X25519 keypair. TS Pro encrypts each backup to the site's **public**\n  key before upload; only the **private** key (shown once at site\n  creation, never stored here) can decrypt it. The server holds only\n  ciphertext, so it can't read your backups even if fully compromised,\n  and the API rejects any upload that isn't already encrypted.\n- 🔒 **Hardened console login** — brute-force lockout (per username and IP),\n  a forced first-login password change (no standing `admin/admin`), and\n  session invalidation on password change.\n- 👥 **Admin / user roles** — `admin` accounts have full access; `user`\n  accounts manage sites and backups but can't rotate keys, delete sites, or\n  change a site's encryption policy.\n- 🔐 **Cloudflare Turnstile** on the console login (optional).\n- 🛡️ **Hardened by default** — non-root container, secure response headers\n  (CSP, HSTS, `X-Frame-Options`, …), `0600` data files, server-enforced\n  upload size/quota caps, and an envelope-structure check on every E2EE\n  upload.\n- 🧱 **Encrypted at rest** — streaming AES-256-GCM, server-wide or\n  per-site. Defense-in-depth for the storage volume with a key the\n  *server* holds, so it is **not** end-to-end; independent of the E2EE\n  layer above (with E2EE on, the bytes are already opaque to us).\n- 🗓️ **GFS retention** — keep N recent days / weeks / months / years.\n- 🌐 **HTTP API** that mirrors TS Pro's existing backup-backend interface\n  (`put / list / delete / fetch`), so wiring up a \"TS Pro Backup\" target\n  in TS Pro is a thin client. Single-shot **and** chunked uploads for\n  multi-GB bundles behind a proxy body cap.\n- 🎛️ **Web console** — dashboard, per-site API keys, backup browser with\n  download/delete, all styled to match Trusted Servants Pro.\n\n## Deploy with Docker Compose\n\nThe recommended way to run TS Pro Backup. You need [Docker](https://docs.docker.com/get-docker/)\nwith the Compose plugin — nothing else.\n\n### 1. Get the compose file\n\nEither clone the repo:\n\n```bash\ngit clone https://github.com/hyprlab/tspro-backup.git\ncd tspro-backup\n```\n\n…or, if you'd rather not clone, just create a `docker-compose.yml` next to\na `data/` directory with this content (it pulls the published image — no\nsource checkout needed):\n\n```yaml\nservices:\n  tspro-backup:\n    image: hyprlab/tspro-backup:latest\n    container_name: tspro-backup\n    ports:\n      - \"${TSPB_PORT:-8095}:8000\"\n    volumes:\n      - ./data:/data\n    environment:\n      - TSPB_SECRET_KEY=${TSPB_SECRET_KEY:?TSPB_SECRET_KEY must be set in .env}\n      - TSPB_ADMIN_USERNAME=${TSPB_ADMIN_USERNAME:-admin}\n      - TSPB_ADMIN_PASSWORD=${TSPB_ADMIN_PASSWORD:-admin}\n      - TSPB_REST_PASSPHRASE=${TSPB_REST_PASSPHRASE:-}\n      - TSPB_MAX_UPLOAD_MB=${TSPB_MAX_UPLOAD_MB:-8192}\n      - TSPB_DEBUG=${TSPB_DEBUG:-0}\n    restart: unless-stopped\n```\n\n### 2. Configure the environment\n\n```bash\ncp .env.example .env\n```\n\nThe full sample `.env` looks like this:\n\n```ini\n# Copy to .env and fill in. Generate a strong secret:\n#   python -c \"import secrets; print(secrets.token_urlsafe(48))\"\nTSPB_SECRET_KEY=change-me-to-a-long-random-string\n\n# Seed admin (used only on first boot, when the DB is empty).\nTSPB_ADMIN_USERNAME=admin\nTSPB_ADMIN_PASSWORD=admin\n\n# Host port to expose the console on (the container always listens on\n# 8000 internally).\nTSPB_PORT=8095\n\n# Optional at-rest encryption passphrase. If set, reproducible across\n# rebuilds; if blank, a random key is generated in ./data/rest.key.\nTSPB_REST_PASSPHRASE=\n\n# Max single upload in MiB (whole-site bundles can be large).\nTSPB_MAX_UPLOAD_MB=8192\n\n# Console sign-in lockout. After this many failed attempts for one username\n# OR one client IP within the window (minutes), sign-ins are refused until\n# the oldest failures age out. Set FAILURES to 0 to disable.\nTSPB_LOGIN_MAX_FAILURES=5\nTSPB_LOGIN_WINDOW_MINUTES=15\n\n# Set to 1 only for local HTTP dev (disables Secure cookie flag).\nTSPB_DEBUG=0\n\n# Trust X-Forwarded-* from the proxy in front. Leave at 1 ONLY when a trusted\n# reverse proxy is the sole ingress and overwrites X-Forwarded-For. If the\n# container port is reachable directly, set to 0 (uses the real socket peer)\n# so the login lockout can't be defeated by a spoofed X-Forwarded-For.\nTSPB_TRUST_PROXY=1\n```\n\nAt minimum, set a strong session secret and change the admin password:\n\n```bash\n# Generate a strong secret:\npython -c \"import secrets; print(secrets.token_urlsafe(48))\"\n```\n\n```ini\nTSPB_SECRET_KEY=\u003cpaste the generated secret\u003e\nTSPB_ADMIN_PASSWORD=\u003ca strong password\u003e\n```\n\n### 3. Start it\n\n```bash\ndocker compose up -d\n```\n\nThe first boot creates the seed admin and initializes the database in\n`./data`. Open the console at **\u003chttp://localhost:8095\u003e** and sign in with\n`TSPB_ADMIN_USERNAME` / `TSPB_ADMIN_PASSWORD`.\n\nCheck logs / status any time:\n\n```bash\ndocker compose logs -f\ndocker compose ps\n```\n\n### 4. First-run setup in the console\n\n0. **Sign in** with the seed credentials. If the admin password is still the\n   default `admin`, the console walks you through a one-time password change\n   before anything else is reachable.\n1. **Settings** → end-to-end encryption is required by default; optionally\n   enable Turnstile, encryption at rest, and set the default retention.\n2. **Sites → Add site** → name it. Copy the **API key** *and* the\n   **private key** shown once — store the private key somewhere safe.\n3. In your TS Pro portal, add a **TS Pro Backup** target pointing at\n   `https://\u003cthis-host\u003e/api/v1` with that API key; it fetches the site's\n   public key automatically and encrypts every backup to it.\n\n\u003e ⚠️ The **private key** is the only thing that can decrypt your backups,\n\u003e and this server never keeps a copy. Lose it and the stored archives are\n\u003e **permanently unrecoverable** — that's what makes the storage\n\u003e zero-knowledge. Keep it in a password manager.\n\n### 5. Put TLS in front of it\n\nThe console sets `Secure` session cookies, so in production it must be\nserved over HTTPS. Terminate TLS at a reverse proxy (Caddy, nginx,\nTraefik, Cloudflare Tunnel, …) in front of the container — for example,\nproxy `https://backup.example.org` → `http://127.0.0.1:8095`. Make sure\nthe proxy's max request body size is large enough for your whole-site\nbundles (or rely on the chunked upload endpoints). Leave `TSPB_DEBUG=0`\nbehind TLS; set it to `1` **only** for local plain-HTTP development.\n\nThe app trusts `X-Forwarded-*` from the proxy by default (`TSPB_TRUST_PROXY=1`),\nwhich is correct when a trusted proxy is the **only** way in. If the container\nport is also reachable directly, either bind it to loopback\n(`127.0.0.1:8095:8000`) or set `TSPB_TRUST_PROXY=0`, so a spoofed\n`X-Forwarded-For` can't defeat the per-IP login lockout.\n\n### Upgrading\n\n```bash\ndocker compose pull          # fetch the new image\ndocker compose up -d         # recreate the container\n```\n\nYour data lives in the `./data` volume and is preserved across upgrades.\nSchema changes are applied automatically at boot (additive SQLite\nmigrations), so no manual migration step is needed.\n\n\u003e **Upgrading to 1.1.0** has three one-time effects: existing console\n\u003e sessions are invalidated (sign in again); an admin still on the `admin`\n\u003e password is sent through a forced change wizard; and a site with E2EE\n\u003e required but no key must have its keypair rotated before it can upload.\n\n### Build from source instead\n\nTo build the image locally rather than pulling it, uncomment `build: .`\nin `docker-compose.yml` and run `docker compose up -d --build`.\n\n## Run locally (without Docker)\n\nFor development:\n\n```bash\npip install -r requirements.txt\nTSPB_SECRET_KEY=dev-secret TSPB_DEBUG=1 python run.py\n```\n\nServes on \u003chttp://localhost:8000\u003e with plain-HTTP dev cookies.\n\n## Configuration\n\n| Env var | Default | Purpose |\n|---|---|---|\n| `TSPB_SECRET_KEY` | — (required) | Flask session secret. |\n| `TSPB_PORT` | `8095` | Host port for the console (compose only). |\n| `TSPB_FERNET_KEY` | auto (`data/secret.key`) | Seed for encrypting the Turnstile secret. |\n| `TSPB_REST_PASSPHRASE` | auto (`data/rest.key`) | At-rest encryption passphrase. |\n| `TSPB_ADMIN_USERNAME` / `TSPB_ADMIN_PASSWORD` | `admin` / `admin` | Seed admin (first boot only). Default password forces a change on first login. |\n| `TSPB_DATA_DIR` | `/data` | Where the DB + archives live. |\n| `TSPB_MAX_UPLOAD_MB` | `8192` | Max backup size (single-shot or reassembled). |\n| `TSPB_SITE_QUOTA_MB` | `0` (off) | Optional per-site storage quota. |\n| `TSPB_TRUST_PROXY` | `1` | Trust `X-Forwarded-*`. Set `0` if the port is reachable without a trusted proxy. |\n| `TSPB_LOGIN_MAX_FAILURES` | `5` | Failed sign-ins (per username/IP) before lockout; `0` disables. |\n| `TSPB_LOGIN_WINDOW_MINUTES` | `15` | Sliding lockout window. |\n| `TSPB_DEBUG` | `0` | `1` = plain-HTTP dev cookies. |\n| `TSPB_FLASK_DEBUG` | `0` | `1` = Werkzeug debugger (loopback dev only). |\n\n\u003e ⚠️ If you rely on at-rest encryption, **back up `TSPB_REST_PASSPHRASE`\n\u003e (or `data/rest.key`)**. Losing it makes the stored archives\n\u003e unrecoverable — by design.\n\n## API\n\nAuthenticate every request with `Authorization: Bearer \u003ckey\u003e` (or\n`X-API-Key: \u003ckey\u003e`).\n\n| Method | Path | Purpose |\n|---|---|---|\n| `GET` | `/api/v1/ping` | Auth check + capabilities. |\n| `POST` | `/api/v1/backups` | Upload (`file`, `scope`, optional `note`). |\n| `POST` | `/api/v1/backups/chunk` | One chunk of a large upload (`upload_id`, `chunk_index`, `total_chunks`, `chunk`). |\n| `POST` | `/api/v1/backups/finalize` | Reassemble chunks + store (`upload_id`, `scope`, `filename`, `total_chunks`). |\n| `GET` | `/api/v1/backups` | List this site's backups (`?scope=`). |\n| `GET` | `/api/v1/backups/\u003cid\u003e` | One backup's metadata. |\n| `GET` | `/api/v1/backups/\u003cid\u003e/download` | Download original bytes. |\n| `DELETE` | `/api/v1/backups/\u003cid\u003e` | Delete one backup. |\n\nExample upload:\n\n```bash\ncurl -H \"Authorization: Bearer tspb_XXXX\" \\\n     -F scope=full -F file=@tsp-export-20260531-030000.zip \\\n     https://backup.example.org/api/v1/backups\n```\n\n## Data \u0026 backups of the backup server\n\nEverything stateful lives under `./data`:\n\n- `tspro_backup.db` — sites, settings, backup metadata.\n- `storage/site-\u003cid\u003e/` — the stored archive blobs.\n- `rest.key` / `secret.key` — auto-generated keys (if you didn't supply\n  your own via env). **These are secrets** — protect the volume, and if\n  you copy `data/` elsewhere you copy the keys with it.\n\nTo migrate hosts, stop the container and copy the whole `data/` directory.\n\n## Versioning \u0026 releases\n\nThis project follows [Semantic Versioning](https://semver.org). See\n[CHANGELOG.md](CHANGELOG.md) for the full history and the\n[releases page](https://github.com/hyprlab/tspro-backup/releases) for\nrelease notes. Images are published to\n[`hyprlab/tspro-backup`](https://hub.docker.com/r/hyprlab/tspro-backup)\ntagged by version (e.g. `1.0.0`) and `latest`.\n\n## License\n\n[AGPL-3.0-or-later](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Ftspro-backup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyprlab%2Ftspro-backup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Ftspro-backup/lists"}