Operations
How to keep a TSMC server running: where the data is, backups and restoring, upgrades, HTTPS, shutdown, monitoring.
All of the server’s state is in one folder (the /data volume in Docker):
| What | Where | Variable |
|---|---|---|
| SQLite database | tsmc.db (and tsmc.db-wal, tsmc.db-shm while the server runs) |
TSMC_DB |
| attachments | files/ next to the database — files by SHA-256, once per identical content |
TSMC_FILES |
| tracker token key | secret.key next to the database |
TSMC_SECRET_KEY instead of the file |
| backups | backups/ next to the database (by default) |
--out |
Backup
Section titled “Backup”# on the serverpnpm tsmc-server backup # → data/backups/tsmc-backup-20261025-103000/# in Dockerdocker exec tsmc tsmc-server backup # → /data/backups/… (in the volume)The backup is made live: the server does not stop and keeps serving requests. The database is copied with
SQLite’s VACUUM INTO — a snapshot of one transaction, consistent even if writes are going on at that moment.
Then the attachment files the snapshot refers to are copied (they do not change once written), and the
secret.key key. The backup folder has a manifest.json: time, version, database schema, sizes, the database
checksum, how many of what it holds. An unfinished backup is named ….partial and is deleted on an error.
Backups should be taken off the server. An example of a daily backup kept for 14 days (cron, Docker):
30 3 * * * docker exec tsmc tsmc-server backup && docker run --rm -v tsmc-data:/data alpine sh -c 'cd /data/backups && ls -dt tsmc-backup-* | tail -n +15 | xargs -r rm -rf'If the key is set with the TSMC_SECRET_KEY variable, it does not go into the backup (manifest.json says so):
keep it next to the backup yourself, otherwise tracker tokens have to be entered again after a restore.
Checking
Section titled “Checking”pnpm tsmc-server check # the server's dataTSMC_DB=backups/tsmc-backup-…/tsmc.db TSMC_FILES=backups/tsmc-backup-…/files pnpm tsmc-server check # a backupIt checks SQLite integrity (integrity_check), referential integrity (foreign_key_check) and that the file of
every attachment is in place. Exit code 2 means something is wrong. Unused files (left over from an interrupted
upload) are not an error: they are removed the next time an attachment is deleted.
Restoring
Section titled “Restoring”- Stop the server (
docker compose stop tsmc,systemctl stop tsmc): a running server keeps the database open, and restoring over it would lose data. - Restore:
Before replacing anything the command verifies the backup’s checksum and integrity. The previous database is kept next to it as
Окно терминала pnpm tsmc-server restore data/backups/tsmc-backup-20261025-103000 --force# in Docker, while the container is stopped:docker run --rm -v tsmc-data:/data tsmc tsmc-server restore /data/backups/tsmc-backup-20261025-103000 --forcetsmc.db.before-restore-<time>, and the previous key likewise. Without--forcean existing database is not replaced. - Start the server. If the backup is older than the server version, the server applies the missing migrations itself; a backup of a newer version cannot be restored — a server of that version is needed.
Upgrading
Section titled “Upgrading”- Make a backup (
tsmc-server backup). - Update the image or the code and restart. Schema migrations are applied at start, each in one transaction: an interrupted migration does not leave the database half changed. Large migrations (for example, rebuilding the results table in stage 20) take seconds on a database with a hundred thousand results.
- If something goes wrong, stop the server and restore the backup with the previous version.
HTTPS and a reverse proxy
Section titled “HTTPS and a reverse proxy”The server does not deal with certificates: a reverse proxy covers it from the outside. Ready examples:
- docs/deploy/Caddyfile — Caddy gets and renews Let’s Encrypt certificates itself; docs/deploy/compose.caddy.yml starts TSMC and Caddy together.
- docs/deploy/nginx.conf — nginx with a certbot certificate.
Behind a proxy the server needs TSMC_SECURE_COOKIES=1 (cookies over HTTPS only), TSMC_TRUST_PROXY=1 (the
client address from X-Forwarded-For — for sign-in attempt limits and the audit log) and TSMC_PUBLIC_URL (the
address in links from filed bugs). The request body limit in the proxy must be at least TSMC_MAX_UPLOAD_MB
(attachments), with room for CSV imports.
Shutdown
Section titled “Shutdown”On SIGTERM (docker stop, systemctl stop) and SIGINT (Ctrl+C) the server stops accepting connections,
waits for the current requests (up to 10 s), moves the SQLite journal into the main file and exits with code 0.
After the shutdown the data is tsmc.db without -wal, the attachments and the key. Give it time in Docker:
stop_grace_period: 20s in compose (Docker waits 10 s by default).
Monitoring
Section titled “Monitoring”-
GET /api/health— liveness, the server and API versions; the image’s HEALTHCHECK uses it. -
Prometheus metrics (
TSMC_METRICS=1):GET /metrics, outside/apiand without signing in; withTSMC_METRICS_TOKEN,Authorization: Bearer <token>is required. Better not to expose them to the outside (the proxy examples close/metrics). What there is:tsmc_http_requests_total{method,route,status}andtsmc_http_request_duration_seconds(a histogram) — by route templates (/api/runs/:id), not addresses;tsmc_projects,tsmc_cases{status},tsmc_runs{status},tsmc_results,tsmc_users;tsmc_attachments_bytes,tsmc_database_bytes{file="main|wal"},tsmc_process_memory_bytes,tsmc_uptime_seconds.
An example for Prometheus:
scrape_configs:- job_name: tsmcauthorization: { credentials: change-me } # TSMC_METRICS_TOKENstatic_configs: [{ targets: ['tsmc:3000'] }] -
The log is JSON on stdout (Fastify/pino):
docker logs tsmc,journalctl -u tsmc.