Quick import from Strava dump to FitTrackee & FitPub
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-22 20:23:34 +02:00
.forgejo/workflows Update non-major updates 2026-07-21 18:22:25 +00:00
src/fitimport fitpub: add fitpub-reconcile to repair the ledger from server records 2026-06-03 21:44:58 +02:00
tests fitpub: add fitpub-reconcile to repair the ledger from server records 2026-06-03 21:44:58 +02:00
.gitignore Add fitpub batch-import target 2026-06-03 17:37:15 +02:00
CHANGELOG.md Release 0.2.0: switch to the tox-uv locked toolchain 2026-07-09 17:36:22 +02:00
LICENSE Initial scaffold: fitimport with FitTrackee Strava-export importer 2026-06-03 17:21:21 +02:00
pyproject.toml Update non-major updates 2026-07-21 18:22:25 +00:00
README.md Rename tox env tests to test to match the other repos 2026-07-09 15:13:55 +02:00
renovate.json Add renovate.json 2026-06-12 18:28:46 +00:00
tox.ini Rename tox env tests to test to match the other repos 2026-07-09 15:13:55 +02:00
uv.lock Update non-major updates 2026-07-21 18:22:25 +00:00

fitimport

Bulk-import activity archives into self-hosted fitness trackers. Two targets are supported: FitTrackee (per-file upload) and FitPub (async zip batch import).

Requirements

  • Python ≥ 3.12
  • An account on the target instance:
    • FitTrackee — email + password.
    • FitPub — username (or email) + password.
  • An unpacked Strava bulk export ("Download your data") — the directory with activities.csv and an activities/ folder of .fit/.gpx/.tcx files (gzipped .fit.gz members are handled transparently)

Install

pipx install -e .
# or, for development:
pip install -e '.[dev]'

Usage

Both subcommands read the same Strava export directory. The password is prompted, or read from FITIMPORT_PASSWORD.

FitTrackee

One synchronous upload per activity (.fit/.gpx/.tcx/.kml), each tagged with a mapped sport:

fitimport fittrackee ~/Downloads/strava-export_XXXXXX \
    --url https://fittrackee.local \
    --email you@example.com \
    --insecure          # only for a self-signed / internal CA; or use --cafile

FitPub

Packs the export into zips and feeds FitPub's async batch importer, polling each job to completion. FitPub derives the sport from each file, so there's no sport mapping; it accepts only .fit/.gpx, so .tcx is skipped:

fitimport fitpub ~/Downloads/strava-export_XXXXXX \
    --url https://fitpub.local \
    --user you@example.com \
    --insecure

Zips are kept under --max-batch-mb (default 20) and FitPub's 1000-files-per-zip cap. The default is deliberately small: FitPub's post-import Phase 2 loads the whole batch into memory for analytics, so large batches spike its JVM heap. Raise it (up to ~50, Spring's per-request cap) only if FitPub has memory headroom.

Skip-and-log behaviour

Every activity ends up imported, skipped (with a reason), or failed (with the server's error). The run prints a summary:

imported=5380 skipped=147 failed=12 already-done=0

Activities are skipped (with the reason logged) when:

  • the row is a manual Strava entry with no track file (both targets);
  • FitTrackee: the Strava type has no sport mapping — indoor/no-GPS types (Yoga, Weight Training, Workout) and types with no native sport (Snowboard) — or the mapped sport isn't present on the target instance;
  • FitPub: the file isn't a format its batch import accepts (only .fit/ .gpx, so .tcx is skipped), or FitPub itself reports the file as skipped.

Failures — a .fit with no GPS track, a file over the server's size limit, or any other per-file error — are logged and the run continues.

Idempotent re-runs

Outcomes are appended to a JSONL ledger (--state), which defaults to fitimport-state.jsonl for fittrackee and fitimport-fitpub-state.jsonl for fitpub. Re-running skips already-imported activities but retries skipped and failed ones — so the workflow of "fix something (a sport mapping, a server-side limit), then re-run" picks up exactly the activities that didn't make it the first time.

Use one consistent --state file per target+instance. The ledger is the only duplicate guard: neither FitTrackee nor FitPub deduplicates an uploaded track, so importing the same file twice creates two activities. In particular, don't trial with one --state and then do the full run with a different one — reuse the same ledger so the trial's activities are remembered and skipped. (--limit N is the right way to trial: it caps how many activities are uploaded this run, against the same ledger you'll keep using.)

Configuration

Sport mapping

The Strava-type → FitTrackee-sport table lives in src/fitimport/sportmap.py. Labels must match the sport names on your instance (GET /api/sports); edit the table to taste. A None value (or an unlisted type) means skip-and-log.

TLS

If the instance is served behind a self-signed or internal CA (common for LAN/self-hosted setups), the importing host won't trust its certificate. Either pass --insecure to skip verification, or point --cafile at the CA's root certificate to verify properly.

Single-file size cap

FitTrackee's default max_single_file_size is 1 MB; a Strava export usually has a handful of .fit files above that. Raise it under Administration → Application in the FitTrackee UI before importing, then re-run to catch the size failures.

Development

tox -e lint
tox -e typecheck
tox -e test
tox -e audit

tox -e format applies ruff autofixes and formatting.

License

GPLv3 — see LICENSE.