Skip to content

notusknot/openibex

Repository files navigation

OpenIbex

CI

A self-hosted training platform for endurance athletes.

OpenIbex Dashboard

Features

  • Activities — upload FIT files, browse, search, filter, and sort your full training history
  • Planned workouts & calendar — schedule sessions and see planned vs. completed side by side
  • Calendar matching — automatically links planned workouts to the activities that fulfilled them
  • Analytics — training load, fitness/fatigue, and trends over any date range
  • Garmin bulk import — load your entire history from a Garmin account data export (no API, no credentials)
  • Garmin Connect sync — optional, experimental auto-sync of new activities (details)
  • Data export & backup — generate a full export of your data at any time
  • Self-contained — single container, SQLite, everything stored under one ./data directory

Quickstart (Docker Compose)

cp .env.example .env

Generate a strong session secret and paste it into .env as SESSION_SECRET=...:

openssl rand -base64 32

If you want to use the experimental Garmin Connect sync feature, generate another secret and paste it into .env as SYNC_ENCRYPTION_KEY:

openssl rand -base64 32

If you are running behind a reverse proxy, set ORIGIN in .env to your domain.

Then start the app:

docker compose up -d --build
curl -s http://localhost:3000/api/health     # -> { "ok": true }

Open http://localhost:3000/register and create your account. Note that the first user becomes the admin. Registration is closed to new users after that. Set OPEN_REGISTRATION=true in .env if you want to allow more.

All data is stored in ./data, which is mounted to /data in the Docker container. The container takes ownership of this directory automatically on startup, so there's no manual chown step — docker compose up -d --build is all you need. By default the app runs as uid/gid 1000; if the host user that owns ./data has a different uid, set PUID/PGID in .env to match (see Configuration). To back up your data, simply back up the ./data directory.

Configuration

All configuration is via environment variables in .env:

Variable Default Description
SESSION_SECRET Required in production. Long random value (16+ chars).
SESSION_TTL_DAYS 30 Session lifetime.
OPEN_REGISTRATION false Allow registration beyond the first user.
SYNC_ENCRYPTION_KEY Required only for Garmin Connect sync (see below).
API_TOKEN Enables the read-only HTTP API. Unset = API disabled.
API_USER_EMAIL first user Which account the API serves (only matters on multi-user installs).
PUID 1000 Host uid that owns ./data; the app runs as it and the entrypoint chowns /data to match.
PGID 1000 Host gid counterpart to PUID.

HTTP API

A small read-only JSON API for external dashboards (e.g. a homelab dashboard). It's disabled by default — set API_TOKEN in .env to turn it on:

openssl rand -base64 32     # paste into .env as API_TOKEN=...

Callers authenticate with that token as a bearer header. The API serves a single account: the first-registered user, or API_USER_EMAIL if you set one.

TOKEN=your-api-token
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/v1/summary
Endpoint Returns
GET /api/v1/summary Current training load — fitness (CTL), fatigue (ATL), form (TSB), week TSS, readiness, monotony, strain.
GET /api/v1/series The 84-day CTL/ATL/TSB time-series, for charting.
GET /api/v1/activities?limit=N Recent activities (default 20, max 100) plus a summary total.
GET /api/v1/planned?days=N Upcoming planned workouts (default 14 days, max 90).

A request with no/invalid token gets 401; if API_TOKEN is unset the endpoints return 503. Adding an endpoint later is one new file under src/routes/api/v1/.

Meant for server-side callers on a trusted LAN/Tailscale network — there is no rate limiting or CORS, and the token grants read access to everything above. Don't expose it to the public internet or call it from browser JavaScript (which would leak the token). On NixOS, provide API_TOKEN via services.openibex.environmentFile (see docs/nixos.md) so it stays out of the Nix store.

Deploy on NixOS (flake module)

OpenIbex ships a flake with a NixOS module that runs it on bare metal (a plain systemd service, no Docker) behind your own reverse proxy. See docs/nixos.md for setup, options, and how to keep the flake input up to date.

Garmin bulk import

Import your full history from a Garmin account data export (no Garmin API or credentials involved). To do so, request the export from your Garmin account settings, download and extract it, then run:

pnpm import:garmin -- --user you@example.com --path /path/to/extracted/garmin-export

The importer recursively finds .fit files, removes duplicates, and logs failures without aborting. It's safe to re-run the same export. Results show at http://localhost:3000/imports.

Garmin Connect sync (experimental)

⚠️ Experimental This talks to Garmin Connect through an unofficial library by logging in as you. Automated access is against Garmin's TOS, two-factor auth is not supported, and Garmin can break the integration at any time.

Once connected, OpenIbex pulls new activities when you open the app (limited to once per 15 minutes) plus a manual Sync now button. There is no background worker. Set SYNC_ENCRYPTION_KEY (openssl rand -base64 32) to encrypt the stored session tokens, then connect under Settings → Integrations → Garmin Connect. For full history, use the bulk importer.

Local development

cp .env.example .env
pnpm install
pnpm db:migrate
pnpm dev          # http://localhost:3000

Requires Node.js 20+ (the repo's Nix flake pins Node 22) and pnpm 10+. If you use Nix, direnv allow sets up the toolchain and environment automatically.

Contributing? See docs/development.md for the full feature lifecycle (branch → PR → merge), the enforcement model, and how to cut a release.

If /api/health returns a better-sqlite3 "Could not locate the bindings file" error, the native build didn't run, you can rebuild it:

pnpm rebuild better-sqlite3

Tests and checks

pnpm test         # vitest
pnpm check        # svelte-check (types + svelte)

Git hooks

pnpm install wires the tracked git hooks automatically (via the prepare script). To enable them by hand:

git config core.hooksPath .githooks

The pre-commit hook runs pnpm check + pnpm test and aborts the commit on failure. It's local and bypassable (git commit --no-verify); CI on pull requests is the real gate. See docs/development.md.

Database

Drizzle ORM with Drizzle Kit migrations. Migrations apply automatically on server startup. During development:

pnpm db:generate  # create a migration from schema changes
pnpm db:migrate   # apply migrations

Tech stack

  • SvelteKit + TypeScript (@sveltejs/adapter-node)
  • SQLite via better-sqlite3
  • Drizzle ORM + Drizzle Kit migrations
  • pnpm, single-container Docker deployment

Notes

  • Run behind HTTPS in production and set a strong SESSION_SECRET.
  • Back up the host ./data directory regularly — it holds the database, uploads, and stream data.

About

Self-hosted platform for endurance athletes

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors