# Project pack: Endurain @ 2f33e45

Facts about **Endurain** for an AI following the [BespokeAI handover guide](https://handoff.bespokeai.software/guide/v1.md). This pack is pinned to one version and never changes after publication.

> Endurain keeps a diary of your exercise on your own server. Upload files from your watch or bike computer, or link Garmin Connect or Strava, and see each run, ride or swim with its map, pace and heart rate. A health section tracks sleep, weight, steps, water and more, and you can set weekly goals. Friends can follow you, but everything is private unless you choose to share it.

## Version and source
- Repository: https://github.com/endurain-project/endurain
- Pinned commit: `2f33e455989278a9c64d50d1994aab63b204ecf2`. Use exactly this version.
- Get it: `git clone https://github.com/endurain-project/endurain endurain && cd endurain && git checkout 2f33e455989278a9c64d50d1994aab63b204ecf2`
- Or download a zip: https://github.com/endurain-project/endurain/archive/2f33e455989278a9c64d50d1994aab63b204ecf2.zip
- Official docs: https://docs.endurain.com
- Live demo: https://demo.endurain.com (username: admin / password: admin). Official shared demo. It resets every night (Lisbon time). Don't upload anything private.
- Licence: **AGPL-3.0-only**. Free to use and change. If you run a changed version that other people use over the internet (not just you or your household), you must offer them your source code under the same licence. "Endurain" is a trademark: self-hosting and non-commercial use of the name are fine, but paid hosting under that name needs the author's permission.

## Running it
- Easiest:

  ```sh
  sudo mkdir -p /var/opt/endurain/backend/{data,logs}
  sudo chown -R 1000:1000 /var/opt/endurain/backend
  cd /var/opt/endurain
  wget -O docker-compose.yml https://raw.githubusercontent.com/endurain-project/endurain/master/docker-compose.yml.example
  wget -O .env https://raw.githubusercontent.com/endurain-project/endurain/master/.env.example
  # edit .env: DB_PASSWORD = POSTGRES_PASSWORD (openssl rand -hex 32), SECRET_KEY (openssl rand -hex 32),
  # FERNET_KEY (openssl rand -base64 32), TZ and ENDURAIN_HOST (your https address)
  sudo docker compose up -d
  # serve it over HTTPS with a reverse proxy, log in as admin/admin and change the password at once
  ```

- Needs: Docker with Docker Compose, A domain and HTTPS reverse proxy (Caddy, Nginx Proxy Manager…). Login cookies are HTTPS-only in production., For development, Python 3.13 with uv, and Node.js 24 (or 22.18+)
- Develop: `docker build -f docker/Dockerfile -t unified-image . && docker compose up -d   # backend + Postgres, see docs/developer-guide/setup-dev-env.md; then cd frontend && npm install && npm run dev`
- Test: `cd backend && uv sync && uv run pytest -q && cd ../frontend && npm test`
- Check/lint: `cd backend && PYTHONPATH=app uv run lint-imports && uv run ruff format --check . && uv run ruff check . && cd ../frontend && npm run check`
- Stack: python, typescript, vue (FastAPI, SQLAlchemy 2, Alembic, Vue 3, Vite, Tailwind CSS 4, shadcn-vue, TanStack Query, Pinia). Difficulty for a non-developer with an AI: **advanced**. A substantial two-part app: a FastAPI backend split into strict modules (backend/app/modules/) with import rules enforced by tests, and a Vue 3 TypeScript frontend organised by feature (frontend/src/features/). The frontend's API types are generated from the backend. Upstream's AGENTS.md and skills are thorough, which helps an AI a lot, but most changes touch both halves.

## The project's own AI instructions
Read these in the repository before changing code: `AGENTS.md`, `.agents/skills/endurain-backend/SKILL.md`, `.agents/skills/endurain-frontend/SKILL.md`, `CONTRIBUTING.md`.

## Customisation points (settings → extend → code)
- **privacy-defaults** [settings]: Default visibility for new activities and which details (start time, map, heart rate…) are hidden from others. → `Settings → My profile → Privacy (docs/features/sharing-and-privacy.md)`
- **server-settings** [settings]: Metric or imperial units, public share links, who can sign up, password rules, map tiles and the login page picture. → `Settings → Server settings (frontend/src/features/serverSettings/), e.g. Sharing, sign-up, login photo`
- **goals-and-targets** [settings]: Goals per sport for distance, time, elevation, calories or number of activities, and targets for sleep, steps, weight and water. → `Settings → My goals (frontend/src/features/goals/); Health → per-metric targets`
- **language** [settings]: The interface language for each person. → `Language menu in the top bar (frontend/src/i18n/locales/, 30+ languages)`
- **integrations** [extend]: Connect Garmin Connect, Strava, Gadgetbridge or RunnerUp so activities arrive by themselves. → `Settings → Integrations (docs/features/integrations.md)`
- **import-and-api** [extend]: Bulk-import years of .fit/.gpx/.tcx files or a Strava export; upload new files from scripts with an API key. → `Settings → Import (docs/features/importing.md); API keys (docs/integrate/api-keys.md)`
- **frontend-features** [code]: New or changed screens, such as a different home page or a new chart. → `frontend/src/features/<feature>/ (home, health, activities, summary…), text in frontend/src/i18n/locales/en/`
- **backend-modules** [code]: New data or behaviour. Regenerate frontend types with `npm run gen:api` afterwards. → `backend/app/modules/<module>/ (router.py → service.py → crud.py, models.py) with Alembic migrations in backend/app/alembic/`

## Must keep working
- Original activity files in the data folder and the database. Back up both before upgrades or code changes.
- Privacy rules. Private and hidden details must never reach followers or public links.
- Duplicate detection on imports, so re-running an import never doubles activities.
- Module boundaries (backend/.importlinter and backend/tests/architecture/). Every database change needs an Alembic migration.
- Never hand-edit frontend/src/types/api.generated.ts; regenerate it with `npm run gen:api`.

## Staying updatable
Privacy, goals, server settings, integrations and imports cover most personal needs without a fork. Endurain is on a feature freeze while the author strengthens the foundations, and CONTRIBUTING.md asks for an issue before any feature work and pull requests under about 300 lines. Keep personal features in your own copy.

## Data, costs and safety
- Personal data: **yes**. Stored in: PostgreSQL (LOCAL_PATH/postgres) for activities, health data and users; original activity files and photos in LOCAL_PATH/backend/data. GPS tracks show where you live and train; health entries are sensitive. Back up both the database and the data folder (see docs/getting-started/operations.md), and use the built-in export before upgrades or code changes.
- Service: Garmin Connect (optional, free with a Garmin account): Pull in activities, sleep, steps and weight from a Garmin watch automatically.
- Service: Strava (optional, free (needs your own Strava API app)): Sync activities to and from Strava. Needs ENDURAIN_HOST on https.
- Service: Reverse geocoding (Nominatim, Photon or geocode.maps.co) (optional, free public services by default): Turns a route's start point into a town name. Coordinates are sent to the chosen service; you can self-host one.
- Service: Email (SMTP) (optional, free with most providers): Password resets and notification emails.
- Service: OIDC single sign-on (Authentik, Authelia, Keycloak, Pocket ID…) (optional, free (self-hosted) or varies): Log in with an existing account.
- Setting `DB_PASSWORD` (**secret**: the person enters it themselves, never in chat): Database password. Must match POSTGRES_PASSWORD. Never paste it into an AI chat.
- Setting `POSTGRES_PASSWORD` (**secret**: the person enters it themselves, never in chat): Same value as DB_PASSWORD, used by the database container.
- Setting `SECRET_KEY` (**secret**: the person enters it themselves, never in chat): Signs login tokens. Make one with `openssl rand -hex 32`.
- Setting `FERNET_KEY` (**secret**: the person enters it themselves, never in chat): Encrypts stored Garmin and Strava tokens. Make one with `openssl rand -base64 32` and keep a copy.
- Setting `ENDURAIN_HOST`: The full address you open Endurain at, e.g. https://fitness.example.com. Login fails if it doesn't match.
- Setting `TZ`: Your time zone, e.g. Europe/London. Used for indoor activities without GPS.
- Setting `BEHIND_PROXY`: Set to true when a reverse proxy sits in front of Endurain.
- Setting `LOCAL_PATH`: Folder on the host for activity files, images, logs and the database (default /var/opt/endurain).
- Setting `REVERSE_GEO_PROVIDER`: Which service names your start locations (nominatim, photon or geocode).
- Setting `SMTP_HOST`: Email server for password resets. SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD (secret) and SMTP_FROM go with it.
- Setting `SMTP_PASSWORD` (**secret**: the person enters it themselves, never in chat): Password for the email account.
- Change the default admin/admin password straight after the first start.
- Serve it only over HTTPS behind a reverse proxy. Production login cookies won't work on plain http.
- GPS tracks reveal your home and routines. Keep activities private or hide start locations before sharing.
- By default, start coordinates are sent to the public OpenStreetMap Nominatim service to name the place. Self-host a geocoder if that worries you.
- Keep SECRET_KEY, FERNET_KEY and database passwords out of AI chats. Losing FERNET_KEY means reconnecting Garmin and Strava.

---

# Customising Endurain

Endurain is a two-part app (Python backend, Vue frontend) with strict internal rules. **Most personal needs are settings, integrations or imports.** Exhaust levels 1 and 2 before touching code.

## Level 1: Settings (no code)
- **Privacy:** Settings → My profile → Privacy.
  - Default visibility for new activities (public, followers or private).
  - Details to hide from others: start time, location, map, heart rate and more.
  - "Existing activities visibility" updates every past activity at once.
  - See `docs/features/sharing-and-privacy.md`.
- **Server settings (admin):** Settings → Server settings.
  - Units (metric or imperial), currency, public share links.
  - Sign-up rules (open, admin approval, email verification) and password rules.
  - Map tile server, login page photo and email.
- **Goals:** Settings → My goals. Per sport, for distance, time, elevation, calories or number of activities.
- **Health targets:** each Health page (sleep, steps, weight, water, fasting) has its own target.
- **Language:** the language menu in the top bar. More than 30 languages, translated on Codeberg Translate.
- **Server options:** environment variables in `.env`, all listed in `docs/getting-started/advanced-setup.md`.

## Level 2: Extend (no fork)
- **Integrations:** Settings → Integrations. Garmin Connect (activities, sleep, steps, weight), Strava, Gadgetbridge and RunnerUp. See `docs/features/integrations.md`.
- **Bulk import:** Settings → Import. Upload a ZIP of .fit/.gpx/.tcx files or a Strava bulk export, or drop files in `data/activity_files/bulk_import/<user id>/`. Duplicates are skipped. See `docs/features/importing.md`.
- **Export:** your data can be exported and re-imported (`docs/features/export-import.md`).
- **API:** API keys (`docs/integrate/api-keys.md`) currently allow uploading activities only. Other API calls need a normal login token (`docs/integrate/authentication.md`).
- **Single sign-on:** log in through Authentik, Authelia, Keycloak, Pocket ID and others (`docs/features/single-sign-on.md`).

## Level 3: Code
Read upstream `AGENTS.md` first, then the skill for the half you are changing: `.agents/skills/endurain-frontend/SKILL.md` or `.agents/skills/endurain-backend/SKILL.md`.

| Change | Where |
|---|---|
| Home page | `frontend/src/features/home/` (`views/HomeView.vue`, `components/`) |
| Health pages | `frontend/src/features/health/` |
| Activity pages | `frontend/src/features/activities/` |
| Totals by week, month or year | `frontend/src/features/summary/` |
| Text | `frontend/src/i18n/locales/en/*.json` (other languages come from Codeberg Translate) |
| New endpoint | `backend/app/modules/<module>/router.py` → `service.py` → `crud.py` |
| New data | `backend/app/modules/<module>/models.py` + an Alembic migration in `backend/app/alembic/` |
| Frontend API types | Run `npm run gen:api` in `frontend/`. Never edit `src/types/api.generated.ts` by hand. |

Rules from upstream: semantic Tailwind tokens and shadcn-vue parts (no hard-coded colours); every string through vue-i18n; never `v-html` on user data; file uploads only through `backend/app/core/file_uploads.py`.

## Running locally
- **Just use it:**
  1. Create `/var/opt/endurain/backend/{data,logs}` owned by user 1000.
  2. Download `docker-compose.yml.example` and `.env.example` (rename to `docker-compose.yml` and `.env`).
  3. Fill in the passwords and keys in `.env`.
  4. Run `docker compose up -d`.
  5. Put an HTTPS reverse proxy in front (`docs/getting-started/reverse-proxy.md`). Log in as admin/admin and change the password.
- **Develop:** follow `docs/developer-guide/setup-dev-env.md`.
  1. Build the image: `docker build -f docker/Dockerfile -t unified-image .`
  2. Run it with Postgres, `ENVIRONMENT=development` and `ENDURAIN_HOST=http://localhost:8080`. Mount `backend/app` so code changes apply after a restart.
  3. In `frontend/`, run `npm install && npm run dev` for the Vite dev server.
- **Check:**
  - Backend: in `backend/`, `uv sync`, `PYTHONPATH=app uv run lint-imports`, `uv run pytest tests/<area> -q`, `uv run ruff check .`
  - Frontend: in `frontend/`, `npm run check` (types, lint, format and tests).

## Pitfalls
- In production, login cookies are HTTPS-only and `ENDURAIN_HOST` must match the address in the browser, or you can't log in.
- Back up the Postgres folder **and** `backend/data`. Activity files are the originals from your watch.
- Keep `FERNET_KEY` safe. Without it, stored Garmin and Strava connections can't be decrypted.
- The backend's module boundaries are enforced by tests. Put code where `docs/developer-guide/module-structure.md` says, or the checks fail.
- Upstream is on a feature freeze and wants an issue before any feature PR. Keep personal features in your own fork.

---
Machine-readable: [bespoke.yaml](https://handoff.bespokeai.software/p/endurain/2f33e45/bespoke.yaml) · Agent skill: [SKILL.md](https://handoff.bespokeai.software/p/endurain/2f33e45/SKILL.md)
