84 lines
4.8 KiB
Markdown
84 lines
4.8 KiB
Markdown
# Dokku deployment
|
|
|
|
App: `stride`; database: `stride-db`; domain: `stride.peterstockings.com`.
|
|
|
|
Training-signal releases migrate private daily recovery and session preferences
|
|
automatically. Back up PostgreSQL before release, then recompute older recordings:
|
|
|
|
```sh
|
|
dokku run stride env FLASK_APP=app flask backfill-analysis --batch-size 20
|
|
dokku run stride env FLASK_APP=app flask install-training-signals --email ACCOUNT
|
|
dokku run stride env FLASK_APP=app flask backfill-health --email ACCOUNT --days 90
|
|
```
|
|
|
|
The installer validates queries before saving, pins five analyses and preserves
|
|
existing edits on repeat runs. Pace and long-run thresholds are editable CLI
|
|
options and account queries. Recovery backfill uses the existing encrypted
|
|
session and account lock, enables recovery only for that account and leaves
|
|
missing watch observations missing. Other accounts can opt in through Settings;
|
|
recovery failures do not stop activity imports. Never run a local copy of the
|
|
same Garmin session concurrently with production. Analysis version 3 persists
|
|
continuous windows; no dashboard query loads full recording samples.
|
|
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.
|