Turn your Strava and Garmin activities into GitHub-style contribution graphs. Automatically generate a free, interactive dashboard updated daily on GitHub Pages.
No coding required.
View the Interactive Activity Dashboard.
Once setup is complete, this dashboard link will automatically update to your own GitHub Pages URL.
bash <(curl -fsSL https://raw.githubusercontent.com/aspain/git-sweaty/main/scripts/bootstrap.sh)You will be prompted for:
- Setup mode:
- Recommended (Online-only, no local clone): setup script will either:
- use an existing fork
- create a new fork
- configure an existing writable repo
- Advanced (Local clone + git remotes): setup script will prefer an existing compatible local clone when available, or guide fork-and-clone setup, then complete the rest of the setup.
- Manual (No setup scripts): follow Manual Setup (No Scripts)
- Recommended (Online-only, no local clone): setup script will either:
- GitHub Pages custom domain (if you have one, for example
yoursite.example.com) - Source (
stravaorgarmin) - Unit preference (
USorMetric) - Heatmap week start (
SundayorMonday) - Optional profile link in the dashboard header for the selected source (
YesorNo) - Optional tooltip links to individual activities for the selected source (
YesorNo) - Source auth credentials:
- Strava: prompt will provide a link to create a Strava API application first, set Authorization Callback Domain to
localhost.- Then paste the
client_id+client_secretvalues in the prompt, then a browser tab will open for OAuth approval
- Then paste the
- Garmin: account email + password
- Strava: prompt will provide a link to create a Strava API application first, set Authorization Callback Domain to
The setup may take several minutes to complete when run for the first time. If any automation step fails, the script prints steps to remedy the failed step.
Once the script succeeds, it will provide the URL for your dashboard.
- To pull in new updates and features from the original repo, use GitHub's Sync fork button on your fork's
mainbranch. - Activity data is stored on a dedicated
dashboard-databranch and deployed from there mainis intentionally kept free of generateddata/andsite/data.jsonartifacts so fork sync process stays cleaner.- After syncing, manually run Sync Heatmaps if you want your dashboard refreshed immediately. Otherwise updates will deploy at the next scheduled run.
You can switch between strava and garmin at any time.
- Re-run
./scripts/bootstrap.sh(or the quickstart curl command) and choose a different source. - If you re-run setup and choose the same source, setup asks whether to force a one-time full backfill. You can also update your response for unit preference, day of week start, placing strava/garmin profle link on your dashboard, and whether you'd like activity links in the tooltips.
- The GitHub Pages site is optimized for responsive desktop/mobile viewing.
- To click activity urls while viewing on desktop, click the graph dot to freeze the tooltip in place.
- If a day contains multiple activity types, that day’s colored square is split into equal segments — one per unique activity type on that day.
- Raw activities are stored locally for processing but are not committed (
activities/raw/is ignored). This prevents publishing detailed per-activity payloads and GPS location traces. - If neither
sync.start_datenorsync.lookback_yearsis set, the sync workflow backfills all available history from the selected source (i.e. Strava/Garmin). - Strava backfill state is stored in
data/backfill_state_strava.json; Garmin backfill state is stored indata/backfill_state_garmin.json. If a backfill hits API limits (unlikely), this state allows the daily refresh automation to pick back up where it left off. - The Sync action workflow includes a toggle labeled
Reset backfill cursor and re-fetch full history for the selected sourcewhich forces a one-time full backfill. This is useful if you add/delete/modify activities which have already been loaded.
Everything in this section is optional. Defaults work without changes.
Base settings live in config.yaml, and config.local.yaml overrides them when present.
Auth + source settings:
source(stravaorgarmin)strava.client_id,strava.client_secret,strava.refresh_token,strava.profile_urlstrava.include_activity_urls(whentrue, yearly tooltip details include links to individual Strava activities)garmin.token_store_b64,garmin.email,garmin.password,garmin.profile_urlgarmin.include_activity_urls(whentrue, yearly tooltip details include links to individual Garmin activities)garmin.strict_token_only(whentrue, Garmin sync requiresgarmin.token_store_b64and does not fall back to email/password auth)
Sync scope + backfill behavior:
sync.start_date(optionalYYYY-MM-DDlower bound for history)sync.lookback_years(optional rolling lower bound; used only whensync.start_dateis unset)sync.recent_days(sync recent activities even while backfilling)sync.resume_backfill(persist cursor so backfills continue across scheduled runs)sync.per_page(page size used when fetching provider activities; default200)sync.prune_deleted(remove local activities no longer returned by the provider; pruning only happens on runs that perform a full backfill scan)
Activity type behavior:
activities.types(featured order in UI, and acts as allowlist whenactivities.include_all_typesisfalse)activities.include_all_types(whentrue, include all seen sport types; whenfalse, include onlyactivities.types)activities.exclude_types(explicit type exclusions, even wheninclude_all_typesistrue)activities.type_aliases(map raw provider type names to canonical type names before grouping/filtering)activities.group_aliases(map canonical type names to explicit grouped labels)activities.group_other_types(whentrue, non-featured types are grouped into broader buckets; repo default isfalse)activities.other_bucket(fallback group name when grouped type matching has no hit)
Display + rate-limit settings:
units.distance(miorkm)units.elevation(ftorm)heatmaps.week_start(sundayormonday)rate_limits.*(Strava API pacing caps used by sync; ignored for Garmin)
Use this if you do not want to run bootstrap.sh or setup_auth.py.
- Fork this repository on GitHub.
- In your fork, keep
maincurrent with upstream using Sync fork. - In your fork, enable GitHub Actions workflows:
Settings->Actions->General
- In your fork, set GitHub Pages to deploy from Actions:
Settings->Pages->Source->GitHub Actions
- In your fork, add these repository variables:
Settings->Secrets and variables->Actions->VariablesDASHBOARD_SOURCE:stravaorgarminDASHBOARD_REPO: your fork slug (example:yourname/git-sweaty)DASHBOARD_DISTANCE_UNIT:miorkmDASHBOARD_ELEVATION_UNIT:ftormDASHBOARD_WEEK_START:sundayormonday
- Optional variables for dashboard links:
- Strava:
DASHBOARD_STRAVA_PROFILE_URL,DASHBOARD_STRAVA_ACTIVITY_LINKS(true/false) - Garmin:
DASHBOARD_GARMIN_PROFILE_URL,DASHBOARD_GARMIN_ACTIVITY_LINKS(true/false)
- Strava:
Set repository secrets here:
Settings->Secrets and variables->Actions->Secrets
- Create a Strava API app at strava.com/settings/api.
- Set
Authorization Callback Domaintolocalhost. - Save your Strava
Client IDandClient Secret. - Open this URL in a browser (replace
YOUR_CLIENT_ID):
https://www.strava.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2Fexchange_token&approval_prompt=force&scope=read%2Cactivity%3Aread_all
- Approve access. You will be redirected to a localhost URL. Copy the
codevalue from that URL. - Exchange the code for tokens:
curl -sS -X POST https://www.strava.com/oauth/token \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d code=YOUR_CODE \
-d grant_type=authorization_code- From the response JSON, copy
refresh_token. - Add these secrets to your fork:
STRAVA_CLIENT_IDSTRAVA_CLIENT_SECRETSTRAVA_REFRESH_TOKEN
- Optional but recommended for automatic token rotation:
- Add
STRAVA_SECRET_UPDATE_TOKEN(a GitHub token with repo write access to this fork).
- Add
Choose one auth path:
- Easiest: add both
GARMIN_EMAILGARMIN_PASSWORD
- Token-only path: add
GARMIN_TOKENS_B64
GARMIN_TOKENS_B64 is optional unless you explicitly run token-only config.
- Go to
Actions->Sync Heatmaps. - Click
Run workflowon themainbranch. - Optional: set
sourceexplicitly (stravaorgarmin) when running manually. - Wait for
Sync Heatmapsto complete successfully. - Confirm
Deploy Pagesruns (automatically after a successful sync). - Open your dashboard at:
https://YOUR_GITHUB_USERNAME.github.io/YOUR_REPO_NAME/