Files
Stride/docs/DEPLOYMENT.md
T

66 lines
3.8 KiB
Markdown

# Dokku deployment
App: `stride`; database: `stride-db`; domain: `stride.peterstockings.com`.
The Dockerfile installs locked dependencies on Python 3.12. Dokku runs database
migrations in the predeploy step, then one web container and one sync worker.
Mount `/var/lib/dokku/data/storage/stride:/storage` with ownership 1000:1000 and
mode 0700. Set APP_ENV=production, INSTANCE_PATH=/storage/instance,
DATA_ROOT=/storage/data, SECRET_KEY (random secret), TOKEN_ENCRYPTION_KEY
(Fernet key). Linking PostgreSQL supplies DATABASE_URL. Preserve the encryption
key separately from database backups; losing it requires Garmin reconnection.
Push to Gitea, then deploy with `dokku git:sync --build stride
https://gitea.peterstockings.com/peterstockings/Stride.git main` on the server.
Map the web port with `dokku ports:set stride http:80:8000`, then enable HTTPS
using `dokku letsencrypt:enable stride`.
## Sync
Upload a garminconnect 0.3.17 session JSON in Settings & imports. Credentials are
validated with Garmin, encrypted, and assigned to the authenticated owner.
Manual sync queues durable work; the worker polls every 30 seconds. Automatic
sync runs hourly and checks the latest 100 activities, reusing download manifests
and unique activity source keys to avoid duplicates. Older activities can be
imported with FIT uploads or the operator's local import command.
Shared storage file locks prevent overlapping jobs and disconnect races. A crash
releases the lock; due or pending work is retried after restart. Refresh tokens
are persisted before downloading and after download failures. Transient failures
back off up to six hours; rejected authentication pauses scheduling until the
user reconnects and enables it again. Disconnect removes credentials and keeps
previously imported activities. Only garmin.com is supported by the web connector.
Inspect `dokku logs stride -p worker`, the status in Settings, and `/health`.
Run `dokku run stride flask --app app sync-worker --once` for an operator check.
Do not run the local sync simultaneously using a copy of the same Garmin session.
Back up PostgreSQL, `/storage`, and configuration securely. Rate limiting still
uses per-process memory; email verification and password recovery are not yet
implemented. For a larger public deployment configure shared rate-limit storage.
## Validation
Local automated suite: 39 passing tests, including manual/scheduled jobs,
encryption, retry/authentication failure, locking, CSRF, and access control.
Live verification on 2026-10-01:
- Built and deployed on Dokku 0.38.23; all PostgreSQL migrations succeeded.
- HTTPS certificate issued; external `/health` returned HTTP 200 and `status: ok`.
- Migrated the existing local account and 18 activities, preserving its password hash.
- Reused the local Garmin session on the Linux server, saved it encrypted, and
imported one new activity (19 total).
- Submitted a manual sync to the public HTTPS endpoint with an authenticated
session, CSRF token, and same-origin Referer. The background worker completed it.
- Moved `next_sync` into the past with `pending=false` and scheduling enabled.
The running worker picked it up automatically and completed the scheduled run.
This exercises the schedule due check without waiting an hour.
- Both repeat syncs imported zero duplicates and reported zero failures.
- Authenticated dashboard returned 200; anonymous private activity access returned 404.
- Production uses one web container (two Gunicorn processes) and one worker;
hourly scheduling remains enabled. The local session copy should no longer be
used concurrently with the server.
- Restarted web and worker successfully; all 19 activities, encrypted connection,
last success, and next hourly schedule persisted. Temporary migration database
and plaintext session copies were removed from the server.