- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| src/fitimport | ||
| tests | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| renovate.json | ||
| tox.ini | ||
| uv.lock | ||
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.csvand anactivities/folder of.fit/.gpx/.tcxfiles (gzipped.fit.gzmembers 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.tcxis 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
--statefile 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--stateand then do the full run with a different one — reuse the same ledger so the trial's activities are remembered and skipped. (--limit Nis 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.