{"id":51457043,"url":"https://github.com/hyprlab/trusted-servants-pro","last_synced_at":"2026-07-06T01:03:07.219Z","repository":{"id":351831118,"uuid":"1212583680","full_name":"hyprlab/trusted-servants-pro","owner":"hyprlab","description":"An open-source web app designed to help addiction recovery fellowships maintain shared documents and accounts. Built with Claude Code.","archived":false,"fork":false,"pushed_at":"2026-07-04T14:51:25.000Z","size":19460,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-04T15:27:49.516Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://gettspro.com","language":"HTML","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-04-16T14:21:34.000Z","updated_at":"2026-07-04T13:21:36.000Z","dependencies_parsed_at":"2026-06-07T14:02:56.463Z","dependency_job_id":null,"html_url":"https://github.com/hyprlab/trusted-servants-pro","commit_stats":null,"previous_names":["viibeware/trusted-servants-portal","viibeware/trusted-servants-pro","hyprlab/trusted-servants-pro"],"tags_count":168,"template":false,"template_full_name":null,"purl":"pkg:github/hyprlab/trusted-servants-pro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftrusted-servants-pro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftrusted-servants-pro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftrusted-servants-pro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftrusted-servants-pro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyprlab","download_url":"https://codeload.github.com/hyprlab/trusted-servants-pro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Ftrusted-servants-pro/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:03:04.228Z","updated_at":"2026-07-06T01:03:06.765Z","avatar_url":"https://github.com/hyprlab.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Trusted Servants Pro\n\nA self-hosted portal for recovery-fellowship trusted servants and members: organize meetings, share readings and files, manage Zoom host accounts, collect access requests, and brand it to your group — all from a single admin UI, no command line required.\n\nFlask + SQLAlchemy + SQLite, packaged to run in a single Docker container with a persistent volume.\n\n## Highlights\n\n### Meetings\n- Full create / edit / archive / restore with per-meeting logo and alert banner.\n- In-person, online, and hybrid types with matching zoom/address fields shown conditionally.\n- Unlimited per-day schedules (day-of-week + start time + duration + optional \"opens\" time).\n- Table and card views with sort by name / day / type, both per-user remembered via cookies.\n- Attach any number of libraries with `all` or `granular` visibility so each meeting can show just the readings it uses.\n- Meeting detail page: schedule table, Zoom info (meeting ID, passcode, link, host account) with click-to-copy and Reveal controls, embedded OTP email credentials for hybrid/online meetings, and per-category file lists (documents, scripts, links, videos, images).\n\n### Libraries\n- Grouped reading collections with optional alert banner and description.\n- Drag-and-drop ordering, inline edit, thumbnail support, optional inline body text, and external-link entries.\n- File uploads or existing-asset selection from the File Browser.\n\n### File Browser\n- Central media library indexed from every upload across the app (`MediaItem` auto-backfilled on startup).\n- Search, sort, grid / table views, rename, upload with progress, delete with reference-count guard.\n- Public shareable URLs at `/pub/\u003coriginal-filename\u003e` — human-readable, no hashes or tokens. Serves the newest file of that name with the correct `Content-Disposition`.\n- Inline **Copy Link** buttons everywhere a file appears (File Browser, Meetings, Libraries).\n\n### Access Requests\n- Public Request Access form on the login screen captures name, phone, email, role(s), and meeting.\n- Submissions emailed to a configurable recipient list via the portal's SMTP settings.\n- Admin-only Access Requests page (sidebar with pending-count badge) for triage: Mark Handled / Reopen / Delete.\n- Recent requests widget on the dashboard.\n\n### Zoom accounts\n- Encrypted credential storage (Fernet with a local key file, see **Security**).\n- Assign any account to any meeting schedule.\n- Weekly assignment calendar starting Sunday, with automatic time-conflict detection (overlapping slots on the same account highlighted red).\n- Separate OTP email credentials shown to members on online/hybrid meeting pages so they can retrieve one-time codes without admin involvement.\n- Viewable by editors and viewers (read-only); admin-only for create/edit/delete.\n\n### Login experience\n- Redesigned split login screen with animated canvas particle background.\n- Nine selectable effects: Off, Network, Starfield, Fireflies, Bubbles, Snow, Waves, Orbits, Rain.\n- Adjustable **speed** and **particle size** sliders, **mouse-reactive physics**, live preview inside Settings.\n- Configurable background: default sine-wave gradient, solid color, or custom gradient with 2–4 color stops and a palette randomizer.\n- Optional 3D **login transition**: doors swing open on successful authentication to reveal a moving full-saturation sine-wave rainbow with the branding logo, then fades to the active theme's background before the next page loads.\n- Theme carries through: the chosen theme is applied to the login screen before paint.\n\n### Themes \u0026 branding\n- Six full palettes: Light, Dark, Neobrutal Light/Dark, Cyberpunk, Solarpunk.\n- Unified accent color (`#0b5cff`) across buttons, links, and active nav states.\n- Inter font (weights 100–900) shipped app-wide.\n- Admin-configurable sidebar footer logo (upload + width slider + link URL) and login screen (particles, background, transition).\n\n### Dashboard\n- Stats row (meeting count, library count, your role).\n- Configurable widgets: Recent Meetings, Libraries, Recent Files, Intergroup, Public Information Chair contact, Access Requests (admin).\n- Each widget toggleable from the Customize Dashboard modal.\n\n### Settings\n- Full-viewport modal on mobile, horizontally-scrollable tabs with fade hint, AJAX-save with in-modal toast — the modal never closes when you save.\n- Tabs: **Appearance** (theme, branding, login screen), **Users**, **Zoom Accounts**, **Meeting Locations**, **External Links**, **Special Sections**, **Email**, **Data**, **About**.\n- Role gating: admins see everything; editors/viewers see Appearance → Theme, Zoom Accounts (read-only), and About.\n\n### Email\n- Global SMTP configuration (host, port, username/password, STARTTLS / SSL / plain).\n- Encrypted password storage using the same Fernet key as Zoom credentials.\n- Configurable From name + address, comma-separated recipient list for access-request notifications, and a one-click Send Test button.\n\n### Data export / import\n- One-click **Export** produces a zip containing a VACUUM-copied SQLite database, every upload, and the `zoom.key` file used to decrypt stored Zoom credentials.\n- One-click **Import** takes an export archive, validates it, moves the existing database + uploads + key to a timestamped `backup-YYYYMMDD-HHMMSS/` folder inside `./data`, restores the archive in place, re-runs migrations, and signs the user out. No command-line access required.\n\n### Frontend staging sync\n- Build your public website on a separate **Staging** copy of the app and move it to your **Live** site over the network — no bundle to download and re-upload. Only the frontend travels (theme, navigation, mega-menus, layouts, fonts, icons, page-builder Pages, and the assets they reference); recovery Stories, users, meetings, libraries, and uploads on the receiving side are never touched.\n- A role-aware setup wizard (Settings → Data → **Frontend staging sync**) asks whether each install is the Live site or the Staging copy and shows only that side's fields. The Live site mints a shared token and is set to receive; the Staging copy pastes the token, points at the Live URL, tests the connection, then pulls or pushes. Pairing is a single Fernet-encrypted shared secret authenticated in both directions, with rate-limiting and a `REPLACE`-style confirm; the receiving side auto-saves a rollback snapshot before applying.\n- On the Staging copy, the **Web Frontend → Overview** Status card gains one-click **Pull from Live** / **Push to Live** controls with a live connection indicator — deploy without opening Settings.\n\n### Session\n- 6-month remember-me cookie so users aren't repeatedly prompted for credentials.\n\n### Mobile\n- Dedicated mobile layouts across the app (meetings/libraries/files, users/zoom/locations inside Settings).\n- Stacked \"data cards\" replace overflowing tables, actions expand to full width.\n- Sidebar is a slide-in drawer with tap-outside-to-close.\n\n## Quick start\n\n```bash\ndocker compose up -d --build\n```\n\nOpen http://localhost:8090 and sign in with the seeded admin (defaults: `admin` / `admin`). Change these in `.env` before first run, or rotate later from the Users tab.\n\n### docker-compose.yml\n\n```yaml\nservices:\n  tsp:\n    image: hyprlab/tspro:latest\n    container_name: tspro\n    ports:\n      - \"8090:8000\"\n    volumes:\n      - ./data:/data\n    environment:\n      - TSP_SECRET_KEY=${TSP_SECRET_KEY:?TSP_SECRET_KEY must be set in .env}\n      - TSP_ADMIN_USERNAME=admin\n      - TSP_ADMIN_PASSWORD=admin\n      - TSP_ADMIN_EMAIL=admin@example.com\n    restart: unless-stopped\n    # Cap container logs so an unattended box can't fill its disk over\n    # time (the default json-file driver is unbounded).\n    logging:\n      driver: json-file\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n```\n\n## One-command install (Ubuntu 24.04)\n\n`install.sh` in this repo is a turnkey installer that provisions Docker, writes a hardened `docker-compose.yml`, generates a `TSP_SECRET_KEY`, configures Caddy for TLS (Let's Encrypt or self-signed), and installs Watchtower so the portal picks up new releases automatically. Follow these steps on a fresh Ubuntu 24.04 server:\n\n### 1. Provision a server\n\nSpin up an Ubuntu 24.04 LTS instance on whatever provider you like (DigitalOcean, Hetzner, AWS Lightsail, bare metal, etc.). You'll need root or sudo access. The portal is happy on 1 vCPU / 1 GB RAM for small groups.\n\n### 2. Point DNS at the server (required for Let's Encrypt)\n\nIf you want a real TLS certificate, the domain's DNS **must resolve to this server's public IP before you run the installer**. Let's Encrypt performs an HTTP-01 challenge on port 80 during issuance — if the hostname resolves anywhere else, the challenge fails and the portal is left unreachable over HTTPS.\n\nTwo gotchas:\n\n- **Cloudflare users: set the record to \"DNS only\" (grey cloud), not proxied (orange cloud), during installation.** Cloudflare's proxy terminates TLS at its edge and intercepts port 80, which breaks the HTTP-01 challenge and returns one of Cloudflare's own IPs for the A record — Let's Encrypt will never see the real server. You can flip the record back to proxied *after* the certificate is issued.\n- If you don't have a domain, or don't want TLS, leave the installer's domain prompt blank. The installer then issues a self-signed cert (browser will warn on the first visit).\n\nThe installer runs a DNS pre-check: if the hostname you enter doesn't resolve to this machine's public IP, it falls back to a self-signed certificate automatically and prints instructions for re-enabling Let's Encrypt. Fix the DNS and rerun `install.sh` to switch to a real cert.\n\n### 3. Run the installer\n\nSSH in and run one of these from the server:\n\n```bash\n# Pipe directly from GitHub (recommended):\ncurl -fsSL https://raw.githubusercontent.com/hyprlab/trusted-servants-pro/main/install.sh | sudo bash\n\n# Or clone and run locally if you'd rather read it first:\ngit clone https://github.com/hyprlab/trusted-servants-pro.git\ncd trusted-servants-pro\nsudo bash install.sh\n```\n\nThe installer will:\n\n1. Apt-update, install Docker Engine + the Compose plugin, and open UFW for 22/80/443.\n2. Prompt for a **domain** — enter the hostname you set up in step 2, or leave blank for a self-signed cert.\n3. Prompt for a **contact email** if you entered a domain (used for Let's Encrypt renewal notices).\n4. Generate a random `TSP_SECRET_KEY` and write it to `/opt/tspro/.env` (mode `600`).\n5. Pull `hyprlab/tspro:latest`, start the container, and wait for it to respond.\n\nTypical runtime is 2–5 minutes on a fresh VM.\n\n### 4. Sign in\n\nThe installer prints the portal URL when it's done (either `https://\u003cyour-domain\u003e` or `https://\u003cserver-ip\u003e`). Sign in with:\n\n- Username: `admin`\n- Password: `admin`\n\n**Change the admin password immediately** from Settings → Users.\n\n### 5. Optional: non-interactive installs\n\nYou can skip all prompts by passing env vars on the same line:\n\n```bash\nsudo TSP_DOMAIN=portal.example.org \\\n     TSP_ACME_EMAIL=you@example.org \\\n     TSP_ADMIN_PASSWORD='a-strong-password' \\\n     bash install.sh\n```\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `TSP_INSTALL_DIR` | `/opt/tspro` | Where compose/data/backups live. |\n| `TSP_IMAGE` | `hyprlab/tspro:latest` | Image tag to deploy. |\n| `TSP_DOMAIN` | _unset_ | Public hostname — if set, Caddy requests a Let's Encrypt cert. |\n| `TSP_ACME_EMAIL` | `admin@$TSP_DOMAIN` | Contact address for cert renewal notices. |\n| `TSP_ADMIN_USERNAME` / `TSP_ADMIN_PASSWORD` / `TSP_ADMIN_EMAIL` | `admin` / `admin` / `admin@example.com` | Seeded on first boot only. |\n\n### 6. Upgrading and day-to-day commands\n\nWatchtower polls Docker Hub every 24 hours and restarts the `tspro` container when a new image is published — no action needed. If you'd like to force an upgrade or inspect state:\n\n```bash\ncd /opt/tspro\ndocker compose ps                             # running containers\ndocker compose logs -f tsp                    # tail portal logs\ndocker compose pull \u0026\u0026 docker compose up -d   # upgrade now\ndocker compose down                           # stop everything\n```\n\nBack up `/opt/tspro/data/` (or use **Settings → Data → Export** from the UI) to preserve the SQLite database, uploads, and Fernet key.\n\n#### Keeping disk usage in check\n\nThe installer's `docker-compose.yml` is configured with three independent safeguards so an unattended box won't fill its own disk:\n\n- **A daily image-prune janitor** (the `docker-prune` service) sweeps every image and build-cache entry unused for more than 72 hours, once a day. This is the real guarantee: it reclaims images **no matter how they were orphaned** — manual `docker compose pull`, re-tagged `:latest` churn, or partial pulls — which the next two safeguards don't cover on their own. *(The prune is host-wide, which is correct for a dedicated TSP host; don't add it on a shared host running other Docker stacks.)*\n- **Watchtower removes the old image after each auto-update** (`WATCHTOWER_CLEANUP=true`). This only covers updates Watchtower itself performs — anything pulled or re-tagged another way is left behind, which is why the janitor above exists. Without *either*, a long-running box can pile up *hundreds* of stale images.\n- **Container logs are capped** (`max-size: 10m`, `max-file: 3` per service), so the default unbounded `json-file` driver can't grow without limit.\n\nOn top of these, the portal shows admins a **low-disk-space warning** — a banner on every admin page and an entry in the Notification Center — when the data volume or host disk crosses 85%, so you get runway to act before anything fails.\n\nIf you installed an **older release** (before these settings shipped) and your disk is filling up, you can reclaim space and adopt the new settings without a reinstall:\n\n```bash\ncd /opt/tspro\ndocker image prune -af        # delete every image not backing a running container\ndocker builder prune -af      # delete build cache\ndf -h /                        # confirm space is back\n# Then refresh your compose with the current hardened version and restart:\ndocker compose pull \u0026\u0026 docker compose up -d\n```\n\nTo pick up the prune janitor, `WATCHTOWER_CLEANUP`, and log-rotation settings on an existing install, re-run `install.sh` (it rewrites `docker-compose.yml` in place and preserves your `.env` and `data/`).\n\n### 7. Uninstalling\n\n`uninstall.sh` ships next to the installer and reverses what it did. Safe defaults: it stops and removes only the TSP containers, named volumes, and the install directory — Docker itself, the firewall, and base packages are left alone unless you ask.\n\n```bash\n# From a clone of the repo:\nsudo bash uninstall.sh\n\n# Or pipe directly from GitHub:\ncurl -fsSL https://raw.githubusercontent.com/hyprlab/trusted-servants-pro/main/uninstall.sh | sudo bash\n```\n\nYou'll be asked to type `yes` before anything is removed. Add flags to go further:\n\n| Flag | Effect |\n| --- | --- |\n| `-y`, `--yes` | Skip the confirmation prompt (required when piping from curl non-interactively). |\n| `--keep-data` | Preserve `/opt/tspro/data/` (database, uploads, `zoom.key`). |\n| `--purge-images` | Also `docker image rm` the pulled TSP, Caddy, and Watchtower images. |\n| `--remove-ufw-rules` | Revert the 80/tcp and 443/tcp UFW rules. OpenSSH is left intact so you don't lock yourself out. |\n| `--remove-docker` | Purge `docker-ce` + the Compose plugin, remove the apt source and keyring the installer added, and delete `/var/lib/docker`. |\n| `--nuke` | Shorthand for `--purge-images --remove-ufw-rules --remove-docker`. |\n\nFull teardown of everything the installer put on the server:\n\n```bash\nsudo bash uninstall.sh --nuke --yes\n```\n\n## Configuration\n\nA `.env` file sits alongside `docker-compose.yml`. At minimum it must define `TSP_SECRET_KEY` — a long, random value used to sign Flask session cookies.\n\nGenerate one with `openssl`:\n\n```bash\nopenssl rand -base64 48 | tr -d '\\n/+=' | cut -c1-64\n```\n\nExample `.env`:\n\n```\nTSP_SECRET_KEY=REPLACE_WITH_OUTPUT_OF_THE_COMMAND_ABOVE\nTSP_ADMIN_USERNAME=admin\nTSP_ADMIN_PASSWORD=change-me-before-first-boot\nTSP_ADMIN_EMAIL=admin@example.com\n```\n\nKeep `.env` out of version control and set it to mode `600` on the host (the installer does this automatically). Rotating `TSP_SECRET_KEY` will sign out all active users but does not affect stored Zoom / SMTP passwords — those are encrypted with a separate Fernet key stored at `data/zoom.key` (see **Security**).\n\nOther environment variables (all with sensible defaults):\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `TSP_SECRET_KEY` | `dev-secret-change-me` | Flask session signing key. Required to be set in production. |\n| `TSP_ADMIN_USERNAME` | `admin` | Seeded on first boot only. |\n| `TSP_ADMIN_PASSWORD` | `admin` | Seeded on first boot only. |\n| `TSP_ADMIN_EMAIL` | `admin@example.com` | Seeded on first boot only. |\n| `TSP_DATA_DIR` | `/data` | Inside-container data directory. Mounted to `./data` on the host by default. |\n| `TSP_UPLOAD_DIR` | `$TSP_DATA_DIR/uploads` | Location of uploaded files. |\n| `TSP_FERNET_KEY` | _auto-generated_ | If set, used directly; otherwise a key is generated and stored in `data/zoom.key`. |\n\nUploads are limited to **256 MB** per file.\n\n## Security\n\n- **Session cookies** are signed with `TSP_SECRET_KEY`. Rotating it will sign users out but does not affect encrypted credentials.\n- **Zoom account passwords, OTP email password, and SMTP password** are encrypted with Fernet. The key lives at `data/zoom.key` (auto-generated on first boot) or is loaded from the `TSP_FERNET_KEY` env var. **Keep this file alongside your database if you restore to another host**, or set `TSP_FERNET_KEY` explicitly — the Data export bundles it for you.\n- Public file URLs (`/pub/\u003cfilename\u003e`) are intentionally human-readable and unauthenticated. Anyone with the link can read the file. Do not upload content you do not want shared.\n- Access-request submissions are public (no login required) but rate-limited by the browser.\n\n## Local development\n\n```bash\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -r requirements.txt\npython run.py\n```\n\nServes on http://localhost:8000 with `debug=True`.\n\n## Backing up \u0026 migrating\n\nUse **Settings → Data → Export** for a portable archive. To restore on a fresh server, start the container once (which creates the data directory), then upload the export through **Settings → Data → Import**. The existing state is moved to `data/backup-\u003ctimestamp\u003e/` before the restore runs.\n\nIf you prefer the command line:\n\n```bash\ndocker compose down\ncp -r ./data ./data.bak\n# copy the export zip contents (tsp.db, uploads/, zoom.key) into ./data/\ndocker compose up -d\n```\n\n## Project layout\n\n```\napp/\n  __init__.py      # app factory, startup migrations, Fernet init\n  auth.py          # login / logout / user CRUD\n  crypto.py        # Fernet helpers\n  mail.py          # SMTP send helper\n  models.py        # SQLAlchemy models (Meeting, Library, Reading, User, ZoomAccount, ...)\n  routes.py        # main blueprint — nearly all feature routes\n  static/          # CSS, JS, images, login_fx engine\n  templates/       # Jinja templates (base + per-feature)\nscripts/           # one-off WP / Zoom import utilities\ndocker-compose.yml\nDockerfile\nrequirements.txt\nrun.py\nREADME.md\n```\n\n## License\n\nTrusted Servants Pro is released under the [GNU Affero General Public License v3.0](LICENSE) (AGPLv3).\n\nYou're free to run, copy, modify, and redistribute the portal. If you host a modified version for other users to interact with over a network, you must make the corresponding source code available to those users under the same license. See the `LICENSE` file for the full text.\n\n© Hyprlab. Open-source contributions welcome.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Ftrusted-servants-pro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyprlab%2Ftrusted-servants-pro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Ftrusted-servants-pro/lists"}