From 90aafe7d5f46118923deeadb8dcf3c001fae501e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= Date: Tue, 23 Dec 2025 12:09:59 +0000 Subject: [PATCH] Deployed 9cffa434 with MkDocs version: 1.6.1 --- 404.html | 2 +- developer-guide/authentication/index.html | 1248 ++++++++++++++--- developer-guide/setup-dev-env/index.html | 2 +- developer-guide/supported-types/index.html | 2 +- features/single-sign-on/index.html | 2 +- features/sleep-scoring/index.html | 2 +- gallery/index.html | 2 +- getting-started/advanced-started/index.html | 55 +- getting-started/bare-metal/index.html | 57 +- getting-started/getting-started/index.html | 2 +- .../maria-to-postgres-migration/index.html | 2 +- index.html | 2 +- integrations/3rd-party-apps/index.html | 2 +- integrations/3rd-party-services/index.html | 2 +- search/search_index.json | 2 +- sitemap.xml.gz | Bin 127 -> 127 bytes 16 files changed, 1152 insertions(+), 232 deletions(-) diff --git a/404.html b/404.html index 50fba869d..ee7ee59be 100644 --- a/404.html +++ b/404.html @@ -376,7 +376,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/developer-guide/authentication/index.html b/developer-guide/authentication/index.html index f09fe3f78..5e4c125f8 100644 --- a/developer-guide/authentication/index.html +++ b/developer-guide/authentication/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide @@ -779,9 +779,18 @@
  • - + - Client-Specific Token Delivery + OAuth 2.1 Token Storage Model (Hybrid Approach) + + + +
  • + +
  • + + + Token Delivery by Client Type @@ -803,9 +812,9 @@ @@ -860,6 +878,15 @@ +
  • + +
  • + + + Session Management Endpoints + + +
  • @@ -874,6 +901,15 @@ +
  • + +
  • + + + Progressive Account Lockout + + +
  • @@ -925,6 +961,75 @@ +
  • + +
  • + + + MFA Backup Codes + + + + +
  • @@ -979,13 +1084,13 @@
  • - + - Mobile SSO Implementation Guide + Mobile SSO with PKCE -
  • @@ -1190,9 +1346,18 @@ +
  • + +
  • + + + Progressive Account Lockout + + +
  • @@ -1483,6 +1684,75 @@ +
  • + +
  • + + + MFA Backup Codes + + + + +
  • @@ -1537,13 +1807,13 @@
  • - + - Mobile SSO Implementation Guide + Mobile SSO with PKCE -
  • @@ -1748,9 +2069,18 @@ +

    MFA Backup Codes

    +

    Backup codes provide a recovery mechanism when users lose access to their authenticator app. When MFA is enabled, users receive 10 one-time backup codes that can be used instead of TOTP codes.

    +

    Backup Code Format

    +
      +
    • Format: XXXX-XXXX (8 alphanumeric characters with hyphen)
    • +
    • Example: A3K9-7BDF
    • +
    • Characters: Uppercase letters and digits (excluding ambiguous: 0, O, 1, I)
    • +
    • One-time use: Each code can only be used once
    • +
    +

    When Backup Codes Are Generated

    +
      +
    1. Automatically on MFA Enable: When a user enables MFA, 10 backup codes are generated and returned in the response
    2. +
    3. Manual Regeneration: Users can regenerate all backup codes via POST /profile/mfa/backup-codes (invalidates all previous codes)
    4. +
    +

    API Endpoints

    + + + + + + + + + + + + + + + + + + + + + + + +
    WhatURLMethodDescription
    Get Backup Code Status/profile/mfa/backup-codes/statusGETReturns count of unused/used codes
    Regenerate Backup Codes/profile/mfa/backup-codesPOSTGenerates new codes (invalidates old)
    +

    Backup Code Status Response

    +
    {
    +  "has_codes": true,
    +  "total": 10,
    +  "unused": 8,
    +  "used": 2,
    +  "created_at": "2025-12-21T10:30:00Z"
    +}
    +
    +

    Regenerate Backup Codes Response

    +
    {
    +  "codes": [
    +    "A3K9-7BDF",
    +    "X2M5-9NPQ",
    +    "..."
    +  ],
    +  "created_at": "2025-12-21T10:30:00Z"
    +}
    +
    +

    Using Backup Codes for Login

    +

    Backup codes can be used in the MFA verification step instead of TOTP codes:

    +
    POST /api/v1/auth/mfa/verify
    +Content-Type: application/json
    +X-Client-Type: web|mobile
    +
    +{
    +  "username": "user@example.com",
    +  "mfa_code": "A3K9-7BDF"
    +}
    +
    +
    +

    Important

    +
      +
    • Backup codes are shown only once when generated - users must save them securely
    • +
    • Each backup code can only be used once
    • +
    • Regenerating codes invalidates ALL previous backup codes
    • +
    • Store backup codes in a secure location separate from your authenticator device
    • +
    +

    OAuth/SSO Integration

    Supported Identity Providers

    Endurain supports OAuth/SSO integration with various identity providers out of the box:

    @@ -2066,105 +2667,223 @@

    When authenticating via OAuth, the response format matches the standard authentication:

    • Web clients: Tokens set as HTTP-only cookies, redirected to app
    • -
    • Mobile clients using WebView: Tokens set as HTTP-only cookies in WebView, redirected to app
    • +
    • Mobile clients: Must use PKCE flow (see Mobile SSO with PKCE below)
    -
    -

    Mobile clients using WebView

    -

    Mobile apps must use WebView for OAuth/SSO flows to properly handle redirects and cookies. Tokens returned in JSON format is not currently supported for SSO.

    +
    +

    Mobile OAuth/SSO

    +

    Mobile apps must use the PKCE flow for OAuth/SSO authentication. This provides enhanced security and a cleaner separation between the WebView and native app.

    -

    Mobile SSO Implementation Guide

    +

    Mobile SSO with PKCE

    Overview

    -

    Mobile applications must use an embedded WebView (or in-app browser) to handle OAuth/SSO authentication. The flow leverages browser-based redirects and cookie storage that are part of the OAuth 2.0 standard.

    -

    Prerequisites

    +

    PKCE (Proof Key for Code Exchange, RFC 7636) is required for mobile OAuth/SSO authentication. It provides enhanced security by eliminating the need to extract tokens from WebView cookies, preventing authorization code interception attacks, and enabling a cleaner separation between the WebView and native app.

    +

    Why Use PKCE?

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Traditional WebView FlowPKCE Flow
    Extract tokens from cookiesTokens delivered via secure API
    Cookies may leak across contextsNo cookie extraction needed
    Complex WebView cookie managementSimple token exchange
    Potential timing issuesAtomic token exchange
    +

    PKCE Flow Overview

    +
    ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    +│  Mobile App │     │   Backend   │     │   WebView   │     │     IdP     │
    +└──────┬──────┘     └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
    +       │                   │                   │                   │
    +       │ Generate verifier │                   │                   │
    +       │ & challenge       │                   │                   │
    +       │──────────────────>│                   │                   │
    +       │                   │                   │                   │
    +       │     Open WebView with challenge       │                   │
    +       │──────────────────────────────────────>│                   │
    +       │                   │                   │                   │
    +       │                   │      Redirect to IdP                  │
    +       │                   │──────────────────────────────────────>│
    +       │                   │                   │                   │
    +       │                   │                   │   User logs in    │
    +       │                   │                   │<─────────────────>│
    +       │                   │                   │                   │
    +       │                   │   Callback with code & state          │
    +       │                   │<──────────────────────────────────────│
    +       │                   │                   │                   │
    +       │     Redirect with session_id          │                   │
    +       │<──────────────────────────────────────│                   │
    +       │                   │                   │                   │
    +       │ POST token exchange with verifier     │                   │
    +       │──────────────────>│                   │                   │
    +       │                   │                   │                   │
    +       │   Return tokens   │                   │                   │
    +       │<──────────────────│                   │                   │
    +       │                   │                   │                   │
    +
    +

    Step-by-Step PKCE Implementation

    +

    Step 1: Generate PKCE Code Verifier and Challenge

    +

    Before initiating the OAuth flow, generate a cryptographically random code verifier and compute its SHA256 challenge:

    +

    Code Verifier Requirements (RFC 7636):

      -
    • WebView component that supports:
        -
      • Cookie storage and management
      • -
      • JavaScript execution
      • -
      • URL interception/monitoring
      • -
      • Custom headers (for subsequent API calls)
      • +
      • Length: 43-128 characters
      • +
      • Characters: A-Z, a-z, 0-9, -, ., _, ~
      • +
      • Cryptographically random
      -
    • -
    • Secure storage for tokens (Keychain on iOS, KeyStore on Android)
    • -
    -

    Step-by-Step Implementation

    -

    Step 1: Fetch Available Identity Providers

    -

    Before presenting SSO options to users, fetch the list of enabled providers:

    -

    Request:

    -
    GET /api/v1/public/idp
    +

    Code Challenge Computation:

    +
    code_challenge = BASE64URL(SHA256(code_verifier))
     
    -

    Response:

    -
    [
    -  {
    -    "id": 1,
    -    "name": "Keycloak",
    -    "slug": "keycloak",
    -    "icon": "keycloak"
    -  },
    -  {
    -    "id": 2,
    -    "name": "Pocket ID",
    -    "slug": "pocket-id",
    -    "icon": "pocketid"
    -  }
    -]
    -
    -

    Step 2: Initialize WebView and Load SSO URL

    -

    When user selects an SSO provider, open a WebView with the SSO initiation URL:

    +

    Step 2: Initiate OAuth with PKCE Challenge

    +

    Open a WebView with the SSO URL including PKCE parameters:

    URL to Load:

    -
    https://your-endurain-instance.com/api/v1/public/idp/login/{idp_slug}?redirect=/dashboard
    +
    https://your-endurain-instance.com/api/v1/public/idp/login/{idp_slug}?code_challenge={challenge}&code_challenge_method=S256&redirect=/dashboard
     
    -

    Parameters:

    -
      -
    • {idp_slug}: The provider slug from Step 1 (e.g., "google", "keycloak")
    • -
    • redirect (optional): Frontend path to navigate to after successful login
    • -
    -

    What Happens:

    -
      -
    1. Backend generates OAuth state and authorization URL
    2. -
    3. WebView redirects to the identity provider's login page
    4. -
    5. User authenticates with the provider (enters credentials, 2FA, etc.)
    6. -
    -

    Step 3: Monitor WebView URL Changes

    -

    Set up URL interception to detect when the OAuth callback completes:

    -

    URLs to Monitor:

    -
      -
    • Success: https://your-endurain-instance.com/login?sso=success&session_id={uuid}
    • -
    • Success with redirect: https://your-endurain-instance.com/login?sso=success&session_id={uuid}&redirect=/dashboard
    • -
    • Error: https://your-endurain-instance.com/login?error=sso_failed
    • -
    -

    Step 4: Extract tokens from WebView Cookies, store tokens securely and clean up the WebView

    -

    When SSO succeeds, extract authentication tokens from the WebView's cookie store and store them securely:

    -

    Cookies to Extract:

    -
      -
    • endurain_access_token: JWT access token (15 min expiry)
    • -
    • endurain_refresh_token: JWT refresh token (7 day expiry)
    • -
    -

    Step 5: Make Authenticated API Requests

    -

    Use extracted tokens for subsequent API calls with the required headers:

    -

    Required Headers:

    -
      -
    • Authorization: Bearer {access_token}
    • -
    • X-Client-Type: mobile
    • -
    -

    Step 6: Implement Token Refresh

    -

    Access tokens expire after 15 minutes. Implement automatic refresh logic:

    -

    Refresh Request:

    -
    POST /api/v1/refresh
    -Authorization: Bearer {refresh_token}
    +

    Query Parameters:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ParameterRequiredDescription
    code_challengeYes (PKCE)Base64url-encoded SHA256 hash of code_verifier
    code_challenge_methodYes (PKCE)Must be S256
    redirectNoFrontend path after successful login
    +

    Step 3: Monitor WebView for Callback

    +

    The OAuth flow proceeds as normal. Monitor the WebView URL for the success redirect:

    +

    Success URL Pattern:

    +
    https://your-endurain-instance.com/login?sso=success&session_id={uuid}&redirect=/dashboard
    +
    +

    Extract the session_id from the URL - this is needed for token exchange.

    +

    Step 4: Exchange Session for Tokens (PKCE Verification)

    +

    After obtaining the session_id, close the WebView and exchange it for tokens using the code verifier:

    +

    Token Exchange Request:

    +
    POST /api/v1/public/idp/session/{session_id}/tokens
    +Content-Type: application/json
     X-Client-Type: mobile
    +
    +{
    +  "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
    +}
     
    -

    Response:

    +

    Successful Response (HTTP 200):

    {
    -  "access_token": "eyJ...",
    -  "refresh_token": "eyJ...",
    -  "session_id": "uuid",
    -  "token_type": "Bearer",
    -  "expires_in": 900
    +  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    +  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    +  "csrf_token": "abc123def456...",
    +  "expires_in": 900,
    +  "token_type": "Bearer"
     }
     
    +

    Error Responses:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    StatusErrorDescription
    400Invalid code_verifierVerifier doesn't match the challenge
    404Session not foundInvalid session_id or not a PKCE flow
    409Tokens already exchangedReplay attack prevention
    429Rate limit exceededMax 10 requests/minute per IP
    +

    Step 5: Store Tokens Securely

    +

    Store the received tokens in secure platform storage:

    +
      +
    • iOS: Keychain Services
    • +
    • Android: EncryptedSharedPreferences or Android Keystore
    • +
    +

    Step 6: Use Tokens for API Requests

    +

    Use the tokens for authenticated API calls:

    +
    GET /api/v1/activities
    +Authorization: Bearer {access_token}
    +X-Client-Type: mobile
    +X-CSRF-Token: {csrf_token}
    +
    +

    Security Features

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FeatureDescription
    PKCE S256SHA256 challenge prevents code interception
    One-time exchangeTokens can only be exchanged once per session
    10-minute expiryOAuth state expires after 10 minutes
    Rate limiting10 token exchange requests per minute
    Session linkingSession is cryptographically bound to OAuth state

    Configuration

    Environment Variables

    The following environment variables control authentication behavior:

    +

    Token Configuration

    @@ -2177,7 +2896,7 @@ - + @@ -2199,23 +2918,102 @@ + +
    SECRET_KEYSecret key for JWT signingSecret key for JWT signing (min 32 characters recommended) - Yes
    7 No
    +

    Session Configuration

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableDescriptionDefaultRequired
    SESSION_IDLE_TIMEOUT_ENABLEDEnable session idle timeoutfalseNo
    SESSION_IDLE_TIMEOUT_HOURSHours of inactivity before session expires1No
    SESSION_ABSOLUTE_TIMEOUT_HOURSMaximum session lifetime in hours24No
    +

    Security Configuration

    + + + + + + + + + + - + + + + + + +
    VariableDescriptionDefaultRequired
    BACKEND_CORS_ORIGINSAllowed CORS originsAllowed CORS origins (JSON array) [] No
    FRONTEND_PROTOCOLProtocol for cookie security (http or https)httpNo
    -

    For web clients, cookies are configured with:

    -
      -
    • HttpOnly: Prevents JavaScript access (security measure)
    • -
    • Secure: Only sent over HTTPS in production
    • -
    • SameSite: Protection against CSRF attacks
    • -
    • Domain: Set to match your application domain
    • -
    • Path: Set to / for application-wide access
    • -
    +

    For web clients, the refresh token cookie is configured with:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeValuePurpose
    HttpOnlytruePrevents JavaScript access (XSS protection)
    Securetrue (in production)Only sent over HTTPS
    SameSiteStrictPrevents CSRF attacks
    Path/Application-wide access
    Expires7 days (default)Matches refresh token lifetime

    Security Scopes

    Endurain uses OAuth-style scopes to control API access. Each scope controls access to specific resource groups:

    Available Scopes

    @@ -2387,33 +3185,55 @@ }

    Best Practices

    -

    For Client Applications

    +

    For Web Client Applications

      -
    1. Always use HTTPS in production to protect credentials and tokens
    2. -
    3. Store tokens securely:
    4. -
    5. Web: Use HTTP-only cookies (handled automatically)
    6. -
    7. Mobile: Use secure storage (Keychain on iOS, KeyStore on Android)
    8. -
    9. Implement token refresh before access token expires
    10. -
    11. Handle rate limits with exponential backoff
    12. -
    13. Validate SSL certificates to prevent man-in-the-middle attacks
    14. -
    15. Clear tokens on logout to prevent unauthorized access
    16. +
    17. Store access and CSRF tokens in memory - Never persist in localStorage or sessionStorage
    18. +
    19. Implement automatic token refresh - Refresh before access token expires (e.g., at 80% of lifetime)
    20. +
    21. Handle concurrent refresh requests - Use a refresh lock pattern to prevent race conditions
    22. +
    23. Always include required headers:
        +
      • Authorization: Bearer {access_token} for all authenticated requests
      • +
      • X-Client-Type: web for all requests
      • +
      • X-CSRF-Token: {csrf_token} for POST/PUT/DELETE/PATCH requests
      • +
      +
    24. +
    25. Handle page reload gracefully - Call /auth/refresh on app initialization to restore in-memory tokens
    26. +
    27. Clear tokens on logout - The httpOnly cookie is cleared by the backend
    28. +
    +

    For Mobile Client Applications

    +
      +
    1. Store tokens securely:
        +
      • iOS: Keychain Services
      • +
      • Android: EncryptedSharedPreferences or Android Keystore
      • +
      +
    2. +
    3. Use PKCE for OAuth/SSO - Required for mobile OAuth flows
    4. +
    5. Include required headers:
        +
      • Authorization: Bearer {access_token} for all authenticated requests
      • +
      • X-Client-Type: mobile for all requests
      • +
      • X-CSRF-Token: {csrf_token} for state-changing requests
      • +
      +
    6. +
    7. Handle token refresh proactively - Refresh before expiration
    8. +
    9. Implement secure token deletion on logout

    For Security

    1. Never expose SECRET_KEY in client code or version control
    2. -
    3. Use strong, randomly generated secrets for production
    4. +
    5. Use strong, randomly generated secrets (minimum 32 characters)
    6. +
    7. Always use HTTPS in production environments
    8. Enable MFA for enhanced account security
    9. -
    10. Monitor failed login attempts for suspicious activity
    11. -
    12. Rotate refresh tokens periodically for long-lived sessions
    13. -
    14. Use appropriate scopes - request only the permissions needed
    15. +
    16. Monitor for token reuse - Indicates potential token theft
    17. +
    18. Enable session idle timeout for sensitive applications
    19. +
    20. Use appropriate scopes - Request only necessary permissions
    -

    For OAuth/SSO

    +

    For OAuth/SSO Integration

      -
    1. Validate state parameter to prevent CSRF attacks
    2. -
    3. Use PKCE (Proof Key for Code Exchange) for mobile apps
    4. -
    5. Implement proper redirect URL validation to prevent open redirects
    6. +
    7. Always use PKCE - Required for mobile, recommended for web
    8. +
    9. Validate state parameter - Prevents CSRF attacks on OAuth flow
    10. +
    11. Implement proper redirect URL validation - Prevents open redirects
    12. Handle provider errors gracefully with user-friendly messages
    13. -
    14. Support account linking to allow users to connect multiple providers
    15. +
    16. Support account linking - Allow users to connect multiple providers
    17. +
    18. Respect token expiry - OAuth state expires after 10 minutes
    diff --git a/developer-guide/setup-dev-env/index.html b/developer-guide/setup-dev-env/index.html index 0b3237fdf..a0a87741c 100644 --- a/developer-guide/setup-dev-env/index.html +++ b/developer-guide/setup-dev-env/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/developer-guide/supported-types/index.html b/developer-guide/supported-types/index.html index e72151921..af3f38ddc 100644 --- a/developer-guide/supported-types/index.html +++ b/developer-guide/supported-types/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/features/single-sign-on/index.html b/features/single-sign-on/index.html index 2c7dfa553..f1689a120 100644 --- a/features/single-sign-on/index.html +++ b/features/single-sign-on/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/features/sleep-scoring/index.html b/features/sleep-scoring/index.html index 65bd187e5..81db38f7f 100644 --- a/features/sleep-scoring/index.html +++ b/features/sleep-scoring/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/gallery/index.html b/gallery/index.html index 9682e59b1..45c77105a 100644 --- a/gallery/index.html +++ b/gallery/index.html @@ -383,7 +383,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/getting-started/advanced-started/index.html b/getting-started/advanced-started/index.html index b36023345..adb047dfa 100644 --- a/getting-started/advanced-started/index.html +++ b/getting-started/advanced-started/index.html @@ -429,6 +429,15 @@ +
  • + +
  • + + + Session Timeout Configuration (Optional) + + +
  • @@ -569,7 +578,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide @@ -1000,6 +1009,15 @@ +
  • + +
  • + + + Session Timeout Configuration (Optional) + + +
  • @@ -1315,6 +1333,24 @@ Time in days +SESSION_IDLE_TIMEOUT_ENABLED +false +Yes +Enforce idle timeouts (supported values are true and false) + + +SESSION_IDLE_TIMEOUT_HOURS +1 +Yes +Time in hours + + +SESSION_ABSOLUTE_TIMEOUT_HOURS +24 +Yes +Time in hours + + JAEGER_ENABLED false Yes @@ -1348,7 +1384,7 @@ ENVIRONMENT production Yes -"production" and "development" allowed. "development" allows connections from localhost:8080 and localhost:5173 at the CORS level +production, demo and development allowed. development allows connections from localhost:8080 and localhost:5173 at the CORS level. demo equals to production except it does not return user sessions SMTP_HOST @@ -1431,6 +1467,21 @@
  • To check npm dependencies used, use npm file (package.json)
  • Logo created on Canva
  • +

    Session Timeout Configuration (Optional)

    +

    By default, Endurain sessions last 7 days without enforcing idle timeouts. +For enhanced security, you can enable automatic session expiration:

    +

    Environment Variables:

    + +

    Example:

    +
    environment:
    +  SESSION_IDLE_TIMEOUT_ENABLED: "true"
    +  SESSION_IDLE_TIMEOUT_HOURS: "2"
    +  SESSION_ABSOLUTE_TIMEOUT_HOURS: "48"
    +

    Docker Secrets Support

    Endurain supports Docker secrets for securely managing sensitive environment variables. For the following environment variables, you can use _FILE variants that read the secret from a file instead of storing it directly in environment variables:

    @@ -979,6 +988,15 @@ + + +
  • + + + 9. Update to a new version of Endurain. + + +
  • @@ -1118,6 +1136,37 @@ sudo -u postgres < systemctl enable endurain systemctl start endurain
    +

    9. Update to a new version of Endurain.

    +

    Remove old version and get the latest.

    +
    systemctl stop endurain
    +rm -rf /path/to/endurain/*
    +cd /path/to/endurain
    +
    +TAG=$(curl -s https://api.github.com/repos/endurain-project/endurain/releases/latest \
    +  | grep -oP '"tag_name": "\K(.*)(?=")')
    +curl -L "https://github.com/endurain-project/endurain/archive/refs/tags/$TAG.tar.gz" \
    +  | tar xz
    +EXTRACTED=$(ls -d endurain-*)
    +shopt -s dotglob
    +mv "$EXTRACTED"/* .
    +shopt -u dotglob
    +rm -rf "$EXTRACTED"
    +
    +

    Build the Frontend.

    +
    cd /path/to/endurain/frontend/app
    +npm ci
    +npm run build
    +
    +

    Set Up the Backend.

    +
    cd /path/to/endurain/backend
    +poetry export -f requirements.txt --output requirements.txt --without-hashes
    +
    +uv venv
    +uv pip install -r requirements.txt
    +
    +

    start the service.

    +
    systemctl start endurain
    +
    diff --git a/getting-started/getting-started/index.html b/getting-started/getting-started/index.html index 0c9c89302..116a6cc6d 100644 --- a/getting-started/getting-started/index.html +++ b/getting-started/getting-started/index.html @@ -585,7 +585,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/getting-started/maria-to-postgres-migration/index.html b/getting-started/maria-to-postgres-migration/index.html index c4d536535..cc38c8f18 100644 --- a/getting-started/maria-to-postgres-migration/index.html +++ b/getting-started/maria-to-postgres-migration/index.html @@ -387,7 +387,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/index.html b/index.html index a2ae47e04..fe8b23d39 100644 --- a/index.html +++ b/index.html @@ -520,7 +520,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/integrations/3rd-party-apps/index.html b/integrations/3rd-party-apps/index.html index 3381bbc59..7f6909058 100644 --- a/integrations/3rd-party-apps/index.html +++ b/integrations/3rd-party-apps/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/integrations/3rd-party-services/index.html b/integrations/3rd-party-services/index.html index f334db6a4..35974b306 100644 --- a/integrations/3rd-party-services/index.html +++ b/integrations/3rd-party-services/index.html @@ -385,7 +385,7 @@ - Bare-Metal Installation Guide + Bare-Metal installation guide diff --git a/search/search_index.json b/search/search_index.json index ad4f51db8..863a34d6e 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Endurain

    A self-hosted fitness tracking service - Mastodon profile - Discord server

    "},{"location":"#try-the-demo","title":"\ud83d\ude80 Try the Demo","text":"

    Experience Endurain without installation:

    Demo URL: https://demo.endurain.com

    Demo Environment

    The demo environment resets every day at midnight. Do not store important data or expect persistence.

    "},{"location":"#what-is-endurain","title":"What is Endurain?","text":"

    Endurain is a self-hosted fitness tracking service designed to give users full control over their data and hosting environment. Built with:

    To deploy Endurain, a Docker image is available, and a comprehensive example can be found in the \"docker-compose.yml.example\" file provided in the project repository. Configuration is facilitated through environment variables, ensuring flexibility and ease of customization.

    "},{"location":"#developers-note","title":"Developer's Note","text":"

    As a non-professional developer, my journey with Endurain involved learning and implementing new technologies and concepts, with invaluable assistance from GitHub Copilot and ChatGPT. The primary motivation behind this project was to gain hands-on experience and expand my understanding of modern development practices. Second motivation is that I'm an amateur triathlete and I want to keep track of my gear and gear components usage.

    If you have any recommendations or insights on improving any aspect of Endurain, whether related to technology choices, user experience, or any other relevant area, I would greatly appreciate your input. The goal is to create a reliable and user-friendly fitness tracking solution that caters to the needs of individuals who prefer self-hosted applications. Your constructive feedback will undoubtedly contribute to the refinement of Endurain.

    "},{"location":"#features","title":"Features","text":"

    Endurain currently supports:

    "},{"location":"#planned-features","title":"Planned Features","text":"

    Please visit the ROADMAP.md file on GitHub.

    "},{"location":"#sponsors","title":"Sponsors","text":"

    A huge thank you to the project sponsors! Your support helps keep this project going.

    Consider sponsoring Endurain on GitHub to ensure continuous development.

    "},{"location":"#contributing","title":"Contributing","text":"

    Contributions are welcomed! Please open an issue to discuss any changes or improvements before submitting a PR. Check out the Contributing Guidelines for more details.

    "},{"location":"#license","title":"License","text":"

    This project is licensed under the AGPL-3.0 or later License.

    "},{"location":"#help-translate","title":"Help Translate","text":"

    Endurain has multi-language support, and you can help translate it into more languages via Crowdin.

    Currently supported in:

    "},{"location":"#star-history","title":"Star History","text":""},{"location":"#trademark-notice","title":"Trademark Notice","text":"

    Endurain\u00ae is a trademark of Jo\u00e3o Vit\u00f3ria Silva.

    You are welcome to self-host Endurain and use the name and logo, including for personal, educational, research, or community (non-commercial) use. Commercial use of the Endurain name or logos (such as offering paid hosting, products, or services) is not permitted without prior written permission.

    See TRADEMARK.md for full details.

    Built with \u2764\ufe0f from Portugal | Part of the Endurain ecosystem"},{"location":"gallery/","title":"Gallery","text":""},{"location":"gallery/#login-and-sign-up-page","title":"Login and sign-up page","text":"

    Image can be changed. Image with a size of 1000x1000 pixels is expected.

    "},{"location":"gallery/#home-page","title":"Home page","text":""},{"location":"gallery/#search-page","title":"Search page","text":""},{"location":"gallery/#activity-page","title":"Activity page","text":""},{"location":"gallery/#activities-pages","title":"Activities pages","text":""},{"location":"gallery/#gears-pages","title":"Gears pages","text":""},{"location":"gallery/#health-pages","title":"Health pages","text":""},{"location":"gallery/#summary-page","title":"Summary page","text":""},{"location":"gallery/#profile-page","title":"Profile page","text":""},{"location":"gallery/#settings-pages","title":"Settings pages","text":"

    Users administrator, server settings and identity providers pages only visible to admin users

    "},{"location":"developer-guide/authentication/","title":"Handling authentication","text":"

    Endurain supports integration with other apps through a comprehensive authentication system that includes standard username/password authentication, Multi-Factor Authentication (MFA), OAuth/SSO integration, and JWT-based session management.

    "},{"location":"developer-guide/authentication/#api-requirements","title":"API Requirements","text":""},{"location":"developer-guide/authentication/#token-handling","title":"Token Handling","text":""},{"location":"developer-guide/authentication/#token-lifecycle","title":"Token Lifecycle","text":""},{"location":"developer-guide/authentication/#client-specific-token-delivery","title":"Client-Specific Token Delivery","text":""},{"location":"developer-guide/authentication/#authentication-flows","title":"Authentication Flows","text":""},{"location":"developer-guide/authentication/#standard-login-flow","title":"Standard Login Flow","text":"
    1. Client sends credentials to /token endpoint
    2. Backend validates credentials
    3. If MFA is enabled, backend requests MFA code
    4. If MFA is disabled or verified, backend generates tokens
    5. Tokens are delivered based on client type (cookies for web, JSON for mobile)
    "},{"location":"developer-guide/authentication/#oauthsso-flow","title":"OAuth/SSO Flow","text":"
    1. Client requests list of enabled providers from /public/idp
    2. Client initiates OAuth by redirecting to /public/idp/login/{idp_slug}
    3. User authenticates with the OAuth provider
    4. Provider redirects back to /public/idp/callback/{idp_slug} with authorization code
    5. Backend exchanges code for provider tokens and user info
    6. Backend creates or links user account and generates session tokens
    7. User is redirected to the app with active session
    "},{"location":"developer-guide/authentication/#token-refresh-flow","title":"Token Refresh Flow","text":"
    1. When access token expires, client sends refresh token to /refresh
    2. Backend validates refresh token and session
    3. New access token is generated and returned
    4. Refresh token may be rotated based on configuration
    "},{"location":"developer-guide/authentication/#api-endpoints","title":"API Endpoints","text":"

    The API is reachable under /api/v1. Below are the authentication-related endpoints. Complete API documentation is available on the backend docs (http://localhost:98/api/v1/docs or http://ip_address:98/api/v1/docs or https://domain/api/v1/docs):

    "},{"location":"developer-guide/authentication/#core-authentication-endpoints","title":"Core Authentication Endpoints","text":"What Url Expected Information Rate Limit Authorize /token FORM with the fields username and password. This will be sent in clear text, use of HTTPS is highly recommended 5 requests/min per IP Refresh Token /refresh header Authorization Bearer: <Refresh Token> - Verify MFA /mfa/verify JSON {'username': <username>, 'mfa_code': '123456'} - Logout /logout header Authorization Bearer: <Access Token> -"},{"location":"developer-guide/authentication/#oauthsso-endpoints","title":"OAuth/SSO Endpoints","text":"What Url Expected Information Rate Limit Get Enabled Providers /public/idp None (public endpoint) - Initiate OAuth Login /public/idp/login/{idp_slug} Query param: redirect=<path> (optional) 10 requests/min per IP OAuth Callback /public/idp/callback/{idp_slug} Query params: code=<code>, state=<state> Configurable Link IdP to Account /profile/idp/{idp_id}/link Requires authenticated session 10 requests/min per IP"},{"location":"developer-guide/authentication/#example-resource-endpoints","title":"Example Resource Endpoints","text":"What Url Expected Information Activity Upload /activities/create/upload .gpx, .tcx, .gz or .fit file Set Weight /health/weight JSON {'weight': <number>, 'created_at': 'yyyy-MM-dd'}"},{"location":"developer-guide/authentication/#mfa-authentication-flow","title":"MFA Authentication Flow","text":"

    When Multi-Factor Authentication (MFA) is enabled for a user, the authentication process requires two steps:

    "},{"location":"developer-guide/authentication/#step-1-initial-login-request","title":"Step 1: Initial Login Request","text":"

    Make a standard login request to /token:

    Request:

    POST /api/v1/token\nContent-Type: application/x-www-form-urlencoded\nX-Client-Type: web|mobile\n\nusername=user@example.com&password=userpassword\n

    Response (when MFA is enabled):

    {\n  \"mfa_required\": true,\n  \"username\": \"example\",\n  \"message\": \"MFA verification required\"\n}\n
    {\n  \"mfa_required\": true,\n  \"username\": \"example\",\n  \"message\": \"MFA verification required\"\n}\n
    "},{"location":"developer-guide/authentication/#step-2-mfa-verification","title":"Step 2: MFA Verification","text":"

    Complete the login by providing the MFA code to /mfa/verify:

    Request:

    POST /api/v1/mfa/verify\nContent-Type: application/json\nX-Client-Type: web|mobile\n\n{\n  \"username\": \"user@example.com\",\n  \"mfa_code\": \"123456\"\n}\n

    Response (successful verification):

    {\n  \"session_id\": \"unique_session_id\"\n}\n
    {\n  \"access_token\": \"eyJ...\",\n  \"refresh_token\": \"eyJ...\",\n  \"session_id\": \"unique_session_id\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 900,\n}\n
    "},{"location":"developer-guide/authentication/#error-handling","title":"Error Handling","text":"
    {\n  \"detail\": \"No pending MFA login found for this username\"\n}\n
    {\n  \"detail\": \"Invalid MFA code\"\n}\n
    "},{"location":"developer-guide/authentication/#important-notes","title":"Important Notes","text":""},{"location":"developer-guide/authentication/#oauthsso-integration","title":"OAuth/SSO Integration","text":""},{"location":"developer-guide/authentication/#supported-identity-providers","title":"Supported Identity Providers","text":"

    Endurain supports OAuth/SSO integration with various identity providers out of the box:

    The system is extensible and can be configured to work with:

    "},{"location":"developer-guide/authentication/#oauth-configuration","title":"OAuth Configuration","text":"

    Identity providers must be configured with the following parameters:

    "},{"location":"developer-guide/authentication/#linking-accounts","title":"Linking Accounts","text":"

    Users can link their Endurain account to an OAuth provider:

    1. User must be authenticated with a valid session
    2. Navigate to /profile/idp/{idp_id}/link
    3. Authenticate with the identity provider
    4. Provider is linked to the existing account
    "},{"location":"developer-guide/authentication/#oauth-token-response","title":"OAuth Token Response","text":"

    When authenticating via OAuth, the response format matches the standard authentication:

    Mobile clients using WebView

    Mobile apps must use WebView for OAuth/SSO flows to properly handle redirects and cookies. Tokens returned in JSON format is not currently supported for SSO.

    "},{"location":"developer-guide/authentication/#mobile-sso-implementation-guide","title":"Mobile SSO Implementation Guide","text":""},{"location":"developer-guide/authentication/#overview","title":"Overview","text":"

    Mobile applications must use an embedded WebView (or in-app browser) to handle OAuth/SSO authentication. The flow leverages browser-based redirects and cookie storage that are part of the OAuth 2.0 standard.

    "},{"location":"developer-guide/authentication/#prerequisites","title":"Prerequisites","text":""},{"location":"developer-guide/authentication/#step-by-step-implementation","title":"Step-by-Step Implementation","text":""},{"location":"developer-guide/authentication/#step-1-fetch-available-identity-providers","title":"Step 1: Fetch Available Identity Providers","text":"

    Before presenting SSO options to users, fetch the list of enabled providers:

    Request:

    GET /api/v1/public/idp\n

    Response:

    [\n  {\n    \"id\": 1,\n    \"name\": \"Keycloak\",\n    \"slug\": \"keycloak\",\n    \"icon\": \"keycloak\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"Pocket ID\",\n    \"slug\": \"pocket-id\",\n    \"icon\": \"pocketid\"\n  }\n]\n
    "},{"location":"developer-guide/authentication/#step-2-initialize-webview-and-load-sso-url","title":"Step 2: Initialize WebView and Load SSO URL","text":"

    When user selects an SSO provider, open a WebView with the SSO initiation URL:

    URL to Load:

    https://your-endurain-instance.com/api/v1/public/idp/login/{idp_slug}?redirect=/dashboard\n

    Parameters:

    What Happens:

    1. Backend generates OAuth state and authorization URL
    2. WebView redirects to the identity provider's login page
    3. User authenticates with the provider (enters credentials, 2FA, etc.)
    "},{"location":"developer-guide/authentication/#step-3-monitor-webview-url-changes","title":"Step 3: Monitor WebView URL Changes","text":"

    Set up URL interception to detect when the OAuth callback completes:

    URLs to Monitor:

    "},{"location":"developer-guide/authentication/#step-4-extract-tokens-from-webview-cookies-store-tokens-securely-and-clean-up-the-webview","title":"Step 4: Extract tokens from WebView Cookies, store tokens securely and clean up the WebView","text":"

    When SSO succeeds, extract authentication tokens from the WebView's cookie store and store them securely:

    Cookies to Extract:

    "},{"location":"developer-guide/authentication/#step-5-make-authenticated-api-requests","title":"Step 5: Make Authenticated API Requests","text":"

    Use extracted tokens for subsequent API calls with the required headers:

    Required Headers:

    "},{"location":"developer-guide/authentication/#step-6-implement-token-refresh","title":"Step 6: Implement Token Refresh","text":"

    Access tokens expire after 15 minutes. Implement automatic refresh logic:

    Refresh Request:

    POST /api/v1/refresh\nAuthorization: Bearer {refresh_token}\nX-Client-Type: mobile\n

    Response:

    {\n  \"access_token\": \"eyJ...\",\n  \"refresh_token\": \"eyJ...\",\n  \"session_id\": \"uuid\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 900\n}\n
    "},{"location":"developer-guide/authentication/#configuration","title":"Configuration","text":""},{"location":"developer-guide/authentication/#environment-variables","title":"Environment Variables","text":"

    The following environment variables control authentication behavior:

    Variable Description Default Required SECRET_KEY Secret key for JWT signing - Yes ALGORITHM JWT signing algorithm HS256 No ACCESS_TOKEN_EXPIRE_MINUTES Access token lifetime in minutes 15 No REFRESH_TOKEN_EXPIRE_DAYS Refresh token lifetime in days 7 No BACKEND_CORS_ORIGINS Allowed CORS origins [] No"},{"location":"developer-guide/authentication/#cookie-configuration","title":"Cookie Configuration","text":"

    For web clients, cookies are configured with:

    "},{"location":"developer-guide/authentication/#security-scopes","title":"Security Scopes","text":"

    Endurain uses OAuth-style scopes to control API access. Each scope controls access to specific resource groups:

    "},{"location":"developer-guide/authentication/#available-scopes","title":"Available Scopes","text":"Scope Description Access Level profile User profile information Read/Write users:read Read user data Read-only users:write Modify user data Write gears:read Read gear/equipment data Read-only gears:write Modify gear/equipment data Write activities:read Read activity data Read-only activities:write Create/modify activities Write health:read Read health metrics (weight, sleep, steps) Read-only health:write Record health metrics Write health_targets:read Read health targets Read-only health_targets:write Modify health targets Write sessions:read View active sessions Read-only sessions:write Manage sessions Write server_settings:read View server configuration Read-only server_settings:write Modify server settings Write (Admin) identity_providers:read View OAuth providers Read-only identity_providers:write Configure OAuth providers Write (Admin)"},{"location":"developer-guide/authentication/#scope-usage","title":"Scope Usage","text":"

    Scopes are automatically assigned based on user permissions and are embedded in JWT tokens. API endpoints validate required scopes before processing requests.

    "},{"location":"developer-guide/authentication/#common-error-responses","title":"Common Error Responses","text":""},{"location":"developer-guide/authentication/#http-status-codes","title":"HTTP Status Codes","text":"Status Code Description Common Causes 400 Bad Request Invalid request format Missing required fields, invalid JSON, no pending MFA login 401 Unauthorized Authentication failed Invalid credentials, expired token, invalid MFA code 403 Forbidden Access denied Invalid client type, insufficient permissions, missing required scope 404 Not Found Resource not found Invalid session ID, user not found, endpoint doesn't exist 429 Too Many Requests Rate limit exceeded Too many login attempts, OAuth requests exceeded limit 500 Internal Server Error Server error Database connection issues, configuration errors"},{"location":"developer-guide/authentication/#example-error-responses","title":"Example Error Responses","text":"

    Invalid Client Type:

    {\n  \"detail\": \"Invalid client type. Must be 'web' or 'mobile'\"\n}\n

    Expired Token:

    {\n  \"detail\": \"Token has expired\"\n}\n

    Invalid Credentials:

    {\n  \"detail\": \"Incorrect username or password\"\n}\n

    Rate Limit Exceeded:

    {\n  \"detail\": \"Rate limit exceeded. Please try again later.\"\n}\n

    Missing Required Scope:

    {\n  \"detail\": \"Insufficient permissions. Required scope: activities:write\"\n}\n
    "},{"location":"developer-guide/authentication/#best-practices","title":"Best Practices","text":""},{"location":"developer-guide/authentication/#for-client-applications","title":"For Client Applications","text":"
    1. Always use HTTPS in production to protect credentials and tokens
    2. Store tokens securely:
    3. Web: Use HTTP-only cookies (handled automatically)
    4. Mobile: Use secure storage (Keychain on iOS, KeyStore on Android)
    5. Implement token refresh before access token expires
    6. Handle rate limits with exponential backoff
    7. Validate SSL certificates to prevent man-in-the-middle attacks
    8. Clear tokens on logout to prevent unauthorized access
    "},{"location":"developer-guide/authentication/#for-security","title":"For Security","text":"
    1. Never expose SECRET_KEY in client code or version control
    2. Use strong, randomly generated secrets for production
    3. Enable MFA for enhanced account security
    4. Monitor failed login attempts for suspicious activity
    5. Rotate refresh tokens periodically for long-lived sessions
    6. Use appropriate scopes - request only the permissions needed
    "},{"location":"developer-guide/authentication/#for-oauthsso","title":"For OAuth/SSO","text":"
    1. Validate state parameter to prevent CSRF attacks
    2. Use PKCE (Proof Key for Code Exchange) for mobile apps
    3. Implement proper redirect URL validation to prevent open redirects
    4. Handle provider errors gracefully with user-friendly messages
    5. Support account linking to allow users to connect multiple providers
    "},{"location":"developer-guide/setup-dev-env/","title":"Setup a dev environment","text":"

    Bellow are the steps to create a dev environment. Examples bellow will use Endurain repo, but you should adapt those for your scenario (forked repo, etc).

    cd <folder_to_store_code>\ngit clone https://github.com/endurain-project/endurain.git # this will clone the repo structure to the previous folder inside a folder called endurain\n
    "},{"location":"developer-guide/setup-dev-env/#docker-image-and-backend-logic","title":"Docker image and backend logic","text":"

    Make sure Docker is installed, more info here.

    docker build -f docker/Dockerfile -t unified-image .\n
    services:\n    endurain:\n        container_name: endurain\n        image: unified-image # based on image that will be created above\n        environment:\n            - TZ=Europe/Lisbon # change if needed. Default is UTC\n            - DB_HOST=postgres\n            - DB_PORT=5432\n            - DB_PASSWORD=changeme\n            - SECRET_KEY=changeme # openssl rand -hex 32\n            - FERNET_KEY=changeme # https://fernetkeygen.com or python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"\n            - ENDURAIN_HOST=http://localhost:8080 # change if needed\n            - BEHIND_PROXY=false\n            - ENVIRONMENT=development\n        volumes:\n            - <folder_to_store_code>/backend/app:/app/backend # this will replace the backend code logic with yours. Any changes in the code need a container reboot for them to apply\n        ports:\n            - \"8080:8080\" # change if needed\n        depends_on:\n            postgres:\n                condition: service_healthy\n        restart: unless-stopped\n\n    postgres:\n        image: postgres:latest\n        container_name: postgres\n        environment:\n            - POSTGRES_PASSWORD=changeme\n            - POSTGRES_DB=endurain\n            - POSTGRES_USER=endurain\n            - PGDATA=/var/lib/postgresql/data/pgdata\n        ports:\n            - \"5432:5432\"\n        healthcheck:\n            test: [\"CMD-SHELL\", \"pg_isready -U endurain\"]\n            interval: 5s\n            timeout: 5s\n            retries: 5\n        volumes:\n            - <path_to_container_folders>/postgres:/var/lib/postgresql/data\n        restart: unless-stopped\n\n    adminer:\n        container_name: adminer\n        image: adminer\n        ports:\n            - 8081:8080\n        restart: unless-stopped\n
    docker compose up -d\n
    docker compose down\n
    docker image remove unified-image\n
    "},{"location":"developer-guide/setup-dev-env/#frontend","title":"Frontend","text":"

    Make sure you have an up-to-date version of Node.js installed.

    cd frontend/app\nnpm install\n
    VITE_ENDURAIN_HOST=http://localhost:8080 # Adapt this based on the docker compose of your dev environment\n
    npm run dev\n

    "},{"location":"developer-guide/supported-types/","title":"Supported types within Endurain","text":""},{"location":"developer-guide/supported-types/#supported-activity-types","title":"Supported activity types","text":"

    The table bellow details the activity types supported by Endurain.

    Name Value Run 1 Trail run 2 Track run 34 Treadmill run 40 Virtual run 3 Road cycling 4 Gravel cycling 5 MTB cycling 6 Commuting cycling 27 Mixed surface cycling 29 Virtual cycling 7 Indoor cycling 28 E-Bike cycling 35 E-Bike mountain cycling 36 Indoor swimming 8 Open water swimming 9 General workout 10 Walk 11 Indoor walk 31 Hike 12 Rowing 13 Yoga 14 Alpine ski 15 Nordic Ski 16 Snowboard 17 Ice Skate 37 Transition 18 Strength Training 19 Crossfit 20 Tennis 21 Table Tennis 22 Badminton 23 Squash 24 Racquetball 25 Pickleball 26 Padel 39 Windsurf 30 Stand up paddling 32 Surf 33 Soccer 38 Cardio training 41 Kayaking 42 Sailing 43 Snow shoeing 44 Inline skating 45"},{"location":"developer-guide/supported-types/#supported-gear-types","title":"Supported gear types","text":"

    The table bellow details the gear types supported by Endurain.

    Name Value Notes bike 1 N/A shoes 2 N/A wetsuit 3 N/A racquet 4 N/A ski 5 N/A snowboard 6 N/A windsurf 7 N/A water_sports_board 8 Example: stand up paddle and surf board"},{"location":"developer-guide/supported-types/#supported-bike-component-gear-types","title":"Supported bike component gear types","text":"

    The table bellow details the bike gear component types supported by Endurain:

    Value back_break_oil back_break_pads back_break_rotor back_tire back_tube back_tubeless_sealant back_tubeless_rim_tape back_wheel back_wheel_valve bottom_bracket bottle_cage cassette chain computer_mount crank_left_power_meter crank_right_power_meter crankset crankset_power_meter fork frame front_break_oil front_break_pads front_break_rotor front_derailleur front_shifter front_tire front_tube front_tubeless_sealant front_tubeless_rim_tape front_wheel front_wheel_valve grips handlebar handlebar_tape headset pedals pedals_left_power_meter pedals_power_meter pedals_right_power_meter rear_derailleur rear_shifter saddle seatpost stem"},{"location":"developer-guide/supported-types/#supported-shoes-component-gear-types","title":"Supported shoes component gear types","text":"

    The table bellow details the shoes component gear types supported by Endurain:

    Value cleats insoles laces"},{"location":"developer-guide/supported-types/#supported-racquet-component-gear-types","title":"Supported racquet component gear types","text":"

    The table bellow details the racquet component gear types supported by Endurain:

    Value basegrip bumpers grommets overgrip strings"},{"location":"developer-guide/supported-types/#supported-windsurf-component-gear-types","title":"Supported windsurf component gear types","text":"

    The table bellow details the windsurf component gear types supported by Endurain:

    Value sail board mast boom mast_extension mast_base mast_universal_joint fin footstraps harness_lines rigging_lines footpad impact_vest lifeguard_vest helmet wing front_foil stabilizer fuselage"},{"location":"features/single-sign-on/","title":"Single Sign-On (SSO) Configuration","text":"

    Endurain supports Single Sign-On (SSO) integration through OAuth 2.0 and OpenID Connect (OIDC) protocols. This allows users to authenticate using their existing identity provider accounts.

    "},{"location":"features/single-sign-on/#important-notes","title":"Important Notes","text":"

    Email Address Matching

    If you already have an existing Endurain account, the email address in your SSO provider must match your Endurain account email. If the email addresses don't match, Endurain will create a new user account with the SSO email address.

    Requirements

    You'll need:

    "},{"location":"features/single-sign-on/#supported-identity-providers","title":"Supported Identity Providers","text":"

    Endurain provides built-in support for the following identity providers:

    The system also supports custom OIDC providers including:

    "},{"location":"features/single-sign-on/#configuration-examples","title":"Configuration Examples","text":""},{"location":"features/single-sign-on/#pocket-id","title":"Pocket ID","text":"

    Pocket ID is a lightweight, self-hosted identity provider that works seamlessly with Endurain.

    "},{"location":"features/single-sign-on/#step-1-configure-pocket-id","title":"Step 1: Configure Pocket ID","text":"
    1. Log into your Pocket ID instance
    2. Navigate to Administration \u2192 OIDC Clients
    3. Select Add OIDC client
    4. Configure the following settings:
      • Name: Endurain
      • Client Launch URL: Your Endurain FQDN (e.g., https://endurain.mydomain.com)
      • Callback URLs: https://endurain.mydomain.com/api/v1/public/idp/callback/pocket-id
    5. Click Save
    6. Important: Make a note of your Client ID and Client Secret
    "},{"location":"features/single-sign-on/#step-2-configure-endurain","title":"Step 2: Configure Endurain","text":"
    1. Log into your Endurain instance
    2. Navigate to Settings \u2192 Identity Providers
    3. Select Add Identity Provider \u2192 Select Pocket ID
    4. Configure the following settings:
      • Provider Name: Pocket ID
      • Slug: pocket-id
      • Provider Type: OIDC
      • Issuer URL: Your Pocket ID FQDN (e.g., https://pocketid.mydomain.com) - no trailing slash
      • Client ID: The Client ID from Step 1
      • Client Secret: The Client Secret from Step 1
      • Scopes: openid profile email
    5. Click Save
    "},{"location":"features/single-sign-on/#step-3-test-the-integration","title":"Step 3: Test the Integration","text":"
    1. Log out of Endurain
    2. On the login page, you should see a Sign in with Pocket ID button
    3. Click the button to test the SSO flow
    "},{"location":"features/single-sign-on/#tailscale-tsidp","title":"Tailscale TSIDP","text":"

    Tailscale's identity provider (TSIDP) can be used for secure authentication within your Tailscale network.

    "},{"location":"features/single-sign-on/#step-1-configure-tsidp","title":"Step 1: Configure TSIDP","text":"
    1. Log into your TSIDP instance
    2. Select Add new client
    3. Configure the following settings:
      • Client Name: Endurain
      • Redirect URIs: https://endurain.mydomain.com/api/v1/public/idp/callback/tsidp
    4. Click Create client
    5. Important: Make a note of your Client ID and Client Secret
    "},{"location":"features/single-sign-on/#step-2-configure-endurain_1","title":"Step 2: Configure Endurain","text":"
    1. Log into your Endurain instance
    2. Navigate to Settings \u2192 Identity Providers
    3. Select Add Identity Provider \u2192 Custom
    4. Configure the following settings:
      • Provider Name: TSIDP
      • Slug: tsidp
      • Provider Type: OIDC
      • Issuer URL: Your TSIDP FQDN (e.g., https://tsidp.mydomain.com) - no trailing slash
      • Client ID: The Client ID from Step 1
      • Client Secret: The Client Secret from Step 1
      • Scopes: openid profile email
    5. Click Save
    "},{"location":"features/single-sign-on/#step-3-test-the-integration_1","title":"Step 3: Test the Integration","text":"
    1. Log out of Endurain
    2. On the login page, you should see a Sign in with TSIDP button
    3. Click the button to test the SSO flow
    "},{"location":"features/single-sign-on/#general-configuration-steps","title":"General Configuration Steps","text":"

    For any OIDC-compliant identity provider, follow these general steps:

    "},{"location":"features/single-sign-on/#1-configure-your-identity-provider","title":"1. Configure Your Identity Provider","text":"

    Create an OAuth 2.0/OIDC client application with the following settings:

    Save the generated Client ID and Client Secret.

    "},{"location":"features/single-sign-on/#2-configure-endurain","title":"2. Configure Endurain","text":"
    1. Log into Endurain as an administrator
    2. Navigate to Settings \u2192 Identity Providers
    3. Click Add Identity Provider
    4. Select your provider type or choose Custom for OIDC providers
    5. Fill in the required fields (see table below)
    6. Click Save
    Field Description Example Provider Name Display name shown on login button Google, GitHub, etc. Slug URL-safe identifier (lowercase, hyphens) google, github Provider Type Protocol type OIDC or OAuth2 Issuer URL Provider's base URL (no trailing slash) https://accounts.google.com Client ID OAuth client identifier From Step 1 Client Secret OAuth client secret From Step 1 Scopes Space-separated OAuth scopes openid profile email"},{"location":"features/single-sign-on/#3-verify-the-configuration","title":"3. Verify the Configuration","text":"
    1. Log out of Endurain
    2. Visit the login page
    3. Verify that a Sign in with Provider Name button appears
    4. Test the authentication flow
    "},{"location":"features/single-sign-on/#troubleshooting","title":"Troubleshooting","text":""},{"location":"features/single-sign-on/#common-issues","title":"Common Issues","text":"

    Problem: \"Invalid redirect URI\" error

    Problem: \"Email address mismatch\" creates duplicate account

    Problem: SSO button doesn't appear on login page

    Problem: \"Invalid issuer URL\" error

    "},{"location":"features/single-sign-on/#logs","title":"Logs","text":"

    For detailed troubleshooting, check the Endurain backend logs:

    docker logs endurain-backend\n# and/or\ntail -n 100 logs/app.log\n

    Look for authentication-related errors that can help identify configuration issues.

    "},{"location":"features/single-sign-on/#security-considerations","title":"Security Considerations","text":"

    Security Best Practices

    "},{"location":"features/single-sign-on/#additional-resources","title":"Additional Resources","text":""},{"location":"features/sleep-scoring/","title":"Sleep Scoring Guide","text":"

    Endurain uses a sleep scoring system to help you understand the quality of your sleep. This guide explains how your sleep is evaluated and what the scores mean.

    "},{"location":"features/sleep-scoring/#what-is-sleep-scoring","title":"What is Sleep Scoring?","text":"

    Sleep scoring is like a report card for your sleep. The system analyzes your sleep data and gives you:

    "},{"location":"features/sleep-scoring/#how-are-scores-calculated","title":"How Are Scores Calculated?","text":""},{"location":"features/sleep-scoring/#overall-sleep-score-0-100","title":"Overall Sleep Score (0-100)","text":"

    Your overall sleep score combines several important factors:

    What the scores mean:

    "},{"location":"features/sleep-scoring/#understanding-each-score-component","title":"Understanding Each Score Component","text":""},{"location":"features/sleep-scoring/#1-sleep-duration-score","title":"1. Sleep Duration Score","text":"

    What it measures: How many hours you slept

    Why it matters: Your body needs enough time to go through all sleep stages and recover properly.

    Scoring guidelines:

    Hours of Sleep Score Rating 7-9 hours 90-100 EXCELLENT - Perfect amount! 6-7 hours 70-89 GOOD - A bit short but okay 9-10 hours 70-89 GOOD - A bit long but okay 5-6 hours 50-69 FAIR - Not enough rest 10-11 hours 50-69 FAIR - Possibly oversleeping Less than 5 hours 0-49 POOR - Seriously insufficient More than 11 hours 0-49 POOR - Too much sleep

    The sweet spot: 7-9 hours, with 8 hours being ideal for most adults.

    "},{"location":"features/sleep-scoring/#2-sleep-quality-score","title":"2. Sleep Quality Score","text":"

    What it measures: How your sleep was distributed across different sleep stages

    Why it matters: Quality sleep isn't just about quantity - your brain and body need the right mix of sleep stages to fully recover.

    The four sleep stages:

    1. Light Sleep - Transition period, your body starts to relax
    2. Deep Sleep - Body repairs muscles and tissues, strengthens immune system
    3. REM Sleep - Brain processes memories and emotions, vivid dreams occur
    4. Awake - Brief wakeful moments (some are normal!)

    Optimal percentages:

    Sleep Stage Ideal Range Why It Matters Deep Sleep 13-23% (peak: 18%) Physical recovery and healing REM Sleep 20-25% (peak: 22.5%) Memory and emotional processing Light Sleep 45-55% (peak: 50%) Transition between stages Awake Time Less than 5% Normal brief awakenings

    How it's scored:

    "},{"location":"features/sleep-scoring/#3-awake-count-score","title":"3. Awake Count Score","text":"

    What it measures: How many times you woke up during the night

    Why it matters: Frequent awakenings disrupt your sleep cycles and prevent deep, restorative sleep.

    Scoring:

    Awakenings Score Rating What It Means 0-1 times 90-100 EXCELLENT Uninterrupted, restorative sleep 2-3 times 70-89 GOOD Some interruptions but still decent 4-5 times 50-69 FAIR Sleep continuity affected 6+ times 0-49 POOR Very fragmented sleep

    Note: It's normal to wake up briefly 1-2 times per night. You might not even remember them!

    "},{"location":"features/sleep-scoring/#4-rem-sleep-percentage-score","title":"4. REM Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was REM (Rapid Eye Movement) sleep

    Why it matters: REM sleep is when your brain consolidates memories, processes emotions, and boosts creativity. It's essential for mental health and learning.

    Optimal range: 20-25% of total sleep time (about 1.5-2 hours for 8 hours of sleep)

    What different levels mean:

    "},{"location":"features/sleep-scoring/#5-deep-sleep-percentage-score","title":"5. Deep Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was deep sleep

    Why it matters: Deep sleep is when your body does most of its physical repair - healing muscles, strengthening bones, and boosting your immune system.

    Optimal range: 13-23% of total sleep time (about 1-2 hours for 8 hours of sleep)

    What different levels mean:

    Did you know? Deep sleep decreases naturally as you age, which is normal!

    "},{"location":"features/sleep-scoring/#6-light-sleep-percentage-score","title":"6. Light Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was light sleep

    Why it matters: Light sleep serves as a transition between sleep stages and makes up the largest portion of your sleep.

    Optimal range: 45-55% of total sleep time (about 3.5-4.5 hours for 8 hours of sleep)

    What different levels mean:

    "},{"location":"features/sleep-scoring/#7-sleep-stress-score","title":"7. Sleep Stress Score","text":"

    What it measures: Your average stress level during sleep and how restless you were

    Why it matters: High stress during sleep indicates your body isn't fully relaxing, which affects recovery quality.

    Stress levels explained (based on Garmin scale):

    Scoring:

    Stress Level Base Score Rating 0-25 (Rest) 100 EXCELLENT 26-50 (Low) 70-90 GOOD 51-75 (Medium) 50-70 FAIR 76-100 (High) 0-50 POOR

    Restless moments penalty: Each restless moment during sleep reduces your score by 2-3 points.

    "},{"location":"features/sleep-scoring/#tips-for-better-sleep-scores","title":"Tips for Better Sleep Scores","text":""},{"location":"features/sleep-scoring/#to-improve-duration-score","title":"To Improve Duration Score:","text":""},{"location":"features/sleep-scoring/#to-improve-quality-score","title":"To Improve Quality Score:","text":""},{"location":"features/sleep-scoring/#to-reduce-awakenings","title":"To Reduce Awakenings:","text":""},{"location":"features/sleep-scoring/#to-reduce-sleep-stress","title":"To Reduce Sleep Stress:","text":""},{"location":"features/sleep-scoring/#frequently-asked-questions","title":"Frequently Asked Questions","text":""},{"location":"features/sleep-scoring/#q-why-is-my-score-low-even-though-i-slept-8-hours","title":"Q: Why is my score low even though I slept 8 hours?","text":"

    A: Duration is only 30% of your overall score. You might have had poor quality sleep, many awakenings, or high stress levels. Check your individual component scores to see what needs improvement.

    "},{"location":"features/sleep-scoring/#q-is-it-bad-if-my-scores-vary-day-to-day","title":"Q: Is it bad if my scores vary day to day?","text":"

    A: Some variation is normal! Factors like stress, exercise, diet, and life events affect your sleep. Look for trends over weeks rather than individual nights.

    "},{"location":"features/sleep-scoring/#q-whats-more-important-duration-or-quality","title":"Q: What's more important - duration or quality?","text":"

    A: Both matter! Quality is weighted slightly higher (40% vs 30%) because you can sleep for 10 hours but still feel tired if the quality is poor. Aim for both good duration AND quality.

    "},{"location":"features/sleep-scoring/#q-my-remdeep-sleep-percentage-seems-low-is-that-bad","title":"Q: My REM/Deep sleep percentage seems low. Is that bad?","text":"

    A: Not necessarily. These percentages are averages based on research. Individual needs vary by age, genetics, and lifestyle. If you feel rested and energetic, your sleep is probably fine!

    "},{"location":"features/sleep-scoring/#q-can-i-compare-my-scores-with-friends","title":"Q: Can I compare my scores with friends?","text":"

    A: While you can, remember that everyone's sleep needs are different. Focus on improving YOUR scores over time rather than comparing with others.

    "},{"location":"features/sleep-scoring/#q-what-if-i-have-a-sleep-disorder","title":"Q: What if I have a sleep disorder?","text":"

    A: These scores are educational tools, not medical diagnoses. If you consistently get poor scores or feel tired despite good scores, consult a healthcare professional or sleep specialist.

    "},{"location":"features/sleep-scoring/#understanding-your-data","title":"Understanding Your Data","text":""},{"location":"features/sleep-scoring/#when-are-scores-calculated","title":"When are scores calculated?","text":"

    Scores are automatically calculated when you:

    The system recalculates all scores to ensure they reflect your current data.

    "},{"location":"features/sleep-scoring/#what-data-is-needed","title":"What data is needed?","text":"

    For the most accurate scores, provide:

    Partial data: Even with incomplete data, the system will calculate scores for the metrics you provide. Missing metrics won't break the scoring system.

    "},{"location":"features/sleep-scoring/#technical-details","title":"Technical Details","text":"

    For developers and technically-minded users:

    "},{"location":"features/sleep-scoring/#summary","title":"Summary","text":"

    Your sleep scores provide valuable insights into your rest quality. By understanding what each score means and following the improvement tips, you can work towards better, more restorative sleep.

    Remember: - \ud83c\udfaf Focus on trends, not single nights - \ud83d\udcaa Small improvements add up over time - \ud83d\ude34 Consistency is key to good sleep - \ud83e\ude7a Consult a doctor for persistent sleep issues

    Sweet dreams and happy tracking! \ud83c\udf19\u2728

    "},{"location":"getting-started/advanced-started/","title":"Getting started advanced","text":""},{"location":"getting-started/advanced-started/#default-credentials","title":"Default Credentials","text":""},{"location":"getting-started/advanced-started/#docker-deployment","title":"Docker Deployment","text":"

    Endurain provides a Docker image for simplified deployment. To get started, check out the docker-compose.yml.example file in the project repository and adjust it according to your setup. Supported tags are:

    "},{"location":"getting-started/advanced-started/#supported-environment-variables","title":"Supported Environment Variables","text":"

    Table below shows supported environment variables. Variables marked with optional \"No\" should be set to avoid errors.

    Environment variable Default value Optional Notes UID 1000 Yes User ID for mounted volumes. Default is 1000 GID 1000 Yes Group ID for mounted volumes. Default is 1000 TZ UTC Yes Timezone definition. Useful for TZ calculation for activities that do not have coordinates associated, like indoor swim or weight training. If not specified UTC will be used. List of available time zones here. Format Europe/Lisbon expected FRONTEND_DIR /app/frontend/dist Yes You will only need to change this value if installing using bare metal method BACKEND_DIR /app/backend Yes You will only need to change this value if installing using bare metal method DATA_DIR /app/backend/data Yes You will only need to change this value if installing using bare metal method LOGS_DIR /app/backend/logs Yes You will only need to change this value if installing using bare metal method ENDURAIN_HOST No default set No Required for internal communication and Strava. For Strava https must be used. Host or local ip (example: http://192.168.1.10:8080 or https://endurain.com) REVERSE_GEO_PROVIDER nominatim Yes Defines reverse geo provider. Expects geocode, photon or nominatim. photon can be the SaaS by komoot or a self hosted version like a self hosted version. Like photon, Nominatim can be the SaaS or a self hosted version PHOTON_API_HOST photon.komoot.io Yes API host for photon. By default it uses the SaaS by komoot PHOTON_API_USE_HTTPS true Yes Protocol used by photon. By default uses HTTPS to be inline with what SaaS by komoot expects NOMINATIM_API_HOST nominatim.openstreetmap.org Yes API host for Nominatim. By default it uses the SaaS NOMINATIM_API_USE_HTTPS true Yes Protocol used by Nominatim. By default uses HTTPS to be inline with what SaaS expects GEOCODES_MAPS_API changeme Yes Geocode maps offers a free plan consisting of 1 Request/Second. Registration necessary. REVERSE_GEO_RATE_LIMIT 1 Yes Change this if you have a paid Geocode maps tier. Other providers also use this variable. Keep it as is if you use photon or Nominatim to keep 1 request per second DB_HOST postgres Yes postgres DB_PORT 5432 Yes 3306 or 5432 DB_USER endurain Yes N/A DB_PASSWORD No default set No Database password. Alternatively, use DB_PASSWORD_FILE for Docker secrets DB_DATABASE endurain Yes N/A SECRET_KEY No default set No Run openssl rand -hex 32 on a terminal to get a secret. Alternatively, use SECRET_KEY_FILE for Docker secrets FERNET_KEY No default set No Run python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\" on a terminal to get a secret or go to https://fernetkeygen.com. Example output is 7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y=. Alternatively, use FERNET_KEY_FILE for Docker secrets ALGORITHM HS256 Yes Currently only HS256 is supported ACCESS_TOKEN_EXPIRE_MINUTES 15 Yes Time in minutes REFRESH_TOKEN_EXPIRE_DAYS 7 Yes Time in days JAEGER_ENABLED false Yes N/A JAEGER_PROTOCOL http Yes N/A JAEGER_HOST jaeger Yes N/A JAEGER_PORT 4317 Yes N/A BEHIND_PROXY false Yes Change to true if behind reverse proxy ENVIRONMENT production Yes \"production\" and \"development\" allowed. \"development\" allows connections from localhost:8080 and localhost:5173 at the CORS level SMTP_HOST No default set Yes The SMTP host of your email provider. Example smtp.protonmail.ch SMTP_PORT 587 Yes The SMTP port of your email provider. Default is 587 SMTP_USERNAME No default set Yes The username of your SMTP email provider, probably your email address SMTP_PASSWORD No default set Yes The password of your SMTP email provider. Some providers allow the use of your account password, others require the creation of an app password. Please refer to your provider documentation. Alternatively, use SMTP_PASSWORD_FILE for Docker secrets SMTP_SECURE true Yes By default it uses secure communications. Accepted values are true and false SMTP_SECURE_TYPE starttls Yes If SMTP_SECURE is set you can set the communication type. Accepted values are starttls and ssl

    Table below shows the obligatory environment variables for postgres container. You should set them based on what was also set for the Endurain container.

    Environemnt variable Default value Optional Notes POSTGRES_PASSWORD changeme No N/A POSTGRES_DB endurain No N/A POSTGRES_USER endurain No N/A PGDATA /var/lib/postgresql/data/pgdata No N/A

    To check Python backend dependencies used, use poetry file (pyproject.toml).

    Frontend dependencies:

    "},{"location":"getting-started/advanced-started/#docker-secrets-support","title":"Docker Secrets Support","text":"

    Endurain supports Docker secrets for securely managing sensitive environment variables. For the following environment variables, you can use _FILE variants that read the secret from a file instead of storing it directly in environment variables:

    "},{"location":"getting-started/advanced-started/#using-file-based-secrets","title":"Using File-Based Secrets","text":"

    Use file-based secrets to securely manage sensitive environment variables:

    1. Create a secrets directory with proper permissions:
    mkdir -p secrets\nchmod 700 secrets\n
    1. Create secret files with strong passwords:
    # Use randomly generated passwords, not hardcoded ones\necho \"$(openssl rand -base64 32)\" > secrets/db_password.txt\necho \"$(openssl rand -hex 32)\" > secrets/secret_key.txt\necho \"$(python3 -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\")\" > secrets/fernet_key.txt\n\n# Set secure file permissions\nchmod 600 secrets/*.txt\nchown $(id -u):$(id -g) secrets/*.txt\n
    1. Configure docker-compose.yml:
    services:\n  endurain:\n    environment:\n      - DB_PASSWORD_FILE=/run/secrets/db_password\n      - SECRET_KEY_FILE=/run/secrets/secret_key\n      - FERNET_KEY_FILE=/run/secrets/fernet_key\n    secrets:\n      - db_password\n      - secret_key\n      - fernet_key\n\nsecrets:\n  db_password:\n    file: ./secrets/db_password.txt\n  secret_key:\n    file: ./secrets/secret_key.txt\n  fernet_key:\n    file: ./secrets/fernet_key.txt\n

    Note: When using _FILE variants, the original environment variables (e.g., DB_PASSWORD) are not needed. The application will automatically read from the file specified by the _FILE environment variable.

    "},{"location":"getting-started/advanced-started/#volumes","title":"Volumes","text":"

    Docker image uses a non-root user, so ensure target folders are not owned by root. Non-root user should use UID and GID 1000. It is recommended to configure the following volumes for data persistence:

    Volume Notes <local_path>/endurain/backend/logs:/app/backend/logs Log files for the backend <local_path>/endurain/backend/data:/app/backend/data Necessary for image and activity files persistence on docker image update"},{"location":"getting-started/advanced-started/#bulk-import-and-file-upload","title":"Bulk import and file upload","text":"

    To perform a bulk import: - Place .fit, .tcx, .gz and/or .gpx files into the data/activity_files/bulk_import folder. Create the folder if needed. - In the \"Settings\" menu select \"Import\". - Click \"Import\" next to \"Bulk Import\".

    .fit files are preferred. I noticed that Strava/Garmin Connect process of converting .fit to .gpx introduces additional data to the activity file leading to minor variances in the data, like for example additional meters in distance and elevation gain. Some notes:

    "},{"location":"getting-started/advanced-started/#importing-information-from-a-strava-bulk-export-beta","title":"Importing information from a Strava bulk export (BETA)","text":"

    Strava allows users to create a bulk export of their historical activity on the site. This information is stored in a zip file, primarily as .csv files, GPS recording files (e.g., .gpx, .fit), and media files (e.g., .jpg, .png).

    "},{"location":"getting-started/advanced-started/#importing-gear-from-a-strava-bulk-export","title":"Importing gear from a Strava bulk export","text":""},{"location":"getting-started/advanced-started/#bike-import","title":"Bike import","text":"

    At the present time, importing bikes from a Strava bulk export is implemented as a beta feature - use with caution. Components of bikes are not imported - just the bikes themselves. There is no mechanism present to undo an import.

    To perform an import of bikes: - Place the bikes.csv file from a Strava bulk export into the data/activity_files/bulk_import folder. Create the folder if needed; - In the Settings menu select Import; - Click Import Strava Bikes next to Strava gear import; - Upon successful import, the bikes.csv file is moved to /data/activity_files/processed folder; - Status messages about the import, including why any gear was not imported, can be found in the logs.

    Ensure the file is named bikes.csv and has a header row with at least the fields 'Bike Name', 'Bike Brand', and 'Bike Model'.

    "},{"location":"getting-started/advanced-started/#shoe-import","title":"Shoe import","text":"

    At the present time, importing shoes from a Strava bulk export is implemented as a beta feature - use with caution. Components of shooes are not imported - just the shoes themselves.

    To perform an import of shoes: - Place the shoes.csv file from a Strava bulk export into the data/activity_files/bulk_import folder. Create the folder if needed; - In the Settings menu select Import; - Click Shoes import next to Strava gear import; - Upon successful import, the shoes.csv file is moved to /data/activity_files/processed folder; - Status messages about the import, including why any gear was not imported, can be found in the logs.

    Ensure the file is named shoes.csv and has a header row with at least the fields 'Shoe Name', 'Shoe Brand', and 'Shoe Model'.

    Note that Strava allows blank shoe names, but Endurain does not. Shoes with a blank name will thus be given a default name of Unnamed Shoe # on import.

    "},{"location":"getting-started/advanced-started/#notes-on-importing-gear","title":"Notes on importing gear","text":"

    NOTE: There is currently no mechanism to undo a gear import.

    All gear will be imported as active, as Strava does not export the active/inactive status of the gear.

    Note that Endurain does not allow the + character in gear field names, and thus +'s will removed from all fields and replaced with spaces (\" \") on import. All beginning and ending space characters (\" \") will be removed on import as well.

    Endurain does not allow duplicate gear nicknames, case insensitively (e.g., Ilves and ilves would not be allowed) and regardless of gear type (e.g., Ilves the bike and ilves the shoe would not be allowed). Gear with duplicate nicknames will not be imported (i.e., only the first item with a given nickname will be imported).

    The import routine checks for duplicate items, and should not import duplicates. Thus it should be safe to re-import the same file mulitple times. However, due to the renaming of un-named shoes, repeated imports of the same shoe file will create duplicate entries of any unnamed shoes present.

    Gear that is already present in Endurain due to having an active link with Strava will not be imported via the manual import process.

    "},{"location":"getting-started/advanced-started/#importing-other-items-from-a-strava-bulk-import","title":"Importing other items from a Strava bulk import","text":"

    Importing activity metadata and media is under development in October 2025.

    "},{"location":"getting-started/advanced-started/#image-personalization","title":"Image personalization","text":"

    It is possible (v0.10.0 or higher) to personalize the login image in the login page. To do that, map the data/server_images directory for image persistence on container updates and: - Set the image in the server settings zone of the settings page - A square image is expected. Default one uses 1000px vs 1000px

    "},{"location":"getting-started/bare-metal/","title":"Bare-Metal Installation Guide","text":"

    This guide explains how to install Endurain bare-metal on Debian without Docker.

    "},{"location":"getting-started/bare-metal/#1-install-required-dependencies","title":"1. Install Required Dependencies","text":"
    apt install -y \\\n  build-essential \\\n  git \\\n  curl \\\n  python3-dev\n
    "},{"location":"getting-started/bare-metal/#2-install-required-runtime-tools","title":"2. Install Required Runtime Tools","text":"

    Install the required tools from their official sources:

    "},{"location":"getting-started/bare-metal/#3-download-endurain-release","title":"3. Download Endurain Release","text":"

    Run the following command to download and unpack the latest release.

    mkdir -p /path/to/endurain\ncd /path/to/endurain\n\nTAG=$(curl -s https://api.github.com/repos/endurain-project/endurain/releases/latest \\\n  | grep -oP '\"tag_name\": \"\\K(.*)(?=\")')\ncurl -L \"https://github.com/endurain-project/endurain/archive/refs/tags/$TAG.tar.gz\" \\\n  | tar xz\nEXTRACTED=$(ls -d endurain-*)\nshopt -s dotglob\nmv \"$EXTRACTED\"/* .\nshopt -u dotglob\nrm -rf \"$EXTRACTED\"\n
    "},{"location":"getting-started/bare-metal/#4-create-environment-configuration","title":"4. Create Environment Configuration","text":"

    Prepare data storage.

    mkdir -p /path/to/endurain_data/{data,logs}\n

    Copy the provided example.

    cp /path/to/endurain/.env.example /path/to/endurain/.env\n

    Generate your SECRET_KEY and FERNET_KEY. These keys are required for Endurain to work, so be sure to paste them into your .env file.

    openssl rand -hex 32 # SECRET_KEY\nopenssl rand -base64 32 # FERNET_KEY\n

    Edit .env file.

    nano /path/to/endurain/.env\n

    Adjust the environment variables and set keys. You definitely have to adjust FRONTEND_DIR, BACKEND_DIR and DB_HOST. Environment variables are explained in the Environment Variables Guide.

    DB_HOST=localhost\nBACKEND_DIR=\"/path/to/endurain/backend/app\"\nFRONTEND_DIR=\"/path/to/endurain/frontend/app/dist\"\nDATA_DIR=\"/path/to/endurain_data/data\"\nLOGS_DIR=\"/path/to/endurain_data/logs\"\n
    "},{"location":"getting-started/bare-metal/#5-build-the-frontend","title":"5. Build the Frontend","text":"
    cd /path/to/endurain/frontend/app\nnpm ci\nnpm run build\n

    Create env.js. Edit the URL if you use a reverse proxy.

    cat << 'EOF' > /path/to/endurain/frontend/app/dist/env.js\nwindow.env = {\n  ENDURAIN_HOST: \"http://YOUR_SERVER_IP:8080\",\n};\nEOF\n
    "},{"location":"getting-started/bare-metal/#6-set-up-the-backend","title":"6. Set Up the Backend","text":"
    cd /path/to/endurain/backend\n\nuv tool install poetry\nuv tool update-shell\nexport PATH=\"/root/.local/bin:$PATH\"\n\npoetry self add poetry-plugin-export\npoetry export -f requirements.txt --output requirements.txt --without-hashes\n\nuv venv\nuv pip install -r requirements.txt\n
    "},{"location":"getting-started/bare-metal/#7-setup-postgres-database","title":"7. Setup Postgres Database","text":"

    Run the following commands to create a PostgreSQL user and database for Endurain:

    sudo -u postgres createuser -P endurain\nsudo -u postgres createdb -O endurain endurain\n

    Check that the PostgreSQL client and server encodings are set to UTF-8.

    sudo -u postgres psql -c \"SHOW client_encoding;\"\nsudo -u postgres psql -c \"SHOW server_encoding;\"\n

    If either value is SQL_ASCII, set UTF-8 explicitly for the user and the database.

    sudo -u postgres psql -c \"ALTER ROLE endurain SET client_encoding = 'UTF8';\"\nsudo -u postgres psql -c \"ALTER DATABASE endurain SET client_encoding = 'UTF8';\"\n

    This ensures that all connections to the endurain database default to proper UTF-8 encoding.

    "},{"location":"getting-started/bare-metal/#8-systemd-service","title":"8. Systemd Service","text":"

    This is an example how you could set up your systemd service.

    cat << 'EOF' > /etc/systemd/system/endurain.service\n[Unit]\nDescription=Endurain FastAPI Backend\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/path/to/endurain/backend/app\nEnvironmentFile=/path/to/endurain/.env\nExecStart=/path/to/endurain/backend/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080\nRestart=always\nRestartSec=5\nUser=root\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n

    Enable and start the service.

    systemctl daemon-reload\nsystemctl enable endurain\nsystemctl start endurain\n
    "},{"location":"getting-started/getting-started/","title":"Getting started","text":"

    Welcome to the guide for getting started on hosting your own production instance of Endurain. Like many other services, Endurain is easiest to get up and running trough Docker compose. It is possible to get Endurain up and running without a domain and reverse proxy, but this guide assumes you want to use a reverse proxy and your domain. Endurain can run on any computer that support OCI containers, but in this guide we are using Debian 13 (should also work with 12).

    "},{"location":"getting-started/getting-started/#prerequisites","title":"Prerequisites","text":""},{"location":"getting-started/getting-started/#installing-docker-and-caddy-reverse-proxy","title":"Installing Docker and Caddy reverse proxy","text":"

    Note: If you have a old-ish distro (Ubuntu 22.04 and older) you need to add the repo for Docker. Read how to do it on Docker documentation. For newer distroes (Debian 13 and Ubuntu 24.04 it is not expected for you to have to do this step).

    Install Docker:

    sudo apt update -y\nsudo apt install docker.io docker-compose -y\n

    Confirm your user has the id 1000:

    id\n

    If you are not the user 1000, you need to set the UID and GID to your id in the .env file. But to keep this guide as easy to follow as possible, we will assume that you are user 1000.

    "},{"location":"getting-started/getting-started/#installing-caddy-reverse-proxy","title":"Installing Caddy reverse proxy","text":"

    Note: If you have a old-ish distro (Ubuntu 22.04 and older) you need to add the repo for Caddy. Read how to do it on Caddy documentation. For newer distroes (Debian 13 and Ubuntu 24.04 it is not expected for you to have to do this step).

    sudo apt update -y\nsudo apt install caddy -y\n
    "},{"location":"getting-started/getting-started/#installing-nginx-proxy-manager-reverse-proxy","title":"Installing Nginx Proxy Manager reverse proxy","text":"

    Nginx Proxy Manager comes as a pre-built Docker image. Please refer to the docs for details on how to install it.

    "},{"location":"getting-started/getting-started/#create-directory-structure","title":"Create directory structure","text":"

    Lets use /opt/endurain/ as the root directory for our project.

    sudo mkdir /opt/endurain\nsudo chown 1000:1000 /opt/endurain\nmkdir -p \\\n  /opt/endurain/backend/{data,logs} \\\n  /opt/endurain/postgres\n
    "},{"location":"getting-started/getting-started/#docker-compose-deployment","title":"Docker compose Deployment","text":"

    In this example of setting up Endurain, we will need two files. One docker-compose.yml and .env.

    Splitting up the setup like this make it easy to handle updates to the containers, without touching the secrets and other variables.

    "},{"location":"getting-started/getting-started/#creating-the-docker-compose-and-env-file","title":"Creating the docker-compose and .env file","text":"

    To make it as easy as possible for selfhoster to get up and running examples of docker-compose.yml and .env is on the git repo. Here are links to the files on the repo:

    cd /opt/endurain\nwget https://raw.githubusercontent.com/endurain-project/endurain/refs/heads/master/docker-compose.yml.example\nwget https://raw.githubusercontent.com/endurain-project/endurain/refs/heads/master/.env.example\n\nmv docker-compose.yml.example docker-compose.yml\nmv .env.example .env\n

    Now we need to make changes to the files to reflect your environment. Inside docker-compose.yml there is not much we need to do. If you want to store the files another place then /opt/endurain this is the file you need to change.

    Here is an explaination on what you can set in the .env:

    Environment variable How to set it DB_PASSWORD Run openssl rand -hex 32 on a terminal to get a secret POSTGRES_PASSWORD Set the same value as DB_PASSWORD. SECRET_KEY Run openssl rand -hex 32 on a terminal to get a secret FERNET_KEY Run python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\" on a terminal to get a secret or go to https://fernetkeygen.com. Example output is 7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y= TZ Timezone definition. Insert your timezone. List of available time zones here. Format Europe/Lisbon expected ENDURAIN_HOST https://endurain.yourdomain.com BEHIND_PROXY Change to true if behind reverse proxy POSTGRES_DB Postgres name for the database. POSTGRES_USER Postgres user for the database.

    Please note:

    POSTGRES_DB and POSTGRES_USER are values for the database. If you change it from endurain, you also need to set the environment variables for the app image. Please leave them as endurain if you are unsure.

    "},{"location":"getting-started/getting-started/#start-the-stack","title":"Start the stack","text":"

    It is finally time to start the stack!

    cd /opt/endurain\nsudo docker compose up -d\n

    Check the log output:

    docker compose logs -f\n

    If you do not get any errors, continue to next step.

    "},{"location":"getting-started/getting-started/#visit-the-site","title":"Visit the site","text":""},{"location":"getting-started/getting-started/#configure-a-reverse-proxy","title":"Configure a reverse proxy","text":""},{"location":"getting-started/getting-started/#configure-caddy-as-reverse-proxy-and-get-ssl-cert-from-letsencrypt","title":"Configure Caddy as reverse proxy and get SSL cert from letsencrypt","text":"

    We use Caddy outside docker. This way Debian handles the updates (you just need to run sudo apt update -y and sudo apt upgrade -y)

    Caddy is configured in the file /etc/caddy/Caddyfile

    Open the file in your favourite editor, delete the default text, and paste in this:

    endurain.yourdomain.com {\n        reverse_proxy localhost:8080\n}\n

    Restart Caddy

    sudo systemctl restart caddy\n

    Check the ouput of Caddy with:

    sudo journalctl -u caddy\n
    "},{"location":"getting-started/getting-started/#configure-nginx-proxy-manager-as-reverse-proxy-and-get-ssl-cert-from-letsencrypt","title":"Configure Nginx Proxy Manager as reverse proxy and get SSL cert from letsencrypt","text":"

    Bellow is an example config file for Endurain:

    ------------------------------------------------------------\nendurain.yourdomain.com\n------------------------------------------------------------\n\nmap $scheme $hsts_header {\n    https \"max-age=63072000; preload\";\n}\n\nserver {\n    set $forward_scheme http;\n    set $server \"your_server_ip\";\n    set $port 8884;\n\n    listen 80;\n    listen [::]:80;\n\n    listen 443 ssl;\n    listen [::]:443 ssl;\n\n    server_name endurain.yourdomain.com;\n\n    http2 on;\n    Let's Encrypt SSL\n\n    include conf.d/include/letsencrypt-acme-challenge.conf;\n    include conf.d/include/ssl-cache.conf;\n    include conf.d/include/ssl-ciphers.conf;\n    ssl_certificate /etc/letsencrypt/live/npm-21/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/npm-21/privkey.pem;\n    Asset Caching\n\n    include conf.d/include/assets.conf;\n    Block Exploits\n\n    include conf.d/include/block-exploits.conf;\n    HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)\n\n    add_header Strict-Transport-Security $hsts_header always;\n    Force SSL\n\n    include conf.d/include/force-ssl.conf;\n\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection $http_connection;\n    proxy_http_version 1.1;\n\n    access_log /data/logs/proxy-host-18_access.log proxy;\n    error_log /data/logs/proxy-host-18_error.log warn;\n\n    location / {\n        HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)\n\n        add_header Strict-Transport-Security $hsts_header always;\n\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $http_connection;\n        proxy_http_version 1.1;\n        Proxy!\n\n        include conf.d/include/proxy.conf;\n    }\n    Custom\n\n    include /data/nginx/custom/server_proxy[.]conf;\n}\n

    "},{"location":"getting-started/getting-started/#access-your-endurain-instance","title":"Access your Endurain instance","text":"

    You should now be able to access your site on endurain.yourdomain.com

    Log in with username: admin password: admin, and remember to change the password

    \ud83c\udf89 Weee \ud83c\udf89 You now have your own instance of Endurain up and running!

    "},{"location":"getting-started/getting-started/#how-to-update","title":"How to update","text":"
    cd /opt/endurain\nsudo docker compose pull\nsudo docker compose up -d\n

    The same is the case for Postgres. Check for breaking changes in release notes on Postgres Website.

    ** It is generally pretty safe to upgrade postgres minor version f.eks 17.4 to 17.5, but major is often breaking change (example 17.2 to 18.1 )

    "},{"location":"getting-started/getting-started/#things-to-think-about","title":"Things to think about","text":"

    You should implement backup strategy for the following directories:

    /opt/endurain/app/data\n/opt/endurain/app/logs\n

    You also need to backup your postgres database. It is not good practice to just backup the volume /opt/endurain/postgres this might be corrupted if the database is in the middle of a wright when the database goes down.

    "},{"location":"getting-started/getting-started/#default-credentials","title":"Default Credentials","text":""},{"location":"getting-started/maria-to-postgres-migration/","title":"MariaDB to Postgres migration guide","text":"

    This will guide you on how to migrate from MariaDB to Postgres. Endurain will drop support for MariaDB on v0.16.0, so you'll need to perform this migration prior to upgrade to v0.16.0 or higher.

    This guide uses Endurain's built-in export/import functionality to migrate your data.

    "},{"location":"getting-started/maria-to-postgres-migration/#prerequisites","title":"Prerequisites","text":"

    \u26a0\ufe0f Important Notes:

    "},{"location":"getting-started/maria-to-postgres-migration/#migration-steps","title":"Migration Steps","text":""},{"location":"getting-started/maria-to-postgres-migration/#step-1-export-data-from-mariadb-instance","title":"Step 1: Export Data from MariaDB Instance","text":"
    1. Instruct each user to log in to Endurain instance (currently running with MariaDB)
    2. Each user should navigate to Settings \u2192 My Profile \u2192 Export/Import
    3. Each user should lick Export to download a .zip file containing the user data
    4. Each user should save this file in a safe location

    \u26a0\ufe0f Do NOT delete your existing MariaDB database - keep it for rollback if needed.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-2-stop-current-endurain-instance","title":"Step 2: Stop Current Endurain Instance","text":"

    Stop your current Endurain container:

    docker compose down\n
    "},{"location":"getting-started/maria-to-postgres-migration/#step-3-update-environment-variables","title":"Step 3: Update Environment Variables","text":"

    Update your environment variables to point to PostgreSQL (adapt to your environment):

    DB_TYPE=postgres\nDB_HOST=postgres\nDB_PORT=5432\nDB_USER=endurain\nDB_PASSWORD=your_postgres_password\nDB_NAME=endurain\n

    Ensure your PostgreSQL database exists and is accessible with these credentials.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-4-start-fresh-endurain-with-postgresql","title":"Step 4: Start Fresh Endurain with PostgreSQL","text":"

    Start Endurain with the new PostgreSQL configuration:

    docker compose up -d\n

    This will start a fresh Endurain instance with: - Empty PostgreSQL database - Default admin credentials: admin/admin

    "},{"location":"getting-started/maria-to-postgres-migration/#step-5-import-data","title":"Step 5: Import Data","text":"
    1. Log in with default credentials: admin/admin
    2. Create a new user for each of your instance users if applicable
    3. Each user should navigate to Settings \u2192 My Profile \u2192 Export/Import
    4. Each user should click Import and select the .zip file exported
    5. Wait for the import to complete (this may take several minutes for large databases)

    \u26a0\ufe0f Note: User passwords are NOT imported for security reasons. All users will need to reset their passwords.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-6-verify-migration","title":"Step 6: Verify Migration","text":"

    Verify the migration was successful by checking:

    "},{"location":"getting-started/maria-to-postgres-migration/#troubleshooting","title":"Troubleshooting","text":""},{"location":"getting-started/maria-to-postgres-migration/#if-import-fails","title":"If Import Fails","text":"

    If the import process fails:

    1. Check the application logs in the container
    2. Check the app.log file
    3. Paste both outputs (container logs and app.log contents) when seeking help
    "},{"location":"getting-started/maria-to-postgres-migration/#rolling-back-to-mariadb","title":"Rolling Back to MariaDB","text":"

    If you need to rollback:

    1. Stop the PostgreSQL instance:
    docker compose down\n
    1. Restore your original environment variables (MariaDB settings)
    2. Start your original MariaDB instance:
    docker compose up -d\n
    "},{"location":"integrations/3rd-party-apps/","title":"3rd party apps","text":""},{"location":"integrations/3rd-party-apps/#runnerup-integration","title":"RunnerUp Integration","text":"

    RunnerUp an app for tracking your sport activities with your Android phone, can automatically sync your activities recorded with it to your Endurain instance.

    RunnerUp is supported until version v0.5.3 of Endurain. An issue is opened to get support for v0.6.0+.

    "},{"location":"integrations/3rd-party-services/","title":"3rd party services","text":""},{"location":"integrations/3rd-party-services/#garmin-connect-integration","title":"Garmin Connect Integration","text":"

    To enable Garmin Connect integration, Endurain will ask for your Garmin Connect credentials. These credentials are not stored, but the authentication tokens (access and refresh tokens) are stored in the DB, similar to the Strava integration. The credentials are sent from the frontend to the backend in plain text, so the use of HTTPS is highly recommended.

    Once the integration with Garmin Connect is configured, on startup, every one and four hours the backend will check if there is new unimported activities and new body composition entries respectively. If yes, the new data is automatically imported.

    For Garmin Connect integration python-garminconnect Python module is used.

    "},{"location":"integrations/3rd-party-services/#strava-integration","title":"Strava Integration","text":"

    \u26a0\ufe0f Warning Due to recent Strava API changes, expect changes in the Strava integration in a following release.

    To enable Strava integration, ensure your Endurain instance is accessible from the internet and follow Strava's API setup guide. After the integration is successful the access and refresh tokens are stored in the DB. Each user will have his/hers own pair.

    Once the integration with Strava is configured, on startup and every hour the backend will check if there is new unimported activities. If yes, the new activity is automatically imported.

    On link, user will need to provide his/her API client ID and secret. Pair will be temporary stored in the DB until the process finishes. Info is sent on a JSON payload and HTTPS end2end is encouraged.

    On Strava unlink action every data imported from Strava, i.e. activities and gears, will be deleted according to Strava API Agreement.

    For Strava integration stravalib Python module is used.

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Endurain

    A self-hosted fitness tracking service - Mastodon profile - Discord server

    "},{"location":"#try-the-demo","title":"\ud83d\ude80 Try the Demo","text":"

    Experience Endurain without installation:

    Demo URL: https://demo.endurain.com

    Demo Environment

    The demo environment resets every day at midnight. Do not store important data or expect persistence.

    "},{"location":"#what-is-endurain","title":"What is Endurain?","text":"

    Endurain is a self-hosted fitness tracking service designed to give users full control over their data and hosting environment. Built with:

    To deploy Endurain, a Docker image is available, and a comprehensive example can be found in the \"docker-compose.yml.example\" file provided in the project repository. Configuration is facilitated through environment variables, ensuring flexibility and ease of customization.

    "},{"location":"#developers-note","title":"Developer's Note","text":"

    As a non-professional developer, my journey with Endurain involved learning and implementing new technologies and concepts, with invaluable assistance from GitHub Copilot and ChatGPT. The primary motivation behind this project was to gain hands-on experience and expand my understanding of modern development practices. Second motivation is that I'm an amateur triathlete and I want to keep track of my gear and gear components usage.

    If you have any recommendations or insights on improving any aspect of Endurain, whether related to technology choices, user experience, or any other relevant area, I would greatly appreciate your input. The goal is to create a reliable and user-friendly fitness tracking solution that caters to the needs of individuals who prefer self-hosted applications. Your constructive feedback will undoubtedly contribute to the refinement of Endurain.

    "},{"location":"#features","title":"Features","text":"

    Endurain currently supports:

    "},{"location":"#planned-features","title":"Planned Features","text":"

    Please visit the ROADMAP.md file on GitHub.

    "},{"location":"#sponsors","title":"Sponsors","text":"

    A huge thank you to the project sponsors! Your support helps keep this project going.

    Consider sponsoring Endurain on GitHub to ensure continuous development.

    "},{"location":"#contributing","title":"Contributing","text":"

    Contributions are welcomed! Please open an issue to discuss any changes or improvements before submitting a PR. Check out the Contributing Guidelines for more details.

    "},{"location":"#license","title":"License","text":"

    This project is licensed under the AGPL-3.0 or later License.

    "},{"location":"#help-translate","title":"Help Translate","text":"

    Endurain has multi-language support, and you can help translate it into more languages via Crowdin.

    Currently supported in:

    "},{"location":"#star-history","title":"Star History","text":""},{"location":"#trademark-notice","title":"Trademark Notice","text":"

    Endurain\u00ae is a trademark of Jo\u00e3o Vit\u00f3ria Silva.

    You are welcome to self-host Endurain and use the name and logo, including for personal, educational, research, or community (non-commercial) use. Commercial use of the Endurain name or logos (such as offering paid hosting, products, or services) is not permitted without prior written permission.

    See TRADEMARK.md for full details.

    Built with \u2764\ufe0f from Portugal | Part of the Endurain ecosystem"},{"location":"gallery/","title":"Gallery","text":""},{"location":"gallery/#login-and-sign-up-page","title":"Login and sign-up page","text":"

    Image can be changed. Image with a size of 1000x1000 pixels is expected.

    "},{"location":"gallery/#home-page","title":"Home page","text":""},{"location":"gallery/#search-page","title":"Search page","text":""},{"location":"gallery/#activity-page","title":"Activity page","text":""},{"location":"gallery/#activities-pages","title":"Activities pages","text":""},{"location":"gallery/#gears-pages","title":"Gears pages","text":""},{"location":"gallery/#health-pages","title":"Health pages","text":""},{"location":"gallery/#summary-page","title":"Summary page","text":""},{"location":"gallery/#profile-page","title":"Profile page","text":""},{"location":"gallery/#settings-pages","title":"Settings pages","text":"

    Users administrator, server settings and identity providers pages only visible to admin users

    "},{"location":"developer-guide/authentication/","title":"Handling authentication","text":"

    Endurain supports integration with other apps through a comprehensive OAuth 2.1 compliant authentication system that includes standard username/password authentication, Multi-Factor Authentication (MFA), OAuth/SSO integration, and JWT-based session management with refresh token rotation.

    "},{"location":"developer-guide/authentication/#api-requirements","title":"API Requirements","text":""},{"location":"developer-guide/authentication/#token-handling","title":"Token Handling","text":""},{"location":"developer-guide/authentication/#token-lifecycle","title":"Token Lifecycle","text":""},{"location":"developer-guide/authentication/#oauth-21-token-storage-model-hybrid-approach","title":"OAuth 2.1 Token Storage Model (Hybrid Approach)","text":"

    Endurain implements an OAuth 2.1 compliant hybrid token storage model that provides both security and usability:

    Token Storage Location Lifetime Security Purpose Access Token In-memory (JavaScript) 15 minutes Short-lived, XSS-resistant (not persisted) Refresh Token httpOnly cookie 7 days CSRF-protected, auto-sent by browser CSRF Token In-memory (JavaScript) Session Prevents CSRF attacks on state-changing requests

    Security Properties:

    "},{"location":"developer-guide/authentication/#token-delivery-by-client-type","title":"Token Delivery by Client Type","text":""},{"location":"developer-guide/authentication/#authentication-flows","title":"Authentication Flows","text":""},{"location":"developer-guide/authentication/#standard-login-flow-usernamepassword","title":"Standard Login Flow (Username/Password)","text":"
    1. Client sends credentials to /auth/login endpoint
    2. Backend validates credentials and checks for account lockout
    3. If MFA is enabled, backend returns MFA-required response
    4. If MFA is disabled or verified, backend generates tokens
    5. Tokens are delivered based on client type:
      • Web: Access token + CSRF token in response body, refresh token as httpOnly cookie
      • Mobile: All tokens in response body
    "},{"location":"developer-guide/authentication/#oauthsso-flow","title":"OAuth/SSO Flow","text":"
    1. Client requests list of enabled providers from /public/idp
    2. Client initiates OAuth by redirecting to /public/idp/login/{idp_slug} with PKCE challenge
    3. User authenticates with the OAuth provider
    4. Provider redirects back to /public/idp/callback/{idp_slug} with authorization code
    5. Backend exchanges code for provider tokens and user info
    6. Backend creates or links user account and generates session tokens based on client type:
      • Web clients: Redirected to app with tokens set automatically
      • Mobile clients: Exchange session for tokens via PKCE token exchange endpoint /public/idp/session/{session_id}/tokens
    "},{"location":"developer-guide/authentication/#token-refresh-flow","title":"Token Refresh Flow","text":"

    The token refresh flow implements OAuth 2.1 compliant refresh token rotation:

    1. When access token expires, client calls POST /auth/refresh:
      • Web clients: Include X-CSRF-Token header with current CSRF token
      • Mobile clients: Include refresh token in request
    2. Backend validates refresh token and session, checks for token reuse
      • If token reuse detected: Entire token family is invalidated (security breach response)
    3. New tokens are generated (access, refresh, CSRF) with refresh token rotation
    4. Old refresh token is stored for reuse detection (grace period: 30 seconds)
    5. Response includes new tokens; web clients receive new httpOnly cookie

    Token Refresh Request (Web):

    POST /api/v1/auth/refresh\nX-Client-Type: web\nX-CSRF-Token: {current_csrf_token}\nCookie: endurain_refresh_token={refresh_token}\n

    Token Refresh Response:

    {\n  \"session_id\": \"uuid\",\n  \"access_token\": \"eyJ...\",\n  \"csrf_token\": \"new_csrf_token\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 1734567890\n}\n
    "},{"location":"developer-guide/authentication/#refresh-token-rotation-reuse-detection","title":"Refresh Token Rotation & Reuse Detection","text":"

    Endurain implements automatic refresh token rotation with reuse detection to prevent token theft:

    Security Feature Description Automatic Rotation New refresh token issued on every /auth/refresh call Token Family Tracking All tokens in a session share a token_family_id Reuse Detection Old tokens are stored and monitored for reuse Grace Period 30-second window allows for network retry scenarios Family Invalidation If reuse detected, ALL tokens in family are invalidated Rotation Count Tracks number of rotations for audit purposes"},{"location":"developer-guide/authentication/#api-endpoints","title":"API Endpoints","text":"

    The API is reachable under /api/v1. Below are the authentication-related endpoints. Complete API documentation is available on the backend docs (http://localhost:98/api/v1/docs or http://ip_address:98/api/v1/docs or https://domain/api/v1/docs):

    "},{"location":"developer-guide/authentication/#core-authentication-endpoints","title":"Core Authentication Endpoints","text":"What Url Expected Information Rate Limit Authorize /auth/login FORM with the fields username and password. HTTPS highly recommended 3 requests/min per IP Refresh Token /auth/refresh Cookie: endurain_refresh_token, Header: X-CSRF-Token (web only) - Verify MFA /auth/mfa/verify JSON {'username': <username>, 'mfa_code': '123456'} 5 requests/min per IP Logout /auth/logout Header: Authorization: Bearer <Access Token> -"},{"location":"developer-guide/authentication/#oauthsso-endpoints","title":"OAuth/SSO Endpoints","text":"What Url Expected Information Rate Limit Get Enabled Providers /public/idp None (public endpoint) - Initiate OAuth Login /public/idp/login/{idp_slug} Query params: redirect, code_challenge, code_challenge_method 10 requests/min per IP OAuth Callback /public/idp/callback/{idp_slug} Query params: code=<code>, state=<state> 10 requests/min per IP Token Exchange (PKCE) /public/idp/session/{session_id}/tokens JSON: {\"code_verifier\": \"<verifier>\"} 10 requests/min per IP Link IdP to Account /profile/idp/{idp_id}/link Requires authenticated session 10 requests/min per IP"},{"location":"developer-guide/authentication/#session-management-endpoints","title":"Session Management Endpoints","text":"What Url Expected Information Get User Sessions /sessions/user/{user_id} Header: Authorization: Bearer <Access Token> Delete Session /sessions/{session_id}/user/{user_id} Header: Authorization: Bearer <Access Token>"},{"location":"developer-guide/authentication/#example-resource-endpoints","title":"Example Resource Endpoints","text":"What Url Expected Information Activity Upload /activities/create/upload .gpx, .tcx, .gz or .fit file Set Weight /health/weight JSON {'weight': <number>, 'created_at': 'yyyy-MM-dd'}"},{"location":"developer-guide/authentication/#progressive-account-lockout","title":"Progressive Account Lockout","text":"

    Endurain implements progressive brute-force protection to prevent credential stuffing attacks:

    Failed Attempts Lockout Duration 5 failures 5 minutes 10 failures 30 minutes 20 failures 24 hours

    Features:

    "},{"location":"developer-guide/authentication/#mfa-authentication-flow","title":"MFA Authentication Flow","text":"

    When Multi-Factor Authentication (MFA) is enabled for a user, the authentication process requires two steps:

    "},{"location":"developer-guide/authentication/#step-1-initial-login-request","title":"Step 1: Initial Login Request","text":"

    Make a standard login request to /auth/login:

    Request:

    POST /api/v1/auth/login\nContent-Type: application/x-www-form-urlencoded\nX-Client-Type: web|mobile\n\nusername=user@example.com&password=userpassword\n

    Response (when MFA is enabled):

    {\n  \"mfa_required\": true,\n  \"username\": \"example\",\n  \"message\": \"MFA verification required\"\n}\n
    {\n  \"mfa_required\": true,\n  \"username\": \"example\",\n  \"message\": \"MFA verification required\"\n}\n
    "},{"location":"developer-guide/authentication/#step-2-mfa-verification","title":"Step 2: MFA Verification","text":"

    Complete the login by providing the MFA code (TOTP or backup code) to /auth/mfa/verify:

    Request:

    POST /api/v1/auth/mfa/verify\nContent-Type: application/json\nX-Client-Type: web|mobile\n\n{\n  \"username\": \"user@example.com\",\n  \"mfa_code\": \"123456\"\n}\n

    Backup Code Format

    Users can also use a backup code instead of a TOTP code. Backup codes are in XXXX-XXXX format (e.g., A3K9-7BDF). See MFA Backup Codes for details.

    Response (successful verification):

    {\n  \"session_id\": \"unique_session_id\",\n  \"access_token\": \"eyJ...\",\n  \"csrf_token\": \"abc123...\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 1734567890\n}\n
    {\n  \"session_id\": \"unique_session_id\",\n  \"access_token\": \"eyJ...\",\n  \"refresh_token\": \"eyJ...\",\n  \"csrf_token\": \"abc123...\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 1734567890\n}\n
    "},{"location":"developer-guide/authentication/#error-handling","title":"Error Handling","text":"
    {\n  \"detail\": \"No pending MFA login found for this username\"\n}\n
    {\n  \"detail\": \"Invalid MFA code. Failed attempts: 1\"\n}\n
    {\n  \"detail\": \"Too many failed MFA attempts. Account locked for 300 seconds.\"\n}\n
    "},{"location":"developer-guide/authentication/#important-notes","title":"Important Notes","text":""},{"location":"developer-guide/authentication/#mfa-backup-codes","title":"MFA Backup Codes","text":"

    Backup codes provide a recovery mechanism when users lose access to their authenticator app. When MFA is enabled, users receive 10 one-time backup codes that can be used instead of TOTP codes.

    "},{"location":"developer-guide/authentication/#backup-code-format","title":"Backup Code Format","text":""},{"location":"developer-guide/authentication/#when-backup-codes-are-generated","title":"When Backup Codes Are Generated","text":"
    1. Automatically on MFA Enable: When a user enables MFA, 10 backup codes are generated and returned in the response
    2. Manual Regeneration: Users can regenerate all backup codes via POST /profile/mfa/backup-codes (invalidates all previous codes)
    "},{"location":"developer-guide/authentication/#api-endpoints_1","title":"API Endpoints","text":"What URL Method Description Get Backup Code Status /profile/mfa/backup-codes/status GET Returns count of unused/used codes Regenerate Backup Codes /profile/mfa/backup-codes POST Generates new codes (invalidates old)"},{"location":"developer-guide/authentication/#backup-code-status-response","title":"Backup Code Status Response","text":"
    {\n  \"has_codes\": true,\n  \"total\": 10,\n  \"unused\": 8,\n  \"used\": 2,\n  \"created_at\": \"2025-12-21T10:30:00Z\"\n}\n
    "},{"location":"developer-guide/authentication/#regenerate-backup-codes-response","title":"Regenerate Backup Codes Response","text":"
    {\n  \"codes\": [\n    \"A3K9-7BDF\",\n    \"X2M5-9NPQ\",\n    \"...\"\n  ],\n  \"created_at\": \"2025-12-21T10:30:00Z\"\n}\n
    "},{"location":"developer-guide/authentication/#using-backup-codes-for-login","title":"Using Backup Codes for Login","text":"

    Backup codes can be used in the MFA verification step instead of TOTP codes:

    POST /api/v1/auth/mfa/verify\nContent-Type: application/json\nX-Client-Type: web|mobile\n\n{\n  \"username\": \"user@example.com\",\n  \"mfa_code\": \"A3K9-7BDF\"\n}\n

    Important

    "},{"location":"developer-guide/authentication/#oauthsso-integration","title":"OAuth/SSO Integration","text":""},{"location":"developer-guide/authentication/#supported-identity-providers","title":"Supported Identity Providers","text":"

    Endurain supports OAuth/SSO integration with various identity providers out of the box:

    The system is extensible and can be configured to work with:

    "},{"location":"developer-guide/authentication/#oauth-configuration","title":"OAuth Configuration","text":"

    Identity providers must be configured with the following parameters:

    "},{"location":"developer-guide/authentication/#linking-accounts","title":"Linking Accounts","text":"

    Users can link their Endurain account to an OAuth provider:

    1. User must be authenticated with a valid session
    2. Navigate to /profile/idp/{idp_id}/link
    3. Authenticate with the identity provider
    4. Provider is linked to the existing account
    "},{"location":"developer-guide/authentication/#oauth-token-response","title":"OAuth Token Response","text":"

    When authenticating via OAuth, the response format matches the standard authentication:

    Mobile OAuth/SSO

    Mobile apps must use the PKCE flow for OAuth/SSO authentication. This provides enhanced security and a cleaner separation between the WebView and native app.

    "},{"location":"developer-guide/authentication/#mobile-sso-with-pkce","title":"Mobile SSO with PKCE","text":""},{"location":"developer-guide/authentication/#overview","title":"Overview","text":"

    PKCE (Proof Key for Code Exchange, RFC 7636) is required for mobile OAuth/SSO authentication. It provides enhanced security by eliminating the need to extract tokens from WebView cookies, preventing authorization code interception attacks, and enabling a cleaner separation between the WebView and native app.

    "},{"location":"developer-guide/authentication/#why-use-pkce","title":"Why Use PKCE?","text":"Traditional WebView Flow PKCE Flow Extract tokens from cookies Tokens delivered via secure API Cookies may leak across contexts No cookie extraction needed Complex WebView cookie management Simple token exchange Potential timing issues Atomic token exchange"},{"location":"developer-guide/authentication/#pkce-flow-overview","title":"PKCE Flow Overview","text":"
    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  Mobile App \u2502     \u2502   Backend   \u2502     \u2502   WebView   \u2502     \u2502     IdP     \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502 Generate verifier \u2502                   \u2502                   \u2502\n       \u2502 & challenge       \u2502                   \u2502                   \u2502\n       \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502                   \u2502                   \u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502     Open WebView with challenge       \u2502                   \u2502\n       \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502                   \u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502                   \u2502      Redirect to IdP                  \u2502\n       \u2502                   \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502                   \u2502                   \u2502   User logs in    \u2502\n       \u2502                   \u2502                   \u2502<\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502                   \u2502   Callback with code & state          \u2502\n       \u2502                   \u2502<\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502     Redirect with session_id          \u2502                   \u2502\n       \u2502<\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502                   \u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502 POST token exchange with verifier     \u2502                   \u2502\n       \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500>\u2502                   \u2502                   \u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n       \u2502   Return tokens   \u2502                   \u2502                   \u2502\n       \u2502<\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502                   \u2502                   \u2502\n       \u2502                   \u2502                   \u2502                   \u2502\n
    "},{"location":"developer-guide/authentication/#step-by-step-pkce-implementation","title":"Step-by-Step PKCE Implementation","text":""},{"location":"developer-guide/authentication/#step-1-generate-pkce-code-verifier-and-challenge","title":"Step 1: Generate PKCE Code Verifier and Challenge","text":"

    Before initiating the OAuth flow, generate a cryptographically random code verifier and compute its SHA256 challenge:

    Code Verifier Requirements (RFC 7636):

    Code Challenge Computation:

    code_challenge = BASE64URL(SHA256(code_verifier))\n
    "},{"location":"developer-guide/authentication/#step-2-initiate-oauth-with-pkce-challenge","title":"Step 2: Initiate OAuth with PKCE Challenge","text":"

    Open a WebView with the SSO URL including PKCE parameters:

    URL to Load:

    https://your-endurain-instance.com/api/v1/public/idp/login/{idp_slug}?code_challenge={challenge}&code_challenge_method=S256&redirect=/dashboard\n

    Query Parameters:

    Parameter Required Description code_challenge Yes (PKCE) Base64url-encoded SHA256 hash of code_verifier code_challenge_method Yes (PKCE) Must be S256 redirect No Frontend path after successful login"},{"location":"developer-guide/authentication/#step-3-monitor-webview-for-callback","title":"Step 3: Monitor WebView for Callback","text":"

    The OAuth flow proceeds as normal. Monitor the WebView URL for the success redirect:

    Success URL Pattern:

    https://your-endurain-instance.com/login?sso=success&session_id={uuid}&redirect=/dashboard\n

    Extract the session_id from the URL - this is needed for token exchange.

    "},{"location":"developer-guide/authentication/#step-4-exchange-session-for-tokens-pkce-verification","title":"Step 4: Exchange Session for Tokens (PKCE Verification)","text":"

    After obtaining the session_id, close the WebView and exchange it for tokens using the code verifier:

    Token Exchange Request:

    POST /api/v1/public/idp/session/{session_id}/tokens\nContent-Type: application/json\nX-Client-Type: mobile\n\n{\n  \"code_verifier\": \"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\"\n}\n

    Successful Response (HTTP 200):

    {\n  \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n  \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n  \"csrf_token\": \"abc123def456...\",\n  \"expires_in\": 900,\n  \"token_type\": \"Bearer\"\n}\n

    Error Responses:

    Status Error Description 400 Invalid code_verifier Verifier doesn't match the challenge 404 Session not found Invalid session_id or not a PKCE flow 409 Tokens already exchanged Replay attack prevention 429 Rate limit exceeded Max 10 requests/minute per IP"},{"location":"developer-guide/authentication/#step-5-store-tokens-securely","title":"Step 5: Store Tokens Securely","text":"

    Store the received tokens in secure platform storage:

    "},{"location":"developer-guide/authentication/#step-6-use-tokens-for-api-requests","title":"Step 6: Use Tokens for API Requests","text":"

    Use the tokens for authenticated API calls:

    GET /api/v1/activities\nAuthorization: Bearer {access_token}\nX-Client-Type: mobile\nX-CSRF-Token: {csrf_token}\n
    "},{"location":"developer-guide/authentication/#security-features","title":"Security Features","text":"Feature Description PKCE S256 SHA256 challenge prevents code interception One-time exchange Tokens can only be exchanged once per session 10-minute expiry OAuth state expires after 10 minutes Rate limiting 10 token exchange requests per minute Session linking Session is cryptographically bound to OAuth state"},{"location":"developer-guide/authentication/#configuration","title":"Configuration","text":""},{"location":"developer-guide/authentication/#environment-variables","title":"Environment Variables","text":"

    The following environment variables control authentication behavior:

    "},{"location":"developer-guide/authentication/#token-configuration","title":"Token Configuration","text":"Variable Description Default Required SECRET_KEY Secret key for JWT signing (min 32 characters recommended) - Yes ALGORITHM JWT signing algorithm HS256 No ACCESS_TOKEN_EXPIRE_MINUTES Access token lifetime in minutes 15 No REFRESH_TOKEN_EXPIRE_DAYS Refresh token lifetime in days 7 No"},{"location":"developer-guide/authentication/#session-configuration","title":"Session Configuration","text":"Variable Description Default Required SESSION_IDLE_TIMEOUT_ENABLED Enable session idle timeout false No SESSION_IDLE_TIMEOUT_HOURS Hours of inactivity before session expires 1 No SESSION_ABSOLUTE_TIMEOUT_HOURS Maximum session lifetime in hours 24 No"},{"location":"developer-guide/authentication/#security-configuration","title":"Security Configuration","text":"Variable Description Default Required BACKEND_CORS_ORIGINS Allowed CORS origins (JSON array) [] No FRONTEND_PROTOCOL Protocol for cookie security (http or https) http No"},{"location":"developer-guide/authentication/#cookie-configuration","title":"Cookie Configuration","text":"

    For web clients, the refresh token cookie is configured with:

    Attribute Value Purpose HttpOnly true Prevents JavaScript access (XSS protection) Secure true (in production) Only sent over HTTPS SameSite Strict Prevents CSRF attacks Path / Application-wide access Expires 7 days (default) Matches refresh token lifetime"},{"location":"developer-guide/authentication/#security-scopes","title":"Security Scopes","text":"

    Endurain uses OAuth-style scopes to control API access. Each scope controls access to specific resource groups:

    "},{"location":"developer-guide/authentication/#available-scopes","title":"Available Scopes","text":"Scope Description Access Level profile User profile information Read/Write users:read Read user data Read-only users:write Modify user data Write gears:read Read gear/equipment data Read-only gears:write Modify gear/equipment data Write activities:read Read activity data Read-only activities:write Create/modify activities Write health:read Read health metrics (weight, sleep, steps) Read-only health:write Record health metrics Write health_targets:read Read health targets Read-only health_targets:write Modify health targets Write sessions:read View active sessions Read-only sessions:write Manage sessions Write server_settings:read View server configuration Read-only server_settings:write Modify server settings Write (Admin) identity_providers:read View OAuth providers Read-only identity_providers:write Configure OAuth providers Write (Admin)"},{"location":"developer-guide/authentication/#scope-usage","title":"Scope Usage","text":"

    Scopes are automatically assigned based on user permissions and are embedded in JWT tokens. API endpoints validate required scopes before processing requests.

    "},{"location":"developer-guide/authentication/#common-error-responses","title":"Common Error Responses","text":""},{"location":"developer-guide/authentication/#http-status-codes","title":"HTTP Status Codes","text":"Status Code Description Common Causes 400 Bad Request Invalid request format Missing required fields, invalid JSON, no pending MFA login 401 Unauthorized Authentication failed Invalid credentials, expired token, invalid MFA code 403 Forbidden Access denied Invalid client type, insufficient permissions, missing required scope 404 Not Found Resource not found Invalid session ID, user not found, endpoint doesn't exist 429 Too Many Requests Rate limit exceeded Too many login attempts, OAuth requests exceeded limit 500 Internal Server Error Server error Database connection issues, configuration errors"},{"location":"developer-guide/authentication/#example-error-responses","title":"Example Error Responses","text":"

    Invalid Client Type:

    {\n  \"detail\": \"Invalid client type. Must be 'web' or 'mobile'\"\n}\n

    Expired Token:

    {\n  \"detail\": \"Token has expired\"\n}\n

    Invalid Credentials:

    {\n  \"detail\": \"Incorrect username or password\"\n}\n

    Rate Limit Exceeded:

    {\n  \"detail\": \"Rate limit exceeded. Please try again later.\"\n}\n

    Missing Required Scope:

    {\n  \"detail\": \"Insufficient permissions. Required scope: activities:write\"\n}\n
    "},{"location":"developer-guide/authentication/#best-practices","title":"Best Practices","text":""},{"location":"developer-guide/authentication/#for-web-client-applications","title":"For Web Client Applications","text":"
    1. Store access and CSRF tokens in memory - Never persist in localStorage or sessionStorage
    2. Implement automatic token refresh - Refresh before access token expires (e.g., at 80% of lifetime)
    3. Handle concurrent refresh requests - Use a refresh lock pattern to prevent race conditions
    4. Always include required headers:
      • Authorization: Bearer {access_token} for all authenticated requests
      • X-Client-Type: web for all requests
      • X-CSRF-Token: {csrf_token} for POST/PUT/DELETE/PATCH requests
    5. Handle page reload gracefully - Call /auth/refresh on app initialization to restore in-memory tokens
    6. Clear tokens on logout - The httpOnly cookie is cleared by the backend
    "},{"location":"developer-guide/authentication/#for-mobile-client-applications","title":"For Mobile Client Applications","text":"
    1. Store tokens securely:
      • iOS: Keychain Services
      • Android: EncryptedSharedPreferences or Android Keystore
    2. Use PKCE for OAuth/SSO - Required for mobile OAuth flows
    3. Include required headers:
      • Authorization: Bearer {access_token} for all authenticated requests
      • X-Client-Type: mobile for all requests
      • X-CSRF-Token: {csrf_token} for state-changing requests
    4. Handle token refresh proactively - Refresh before expiration
    5. Implement secure token deletion on logout
    "},{"location":"developer-guide/authentication/#for-security","title":"For Security","text":"
    1. Never expose SECRET_KEY in client code or version control
    2. Use strong, randomly generated secrets (minimum 32 characters)
    3. Always use HTTPS in production environments
    4. Enable MFA for enhanced account security
    5. Monitor for token reuse - Indicates potential token theft
    6. Enable session idle timeout for sensitive applications
    7. Use appropriate scopes - Request only necessary permissions
    "},{"location":"developer-guide/authentication/#for-oauthsso-integration","title":"For OAuth/SSO Integration","text":"
    1. Always use PKCE - Required for mobile, recommended for web
    2. Validate state parameter - Prevents CSRF attacks on OAuth flow
    3. Implement proper redirect URL validation - Prevents open redirects
    4. Handle provider errors gracefully with user-friendly messages
    5. Support account linking - Allow users to connect multiple providers
    6. Respect token expiry - OAuth state expires after 10 minutes
    "},{"location":"developer-guide/setup-dev-env/","title":"Setup a dev environment","text":"

    Bellow are the steps to create a dev environment. Examples bellow will use Endurain repo, but you should adapt those for your scenario (forked repo, etc).

    cd <folder_to_store_code>\ngit clone https://github.com/endurain-project/endurain.git # this will clone the repo structure to the previous folder inside a folder called endurain\n
    "},{"location":"developer-guide/setup-dev-env/#docker-image-and-backend-logic","title":"Docker image and backend logic","text":"

    Make sure Docker is installed, more info here.

    docker build -f docker/Dockerfile -t unified-image .\n
    services:\n    endurain:\n        container_name: endurain\n        image: unified-image # based on image that will be created above\n        environment:\n            - TZ=Europe/Lisbon # change if needed. Default is UTC\n            - DB_HOST=postgres\n            - DB_PORT=5432\n            - DB_PASSWORD=changeme\n            - SECRET_KEY=changeme # openssl rand -hex 32\n            - FERNET_KEY=changeme # https://fernetkeygen.com or python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"\n            - ENDURAIN_HOST=http://localhost:8080 # change if needed\n            - BEHIND_PROXY=false\n            - ENVIRONMENT=development\n        volumes:\n            - <folder_to_store_code>/backend/app:/app/backend # this will replace the backend code logic with yours. Any changes in the code need a container reboot for them to apply\n        ports:\n            - \"8080:8080\" # change if needed\n        depends_on:\n            postgres:\n                condition: service_healthy\n        restart: unless-stopped\n\n    postgres:\n        image: postgres:latest\n        container_name: postgres\n        environment:\n            - POSTGRES_PASSWORD=changeme\n            - POSTGRES_DB=endurain\n            - POSTGRES_USER=endurain\n            - PGDATA=/var/lib/postgresql/data/pgdata\n        ports:\n            - \"5432:5432\"\n        healthcheck:\n            test: [\"CMD-SHELL\", \"pg_isready -U endurain\"]\n            interval: 5s\n            timeout: 5s\n            retries: 5\n        volumes:\n            - <path_to_container_folders>/postgres:/var/lib/postgresql/data\n        restart: unless-stopped\n\n    adminer:\n        container_name: adminer\n        image: adminer\n        ports:\n            - 8081:8080\n        restart: unless-stopped\n
    docker compose up -d\n
    docker compose down\n
    docker image remove unified-image\n
    "},{"location":"developer-guide/setup-dev-env/#frontend","title":"Frontend","text":"

    Make sure you have an up-to-date version of Node.js installed.

    cd frontend/app\nnpm install\n
    VITE_ENDURAIN_HOST=http://localhost:8080 # Adapt this based on the docker compose of your dev environment\n
    npm run dev\n

    "},{"location":"developer-guide/supported-types/","title":"Supported types within Endurain","text":""},{"location":"developer-guide/supported-types/#supported-activity-types","title":"Supported activity types","text":"

    The table bellow details the activity types supported by Endurain.

    Name Value Run 1 Trail run 2 Track run 34 Treadmill run 40 Virtual run 3 Road cycling 4 Gravel cycling 5 MTB cycling 6 Commuting cycling 27 Mixed surface cycling 29 Virtual cycling 7 Indoor cycling 28 E-Bike cycling 35 E-Bike mountain cycling 36 Indoor swimming 8 Open water swimming 9 General workout 10 Walk 11 Indoor walk 31 Hike 12 Rowing 13 Yoga 14 Alpine ski 15 Nordic Ski 16 Snowboard 17 Ice Skate 37 Transition 18 Strength Training 19 Crossfit 20 Tennis 21 Table Tennis 22 Badminton 23 Squash 24 Racquetball 25 Pickleball 26 Padel 39 Windsurf 30 Stand up paddling 32 Surf 33 Soccer 38 Cardio training 41 Kayaking 42 Sailing 43 Snow shoeing 44 Inline skating 45"},{"location":"developer-guide/supported-types/#supported-gear-types","title":"Supported gear types","text":"

    The table bellow details the gear types supported by Endurain.

    Name Value Notes bike 1 N/A shoes 2 N/A wetsuit 3 N/A racquet 4 N/A ski 5 N/A snowboard 6 N/A windsurf 7 N/A water_sports_board 8 Example: stand up paddle and surf board"},{"location":"developer-guide/supported-types/#supported-bike-component-gear-types","title":"Supported bike component gear types","text":"

    The table bellow details the bike gear component types supported by Endurain:

    Value back_break_oil back_break_pads back_break_rotor back_tire back_tube back_tubeless_sealant back_tubeless_rim_tape back_wheel back_wheel_valve bottom_bracket bottle_cage cassette chain computer_mount crank_left_power_meter crank_right_power_meter crankset crankset_power_meter fork frame front_break_oil front_break_pads front_break_rotor front_derailleur front_shifter front_tire front_tube front_tubeless_sealant front_tubeless_rim_tape front_wheel front_wheel_valve grips handlebar handlebar_tape headset pedals pedals_left_power_meter pedals_power_meter pedals_right_power_meter rear_derailleur rear_shifter saddle seatpost stem"},{"location":"developer-guide/supported-types/#supported-shoes-component-gear-types","title":"Supported shoes component gear types","text":"

    The table bellow details the shoes component gear types supported by Endurain:

    Value cleats insoles laces"},{"location":"developer-guide/supported-types/#supported-racquet-component-gear-types","title":"Supported racquet component gear types","text":"

    The table bellow details the racquet component gear types supported by Endurain:

    Value basegrip bumpers grommets overgrip strings"},{"location":"developer-guide/supported-types/#supported-windsurf-component-gear-types","title":"Supported windsurf component gear types","text":"

    The table bellow details the windsurf component gear types supported by Endurain:

    Value sail board mast boom mast_extension mast_base mast_universal_joint fin footstraps harness_lines rigging_lines footpad impact_vest lifeguard_vest helmet wing front_foil stabilizer fuselage"},{"location":"features/single-sign-on/","title":"Single Sign-On (SSO) Configuration","text":"

    Endurain supports Single Sign-On (SSO) integration through OAuth 2.0 and OpenID Connect (OIDC) protocols. This allows users to authenticate using their existing identity provider accounts.

    "},{"location":"features/single-sign-on/#important-notes","title":"Important Notes","text":"

    Email Address Matching

    If you already have an existing Endurain account, the email address in your SSO provider must match your Endurain account email. If the email addresses don't match, Endurain will create a new user account with the SSO email address.

    Requirements

    You'll need:

    "},{"location":"features/single-sign-on/#supported-identity-providers","title":"Supported Identity Providers","text":"

    Endurain provides built-in support for the following identity providers:

    The system also supports custom OIDC providers including:

    "},{"location":"features/single-sign-on/#configuration-examples","title":"Configuration Examples","text":""},{"location":"features/single-sign-on/#pocket-id","title":"Pocket ID","text":"

    Pocket ID is a lightweight, self-hosted identity provider that works seamlessly with Endurain.

    "},{"location":"features/single-sign-on/#step-1-configure-pocket-id","title":"Step 1: Configure Pocket ID","text":"
    1. Log into your Pocket ID instance
    2. Navigate to Administration \u2192 OIDC Clients
    3. Select Add OIDC client
    4. Configure the following settings:
      • Name: Endurain
      • Client Launch URL: Your Endurain FQDN (e.g., https://endurain.mydomain.com)
      • Callback URLs: https://endurain.mydomain.com/api/v1/public/idp/callback/pocket-id
    5. Click Save
    6. Important: Make a note of your Client ID and Client Secret
    "},{"location":"features/single-sign-on/#step-2-configure-endurain","title":"Step 2: Configure Endurain","text":"
    1. Log into your Endurain instance
    2. Navigate to Settings \u2192 Identity Providers
    3. Select Add Identity Provider \u2192 Select Pocket ID
    4. Configure the following settings:
      • Provider Name: Pocket ID
      • Slug: pocket-id
      • Provider Type: OIDC
      • Issuer URL: Your Pocket ID FQDN (e.g., https://pocketid.mydomain.com) - no trailing slash
      • Client ID: The Client ID from Step 1
      • Client Secret: The Client Secret from Step 1
      • Scopes: openid profile email
    5. Click Save
    "},{"location":"features/single-sign-on/#step-3-test-the-integration","title":"Step 3: Test the Integration","text":"
    1. Log out of Endurain
    2. On the login page, you should see a Sign in with Pocket ID button
    3. Click the button to test the SSO flow
    "},{"location":"features/single-sign-on/#tailscale-tsidp","title":"Tailscale TSIDP","text":"

    Tailscale's identity provider (TSIDP) can be used for secure authentication within your Tailscale network.

    "},{"location":"features/single-sign-on/#step-1-configure-tsidp","title":"Step 1: Configure TSIDP","text":"
    1. Log into your TSIDP instance
    2. Select Add new client
    3. Configure the following settings:
      • Client Name: Endurain
      • Redirect URIs: https://endurain.mydomain.com/api/v1/public/idp/callback/tsidp
    4. Click Create client
    5. Important: Make a note of your Client ID and Client Secret
    "},{"location":"features/single-sign-on/#step-2-configure-endurain_1","title":"Step 2: Configure Endurain","text":"
    1. Log into your Endurain instance
    2. Navigate to Settings \u2192 Identity Providers
    3. Select Add Identity Provider \u2192 Custom
    4. Configure the following settings:
      • Provider Name: TSIDP
      • Slug: tsidp
      • Provider Type: OIDC
      • Issuer URL: Your TSIDP FQDN (e.g., https://tsidp.mydomain.com) - no trailing slash
      • Client ID: The Client ID from Step 1
      • Client Secret: The Client Secret from Step 1
      • Scopes: openid profile email
    5. Click Save
    "},{"location":"features/single-sign-on/#step-3-test-the-integration_1","title":"Step 3: Test the Integration","text":"
    1. Log out of Endurain
    2. On the login page, you should see a Sign in with TSIDP button
    3. Click the button to test the SSO flow
    "},{"location":"features/single-sign-on/#general-configuration-steps","title":"General Configuration Steps","text":"

    For any OIDC-compliant identity provider, follow these general steps:

    "},{"location":"features/single-sign-on/#1-configure-your-identity-provider","title":"1. Configure Your Identity Provider","text":"

    Create an OAuth 2.0/OIDC client application with the following settings:

    Save the generated Client ID and Client Secret.

    "},{"location":"features/single-sign-on/#2-configure-endurain","title":"2. Configure Endurain","text":"
    1. Log into Endurain as an administrator
    2. Navigate to Settings \u2192 Identity Providers
    3. Click Add Identity Provider
    4. Select your provider type or choose Custom for OIDC providers
    5. Fill in the required fields (see table below)
    6. Click Save
    Field Description Example Provider Name Display name shown on login button Google, GitHub, etc. Slug URL-safe identifier (lowercase, hyphens) google, github Provider Type Protocol type OIDC or OAuth2 Issuer URL Provider's base URL (no trailing slash) https://accounts.google.com Client ID OAuth client identifier From Step 1 Client Secret OAuth client secret From Step 1 Scopes Space-separated OAuth scopes openid profile email"},{"location":"features/single-sign-on/#3-verify-the-configuration","title":"3. Verify the Configuration","text":"
    1. Log out of Endurain
    2. Visit the login page
    3. Verify that a Sign in with Provider Name button appears
    4. Test the authentication flow
    "},{"location":"features/single-sign-on/#troubleshooting","title":"Troubleshooting","text":""},{"location":"features/single-sign-on/#common-issues","title":"Common Issues","text":"

    Problem: \"Invalid redirect URI\" error

    Problem: \"Email address mismatch\" creates duplicate account

    Problem: SSO button doesn't appear on login page

    Problem: \"Invalid issuer URL\" error

    "},{"location":"features/single-sign-on/#logs","title":"Logs","text":"

    For detailed troubleshooting, check the Endurain backend logs:

    docker logs endurain-backend\n# and/or\ntail -n 100 logs/app.log\n

    Look for authentication-related errors that can help identify configuration issues.

    "},{"location":"features/single-sign-on/#security-considerations","title":"Security Considerations","text":"

    Security Best Practices

    "},{"location":"features/single-sign-on/#additional-resources","title":"Additional Resources","text":""},{"location":"features/sleep-scoring/","title":"Sleep Scoring Guide","text":"

    Endurain uses a sleep scoring system to help you understand the quality of your sleep. This guide explains how your sleep is evaluated and what the scores mean.

    "},{"location":"features/sleep-scoring/#what-is-sleep-scoring","title":"What is Sleep Scoring?","text":"

    Sleep scoring is like a report card for your sleep. The system analyzes your sleep data and gives you:

    "},{"location":"features/sleep-scoring/#how-are-scores-calculated","title":"How Are Scores Calculated?","text":""},{"location":"features/sleep-scoring/#overall-sleep-score-0-100","title":"Overall Sleep Score (0-100)","text":"

    Your overall sleep score combines several important factors:

    What the scores mean:

    "},{"location":"features/sleep-scoring/#understanding-each-score-component","title":"Understanding Each Score Component","text":""},{"location":"features/sleep-scoring/#1-sleep-duration-score","title":"1. Sleep Duration Score","text":"

    What it measures: How many hours you slept

    Why it matters: Your body needs enough time to go through all sleep stages and recover properly.

    Scoring guidelines:

    Hours of Sleep Score Rating 7-9 hours 90-100 EXCELLENT - Perfect amount! 6-7 hours 70-89 GOOD - A bit short but okay 9-10 hours 70-89 GOOD - A bit long but okay 5-6 hours 50-69 FAIR - Not enough rest 10-11 hours 50-69 FAIR - Possibly oversleeping Less than 5 hours 0-49 POOR - Seriously insufficient More than 11 hours 0-49 POOR - Too much sleep

    The sweet spot: 7-9 hours, with 8 hours being ideal for most adults.

    "},{"location":"features/sleep-scoring/#2-sleep-quality-score","title":"2. Sleep Quality Score","text":"

    What it measures: How your sleep was distributed across different sleep stages

    Why it matters: Quality sleep isn't just about quantity - your brain and body need the right mix of sleep stages to fully recover.

    The four sleep stages:

    1. Light Sleep - Transition period, your body starts to relax
    2. Deep Sleep - Body repairs muscles and tissues, strengthens immune system
    3. REM Sleep - Brain processes memories and emotions, vivid dreams occur
    4. Awake - Brief wakeful moments (some are normal!)

    Optimal percentages:

    Sleep Stage Ideal Range Why It Matters Deep Sleep 13-23% (peak: 18%) Physical recovery and healing REM Sleep 20-25% (peak: 22.5%) Memory and emotional processing Light Sleep 45-55% (peak: 50%) Transition between stages Awake Time Less than 5% Normal brief awakenings

    How it's scored:

    "},{"location":"features/sleep-scoring/#3-awake-count-score","title":"3. Awake Count Score","text":"

    What it measures: How many times you woke up during the night

    Why it matters: Frequent awakenings disrupt your sleep cycles and prevent deep, restorative sleep.

    Scoring:

    Awakenings Score Rating What It Means 0-1 times 90-100 EXCELLENT Uninterrupted, restorative sleep 2-3 times 70-89 GOOD Some interruptions but still decent 4-5 times 50-69 FAIR Sleep continuity affected 6+ times 0-49 POOR Very fragmented sleep

    Note: It's normal to wake up briefly 1-2 times per night. You might not even remember them!

    "},{"location":"features/sleep-scoring/#4-rem-sleep-percentage-score","title":"4. REM Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was REM (Rapid Eye Movement) sleep

    Why it matters: REM sleep is when your brain consolidates memories, processes emotions, and boosts creativity. It's essential for mental health and learning.

    Optimal range: 20-25% of total sleep time (about 1.5-2 hours for 8 hours of sleep)

    What different levels mean:

    "},{"location":"features/sleep-scoring/#5-deep-sleep-percentage-score","title":"5. Deep Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was deep sleep

    Why it matters: Deep sleep is when your body does most of its physical repair - healing muscles, strengthening bones, and boosting your immune system.

    Optimal range: 13-23% of total sleep time (about 1-2 hours for 8 hours of sleep)

    What different levels mean:

    Did you know? Deep sleep decreases naturally as you age, which is normal!

    "},{"location":"features/sleep-scoring/#6-light-sleep-percentage-score","title":"6. Light Sleep Percentage Score","text":"

    What it measures: What percentage of your sleep was light sleep

    Why it matters: Light sleep serves as a transition between sleep stages and makes up the largest portion of your sleep.

    Optimal range: 45-55% of total sleep time (about 3.5-4.5 hours for 8 hours of sleep)

    What different levels mean:

    "},{"location":"features/sleep-scoring/#7-sleep-stress-score","title":"7. Sleep Stress Score","text":"

    What it measures: Your average stress level during sleep and how restless you were

    Why it matters: High stress during sleep indicates your body isn't fully relaxing, which affects recovery quality.

    Stress levels explained (based on Garmin scale):

    Scoring:

    Stress Level Base Score Rating 0-25 (Rest) 100 EXCELLENT 26-50 (Low) 70-90 GOOD 51-75 (Medium) 50-70 FAIR 76-100 (High) 0-50 POOR

    Restless moments penalty: Each restless moment during sleep reduces your score by 2-3 points.

    "},{"location":"features/sleep-scoring/#tips-for-better-sleep-scores","title":"Tips for Better Sleep Scores","text":""},{"location":"features/sleep-scoring/#to-improve-duration-score","title":"To Improve Duration Score:","text":""},{"location":"features/sleep-scoring/#to-improve-quality-score","title":"To Improve Quality Score:","text":""},{"location":"features/sleep-scoring/#to-reduce-awakenings","title":"To Reduce Awakenings:","text":""},{"location":"features/sleep-scoring/#to-reduce-sleep-stress","title":"To Reduce Sleep Stress:","text":""},{"location":"features/sleep-scoring/#frequently-asked-questions","title":"Frequently Asked Questions","text":""},{"location":"features/sleep-scoring/#q-why-is-my-score-low-even-though-i-slept-8-hours","title":"Q: Why is my score low even though I slept 8 hours?","text":"

    A: Duration is only 30% of your overall score. You might have had poor quality sleep, many awakenings, or high stress levels. Check your individual component scores to see what needs improvement.

    "},{"location":"features/sleep-scoring/#q-is-it-bad-if-my-scores-vary-day-to-day","title":"Q: Is it bad if my scores vary day to day?","text":"

    A: Some variation is normal! Factors like stress, exercise, diet, and life events affect your sleep. Look for trends over weeks rather than individual nights.

    "},{"location":"features/sleep-scoring/#q-whats-more-important-duration-or-quality","title":"Q: What's more important - duration or quality?","text":"

    A: Both matter! Quality is weighted slightly higher (40% vs 30%) because you can sleep for 10 hours but still feel tired if the quality is poor. Aim for both good duration AND quality.

    "},{"location":"features/sleep-scoring/#q-my-remdeep-sleep-percentage-seems-low-is-that-bad","title":"Q: My REM/Deep sleep percentage seems low. Is that bad?","text":"

    A: Not necessarily. These percentages are averages based on research. Individual needs vary by age, genetics, and lifestyle. If you feel rested and energetic, your sleep is probably fine!

    "},{"location":"features/sleep-scoring/#q-can-i-compare-my-scores-with-friends","title":"Q: Can I compare my scores with friends?","text":"

    A: While you can, remember that everyone's sleep needs are different. Focus on improving YOUR scores over time rather than comparing with others.

    "},{"location":"features/sleep-scoring/#q-what-if-i-have-a-sleep-disorder","title":"Q: What if I have a sleep disorder?","text":"

    A: These scores are educational tools, not medical diagnoses. If you consistently get poor scores or feel tired despite good scores, consult a healthcare professional or sleep specialist.

    "},{"location":"features/sleep-scoring/#understanding-your-data","title":"Understanding Your Data","text":""},{"location":"features/sleep-scoring/#when-are-scores-calculated","title":"When are scores calculated?","text":"

    Scores are automatically calculated when you:

    The system recalculates all scores to ensure they reflect your current data.

    "},{"location":"features/sleep-scoring/#what-data-is-needed","title":"What data is needed?","text":"

    For the most accurate scores, provide:

    Partial data: Even with incomplete data, the system will calculate scores for the metrics you provide. Missing metrics won't break the scoring system.

    "},{"location":"features/sleep-scoring/#technical-details","title":"Technical Details","text":"

    For developers and technically-minded users:

    "},{"location":"features/sleep-scoring/#summary","title":"Summary","text":"

    Your sleep scores provide valuable insights into your rest quality. By understanding what each score means and following the improvement tips, you can work towards better, more restorative sleep.

    Remember: - \ud83c\udfaf Focus on trends, not single nights - \ud83d\udcaa Small improvements add up over time - \ud83d\ude34 Consistency is key to good sleep - \ud83e\ude7a Consult a doctor for persistent sleep issues

    Sweet dreams and happy tracking! \ud83c\udf19\u2728

    "},{"location":"getting-started/advanced-started/","title":"Getting started advanced","text":""},{"location":"getting-started/advanced-started/#default-credentials","title":"Default Credentials","text":""},{"location":"getting-started/advanced-started/#docker-deployment","title":"Docker Deployment","text":"

    Endurain provides a Docker image for simplified deployment. To get started, check out the docker-compose.yml.example file in the project repository and adjust it according to your setup. Supported tags are:

    "},{"location":"getting-started/advanced-started/#supported-environment-variables","title":"Supported Environment Variables","text":"

    Table below shows supported environment variables. Variables marked with optional \"No\" should be set to avoid errors.

    Environment variable Default value Optional Notes UID 1000 Yes User ID for mounted volumes. Default is 1000 GID 1000 Yes Group ID for mounted volumes. Default is 1000 TZ UTC Yes Timezone definition. Useful for TZ calculation for activities that do not have coordinates associated, like indoor swim or weight training. If not specified UTC will be used. List of available time zones here. Format Europe/Lisbon expected FRONTEND_DIR /app/frontend/dist Yes You will only need to change this value if installing using bare metal method BACKEND_DIR /app/backend Yes You will only need to change this value if installing using bare metal method DATA_DIR /app/backend/data Yes You will only need to change this value if installing using bare metal method LOGS_DIR /app/backend/logs Yes You will only need to change this value if installing using bare metal method ENDURAIN_HOST No default set No Required for internal communication and Strava. For Strava https must be used. Host or local ip (example: http://192.168.1.10:8080 or https://endurain.com) REVERSE_GEO_PROVIDER nominatim Yes Defines reverse geo provider. Expects geocode, photon or nominatim. photon can be the SaaS by komoot or a self hosted version like a self hosted version. Like photon, Nominatim can be the SaaS or a self hosted version PHOTON_API_HOST photon.komoot.io Yes API host for photon. By default it uses the SaaS by komoot PHOTON_API_USE_HTTPS true Yes Protocol used by photon. By default uses HTTPS to be inline with what SaaS by komoot expects NOMINATIM_API_HOST nominatim.openstreetmap.org Yes API host for Nominatim. By default it uses the SaaS NOMINATIM_API_USE_HTTPS true Yes Protocol used by Nominatim. By default uses HTTPS to be inline with what SaaS expects GEOCODES_MAPS_API changeme Yes Geocode maps offers a free plan consisting of 1 Request/Second. Registration necessary. REVERSE_GEO_RATE_LIMIT 1 Yes Change this if you have a paid Geocode maps tier. Other providers also use this variable. Keep it as is if you use photon or Nominatim to keep 1 request per second DB_HOST postgres Yes postgres DB_PORT 5432 Yes 3306 or 5432 DB_USER endurain Yes N/A DB_PASSWORD No default set No Database password. Alternatively, use DB_PASSWORD_FILE for Docker secrets DB_DATABASE endurain Yes N/A SECRET_KEY No default set No Run openssl rand -hex 32 on a terminal to get a secret. Alternatively, use SECRET_KEY_FILE for Docker secrets FERNET_KEY No default set No Run python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\" on a terminal to get a secret or go to https://fernetkeygen.com. Example output is 7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y=. Alternatively, use FERNET_KEY_FILE for Docker secrets ALGORITHM HS256 Yes Currently only HS256 is supported ACCESS_TOKEN_EXPIRE_MINUTES 15 Yes Time in minutes REFRESH_TOKEN_EXPIRE_DAYS 7 Yes Time in days SESSION_IDLE_TIMEOUT_ENABLED false Yes Enforce idle timeouts (supported values are true and false) SESSION_IDLE_TIMEOUT_HOURS 1 Yes Time in hours SESSION_ABSOLUTE_TIMEOUT_HOURS 24 Yes Time in hours JAEGER_ENABLED false Yes N/A JAEGER_PROTOCOL http Yes N/A JAEGER_HOST jaeger Yes N/A JAEGER_PORT 4317 Yes N/A BEHIND_PROXY false Yes Change to true if behind reverse proxy ENVIRONMENT production Yes production, demo and development allowed. development allows connections from localhost:8080 and localhost:5173 at the CORS level. demo equals to production except it does not return user sessions SMTP_HOST No default set Yes The SMTP host of your email provider. Example smtp.protonmail.ch SMTP_PORT 587 Yes The SMTP port of your email provider. Default is 587 SMTP_USERNAME No default set Yes The username of your SMTP email provider, probably your email address SMTP_PASSWORD No default set Yes The password of your SMTP email provider. Some providers allow the use of your account password, others require the creation of an app password. Please refer to your provider documentation. Alternatively, use SMTP_PASSWORD_FILE for Docker secrets SMTP_SECURE true Yes By default it uses secure communications. Accepted values are true and false SMTP_SECURE_TYPE starttls Yes If SMTP_SECURE is set you can set the communication type. Accepted values are starttls and ssl

    Table below shows the obligatory environment variables for postgres container. You should set them based on what was also set for the Endurain container.

    Environemnt variable Default value Optional Notes POSTGRES_PASSWORD changeme No N/A POSTGRES_DB endurain No N/A POSTGRES_USER endurain No N/A PGDATA /var/lib/postgresql/data/pgdata No N/A

    To check Python backend dependencies used, use poetry file (pyproject.toml).

    Frontend dependencies:

    "},{"location":"getting-started/advanced-started/#session-timeout-configuration-optional","title":"Session Timeout Configuration (Optional)","text":"

    By default, Endurain sessions last 7 days without enforcing idle timeouts. For enhanced security, you can enable automatic session expiration:

    Environment Variables:

    Example:

    environment:\n  SESSION_IDLE_TIMEOUT_ENABLED: \"true\"\n  SESSION_IDLE_TIMEOUT_HOURS: \"2\"\n  SESSION_ABSOLUTE_TIMEOUT_HOURS: \"48\"\n
    "},{"location":"getting-started/advanced-started/#docker-secrets-support","title":"Docker Secrets Support","text":"

    Endurain supports Docker secrets for securely managing sensitive environment variables. For the following environment variables, you can use _FILE variants that read the secret from a file instead of storing it directly in environment variables:

    "},{"location":"getting-started/advanced-started/#using-file-based-secrets","title":"Using File-Based Secrets","text":"

    Use file-based secrets to securely manage sensitive environment variables:

    1. Create a secrets directory with proper permissions:
    mkdir -p secrets\nchmod 700 secrets\n
    1. Create secret files with strong passwords:
    # Use randomly generated passwords, not hardcoded ones\necho \"$(openssl rand -base64 32)\" > secrets/db_password.txt\necho \"$(openssl rand -hex 32)\" > secrets/secret_key.txt\necho \"$(python3 -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\")\" > secrets/fernet_key.txt\n\n# Set secure file permissions\nchmod 600 secrets/*.txt\nchown $(id -u):$(id -g) secrets/*.txt\n
    1. Configure docker-compose.yml:
    services:\n  endurain:\n    environment:\n      - DB_PASSWORD_FILE=/run/secrets/db_password\n      - SECRET_KEY_FILE=/run/secrets/secret_key\n      - FERNET_KEY_FILE=/run/secrets/fernet_key\n    secrets:\n      - db_password\n      - secret_key\n      - fernet_key\n\nsecrets:\n  db_password:\n    file: ./secrets/db_password.txt\n  secret_key:\n    file: ./secrets/secret_key.txt\n  fernet_key:\n    file: ./secrets/fernet_key.txt\n

    Note: When using _FILE variants, the original environment variables (e.g., DB_PASSWORD) are not needed. The application will automatically read from the file specified by the _FILE environment variable.

    "},{"location":"getting-started/advanced-started/#volumes","title":"Volumes","text":"

    Docker image uses a non-root user, so ensure target folders are not owned by root. Non-root user should use UID and GID 1000. It is recommended to configure the following volumes for data persistence:

    Volume Notes <local_path>/endurain/backend/logs:/app/backend/logs Log files for the backend <local_path>/endurain/backend/data:/app/backend/data Necessary for image and activity files persistence on docker image update"},{"location":"getting-started/advanced-started/#bulk-import-and-file-upload","title":"Bulk import and file upload","text":"

    To perform a bulk import: - Place .fit, .tcx, .gz and/or .gpx files into the data/activity_files/bulk_import folder. Create the folder if needed. - In the \"Settings\" menu select \"Import\". - Click \"Import\" next to \"Bulk Import\".

    .fit files are preferred. I noticed that Strava/Garmin Connect process of converting .fit to .gpx introduces additional data to the activity file leading to minor variances in the data, like for example additional meters in distance and elevation gain. Some notes:

    "},{"location":"getting-started/advanced-started/#importing-information-from-a-strava-bulk-export-beta","title":"Importing information from a Strava bulk export (BETA)","text":"

    Strava allows users to create a bulk export of their historical activity on the site. This information is stored in a zip file, primarily as .csv files, GPS recording files (e.g., .gpx, .fit), and media files (e.g., .jpg, .png).

    "},{"location":"getting-started/advanced-started/#importing-gear-from-a-strava-bulk-export","title":"Importing gear from a Strava bulk export","text":""},{"location":"getting-started/advanced-started/#bike-import","title":"Bike import","text":"

    At the present time, importing bikes from a Strava bulk export is implemented as a beta feature - use with caution. Components of bikes are not imported - just the bikes themselves. There is no mechanism present to undo an import.

    To perform an import of bikes: - Place the bikes.csv file from a Strava bulk export into the data/activity_files/bulk_import folder. Create the folder if needed; - In the Settings menu select Import; - Click Import Strava Bikes next to Strava gear import; - Upon successful import, the bikes.csv file is moved to /data/activity_files/processed folder; - Status messages about the import, including why any gear was not imported, can be found in the logs.

    Ensure the file is named bikes.csv and has a header row with at least the fields 'Bike Name', 'Bike Brand', and 'Bike Model'.

    "},{"location":"getting-started/advanced-started/#shoe-import","title":"Shoe import","text":"

    At the present time, importing shoes from a Strava bulk export is implemented as a beta feature - use with caution. Components of shooes are not imported - just the shoes themselves.

    To perform an import of shoes: - Place the shoes.csv file from a Strava bulk export into the data/activity_files/bulk_import folder. Create the folder if needed; - In the Settings menu select Import; - Click Shoes import next to Strava gear import; - Upon successful import, the shoes.csv file is moved to /data/activity_files/processed folder; - Status messages about the import, including why any gear was not imported, can be found in the logs.

    Ensure the file is named shoes.csv and has a header row with at least the fields 'Shoe Name', 'Shoe Brand', and 'Shoe Model'.

    Note that Strava allows blank shoe names, but Endurain does not. Shoes with a blank name will thus be given a default name of Unnamed Shoe # on import.

    "},{"location":"getting-started/advanced-started/#notes-on-importing-gear","title":"Notes on importing gear","text":"

    NOTE: There is currently no mechanism to undo a gear import.

    All gear will be imported as active, as Strava does not export the active/inactive status of the gear.

    Note that Endurain does not allow the + character in gear field names, and thus +'s will removed from all fields and replaced with spaces (\" \") on import. All beginning and ending space characters (\" \") will be removed on import as well.

    Endurain does not allow duplicate gear nicknames, case insensitively (e.g., Ilves and ilves would not be allowed) and regardless of gear type (e.g., Ilves the bike and ilves the shoe would not be allowed). Gear with duplicate nicknames will not be imported (i.e., only the first item with a given nickname will be imported).

    The import routine checks for duplicate items, and should not import duplicates. Thus it should be safe to re-import the same file mulitple times. However, due to the renaming of un-named shoes, repeated imports of the same shoe file will create duplicate entries of any unnamed shoes present.

    Gear that is already present in Endurain due to having an active link with Strava will not be imported via the manual import process.

    "},{"location":"getting-started/advanced-started/#importing-other-items-from-a-strava-bulk-import","title":"Importing other items from a Strava bulk import","text":"

    Importing activity metadata and media is under development in October 2025.

    "},{"location":"getting-started/advanced-started/#image-personalization","title":"Image personalization","text":"

    It is possible (v0.10.0 or higher) to personalize the login image in the login page. To do that, map the data/server_images directory for image persistence on container updates and: - Set the image in the server settings zone of the settings page - A square image is expected. Default one uses 1000px vs 1000px

    "},{"location":"getting-started/bare-metal/","title":"Bare-Metal Installation Guide","text":"

    This guide explains how to install Endurain bare-metal on Debian without Docker.

    "},{"location":"getting-started/bare-metal/#1-install-required-dependencies","title":"1. Install Required Dependencies","text":"
    apt install -y \\\n  build-essential \\\n  git \\\n  curl \\\n  python3-dev\n
    "},{"location":"getting-started/bare-metal/#2-install-required-runtime-tools","title":"2. Install Required Runtime Tools","text":"

    Install the required tools from their official sources:

    "},{"location":"getting-started/bare-metal/#3-download-endurain-release","title":"3. Download Endurain Release","text":"

    Run the following command to download and unpack the latest release.

    mkdir -p /path/to/endurain\ncd /path/to/endurain\n\nTAG=$(curl -s https://api.github.com/repos/endurain-project/endurain/releases/latest \\\n  | grep -oP '\"tag_name\": \"\\K(.*)(?=\")')\ncurl -L \"https://github.com/endurain-project/endurain/archive/refs/tags/$TAG.tar.gz\" \\\n  | tar xz\nEXTRACTED=$(ls -d endurain-*)\nshopt -s dotglob\nmv \"$EXTRACTED\"/* .\nshopt -u dotglob\nrm -rf \"$EXTRACTED\"\n
    "},{"location":"getting-started/bare-metal/#4-create-environment-configuration","title":"4. Create Environment Configuration","text":"

    Prepare data storage.

    mkdir -p /path/to/endurain_data/{data,logs}\n

    Copy the provided example.

    cp /path/to/endurain/.env.example /path/to/endurain/.env\n

    Generate your SECRET_KEY and FERNET_KEY. These keys are required for Endurain to work, so be sure to paste them into your .env file.

    openssl rand -hex 32 # SECRET_KEY\nopenssl rand -base64 32 # FERNET_KEY\n

    Edit .env file.

    nano /path/to/endurain/.env\n

    Adjust the environment variables and set keys. You definitely have to adjust FRONTEND_DIR, BACKEND_DIR and DB_HOST. Environment variables are explained in the Environment Variables Guide.

    DB_HOST=localhost\nBACKEND_DIR=\"/path/to/endurain/backend/app\"\nFRONTEND_DIR=\"/path/to/endurain/frontend/app/dist\"\nDATA_DIR=\"/path/to/endurain_data/data\"\nLOGS_DIR=\"/path/to/endurain_data/logs\"\n
    "},{"location":"getting-started/bare-metal/#5-build-the-frontend","title":"5. Build the Frontend","text":"
    cd /path/to/endurain/frontend/app\nnpm ci\nnpm run build\n

    Create env.js. Edit the URL if you use a reverse proxy.

    cat << 'EOF' > /path/to/endurain/frontend/app/dist/env.js\nwindow.env = {\n  ENDURAIN_HOST: \"http://YOUR_SERVER_IP:8080\",\n};\nEOF\n
    "},{"location":"getting-started/bare-metal/#6-set-up-the-backend","title":"6. Set Up the Backend","text":"
    cd /path/to/endurain/backend\n\nuv tool install poetry\nuv tool update-shell\nexport PATH=\"/root/.local/bin:$PATH\"\n\npoetry self add poetry-plugin-export\npoetry export -f requirements.txt --output requirements.txt --without-hashes\n\nuv venv\nuv pip install -r requirements.txt\n
    "},{"location":"getting-started/bare-metal/#7-setup-postgres-database","title":"7. Setup Postgres Database","text":"

    Run the following commands to create a PostgreSQL user and database for Endurain:

    sudo -u postgres createuser -P endurain\nsudo -u postgres createdb -O endurain endurain\n

    Check that the PostgreSQL client and server encodings are set to UTF-8.

    sudo -u postgres psql -c \"SHOW client_encoding;\"\nsudo -u postgres psql -c \"SHOW server_encoding;\"\n

    If either value is SQL_ASCII, set UTF-8 explicitly for the user and the database.

    sudo -u postgres psql -c \"ALTER ROLE endurain SET client_encoding = 'UTF8';\"\nsudo -u postgres psql -c \"ALTER DATABASE endurain SET client_encoding = 'UTF8';\"\n

    This ensures that all connections to the endurain database default to proper UTF-8 encoding.

    "},{"location":"getting-started/bare-metal/#8-systemd-service","title":"8. Systemd Service","text":"

    This is an example how you could set up your systemd service.

    cat << 'EOF' > /etc/systemd/system/endurain.service\n[Unit]\nDescription=Endurain FastAPI Backend\nAfter=network.target postgresql.service\n\n[Service]\nWorkingDirectory=/path/to/endurain/backend/app\nEnvironmentFile=/path/to/endurain/.env\nExecStart=/path/to/endurain/backend/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080\nRestart=always\nRestartSec=5\nUser=root\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\nEOF\n

    Enable and start the service.

    systemctl daemon-reload\nsystemctl enable endurain\nsystemctl start endurain\n
    "},{"location":"getting-started/bare-metal/#9-update-to-a-new-version-of-endurain","title":"9. Update to a new version of Endurain.","text":"

    Remove old version and get the latest.

    systemctl stop endurain\nrm -rf /path/to/endurain/*\ncd /path/to/endurain\n\nTAG=$(curl -s https://api.github.com/repos/endurain-project/endurain/releases/latest \\\n  | grep -oP '\"tag_name\": \"\\K(.*)(?=\")')\ncurl -L \"https://github.com/endurain-project/endurain/archive/refs/tags/$TAG.tar.gz\" \\\n  | tar xz\nEXTRACTED=$(ls -d endurain-*)\nshopt -s dotglob\nmv \"$EXTRACTED\"/* .\nshopt -u dotglob\nrm -rf \"$EXTRACTED\"\n

    Build the Frontend.

    cd /path/to/endurain/frontend/app\nnpm ci\nnpm run build\n

    Set Up the Backend.

    cd /path/to/endurain/backend\npoetry export -f requirements.txt --output requirements.txt --without-hashes\n\nuv venv\nuv pip install -r requirements.txt\n

    start the service.

    systemctl start endurain\n
    "},{"location":"getting-started/getting-started/","title":"Getting started","text":"

    Welcome to the guide for getting started on hosting your own production instance of Endurain. Like many other services, Endurain is easiest to get up and running trough Docker compose. It is possible to get Endurain up and running without a domain and reverse proxy, but this guide assumes you want to use a reverse proxy and your domain. Endurain can run on any computer that support OCI containers, but in this guide we are using Debian 13 (should also work with 12).

    "},{"location":"getting-started/getting-started/#prerequisites","title":"Prerequisites","text":""},{"location":"getting-started/getting-started/#installing-docker-and-caddy-reverse-proxy","title":"Installing Docker and Caddy reverse proxy","text":"

    Note: If you have a old-ish distro (Ubuntu 22.04 and older) you need to add the repo for Docker. Read how to do it on Docker documentation. For newer distroes (Debian 13 and Ubuntu 24.04 it is not expected for you to have to do this step).

    Install Docker:

    sudo apt update -y\nsudo apt install docker.io docker-compose -y\n

    Confirm your user has the id 1000:

    id\n

    If you are not the user 1000, you need to set the UID and GID to your id in the .env file. But to keep this guide as easy to follow as possible, we will assume that you are user 1000.

    "},{"location":"getting-started/getting-started/#installing-caddy-reverse-proxy","title":"Installing Caddy reverse proxy","text":"

    Note: If you have a old-ish distro (Ubuntu 22.04 and older) you need to add the repo for Caddy. Read how to do it on Caddy documentation. For newer distroes (Debian 13 and Ubuntu 24.04 it is not expected for you to have to do this step).

    sudo apt update -y\nsudo apt install caddy -y\n
    "},{"location":"getting-started/getting-started/#installing-nginx-proxy-manager-reverse-proxy","title":"Installing Nginx Proxy Manager reverse proxy","text":"

    Nginx Proxy Manager comes as a pre-built Docker image. Please refer to the docs for details on how to install it.

    "},{"location":"getting-started/getting-started/#create-directory-structure","title":"Create directory structure","text":"

    Lets use /opt/endurain/ as the root directory for our project.

    sudo mkdir /opt/endurain\nsudo chown 1000:1000 /opt/endurain\nmkdir -p \\\n  /opt/endurain/backend/{data,logs} \\\n  /opt/endurain/postgres\n
    "},{"location":"getting-started/getting-started/#docker-compose-deployment","title":"Docker compose Deployment","text":"

    In this example of setting up Endurain, we will need two files. One docker-compose.yml and .env.

    Splitting up the setup like this make it easy to handle updates to the containers, without touching the secrets and other variables.

    "},{"location":"getting-started/getting-started/#creating-the-docker-compose-and-env-file","title":"Creating the docker-compose and .env file","text":"

    To make it as easy as possible for selfhoster to get up and running examples of docker-compose.yml and .env is on the git repo. Here are links to the files on the repo:

    cd /opt/endurain\nwget https://raw.githubusercontent.com/endurain-project/endurain/refs/heads/master/docker-compose.yml.example\nwget https://raw.githubusercontent.com/endurain-project/endurain/refs/heads/master/.env.example\n\nmv docker-compose.yml.example docker-compose.yml\nmv .env.example .env\n

    Now we need to make changes to the files to reflect your environment. Inside docker-compose.yml there is not much we need to do. If you want to store the files another place then /opt/endurain this is the file you need to change.

    Here is an explaination on what you can set in the .env:

    Environment variable How to set it DB_PASSWORD Run openssl rand -hex 32 on a terminal to get a secret POSTGRES_PASSWORD Set the same value as DB_PASSWORD. SECRET_KEY Run openssl rand -hex 32 on a terminal to get a secret FERNET_KEY Run python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\" on a terminal to get a secret or go to https://fernetkeygen.com. Example output is 7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y= TZ Timezone definition. Insert your timezone. List of available time zones here. Format Europe/Lisbon expected ENDURAIN_HOST https://endurain.yourdomain.com BEHIND_PROXY Change to true if behind reverse proxy POSTGRES_DB Postgres name for the database. POSTGRES_USER Postgres user for the database.

    Please note:

    POSTGRES_DB and POSTGRES_USER are values for the database. If you change it from endurain, you also need to set the environment variables for the app image. Please leave them as endurain if you are unsure.

    "},{"location":"getting-started/getting-started/#start-the-stack","title":"Start the stack","text":"

    It is finally time to start the stack!

    cd /opt/endurain\nsudo docker compose up -d\n

    Check the log output:

    docker compose logs -f\n

    If you do not get any errors, continue to next step.

    "},{"location":"getting-started/getting-started/#visit-the-site","title":"Visit the site","text":""},{"location":"getting-started/getting-started/#configure-a-reverse-proxy","title":"Configure a reverse proxy","text":""},{"location":"getting-started/getting-started/#configure-caddy-as-reverse-proxy-and-get-ssl-cert-from-letsencrypt","title":"Configure Caddy as reverse proxy and get SSL cert from letsencrypt","text":"

    We use Caddy outside docker. This way Debian handles the updates (you just need to run sudo apt update -y and sudo apt upgrade -y)

    Caddy is configured in the file /etc/caddy/Caddyfile

    Open the file in your favourite editor, delete the default text, and paste in this:

    endurain.yourdomain.com {\n        reverse_proxy localhost:8080\n}\n

    Restart Caddy

    sudo systemctl restart caddy\n

    Check the ouput of Caddy with:

    sudo journalctl -u caddy\n
    "},{"location":"getting-started/getting-started/#configure-nginx-proxy-manager-as-reverse-proxy-and-get-ssl-cert-from-letsencrypt","title":"Configure Nginx Proxy Manager as reverse proxy and get SSL cert from letsencrypt","text":"

    Bellow is an example config file for Endurain:

    ------------------------------------------------------------\nendurain.yourdomain.com\n------------------------------------------------------------\n\nmap $scheme $hsts_header {\n    https \"max-age=63072000; preload\";\n}\n\nserver {\n    set $forward_scheme http;\n    set $server \"your_server_ip\";\n    set $port 8884;\n\n    listen 80;\n    listen [::]:80;\n\n    listen 443 ssl;\n    listen [::]:443 ssl;\n\n    server_name endurain.yourdomain.com;\n\n    http2 on;\n    Let's Encrypt SSL\n\n    include conf.d/include/letsencrypt-acme-challenge.conf;\n    include conf.d/include/ssl-cache.conf;\n    include conf.d/include/ssl-ciphers.conf;\n    ssl_certificate /etc/letsencrypt/live/npm-21/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/npm-21/privkey.pem;\n    Asset Caching\n\n    include conf.d/include/assets.conf;\n    Block Exploits\n\n    include conf.d/include/block-exploits.conf;\n    HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)\n\n    add_header Strict-Transport-Security $hsts_header always;\n    Force SSL\n\n    include conf.d/include/force-ssl.conf;\n\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection $http_connection;\n    proxy_http_version 1.1;\n\n    access_log /data/logs/proxy-host-18_access.log proxy;\n    error_log /data/logs/proxy-host-18_error.log warn;\n\n    location / {\n        HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)\n\n        add_header Strict-Transport-Security $hsts_header always;\n\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $http_connection;\n        proxy_http_version 1.1;\n        Proxy!\n\n        include conf.d/include/proxy.conf;\n    }\n    Custom\n\n    include /data/nginx/custom/server_proxy[.]conf;\n}\n

    "},{"location":"getting-started/getting-started/#access-your-endurain-instance","title":"Access your Endurain instance","text":"

    You should now be able to access your site on endurain.yourdomain.com

    Log in with username: admin password: admin, and remember to change the password

    \ud83c\udf89 Weee \ud83c\udf89 You now have your own instance of Endurain up and running!

    "},{"location":"getting-started/getting-started/#how-to-update","title":"How to update","text":"
    cd /opt/endurain\nsudo docker compose pull\nsudo docker compose up -d\n

    The same is the case for Postgres. Check for breaking changes in release notes on Postgres Website.

    ** It is generally pretty safe to upgrade postgres minor version f.eks 17.4 to 17.5, but major is often breaking change (example 17.2 to 18.1 )

    "},{"location":"getting-started/getting-started/#things-to-think-about","title":"Things to think about","text":"

    You should implement backup strategy for the following directories:

    /opt/endurain/app/data\n/opt/endurain/app/logs\n

    You also need to backup your postgres database. It is not good practice to just backup the volume /opt/endurain/postgres this might be corrupted if the database is in the middle of a wright when the database goes down.

    "},{"location":"getting-started/getting-started/#default-credentials","title":"Default Credentials","text":""},{"location":"getting-started/maria-to-postgres-migration/","title":"MariaDB to Postgres migration guide","text":"

    This will guide you on how to migrate from MariaDB to Postgres. Endurain will drop support for MariaDB on v0.16.0, so you'll need to perform this migration prior to upgrade to v0.16.0 or higher.

    This guide uses Endurain's built-in export/import functionality to migrate your data.

    "},{"location":"getting-started/maria-to-postgres-migration/#prerequisites","title":"Prerequisites","text":"

    \u26a0\ufe0f Important Notes:

    "},{"location":"getting-started/maria-to-postgres-migration/#migration-steps","title":"Migration Steps","text":""},{"location":"getting-started/maria-to-postgres-migration/#step-1-export-data-from-mariadb-instance","title":"Step 1: Export Data from MariaDB Instance","text":"
    1. Instruct each user to log in to Endurain instance (currently running with MariaDB)
    2. Each user should navigate to Settings \u2192 My Profile \u2192 Export/Import
    3. Each user should lick Export to download a .zip file containing the user data
    4. Each user should save this file in a safe location

    \u26a0\ufe0f Do NOT delete your existing MariaDB database - keep it for rollback if needed.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-2-stop-current-endurain-instance","title":"Step 2: Stop Current Endurain Instance","text":"

    Stop your current Endurain container:

    docker compose down\n
    "},{"location":"getting-started/maria-to-postgres-migration/#step-3-update-environment-variables","title":"Step 3: Update Environment Variables","text":"

    Update your environment variables to point to PostgreSQL (adapt to your environment):

    DB_TYPE=postgres\nDB_HOST=postgres\nDB_PORT=5432\nDB_USER=endurain\nDB_PASSWORD=your_postgres_password\nDB_NAME=endurain\n

    Ensure your PostgreSQL database exists and is accessible with these credentials.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-4-start-fresh-endurain-with-postgresql","title":"Step 4: Start Fresh Endurain with PostgreSQL","text":"

    Start Endurain with the new PostgreSQL configuration:

    docker compose up -d\n

    This will start a fresh Endurain instance with: - Empty PostgreSQL database - Default admin credentials: admin/admin

    "},{"location":"getting-started/maria-to-postgres-migration/#step-5-import-data","title":"Step 5: Import Data","text":"
    1. Log in with default credentials: admin/admin
    2. Create a new user for each of your instance users if applicable
    3. Each user should navigate to Settings \u2192 My Profile \u2192 Export/Import
    4. Each user should click Import and select the .zip file exported
    5. Wait for the import to complete (this may take several minutes for large databases)

    \u26a0\ufe0f Note: User passwords are NOT imported for security reasons. All users will need to reset their passwords.

    "},{"location":"getting-started/maria-to-postgres-migration/#step-6-verify-migration","title":"Step 6: Verify Migration","text":"

    Verify the migration was successful by checking:

    "},{"location":"getting-started/maria-to-postgres-migration/#troubleshooting","title":"Troubleshooting","text":""},{"location":"getting-started/maria-to-postgres-migration/#if-import-fails","title":"If Import Fails","text":"

    If the import process fails:

    1. Check the application logs in the container
    2. Check the app.log file
    3. Paste both outputs (container logs and app.log contents) when seeking help
    "},{"location":"getting-started/maria-to-postgres-migration/#rolling-back-to-mariadb","title":"Rolling Back to MariaDB","text":"

    If you need to rollback:

    1. Stop the PostgreSQL instance:
    docker compose down\n
    1. Restore your original environment variables (MariaDB settings)
    2. Start your original MariaDB instance:
    docker compose up -d\n
    "},{"location":"integrations/3rd-party-apps/","title":"3rd party apps","text":""},{"location":"integrations/3rd-party-apps/#runnerup-integration","title":"RunnerUp Integration","text":"

    RunnerUp an app for tracking your sport activities with your Android phone, can automatically sync your activities recorded with it to your Endurain instance.

    RunnerUp is supported until version v0.5.3 of Endurain. An issue is opened to get support for v0.6.0+.

    "},{"location":"integrations/3rd-party-services/","title":"3rd party services","text":""},{"location":"integrations/3rd-party-services/#garmin-connect-integration","title":"Garmin Connect Integration","text":"

    To enable Garmin Connect integration, Endurain will ask for your Garmin Connect credentials. These credentials are not stored, but the authentication tokens (access and refresh tokens) are stored in the DB, similar to the Strava integration. The credentials are sent from the frontend to the backend in plain text, so the use of HTTPS is highly recommended.

    Once the integration with Garmin Connect is configured, on startup, every one and four hours the backend will check if there is new unimported activities and new body composition entries respectively. If yes, the new data is automatically imported.

    For Garmin Connect integration python-garminconnect Python module is used.

    "},{"location":"integrations/3rd-party-services/#strava-integration","title":"Strava Integration","text":"

    \u26a0\ufe0f Warning Due to recent Strava API changes, expect changes in the Strava integration in a following release.

    To enable Strava integration, ensure your Endurain instance is accessible from the internet and follow Strava's API setup guide. After the integration is successful the access and refresh tokens are stored in the DB. Each user will have his/hers own pair.

    Once the integration with Strava is configured, on startup and every hour the backend will check if there is new unimported activities. If yes, the new activity is automatically imported.

    On link, user will need to provide his/her API client ID and secret. Pair will be temporary stored in the DB until the process finishes. Info is sent on a JSON payload and HTTPS end2end is encouraged.

    On Strava unlink action every data imported from Strava, i.e. activities and gears, will be deleted according to Strava API Agreement.

    For Strava integration stravalib Python module is used.

    "}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 95dbea34b917dde37ea560c7d674c3047637bd5d..dd8c438266ce901a710413b1d404af081fadc006 100644 GIT binary patch delta 13 Ucmb=gXP58h;Apt*Igz~r03G%ObN~PV delta 13 Ucmb=gXP58h;9ywjFp<3i02#0Yu>b%7