TransitPin Deployment Runbook¶
This is the operational runbook for deploying changes to TransitPin. It documents where everything lives and the exact steps to ship frontend and backend changes safely. It is for IT Pro Partner engineers operating the platform.
Where things live¶
Everything runs on one server, app3:
- Host: 152.53.241.111
- SSH: ssh -i /root/.ssh/itpp-infra root@152.53.241.111
- Frontend docroot: /home/transitpin-dash/htdocs/my.transitpin.com/
- Backend: /opt/transitpin-api/ (FastAPI + SQLite)
- Database: /opt/transitpin-api/data/transitpin.db
- API service: systemd transitpin-api, uvicorn on 127.0.0.1:8900
- Relay service: systemd transitpin-relay (GPS WebSocket) on port 8210
nginx proxies /api/ to 127.0.0.1:8900 on my.transitpin.com. The ops console is served at ops.transitpin.com from the /ops/ subdirectory of the docroot.
Frontend files are owned by transitpin-dash:transitpin-dash. Backend files are owned by root. Keep ownership consistent when you write files, or the web server may fail to read them.
Deploying frontend changes¶
Frontend changes are static HTML/CSS/JS files in the docroot. The deploy pattern is: back up, write, fix ownership, swap atomically.
-
SSH in:
ssh -i /root/.ssh/itpp-infra root@152.53.241.111 cd /home/transitpin-dash/htdocs/my.transitpin.com/
-
Back up the current file with a timestamp (the .bak-
pattern): cp admin.html admin.html.bak-$(date +%s)
-
Write the new file to a temporary name first, then swap it in with mv so there is never a half-written file served. For example, streaming content over SSH:
cat > admin.html.new <<'EOF' ... new file contents ... EOF
-
Fix ownership to match the web user:
chown transitpin-dash:transitpin-dash admin.html.new
-
Atomically swap the temp file into place:
mv admin.html.new admin.html
Because the swap is a rename, visitors never see a partial file. To roll back, restore the .bak file with cp and chown (see the Rollback section below).
There is no build step for the frontend; the files are served as-is. After a swap, hard-refresh a browser (or use a private window) to bypass the service worker cache when verifying.
Deploying backend changes¶
Backend changes are Python files in /opt/transitpin-api/. The pattern is: back up, edit, restart, verify.
-
SSH in:
ssh -i /root/.ssh/itpp-infra root@152.53.241.111 cd /opt/transitpin-api/
-
Back up the file:
cp main.py main.py.bak-$(date +%s)
-
Edit the file (main.py, billing_routes.py, maps_routes.py, ops_routes.py, or common.py) with your changes.
-
Restart the service:
systemctl restart transitpin-api
-
Verify the health endpoint:
curl http://127.0.0.1:8900/api/health
Expect: {"status":"ok","db":"ok","version":"1.0.0",...}
-
If it does not come up, check the log:
systemctl status transitpin-api --no-pager -l
Back up the database before any change that touches the schema:
cp /opt/transitpin-api/data/transitpin.db /opt/transitpin-api/data/transitpin.db.bak-$(date +%s)
Adding a new tenant¶
A tenant is a white-label customer with their own subdomain, theme, and schools. Adding one is a data operation, not a code change.
-
Insert the tenant row. The tenants table holds the tenant identity and its theme as a JSON string. The tenant id is the slug (the subdomain label):
INSERT INTO tenants (id, name, tier, theme, domain) VALUES ('newslug', 'Client Company', 'enterprise', '{"logo":"","primary":"#0ea5e9","accent":"","bg":"","font":""}', 'newslug.transitpin.com');
The theme JSON keys are logo, primary, accent, bg, and font. Adjust the primary (and any other) color to the client's brand.
There is no self-service tenant-create endpoint today; tenant creation is done directly against the SQLite database (or by extending the seed routine). Credentials and any client-specific details live in Vaultwarden.
-
Point the subdomain's DNS at the platform (a CNAME for newslug.transitpin.com), matching how existing tenant subdomains are configured.
-
Load the client's schools into the schools table with tenant_id set to the slug. The registration dropdown and the AI Route Builder read from this, so the school list must be complete before the tenant goes live.
-
Verify:
curl http://127.0.0.1:8900/api/theme/newslug
Expect the tenant's name and theme to come back. Then load https://newslug.transitpin.com/signin.html and confirm the page is branded with the client's colors and name (and that the marketing nav links are hidden on the tenant subdomain).
Use CUSTOMER-ONBOARDING.md in this repository as the checklist of what to collect from the client before onboarding.
Rollback¶
Every change produces a timestamped .bak-
Frontend rollback:
cd /home/transitpin-dash/htdocs/my.transitpin.com/
cp admin.html.bak-<epoch> admin.html
chown transitpin-dash:transitpin-dash admin.html
Backend rollback:
cd /opt/transitpin-api/
cp main.py.bak-<epoch> main.py
systemctl restart transitpin-api
curl http://127.0.0.1:8900/api/health
Pick the backup by timestamp (larger epoch = more recent). Verify the rolled back page or endpoint before considering the rollback complete.
Verification checklist¶
After any deploy, confirm:
- Frontend: the changed page loads with HTTP 200 over HTTPS.
- Backend: /api/health returns status "ok" and db "ok".
- Billing: /api/billing/rates returns the expected rates if pricing was touched.
- Ownership: frontend files are transitpin-dash:transitpin-dash.
- A .bak-
backup exists for the file you changed.