From fe9888092ca49674e1f6f34b4661fcae8bfa0570 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Thu, 1 Oct 2026 20:47:58 +1000 Subject: [PATCH] Document verified Dokku deployment and live sync results --- README.md | 19 ++++++++++--------- docs/DEPLOYMENT.md | 22 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d8a9802..c254931 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ account exists. Subsequent accounts start empty. The local app uses SQLite in `instance/dashboard.db`. Set `DATABASE_URL` to a PostgreSQL connection string to use PostgreSQL. Database changes use Alembic -migrations. Local schema creation/upgrade has been verified on SQLite; PostgreSQL -runtime validation is recorded in docs/DEPLOYMENT.md. +migrations. SQLite and deployed PostgreSQL migrations have been verified; see +[deployment validation](docs/DEPLOYMENT.md). ### Accounts and sharing @@ -49,9 +49,10 @@ runtime validation is recorded in docs/DEPLOYMENT.md. ### Bringing in more activities -Run the local sync below, then choose **Settings & imports -> Load new downloads**. -This button imports the cache assigned to your account, without contacting Garmin. -Any account can also upload an original FIT file through Settings & imports. +Use **Settings & imports -> Connect Garmin** to upload an exported session, then +choose **Sync now** or leave hourly sync enabled. Any account can also upload an +original FIT file through Settings & imports. Operators can import local caches +with the command below. Original files remain private and are not exposed by web download routes. If multiple Garmin caches existed at setup, an operator can explicitly assign one: @@ -61,10 +62,10 @@ uv run --locked flask --app app import-local --email owner@example.com --account ``` The application uses CSRF protection, hashed passwords, login throttling, and -server-side authorization on every activity page and data endpoint. Before public -deployment, finish email verification/recovery, shared production rate-limit -storage, and account recovery. Deployment instructions and sync behavior are in docs/DEPLOYMENT.md. -The current server binds only to loopback and runs without debug mode. +server-side authorization on every activity page and data endpoint. Email +verification/recovery and shared production rate-limit storage remain outstanding. +Deployment instructions and sync behavior are in docs/DEPLOYMENT.md. +The local server binds only to loopback; production runs behind the Dokku HTTPS proxy. Users will upload FIT files or connect Garmin by authenticating locally with `python-garminconnect` and uploading their exported session tokens. The server diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 939df9f..30dac76 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -12,7 +12,8 @@ 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. -Enable HTTPS using `dokku letsencrypt:enable stride`. +Map the web port with `dokku ports:set stride http:80:8000`, then enable HTTPS +using `dokku letsencrypt:enable stride`. ## Sync @@ -42,4 +43,21 @@ implemented. For a larger public deployment configure shared rate-limit storage. Local automated suite: 39 passing tests, including manual/scheduled jobs, encryption, retry/authentication failure, locking, CSRF, and access control. -Live deployment and Garmin results will be recorded after verification. +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. +