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:
- Local: setup script will create the fork, clone the repo, and complete the rest of the setup.
- Online (default): no local clone; setup script will configure a fork or an existing writable repo
- 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)
