## 1. Project Overview & Quickstart (wger-project/wger) ## File: README.md # wger wger (ˈvɛɑɐ) is a free workout and fitness manager. - πŸ‹οΈ **Custom Workout Routines** – Create flexible routines with automatic weight progression rules. - πŸ“Š **Comprehensive Tracking** – Track diet plans, body weight, and custom measurements. - 🍽️ **Nutrition Management** – Log your calories with a food database from [Open Food Facts](https://openfoodfacts.org). - πŸ“Έ **Progress Gallery** – Upload and track your fitness progress with photos. - πŸ“š **Exercise Wiki** – Access and contribute to the built-in exercises. - πŸ“± **Cross-Platform Apps** – Available on [Android](https://play.google.com/store/apps/details?id=de.wger.flutter), [iOS](https://apps.apple.com/us/app/wger-workout-manager/id6502226792), [F-Droid](https://f-droid.org/en/packages/de.wger.flutter/), and [Flathub](https://flathub.org/apps/de.wger.flutter). - 🐳 **Self-Hostable** – Deploy easily with Docker for full control. - 🌍 **Multilingual Support** – Translated by the community via Weblate. - πŸ”— **Powerful API** – REST API for third-party integrations or automations. - πŸ‘₯ **Multi-User Support** – Includes basic gym management features. - πŸ†“ **100% Free & Open Source** – Licensed under AGPL-3.0 or later. For a live system, visit: ## Self-hosting Hosting your own instance is basically just a `docker compose up -d` away. For more detailed setup instructions take a look at the provided [docker compose file](https://github.com/wger-project/docker) and the [corresponding documentation](https://wger.readthedocs.io/en/latest/installation/docker.html). ## Developing and contributing Our goal is to build an awesome and flexible fitness and nutrition manager, along with a comprehensive list of exercises and ingredients, all released under a free license. For this, we’d love your help! Whether it’s code, translations, exercises or reporting issues and ideas, check out our [contribution guide](https://wger.readthedocs.io/en/latest/contributing.html) to get started. A huge thank you to everyone who has contributed so far! ❀️ See the full list in [AUTHORS.md](AUTHORS.md). ## Documentation Consult the online documentation at * for installation instructions, API documentation, development guidelines, and other information ## Contact Feel free to contact us if you found this useful or if there was something that didn't behave as you expected. We can't fix what we don't know about, so please report liberally. If you're not sure if something is a bug or not, feel free to file a bug anyway. * **Discord:** * **Mastodon:** * **Issue tracker:** ## Sources All the code and the content is available on github: * ## Translation Translate the app to your language on [Weblate](https://hosted.weblate.org/engage/wger/). [](https://hosted.weblate.org/engage/wger/) ## License * Application Code: [AGPL-3.0-or-later](https://www.gnu.org/licenses/agpl-3.0.html) * Exercise/Ingredient Data: Creative Commons (see individual entries) * Documentation: [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/) --- ## File: extras/open-food-facts/README.md # Import Open Food Facts products This docker compose helps import or update products from the Open Food Facts database into wger. Note that the OFF database dump is very large, and you will need several times this size available on your computer (tar.gz-file, extracted dump, mongo). ## 1 Download a current dump of their database ```shell cd dump wget https://static.openfoodfacts.org/data/openfoodfacts-mongodbdump.tar.gz tar xzvf openfoodfacts-mongodbdump.tar.gz ``` ## 2 Import the data into mongo. Note that we are running this as a manual step since the import takes a while ```shell docker compose up docker compose exec mongodb mongorestore --username off --password off-wger -d admin -c products /dump/off/products.bson ``` There is also an admin interface available at , log in with these credentials: * admin * pass ## 3 Run the import script ```shell python manage.py import-off-products ``` To update the data fixtures: ```shell python manage.py dumpdata nutrition > extras/scripts/data.json cd extras/scripts python filter-fixtures.py zip ingredients.json.zip ingredients.json zip weight_units.json.zip weight_units.json zip ingredient_units.json.zip ingredient_units.json ``` ## 4 Don't forget to delete the dump and remove the containers if you love your hard disk ```shell docker compose down rm dump -r openfoodfacts-mongodbdump.tar.gz dump/dump ``` --- ## File: extras/docker/production/README.md # Production image for wger wger (ˈvɛɑɐ) Workout Manager is a free, open source web application that help you manage your personal workouts, weight and diet plans and can also be used as a simple gym management utility. It offers a REST API as well, for easy integration with other projects and tools. ## Usage It is recommended to use this image with the provided docker compose, which has all the different services configured: ## Contact Feel free to contact us if you found this useful or if there was something that didn't behave as you expected. We can't fix what we don't know about, so please report liberally. If you're not sure if something is a bug or not, feel free to file a bug anyway. * Discord: * Issue tracker: * Mastodon: ## Sources All the code and the content is freely available: * Main repository: ## Licence The application is licenced under the Affero GNU General Public License 3 or later (AGPL 3+). The initial exercise and ingredient data is licensed additionally under one of the Creative Commons licenses, see the individual exercises for more details. The documentation is released under a CC-BY-SA either version 4 of the License, or (at your option) any later version. Some images where taken from Wikipedia, see the SOURCES file in their respective folders for more details. --- ## File: extras/docker/development/README.md # Development image for wger Please consult for instructions on how to use this image. --- ## File: extras/docker/demo/README.md Demo image for wger =================== wger (ˈvɛɑɐ) Workout Manager is a free, open source web application that help you manage your personal workouts, weight and diet plans and can also be used as a simple gym management utility. It offers a REST API as well, for easy integration with other projects and tools. Please note that this image will overwrite your data when you pull a new version, it is only intended as an easy to setup demo. If you want to host your own instance, take a look at the provided docker compose file. That config will persist your database and uploaded images: Installation ------------ This docker image contains an instance of the application running as a WSGI process under apache with a sqlite database. It is useful to just try it out and play around. To start it: ```docker run -ti --name wger.demo --publish 8000:80 wger/demo``` Then just open and log in as: **admin**, password **adminadmin** To stop the container: ```sudo docker container stop wger.demo``` To start developing again: ```sudo docker container start --attach wger.demo``` Building -------- If you build this yourself, keep in mind that you **must** build from the project root! ```docker build -f extras/docker/demo/Dockerfile --tag wger/demo .``` Contact ------- Feel free to contact us if you found this useful or if there was something that didn't behave as you expected. We can't fix what we don't know about, so please report liberally. If you're not sure if something is a bug or not, feel free to file a bug anyway. * Discord: * Issue tracker: * Mastodon: Sources ------- All the code and the content is freely available: * Main repository: Licence ------- The application is licenced under the Affero GNU General Public License 3 or later (AGPL 3+). The initial exercise and ingredient data is licensed additionally under one of the Creative Commons licenses, see the individual exercises for more details. The documentation is released under a CC-BY-SA either version 4 of the License, or (at your option) any later version. Some images where taken from Wikipedia, see the SOURCES file in their respective folders for more details. --- ## File: extras/docker/base/README.md Development image for wger - Base image =================================================== This is the base image for some of the other wger images and offers no functionality, it's only use is to provide some common dependencies. If you want to develop, try ``wger/server``. Contact ------- Feel free to contact us if you found this useful or if there was something that didn't behave as you expected. We can't fix what we don't know about, so please report liberally. If you're not sure if something is a bug or not, feel free to file a bug anyway. * Discord: * Issue tracker: * Mastodon: Sources ------- All the code and the content is freely available: * Main repository: Licence ------- The application is licenced under the Affero GNU General Public License 3 or later (AGPL 3+). The initial exercise and ingredient data is licensed additionally under one of the Creative Commons licenses, see the individual exercises for more details. The documentation is released under a CC-BY-SA either version 4 of the License, or (at your option) any later version. Some images where taken from Wikipedia, see the SOURCES file in their respective folders for more details. --- ## File: extras/authors/README.md # Generate authors list This small script reads the commit information and generates a list of authors and links them to their GitHub profile, if available. This can be run before each release to keep the data up to date. ## Usage Note that you might run into rate-limiting issues if you run this script too often. If so, you can generate a token and use it in the script: (no need to select any permissions, we're just performing reading operations on public data) Run the script and optionally copy the generated files to their correct locations ```bash uv run generate_authors_api.py ``` --- ## File: wger/core/static/images/logos/app-store/README.md # App Store App Store ist a trademark of Apple Inc., registered in the U.S. and other countries. ## 2. Official Technical Reference & Guides (wger-project/docs) ## File: README.md # Documentation for wger This repository contains the installation documentation for wger. They can be viewed online at [wger.readthedocs.io](https://wger.readthedocs.io/) To build the documentation, run `make html` in the docs directory. ## Contact Feel free to contact us if you found this useful or if there was something that didn't behave as you expected. We can't fix what we don't know about, so please report liberally. If you're not sure if something is a bug or not, feel free to file a bug anyway. * **Discord:** * **Issue tracker:** * **Mastodon:** ## License The application is licensed under the Affero GNU General Public License 3 or at your option any later version (AGPL 3+). --- ## File: docs/api/api.rst Using the API ============= The wger REST API is served under ``/api/v2/``. It returns JSON by default, supports filtering, ordering and pagination, and uses standard HTTP status codes and verbs. Public endpoints, such as the list of exercises or the ingredients, can be accessed without authentication. For user-owned objects such as routines, you need to authenticate. For specific info on how to create routines over the API, see :doc:`routines`. Interactive reference --------------------- wger ships a complete OpenAPI specification for every endpoint, generated automatically from the code. The following (interactive) viewers are exposed: * ``/api/v2/`` returns a JSON listing of every endpoint (great for discovery), or renders an HTML index when opened in a browser * ``/api/v2/schema`` the raw OpenAPI schema in JSON * ``/api/v2/schema/ui/`` Swagger UI, lets you try requests directly in the browser * ``/api/v2/schema/redoc/`` ReDoc, cleaner read-only view JWT Tokens ---------- The recommended authentication mechanism. You exchange a username/password pair for a short-lived access token (10 minutes in the Docker default) and a long-lived refresh token (120 days), then send the access token in the header with each authenticated request. The lifetimes are configurable via the ``ACCESS_TOKEN_LIFETIME`` and ``REFRESH_TOKEN_LIFETIME`` env vars (see :doc:`/administration/settings`). 1. Get the tokens wger does not have a credentials-to-JWT endpoint, since that would bypass 2FA. The two supported ways to obtain a refresh token are: **a) From the allauth-headless login endpoint** (recommended for apps that have a proper login UI, and for scripts targeting accounts *without* 2FA): ``POST /allauth/app/v1/auth/login`` with ``{"username", "password"}``:: result = requests.post( 'https://wger.de/allauth/app/v1/auth/login', json={'username': 'user', 'password': 'admin'}, ) data = result.json()['data'] access_token = data['access'] refresh_token = data['refresh'] For accounts *with* 2FA enabled the response is a partial-login: it contains an ``X-Session-Token`` header plus a ``requires_mfa`` flag instead of the tokens. You then send the TOTP/recovery code to ``POST /allauth/app/v1/auth/2fa/authenticate``, passing the session token in the ``X-Session-Token`` header, to receive the real access and refresh tokens. The full request/response shapes (including the partial-login, email-verification and password-reset flows) are documented upstream: * Conceptual introduction: https://docs.allauth.org/en/latest/headless/introduction.html * OpenAPI specification of every endpoint: https://docs.allauth.org/en/latest/headless/openapi-specification/ **b) From the web "API key" page** (recommended for personal scripts and long-running integrations, especially when 2FA is enabled): log into the web app, open *User settings β†’ API key*, and mint a long-lived refresh token. You only see the value once, so store it immediately. Because reaching that page requires a regular log-in, any 2FA configured on the account is enforced before the token is issued. 2. Authenticate Pass the access token in the Authorization header as ``"Bearer your-token"``:: result = requests.get( 'https://wger.de/api/v2/routine/', headers={'Authorization': f'Bearer {access_token}'} ) print(result.json()) >>> {'count': 5, 'next': None, 'previous': None, 'results': [{'id':..... Additionally, you can send the access token to the ``/api/v2/token/verify`` endpoint to verify it:: result = requests.post('https://wger.de/api/v2/token/verify', data={'token': access_token}) 3. Refresh When the short-lived access token expires, use the longer-lived ``refresh`` token to obtain a new pair. Both tokens get rotated: the response contains a fresh access *and* refresh token, and the previous refresh token is blacklisted immediately. Store the new refresh token for the next cycle:: result = requests.post( 'https://wger.de/api/v2/token/refresh', data={'refresh': refresh_token} ) print(result.json()) >>> {'access': 'eyJhbGciOiJI...', 'refresh': 'eyJhbGciOiJI...'} Permanent Token --------------- You can also pass a permanent token in the header to authenticate, but this method is intended for personal scripts or one-off integrations:: token = 'abcdef123...' result = requests.get( 'https://wger.de/api/v2/routine/', headers={'Authorization': f'Token {token}'} ) print(result.json()) >>> {'count': 5, 'next': None, 'previous': None, 'results': [{'id':..... To generate a key, log in to the web app and open the "API key" section in your user settings. The same page also lets you mint a long-lived JWT refresh token for use with the headless auth surface. Pagination ---------- By default lists are paginated at 20 elements per page. Override with ``?limit=``. There is no hard cap on the limit, but very large pages will be slow and may put load on the server. The response JSON contains ``next`` and ``previous`` URLs for navigation, and ``count`` for the total number of results. Rate limiting ------------- A few sensitive endpoints are rate-limited (per IP for anonymous callers, per user for authenticated ones): * ``/allauth/app/v1/auth/login`` and ``/allauth/app/v1/auth/2fa/authenticate`` (authentication): 10 requests/min * ``/api/v2/userprofile/`` (registration): 5 requests/min * ``/api/v2/ingredient/`` and ``/api/v2/ingredientinfo/`` (list): 120 requests/min * ``/api/v2/ingredient//`` and ``/api/v2/ingredientinfo//`` (detail): 300 requests/min * ``/api/v2/ingredient-sync/`` (bulk sync): 600 requests/min Exceeding a limit returns HTTP 429 with a ``Retry-After`` header. All other endpoints are unthrottled. Format negotiation ------------------ The API returns JSON by default. For clients you usually don't need to set anything explicitly. To request a specific format: * Set the ``Accept`` header to ``application/json``, or ``application/json; indent=4`` for indented output (useful when debugging). * Or use a URL suffix: ``/api/v2/.json`` for raw JSON, or ``/api/v2/.api`` for the browsable HTML view (same as opening the endpoint in a browser). Ordering -------- Use ``?ordering=`` to order results by that field. Combine multiple fields with commas: ``?ordering=,``. Prefix a field with ``-`` to reverse, as in Django: ``?ordering=-date``. Filtering resources ------------------- Filter list endpoints by appending query parameters: ``?=``. Multiple filters are AND-joined: ``?=&=``. For boolean fields, pass ``True`` or ``False`` (case-sensitive); other values like ``1``, ``0`` or ``false`` are ignored. Specifying multiple values for the same field (e.g., category 1 *or* 2) is not currently supported. --- ## File: docs/api/index.rst .. _api: API === REST API reference for client integrations with the wger backend. .. toctree:: :maxdepth: 1 :hidden: api routines :doc:`api` General API documentation, authentication, and an overview of the available endpoints. :doc:`routines` Data model used for the flexible routines and how to create and read them via the API. --- ## File: docs/api/routines.rst .. _api_routines: Using the routine API ===================== The routine data model is split across a handful of related objects and has a few non-obvious moving parts (iterations, day sequencing, progression rules). This page explains how everything fits together. It is **not** a field reference: for the full request/response schemas of every endpoint, use ``/api/v2/schema/redoc/``. A routine has a maximum duration of 120 days. Object hierarchy ---------------- The routine itself is just a shell. The actual training data is split across several linked objects, each with its own endpoint: .. code-block:: text Routine /api/v2/routine/ └── Day /api/v2/day/ └── Slot /api/v2/slot/ └── SlotEntry /api/v2/slot-entry/ β”œβ”€β”€ WeightConfig /api/v2/weight-config/ (+ max-) β”œβ”€β”€ RepetitionsConfig /api/v2/repetitions-config/ (+ max-) β”œβ”€β”€ SetsConfig /api/v2/sets-config/ (+ max-) β”œβ”€β”€ RirConfig /api/v2/rir-config/ (+ max-) └── RestConfig /api/v2/rest-config/ (+ max-) The actual training history lives in two more objects, both linked back to the routine: .. code-block:: text WorkoutSession /api/v2/workoutsession/ └── WorkoutLog /api/v2/workoutlog/ Templates are exposed read-only through ``/api/v2/templates/`` (your own) and ``/api/v2/public-templates/`` (those shared by others). Iterations ---------- An "iteration" is one complete cycle through all the days of a routine. Once the last day has been done, the next occurrence of the first day starts a new iteration. In practice this will most often be a week, but the system doesn't require that. The iteration number is the key that ties progression rules to specific points in the routine: a ``WeightConfig`` with ``iteration=3`` and ``value=60`` applies starting on the third pass through the days. Days and day sequencing ----------------------- A routine is built from an ordered list of days (controlled by each day's ``order`` field). From this list, plus the routine's start and end date, wger computes a concrete date-by-date sequence. For a routine that starts on the 1.1 with three days, the basic sequence is: .. list-table:: :header-rows: 1 * - - 1.1 - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 - 1.8 - 1.9 - 1.10 * - Day - Day 1 - Day 2 - Day 3 - Day 1 - Day 2 - Day 3 - Day 1 - Day 2 - Day 3 - Day 1 * - Iteration - 1 - 1 - 1 - 2 - 2 - 2 - 3 - 3 - 3 - 4 Two flags change this default behaviour: **need_logs_to_advance** (on the day) stalls the sequence on a day until the user logs a session for it. Below, Day 3 has the flag set, the user finally logged a session on 1.8, and the sequence resumes on 1.9: .. list-table:: :header-rows: 1 * - - 1.1 - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 - 1.8 - 1.9 - 1.10 * - Day - Day 1 - Day 2 - Day 3 - Day 1 - Day 2 - Day 3 - Day 3 - Day 3 - Day 1 - Day 2 * - Iteration - 1 - 1 - 1 - 2 - 2 - 2 - 2 - 2 - 3 - 3 **fit_in_week** (on the routine) pads each iteration with empty placeholder days (no day, no exercises) so the routine always restarts on a fixed weekday: .. list-table:: :header-rows: 1 * - - 1.1 (Mon) - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 - 1.8 (Mon) - 1.9 - 1.10 * - Day - Day 1 - Day 2 - Day 3 - -- - -- - -- - -- - Day 1 - Day 2 - Day 3 * - Iteration - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 2 - 2 - 2 Rest days are regular days with ``is_rest=true`` and no slots attached. They occupy a position in the sequence but produce no exercises. Slots and slot entries ---------------------- Inside a day, exercises aren't attached directly. They go through a two-level structure: * a **slot** is one "position" in the day: roughly one exercise (or one superset) * a **slot entry** attaches a specific exercise to that slot If a slot has more than one entry, it automatically becomes a superset. The ``/date-sequence-gym`` view interleaves the sets across the entries; they don't need to have the same number of sets. With * Exercise 1, 4 sets * Exercise 2, 2 sets * Exercise 3, 3 sets the gym-mode output is: .. code-block:: text Exercise 1 Exercise 2 Exercise 3 Exercise 1 Exercise 2 Exercise 3 Exercise 1 Exercise 3 Exercise 1 The ``repetition_rounding`` and ``weight_rounding`` fields on a slot entry control how the computed values are rounded for display. If left empty when creating the entry, the defaults from the user profile are copied in. Progression rules ----------------- The actual values for weight, repetitions, sets, RiR and rest aren't stored on the slot entry. They live in separate config objects (one endpoint per field), each with a ``max-`` variant for ranges like "8 to 10 reps". Each config record applies starting at a given ``iteration``. The value at iteration *n* is the stacked result of all preceding records: each record either replaces the value outright (``operation=r``) or adds / subtracts a value (``+`` / ``-``), either as an absolute number (``step=abs``) or a percentage (``step=percent``). The ``repeat`` flag keeps a rule active for every following iteration until another rule takes over, so "+1 kg every week" is a single record. A weight config example: .. list-table:: :header-rows: 0 * - **Iteration** - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 * - **Config** - 50kg - -- - -- - +10% - -- - +2kg - +1kg - 45kg * - **Result** - 50kg - 50kg - 50kg - 55kg - 55kg - 57kg - 58kg - 45kg All configs are optional. If none is set for a field, that field will be ``null`` in the computed output, except for sets, which defaults to 1. Requirements ~~~~~~~~~~~~ A progression rule can be made conditional on the user actually hitting the prescribed values in the previous iteration. Set the ``requirements`` field to a JSON object of the form:: { "rules": [ "weight", "repetitions", "rir", "rest" ] } If the ``rules`` list is non-empty, the rule only applies when *all* listed fields were met in at least one log of the previous iteration. For example: if the target is to move from 8x60 kg to 8x65 kg, with ``rules`` containing ``weight`` and ``repetitions``, the increase only happens once the user has actually logged 8 reps at 60 kg. Until then the field stays at the previous value. Custom calculation logic ~~~~~~~~~~~~~~~~~~~~~~~~ If the rule-based system is not flexible enough, a slot entry's ``class_name`` field can point to a Python class under ``wger.manager.config_calculations`` that takes over all calculations for that entry. This is an escape hatch and not currently used; please get in touch if you have a use case for it. Sessions and logs ----------------- The training history is stored in two objects. A **WorkoutSession** represents one workout (one date, optional notes and impression). A **WorkoutLog** is one performed set, attached to a session and optionally back-linked to a routine, day, slot entry and iteration. Each log carries both what was actually performed (``weight``, ``repetitions``, ``rir``, ``rest``) and the originally prescribed values (``weight_target``, ``repetitions_target``, ``rir_target``, ``rest_target``), so a log preserves both sides even when the routine later changes. Computed endpoints ------------------ Once a routine is set up, these read-only sub-resources return calculated views of it: ``/api/v2/routine/{id}/structure/`` The full nested structure (routine β†’ days β†’ slots β†’ slot entries), suitable for an editor. ``/api/v2/routine/{id}/date-sequence-display/`` One entry per date, with the matching day and slots already resolved. Slots that contain repeated sets of the same exercise are folded together for display. ``/api/v2/routine/{id}/date-sequence-gym/`` Same data, but the slots are split into individual sets and supersets are interleaved as described above. Use this for the gym-mode view. ``/api/v2/routine/{id}/logs/`` The workout sessions and logs for this routine, grouped by session. ``/api/v2/routine/{id}/stats/`` Aggregated statistics from the logs: volume, set count and average intensity (estimated 1RM via the Brzycki formula), each broken down by **day**, **ISO week**, **iteration** and the **whole routine**, and further split into total, upper/lower body, per muscle and per exercise. All five endpoints are cached server-side and invalidated when the underlying routine changes. --- ## File: docs/administration/anubis.rst .. _anubis: Anubis (anti-AI-crawler proxy) ============================== For public-facing instances, AI scrapers can and will flood the application with requests, resulting in high database loads. `Anubis `_ is a proof-of-work proxy that sits in front of wger and challenges suspicious clients before they reach the application. The wger.de instance runs this setup in production. Legitimate users might see a brief "checking your browser" page on their first visit, then continue as normal. API clients (including the wger mobile app) and known search engines are not challenged. Setup ----- The docker repo ships everything you need: * the ``anubis`` service block in `docker-compose.override.example.yml `_ * a curated rules file at `config/anubis-rules.yml `_, mounted into the container automatically Three steps: 1. **Generate an ED25519 private key.** Anubis uses it to sign challenge cookies, so don't reuse the example value from the override file. .. code-block:: bash openssl rand -hex 32 2. **Add a new anubis service to your docker-compose.override.yml.** Copy the block from ``docker-compose.override.example.yml``, paste the key into ``ED25519_PRIVATE_KEY_HEX`` and set ``WEBMASTER_EMAIL`` to a real address. 3. **Point your reverse proxy at Anubis instead of the web service.** With Caddy, swap the target in your ``Caddyfile``: .. code-block:: reverse_proxy anubis:3000 { header_up Host {host} # ... keep your existing header_up directives } With nginx, change the upstream in ``config/nginx.conf``: .. code-block:: nginx upstream wger { server anubis:3000; } Restart with ``docker compose up -d``. Hitting the site in a browser should briefly show the Anubis challenge page; ``/api/v2/*`` stays challenge-free. Rules ----- The bundled ``anubis-rules.yml`` is a curated preset that: * always allows ``/api/v2/*`` (so the mobile app and other API clients are never challenged) * allows well-known good search engine crawlers (Google, Bing, DuckDuckGo, and others) * allows ``robots.txt``, ``favicon.ico`` and ``/.well-known`` routes * blocks pathological bots and AI scrapers (Anubis's bundled ``ai-block-moderate`` preset) * applies browser-fingerprint heuristics so requests that look like a real browser get an easier challenge Read the full rules at `config/anubis-rules.yml `_. .. warning:: The ``allow-api-v2`` rule sits at the top of the file for a reason. Anubis evaluates rules top to bottom and stops at the first match. If you reorder or remove this rule, the mobile app and other API clients will start getting proof-of-work challenges they can't solve, and effectively break. Monitoring ---------- Anubis exposes Prometheus metrics on its ``METRICS_BIND`` port (``:9090`` in the example override). The docker repo ships a Grafana dashboard at ``grafana/dashboards/anubis.json`` that visualises traffic, challenge rate and pass/block ratio. For general Grafana setup, see :doc:`monitoring`. .. note:: On from-source installations, Anubis runs as a separate service alongside gunicorn and your reverse proxy. The wger docs don't cover that setup; refer to the `upstream Anubis project `_ for installation instructions. --- ## File: docs/administration/auth_proxy.rst .. _auth_proxy: Authentication Proxy (SSO) ========================== This feature allows you to delegate user authentication to a trusted reverse proxy server (e.g., Nginx, Caddy, Apache, Traefik) or an external authentication provider (e.g., Authelia, Authentik, Keycloak, systems using LDAP, SAML, OAuth2) fronting the application. When enabled, if the trusted proxy successfully authenticates a user and sets a specific HTTP header containing their username, wger will automatically log that user in. If the user doesn't exist, it can optionally be created automatically. .. warning:: Misconfiguration of this feature can lead to severe security vulnerabilities, potentially allowing attackers to impersonate any user! The core risk is that an attacker could directly send the authentication header (e.g., ``X-Remote-User: admin``) to the application, bypassing the proxy's authentication, and gain access as that user. Because of this, your reverse proxy **must** be configured to overwrite the authentication header from any incoming request before it processes the request, e.g. pseudocode for Caddy:: your.wger.domain.com { # ... authentication configuration (e.g., forward_auth to Authelia/Authentik) ... # Assuming auth service sets Upstream-User header upon success reverse_proxy http://: { # Remove header header_up delete X-Remote-User # Set header based on auth service response header_up +X-Remote-User {http.reverse_proxy.upstream.header.Upstream-User} } } If you plan to use the mobile app, make sure to bypass the ``api/*`` subfolder from your proxy auth so that the app can access the api endpoint. In caddy, it might look something like this:: wger.example.com { import cert # allow api path to bypass auth (requires api key) handle /api/* { reverse_proxy wger_server:8000 } # Enclosing in `route` forces execution order route { # Forward outpost path to actual outpost reverse_proxy /outpost.goauthentik.io/* authentik_server:9000 ... } Settings -------- ``AUTH_PROXY_HEADER`` *Required.* The name of the HTTP header that the reverse proxy sets with the authenticated user's username. Usually ``Remote-User``, but can be any name you choose. Default is empty, which disables the feature. Apply Django's header-name convention: uppercase, replace hyphens with underscores, prefix with ``HTTP_``. So if the proxy sets ``X-Remote-User``, configure ``HTTP_X_REMOTE_USER`` here. ``AUTH_PROXY_TRUSTED_IPS`` *Required.* Comma-separated list of trusted IP addresses from which the application accepts authentication headers. Usually this is the IP of your reverse proxy. If the proxy runs on the same host, include ``127.0.0.1`` and/or ``::1``. ``AUTH_PROXY_CREATE_UNKNOWN_USER`` Default ``False``. Whether to automatically create a new user account if the authenticated user does not exist in the database yet. ``AUTH_PROXY_USER_EMAIL_HEADER`` The header to read the email from for auto-created users. Ignored if ``AUTH_PROXY_CREATE_UNKNOWN_USER`` is not set. Same Django header-name convention as ``AUTH_PROXY_HEADER``. ``AUTH_PROXY_USER_NAME_HEADER`` The header to read the display name from for auto-created users. Ignored if ``AUTH_PROXY_CREATE_UNKNOWN_USER`` is not set. Same Django header-name convention as ``AUTH_PROXY_HEADER``. --- ## File: docs/administration/backup.rst .. _backup: Backup ====== The most important thing to back up is the **Postgres database**, that's where all user data lives. The media volume can usually be re-fetched from upstream, and static files are regenerated on every container start. You should perform a test restore at least once, so you know the procedure works when you actually need it. Database -------- Make a dump of the database: .. code-block:: bash # Stop the other services so the database isn't changed mid-export docker compose stop web nginx cache celery_worker celery_beat docker compose exec db pg_dumpall --clean --username wger > backup.sql docker compose start To restore from a dump: .. code-block:: bash docker compose stop docker volume remove docker_postgres-data docker compose up db cat backup.sql | docker compose exec -T db psql --username wger --dbname wger docker compose up The PowerSync bucket-storage tables (``powersync.*`` schema) are effectively a **cache** rebuilt from the ``public.*`` wger tables and ``sync_rules.yaml``. Nothing in there is a primary source of truth, every row can be regenerated via a fresh snapshot against the main wger database. ``pg_dumpall`` above captures the schema automatically, so there is nothing extra to do. If backup size matters (``bucket_data`` scales with users Γ— sync-rule complexity), you can safely exclude it:: docker compose exec db pg_dump --username wger \ --exclude-schema=powersync --clean --create wger > backup.sql On restore, PowerSync re-bootstraps the schema on next startup and takes a fresh snapshot. Clients silently re-sync their local SQLite from scratch. .. important:: **After restoring the database**, drop the PowerSync replication slot and restart the service so it re-reads from a valid WAL position: .. code-block:: bash docker compose exec db psql -U wger -c \ "SELECT pg_drop_replication_slot(slot_name) \ FROM pg_replication_slots \ WHERE slot_name LIKE 'powersync_%';" docker compose restart powersync Skipping this leaves the slot pointing at a WAL position that no longer exists, and PowerSync fails with cryptic ``operator does not exist`` errors until it is re-created. Media ----- If you haven't uploaded your own exercise images, exercise videos or gallery images, you don't need to back up the media volume, the contents can be re-downloaded from the upstream wger instance. Truncate the relevant tables and run the sync commands again: .. code-block:: bash docker compose exec db psql -U wger -c "TRUNCATE TABLE exercises_exerciseimage, exercises_exercisevideo;" docker compose exec db psql -U wger -c "TRUNCATE TABLE nutrition_image;" docker compose exec web python3 manage.py download-exercise-images docker compose exec web python3 manage.py download-exercise-videos If you do have uploaded media that needs to be preserved, such as gallery entries, consult these options for backing up Docker volumes: * https://www.docker.com/blog/back-up-and-share-docker-volumes-with-this-extension/ * https://github.com/BretFisher/docker-vackup Static files ------------ The contents of the static volume are 100% generated and recreated on startup, no need to back up anything. --- ## File: docs/administration/commands.rst Commands ======== Please note that the administration commands are intended e.g. to bootstrap/install an application to a new system, while the management ones are made to administer a running application (to e.g. delete guest users, send emails, etc.). Administration Commands ----------------------- Use the ``wger`` command to perform different administration and bootstrapping tasks such as initialising the database. You can get a list of all available commands by calling ``wger`` without any arguments as well as help on a specific command with ``wger --help ``. Here are some of the most important ones: ``bootstrap`` This command bootstraps the application: it creates a settings file, initialises a SQLite database, loads all necessary fixtures for the application to work and creates a default administrator user. While it can also work with e.g. a PostgreSQL database, you will need to create it yourself: ``create-or-reset-admin`` Makes sure that the default administrator user exists. If you change the password, it is reset. ``load-fixtures`` loads all fixture files with the default data. This data includes all data necessary for the application to work such as: * exercises, muscles, equipment * ingredients, units * languages * permission groups * etc. Note that ingredients are not included and need to be installed separately with download-online-fixtures. ``load-online-fixtures`` Downloads a subset of ingredients and the weight units fixtures, then installs them. To download all ingredients, use the manage.py command with the ``sync-ingredients-bulk`` option (see below). Management commands ------------------- wger also implements a series of Django commands that perform different management functions that are sometimes needed. Call them with ``python manage.py ``. To retrieve a full list of available commands, call ``python manage.py`` without any arguments and look under the app names (weight, nutrition, manager, core, exercises). To get help on a specific command, call ``python manage.py --help``. Here are some of the most important ones: ``sync-ingredients-bulk`` Options: `--set-mode=update|replace` Synchronizes the ingredient database from the default wger instance to the local installation. Ingredients that you added manually to the database are not touched. This command downloads a dump of the ingredient database from the default wger instance, imports the data and then deletes it. This is the recommended way to synchronize the ingredients. Setting the ``SYNC_INGREDIENTS_CELERY`` option uses Celery to perform the synchronization in the background in regular intervals. ``sync-ingredients[-async]`` Same as ``sync-ingredients-bulk``, but instead of downloading a dump, it uses the API to retrieve the ingredients. This way is much slower, but is kept for backwards compatibility. The ``-async`` version will use Celery to perform the synchronization in the background. ``sync-exercises`` synchronizes the exercise database from the default wger instance to the local installation. This will also update categories, equipment, languages, muscles and will delete entries that were removed on the remote server (this basically only applies to exercises that were submitted several times). Exercises that you added manually to the database are not touched. ``download-exercise-images`` synchronizes the exercise images from the default wger instance to the local installation, does not overwrite existing images. ``download-exercise-videos`` synchronizes the exercise videos from the default wger instance to the local installation, does not overwrite existing videos. ``import-off-products`` Imports and updates products from the Open Food Facts database. You can select whether to use a local file with the full database dump, the daily delta updates or use a mongo database, see the help for more information. Note that this command is not intended to be used on a regular basis in local installations, in order to not put too much load on the Open Food Facts servers. To keep your database up-to-date, you can use the ``sync-ingredients-bulk`` management command. ``extract-i18n`` Used for development only. Extracts strings from the database that need to be translated. See the :ref:`i18n` section for more information. ``dummy-generator-*`` Use to generate dummy data for the different entry types. For more information see the :ref:`dummy_generator` section. Celery ------ To list the currently scheduled tasks: .. code-block:: bash celery -A wger inspect active To to clear the currently waiting tasks: .. code-block:: python from wger.celery_configuration import app app.control.purge() For other possible commands, consult the `celery documentation `_. --- ## File: docs/administration/errors.rst .. _errors_and_pitfalls: Common errors and pitfalls ========================== Missing static files -------------------- If you start the application and don't see any CSS styles, images, etc., there's a problem with the static files. This happens often. The reason for this is that for performance reasons, django itself does not serve any of the static files in production. Instead, it relies on a separate dedicated process server (in our case, the nginx service, but it could be an external CDN or similar) to do it. The process works in two steps: * **Collect:** the django service runs a command to gather all static files into a single directory. This step happens automatically when you start the ``web`` service but can be manually triggered with ``docker compose exec web python3 manage.py collectstatic``. * **Serve:** the nginx service reads files from that exact same directory and serves them to the user. These two steps need access to shared docker volume. If you change the volume configuration, django might not be able to write the file or the web server might no longer find them. This can happen very easily if you use mounted folders and the permissions aren't correctly set (make sure they are ``chown``-ed to the UID and GID 1000, even if this user doesn't exist on your system, and are readable by everyone). The solution is always to ensure that the volume or folder which holds your static files is correctly mounted by both the django and the web server services. If you want to use your own, exising, web server, you need to make sure that the files are read and served under the right URLS. Take a look at our nginx.conf to see how this can look like. For more information, consult `django's documentation `_. Email verification links ------------------------ When self-hosting, verification emails may otherwise contain links like ``http://localhost/...``. Set ``SITE_URL`` in your environment to your public base URL (no trailing slash) so links use your domain instead: .. code-block:: bash SITE_URL=https://your.public.domain This value is used by the application to build absolute links in outgoing emails and other places where a full URL is required. CSRF errors ----------- You will most probably run into CSRF errors when you try to use the application, specially if you configured a domain and django's `CSRF protection `_ kicks in. To solve this, update the env file and either * manually set a list of your domain names and/or server IPs ``CSRF_TRUSTED_ORIGINS=https://my.domain.example.com,https://118.999.881.119:8008`` If you are unsure what origin to add here, set set ``DJANGO_DEBUG`` to true, restart the service and the error message will tell you exactly which one django has a problem with. Note: the port is important! * or set the ``X-Forwarded-Proto`` header like in the example and set ``X_FORWARDED_PROTO_HEADER_SET=True``. If you do this consult the `documentation `_ as there are some security considerations. Wrong pagination links ---------------------- (note that this mostly applies if you are running your own reverse proxy) The application builds absolute URLs (for example the "next" links in the API pagination) from the headers of the incoming request. When these headers get lost on the way through a reverse proxy, those URLs point to the wrong host or use ``http`` instead of ``https``. Some features then break in subtle ways: pagination links might point to "localhost" or only work inside your home network, and the mobile app shows a warning at login ("Server misconfiguration detected, headers are not being passed correctly"). Two things need to be in place: 1. The reverse proxy must forward the host and protocol headers to the application. The nginx service in the docker compose setup already does this, but if you put your own proxy in front of it (or replace it), make sure it sets these headers:: proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; When chaining more than one proxy, the outermost one must set the headers and the inner ones must pass them through unchanged (see the ``X-Forwarded-Proto`` handling in our nginx.conf for an example). 2. The application ignores these headers unless you explicitly allow them. Set the following in your env file and recreate the containers:: X_FORWARDED_PROTO_HEADER_SET=True USE_X_FORWARDED_HOST=True See :doc:`settings` for details and the security considerations. To check that everything works, open ``https://your.wger.url/api/v2/exercise/?limit=1`` in your browser: the ``next`` link in the response has to start with exactly the URL and protocol (http / https) you use to reach the application. Mobile app stuck on "Connecting" -------------------------------- If you can log into the mobile app, but the sync status stays on "Connecting" forever and your data never show up, something on your network is most likely blocking the connection the app uses to synchronise it. Typical culprits are VPN apps, ad blockers such as Pi-hole or AdGuard, antivirus apps and some mobile providers. Try switching between WiFi and mobile data, or turn off the VPN or ad blocker for a moment and check whether the sync starts working. Mobile app reports "Sync Service Unavailable" --------------------------------------------- This message means the app can reach your wger server (so logging in works), but not the PowerSync service that handles the offline synchronisation. The usual suspects: * ``SITE_URL`` is wrong: the server tells the app to sync against ``SITE_URL`` plus ``/ps/``, so a wrong default, a missing port or a domain your phone can't reach all break the sync. Set it to the exact URL you use to reach the application and recreate the containers. Check the current value by opening ``https://your.wger.url/api/v2/powersync-token`` in a browser (logged in): the ``powersync_url`` field is the address the app will use. * The app doesn't go through nginx: the sync service is only reachable through the nginx service under ``/ps/``. Don't point the app (or ``SITE_URL``) directly at the django container. * Your own reverse proxy doesn't forward ``/ps/`` or cuts off long-running connections; see the ``/ps/`` section in our nginx.conf for reference. * The PowerSync container isn't running: check ``docker compose ps`` and ``docker compose logs powersync``. You might be using an older docker compose setup without the powersync service. To test that the service is configured correctly, open ``https://your.wger.url/ps/probes/liveness`` with your browser. A short status response means the sync service is reachable. --- ## File: docs/administration/gym.rst .. _gym: Gym administration ================== wger provides support for managing both gyms and members. For example, trainers/coaches can follow their students progress, and gym managers are able to keep track of their members contracts. If the installation is being used for a single gym, you can set the default gym in the global configuration options in the gym list. This will update all existing users as well as newly registered ones so they belong to that gym. There are 3 groups used for the different administrative roles: * **General Manager:** Can manage (add, edit, delete) the different gyms for the installation as well as add gym managers, trainers, and members but is not allowed to see the members' workout data. * **Gym Manager:** Can manage the users for a single gym (editing, deactivating, and adding contracts, etc.). * **Trainer:** Can manage the workouts and other data for the members of a single gym. These roles are not mutually exclusive, if your workflow demands it, you can combine all three roles into one account. Except for general managers, administrative users belong to a single gym (the one they were created in) and can access only those members. This setting cannot be changed later. The user's gym appears in the top-right menu. Member Management ----------------- To add members to a gym: 1. Click the ``Add Member`` button at the top of the member overview. 2. Fill in the form to generate a password for the user. 3. Save this password and give it to the user and it cannot be retrieved later. OR 1. Click the ``Add Member`` button at the top of the member overview. 2. Instruct new members to use the reset password links when logging in for the first time. To export all gym members: 1. Navigate to the ``Actions`` button on the gym detail page. 2. Here, you are provided with a CSV file that can be imported into a spreadsheet program for further processing. Trainers can click on a user and access an overview of the user's workouts, body weights, nutrition plans, etc. When clicking on the "log in as this user", the trainer can assume the identity of the user to create new workouts for example. Additionally, Trainers can add notes and upload documents related to individual members. A note is a free text, while a document can be any file. This information can be used to save information on specific injuries or other important notes related to the member. Note that these entries are not accessible by the members themselves, but only by the trainers. Individual members can be deactivated by clicking on the "actions" button on the top of the member's detail table. Deactivated users can't log in, but are not deleted from the system and can be reactivated at any time in the future. If you wish to completely delete a user from the system, use the "delete" option but keep in mind that this action cannot be undone. Contracts --------- It is also possible to manage the members' contracts with the application. A contract is composed of a base form and optional *type* and *options*. The type is a single attribute, such as "Student contract" or "Special offer 2016". The options are basically the same but more than one can be selected at once and can be used for items that can e.g. be booked in addition to the default contract such as "Sauna" or "Protein drink flatrate". The types and the options are added gym-wide in the member overview by the managers. Once these are saved, they can be used when adding or editing a contract to a specific user. E-Mails ------- You can send a batch e-mail to all members of a gym. Currently, there is no support for filtering members based on specific criterion. How to send e-mails: 1. Navigate to the gym's overview and click "Add" on the Email actions button. 2. Fill in the subject and the body. 3. Review, and accept the e-mail's preview. 4. After submitting, emails will be delivered in batch format based on your cron jobs configuration. Configuration ------------- Inactive members ~~~~~~~~~~~~~~~~ Inactive members are members that have not logged in for X weeks. For example, a trainer can check to see which users have not visited the gym in X weeks. This can be configured in the following ways: **Number of Weeks** The value in weeks after which users are considered inactive (default is 8). This applies to the whole gym and can be deactivated by entering a 0. **Trainer Configuration** Each trainer can opt-out of receiving such emails. **User Configuration** Individual users can be opt-out of being included in the reminder emails if they don't want to use the log for any other reason. Gym name in the header ~~~~~~~~~~~~~~~~~~~~~~ A checkbox to control whether the gym's name will appear in the header instead of the application's name for all logged-in users of this gym. This applies to members, trainers, and managers