-
Notifications
You must be signed in to change notification settings - Fork 0
Installation
This guide covers installing StravaStats on an OpenWrt router from scratch.
- OpenWrt 21.02+ with SSH access as
root - ~1–2 MB free space for
curl,jq,ca-bundleand their libs - The repo cloned or downloaded to your PC
Your router's LAN/gateway IP is usually 192.168.1.1. On Windows:
ipconfig | Select-String "Default Gateway"SSH must be enabled (LuCI → System → Administration → SSH Access, or on by default on a fresh OpenWrt). Test:
ssh root@192.168.1.1
exitWindows 10/11 ship with ssh and scp built in. If not found, enable Settings → Apps → Optional features → OpenSSH Client.
From the repo root on your PC:
scp -r . root@192.168.1.1:/tmp/strava
ssh root@192.168.1.1 sh /tmp/strava/install.shOptional overrides:
CRON_TIME="0 6 * * *" sh /tmp/strava/install.sh # different cron time
TZ_POSIX="" sh /tmp/strava/install.sh # don't change router timezone- Installs deps
curl jq ca-bundle(auto-detectsapkon OpenWrt 24.10+/snapshots, oropkgon older releases) - Installs
strava-leaderboardto/usr/bin/strava-leaderboard - Installs
strava-my-activitiesto/usr/bin/strava-my-activities - Installs
healthsync-activitiesto/usr/bin/healthsync-activities - Installs all HTML helper scripts to
/usr/bin/ - Drops config templates at
/etc/strava-leaderboard.conf,/etc/strava-my-activities.conf,/etc/healthsync-activities.conf - Sets the router timezone to Europe/Warsaw (POSIX
CET-1CEST,M3.5.0,M10.5.0/3, DST-aware) - Adds cron entries: leaderboard at 23:50, my-activities at 23:55, healthsync at 23:55 (Warsaw time), then (re)starts cron
- Creates
HEALTHSYNC_STATE_DIRif set in an existing config (safe for USB mounts)
Config files are never overwritten. If a config already exists, install.sh leaves it untouched. Re-running after an upgrade is safe.
Both my-activities and healthsync are scheduled. They write to the same output directory, so only run one at a time. When you switch to HealthSync, remove the strava-my-activities cron line:
crontab -l | grep -v 'strava-my-activities' | crontab -The writable overlay on a 128 MB flash device is only ~16 MB. Check free space:
df -h /overlayIf it is tight, consider extroot / a USB drive and point STRAVA_STATE_DIR at it.
A failed package-list update (apk update / opkg update) usually means no internet or DNS on the router. Check with ping -c1 downloads.openwrt.org.
You can also move web output and state off flash by setting path variables in the config:
| Config | State dir variable | Web dir variable |
|---|---|---|
/etc/strava-my-activities.conf |
STRAVA_MY_STATE_DIR |
STRAVA_MY_WEB_DIR |
/etc/strava-leaderboard.conf |
STRAVA_STATE_DIR |
STRAVA_WEB_DIR |
/etc/healthsync-activities.conf |
HEALTHSYNC_STATE_DIR |
HEALTHSYNC_WEB_DIR |
Example for a USB drive at /mnt/sda5:
HEALTHSYNC_STATE_DIR="/mnt/sda5/healthsync"
HEALTHSYNC_BIKE_DATA="/mnt/sda5/healthsync/bike-service.json"
HEALTHSYNC_BIKE_ASSIGN="/mnt/sda5/healthsync/bike-assignments.json"uhttpd only serves /www, so install.sh recreates the bridging symlinks under /www/strava on every run, and creates HEALTHSYNC_STATE_DIR if it does not exist. Re-run install.sh after changing any path.
Club leaderboard:
vi /etc/strava-leaderboard.conf # fill in client id/secret, refresh token, club id(s)
strava-leaderboard # run once now to verifyBrowse to http://<router-ip>/strava/. For credentials setup see Data-Source-Strava-API or Data-Source-Scrape-Mode.
My Activities:
vi /etc/strava-my-activities.conf # fill in credentials
strava-my-activities # run once now to verifyBrowse to http://<router-ip>/strava/me/.
HealthSync / Google Drive:
vi /etc/healthsync-activities.conf # fill in GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
# GOOGLE_REFRESH_TOKEN, DRIVE_FOLDER_ID
healthsync-activities # run once now to verifySee Data-Source-HealthSync for the Google OAuth setup steps.
A healthy run ends with done.:
2026-06-01 23:50:01 reusing cached access token (valid for 18230s more)
2026-06-01 23:50:01 fetching club 1234567 activities (up to 5 pages)...
2026-06-01 23:50:02 page 1: 143 activities
2026-06-01 23:50:02 short page, stopping
2026-06-01 23:50:02 club 1234567: +12 new (firstSeen 2026-06-01), 387 total
2026-06-01 23:50:03 wrote /www/strava/activities.json and per-club leaderboard JSON (snapshot 20260601)
2026-06-01 23:50:03 wrote /www/strava/index.html
2026-06-01 23:50:03 done.
Any line starting with ERROR: means the run aborted. Common causes: wrong STRAVA_REFRESH_TOKEN (token refresh request failed), bad STRAVA_CLUB_IDS (activities fetch failed), or curl/jq not installed.
To check scheduled (cron) runs:
tail -n 40 /var/log/strava-leaderboard.log
tail -n 40 /var/log/strava-my-activities.logIf the log is missing or empty after the scheduled time:
crontab -l | grep strava # is the cron line installed?
/etc/init.d/cron status # is the cron daemon running?
logread | grep cron # did cron actually fire the job?
date # is the router clock/zone right?Note: /var/log lives in RAM (tmpfs) on OpenWrt, so logs are cleared on reboot. To keep logs across reboots, point cron redirects at persistent storage, e.g. >> /mnt/sda5/strava-leaderboard.log 2>&1 via crontab -e.
Cron runs daily: leaderboard at 23:50, my-activities at 23:55, both in Warsaw time. OpenWrt's cron uses the router's local timezone, so install.sh sets it to Europe/Warsaw. Verify with date.
To change the times:
crontab -e
# 50 23 * * * /usr/bin/strava-cron-guard strava-leaderboard >> /var/log/strava-leaderboard.log 2>&1
# 55 23 * * * /usr/bin/strava-cron-guard strava-my-activities >> /var/log/strava-my-activities.log 2>&1
# 0 8 1 * * /usr/bin/strava-email-monthly >> /var/log/strava-email-monthly.log 2>&1Or reinstall with custom times:
CRON_TIME="0 6 * * *" CRON_TIME_ME="5 6 * * *" sh install.shUse TZ_POSIX="" sh install.sh to leave the router's timezone untouched.
The longest-climb algorithm on the activity detail page uses sport-specific thresholds that can be overridden in /etc/strava-my-activities.conf. All values are commented out in config-my.example; the defaults below are used when absent.
| Variable | Default | Description |
|---|---|---|
STRAVA_MY_CLIMB_MIN_GAIN_RIDE |
25 |
Minimum elevation gain (m) to qualify a Ride climb |
STRAVA_MY_CLIMB_MIN_GAIN_RUN |
5 |
Minimum elevation gain (m) for a Run |
STRAVA_MY_CLIMB_MIN_GAIN_HIKE |
10 |
Minimum elevation gain (m) for a Hike |
STRAVA_MY_CLIMB_MIN_GAIN_WALK |
3 |
Minimum elevation gain (m) for a Walk |
STRAVA_MY_CLIMB_MIN_GAIN_OTHER |
10 |
Minimum elevation gain (m) for all other sport types |
STRAVA_MY_CLIMB_MIN_DISTANCE |
100 |
Minimum horizontal distance (m) for any climb |
STRAVA_MY_CLIMB_MIN_GRADE_RIDE |
3 |
Minimum average grade (%) for a Ride climb |
STRAVA_MY_CLIMB_MIN_GRADE_RUN |
2 |
Minimum average grade (%) for a Run climb |
STRAVA_MY_CLIMB_MIN_GRADE_HIKE |
2 |
Minimum average grade (%) for a Hike climb |
STRAVA_MY_CLIMB_MIN_GRADE_WALK |
1 |
Minimum average grade (%) for a Walk climb |
STRAVA_MY_CLIMB_MIN_GRADE_OTHER |
2 |
Minimum average grade (%) for other sport types |
STRAVA_MY_CLIMB_DESCENT_RESET |
30 |
Drop (m) from the highest point that resets the climb window |
STRAVA_CLIMB_BATCH |
50 |
Max GPX files to process per run; remaining are processed on the next cron run (prevents router overload on first run after cache clear) |
These settings only affect the activity detail page (activity.html). The dashboard and stats pages use the same algorithm with fixed defaults that match the values above.