Files
mywhoosh-garmin-sync/README.md
2026-05-07 13:40:39 +02:00

181 lines
4.9 KiB
Markdown

# MyWhoosh Garmin Sync
Containerized background sync for personal MyWhoosh activities:
1. Log in to MyWhoosh with Playwright.
2. Find and download new `.fit` activity files.
3. Rewrite FIT device metadata locally to Garmin Edge 1030 Plus.
4. Upload the converted activity to Garmin Connect.
5. Persist sessions, downloaded files, converted files, and sync state under `/data`.
## Quick Start
Create your local env file:
```sh
cp .env.example .env
```
Edit `.env`, then run:
```sh
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:
```sh
ssh -L 6080:localhost:6080 user@your-server
```
Then open noVNC:
```text
http://localhost:6080/vnc.html
```
Login into your MyWhoosh account with keep me signed in (Important!).
Exit.
Start the production container
```sh
docker compose up -d --build
docker compose logs -f sync
```
The default polling interval is hourly.
Open the local dashboard:
```text
http://localhost:8080/
```
It shows the last sync check state and the last five uploaded Garmin activities.
## Configuration
Required values:
```env
GARMIN_EMAIL=
GARMIN_PASSWORD=
```
Useful optional values:
```env
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:
```sh
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:
```sh
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:
```sh
docker compose -f docker-compose.debug.yml up --build
```
Then open noVNC:
```text
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:
```env
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:
```sh
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:
```text
./data/debug
```
After a successful manual login, stop the debug container and run the normal service:
```sh
docker compose up -d --build
```
## Session Persistence
MyWhoosh auth is persisted in two places under the mounted `./data` directory:
```text
./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:
```text
./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:
```env
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.