Skip to content

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
Окно терминала
# on the server
pnpm tsmc-server backup # → data/backups/tsmc-backup-20261025-103000/
# in Docker
docker 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.

Окно терминала
pnpm tsmc-server check # the server's data
TSMC_DB=backups/tsmc-backup-…/tsmc.db TSMC_FILES=backups/tsmc-backup-…/files pnpm tsmc-server check # a backup

It 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.

  1. Stop the server (docker compose stop tsmc, systemctl stop tsmc): a running server keeps the database open, and restoring over it would lose data.
  2. Restore:
    Окно терминала
    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 --force
    Before replacing anything the command verifies the backup’s checksum and integrity. The previous database is kept next to it as tsmc.db.before-restore-<time>, and the previous key likewise. Without --force an existing database is not replaced.
  3. 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.
  1. Make a backup (tsmc-server backup).
  2. 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.
  3. If something goes wrong, stop the server and restore the backup with the previous version.

The server does not deal with certificates: a reverse proxy covers it from the outside. Ready examples:

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.

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).

  • GET /api/health — liveness, the server and API versions; the image’s HEALTHCHECK uses it.

  • Prometheus metrics (TSMC_METRICS=1): GET /metrics, outside /api and without signing in; with TSMC_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} and tsmc_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: tsmc
    authorization: { credentials: change-me } # TSMC_METRICS_TOKEN
    static_configs: [{ targets: ['tsmc:3000'] }]
  • The log is JSON on stdout (Fastify/pino): docker logs tsmc, journalctl -u tsmc.