MyWhoosh Garmin Sync
Containerized background sync for personal MyWhoosh activities:
- Log in to MyWhoosh with Playwright.
- Find and download new
.fitactivity files. - Rewrite FIT device metadata locally to Garmin Edge 1030 Plus.
- Upload the converted activity to Garmin Connect.
- Persist sessions, downloaded files, converted files, and sync state under
/data.
Quick Start
Create your local env file:
cp .env.example .env
Edit .env, then run:
docker compose -f docker-compose.debug.yml up --build
If this runs on a remote Linux server, keep the default localhost-only port binding and tunnel it:
ssh -L 6080:localhost:6080 user@your-server
Then open noVNC:
http://localhost:6080/vnc.html
Login into your MyWhoosh account with keep me signed in (Important!). Exit.
Start the production container
docker compose up -d --build
docker compose logs -f sync
The default polling interval is hourly.
Open the local dashboard:
http://localhost:8080/
It shows the last sync check state and the last five uploaded Garmin activities.
Configuration
Required values:
GARMIN_EMAIL=
GARMIN_PASSWORD=
Useful optional values:
POLL_INTERVAL_SECONDS=3600
DATA_DIR=/data
LOG_LEVEL=INFO
DRY_RUN=false
DASHBOARD_HOST=0.0.0.0
DASHBOARD_PORT=8080
MYWHOOSH_LOGIN_URL=https://www.mywhoosh.com/login/
MYWHOOSH_ACTIVITY_URL=https://www.mywhoosh.com/profile/
MYWHOOSH_ACTIVITIES_BUTTON_TEXT=ACTIVITIES
MYWHOOSH_DOWNLOAD_BUTTON_SELECTOR=.btnDownload
TARGET_GARMIN_PRODUCT_ID=3578
TARGET_GARMIN_SERIAL_NUMBER=
If Garmin MFA is required on first login, set GARMIN_MFA_CODE for one run, let the token store persist under /data/garmin_tokens, then remove the variable.
Commands
Inside the container:
python -m mywhoosh_garmin_sync run-once
python -m mywhoosh_garmin_sync serve
python -m mywhoosh_garmin_sync convert --input /data/raw/example.fit --output /data/converted/example.fit
DRY_RUN=true downloads and converts files but does not upload to Garmin.
Run tests from the built image:
docker build -t mywhoosh-garmin-sync:test .
docker run --rm --entrypoint sh mywhoosh-garmin-sync:test -c "pip install -r requirements-dev.txt && pytest"
Browser Debugging
Use the debug compose file when MyWhoosh blocks automated login with cookies, captcha, or bot checks:
docker compose -f docker-compose.debug.yml up --build
Then open noVNC:
http://localhost:6080/vnc.html
Click Connect. You should see Chromium inside the container. Cookie consent is accepted automatically where the banner uses a normal consent button. Captcha/bot checks are left for you to solve manually. The debug run uses:
MYWHOOSH_HEADLESS=false
MYWHOOSH_MANUAL_LOGIN_WAIT_SECONDS=900
MYWHOOSH_SLOW_MO_MS=250
MYWHOOSH_DEBUG_SCREENSHOTS=true
DRY_RUN=true
If this runs on a remote Linux server, keep the default localhost-only port binding and tunnel it:
ssh -L 6080:localhost:6080 user@your-server
Then open http://localhost:6080/vnc.html on your own machine. During the 900-second manual-login window, accept cookies, solve the challenge, and finish the MyWhoosh login in the visible browser. The browser profile is stored in ./data/browser, so the normal headless service can reuse the session later.
Debug screenshots are written to:
./data/debug
After a successful manual login, stop the debug container and run the normal service:
docker compose up -d --build
Session Persistence
MyWhoosh auth is persisted in two places under the mounted ./data directory:
./data/browser
./data/mywhoosh_auth_state.json
Keep that directory when recreating containers. Use docker compose down when switching between debug and normal mode, but do not use docker compose down -v and do not delete ./data unless you want to log in again.
If Chromium reports that the profile is locked after a crash, stop all containers and remove only these root files:
./data/browser/SingletonCookie
./data/browser/SingletonLock
./data/browser/SingletonSocket
Notes
MyWhoosh does not appear to publish a stable public activity export API. This project uses a persisted headless browser session and conservative hourly polling. If MyWhoosh changes its page structure, adjust these .env values before changing code:
MYWHOOSH_LOGIN_URL=
MYWHOOSH_ACTIVITY_URL=
MYWHOOSH_DOWNLOAD_TEXT_HINTS=fit,download
MYWHOOSH_ACTIVITIES_BUTTON_TEXT=ACTIVITIES
MYWHOOSH_DOWNLOAD_BUTTON_SELECTOR=.btnDownload
The FIT conversion patches file_id and device_info messages where Garmin manufacturer/product fields are present, then recalculates header and file CRCs. The default Garmin product ID is configurable so it can be corrected without a rebuild if Garmin FIT profile values change.
This automates personal account actions. Keep polling conservative and make sure the way you use it is compatible with the services' terms.