withings-sync 5.3.0
A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road.
License: MIT
Requires Python: <4.0,>=3.12
Keywords: garmin, withings, sync, api, scale, smarthome

> [!CAUTION]
> This Release introduces breaking changes that users need to be aware of before upgrading or using it.
> These changes were made to enhance security and compatibility but may require modifications to your existing setup.
>
> - The container now runs without root privileges.
> - Dependencies, virtual envs, packaging is now done by Poetry.
> - This fork requires a recent version of Python, currently capped at >= python 3.12.
>
> Make sure to go over the updated readme and test these new changes thoroughly for your environment.
> Chances are quite high you will have to make changes to make this work again. 

# withings-sync

A tool for synchronisation of the Withings API to:

- Garmin Connect
- Trainer Road
- raw JSON output

## 1. Installation Instructions
### 1.1 Installation of withings-sync with pip
> This method installs the package asuming you have a working python and pip installation. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
<details>
  <summary>Expand to show installation steps.</summary>

  <ins>1. installing the package:</ins>

  ```bash
  $ pip install withings-sync
  ```

  <ins>2. obtaining Withings authorization:</ins>

  When running for a very first time, you need to obtain Withings authorization:
  ```bash
  $ withings-sync
  2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json
  2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings!
  2024-12-01 01:29:02,603 - withings - WARNING -
  2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
  2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity)
  2024-12-01 01:29:02,604 - withings - WARNING -
  2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html&
  2024-12-01 01:29:02,604 - withings - INFO -

  Token : <PASTE TOKEN>

  2024-12-01 01:31:07,832 - withings - INFO - Get Access Token
  2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token
  2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59
  2024-12-01 01:31:09,406 - withings - INFO - Get Measurements
  2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified
  ```
  You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt.

  This is one-time activity and it will not be needed to repeat. 

  <ins>3. running the application:</ins>

  Subsequent runs will use the saved access tokens in `~/.withings_user.json`
  ```bash
  $ withings-sync
  2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token
  2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59
  2024-12-01 01:37:42,563 - withings - INFO - Get Measurements
  2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified
  ```
</details>

### 1.2 Installation of withings-sync with docker compose (not using cron)
> This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
<details>
  <summary>Expand to show installation steps.</summary>

  <ins>1. create the following file/directory structure:</ins>

  ```bash
  .                                          # STACK_PATH
  ./.env                                     # .env file containing your variables
  ./docker-compose.yml                       # docker-compose file
  ./config/withings-sync/                    # config directory for withings-sync (will be created automatically)
  ```

  <ins>2. contents of an example `.env` file:</ins>

  ```bash
  TZ=Europe/Kyiv
  STACK_PATH=/home/your_user/your_stack_name
  GARMIN_USERNAME="your.name@domain.ext"
  GARMIN_PASSWORD="YourPasswordHere"
  ```
 
  <ins>3. contents of an example `docker-compose.yml` file (simplified with --config-folder):</ins>
 
  ```yaml
  services:
    withings-sync:
      image: ghcr.io/jaroslawhartman/withings-sync:latest
      container_name: withings-sync
      stdin_open: true # docker run -i
      tty: true        # docker run -t
      environment:
        - TZ=${TZ:?err}
        - GARMIN_USERNAME=${GARMIN_USERNAME:?err}
        - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err}
      volumes:
        - /etc/localtime:/etc/localtime:ro
        - ${STACK_PATH:?err}/config/withings-sync:/config
      command: ["--config-folder", "/config"]
      restart: unless-stopped
  ```

  **Note**: Using `--config-folder` simplifies the setup by:
  - Storing both Withings and Garmin session files in one directory
  - No need to mount individual files
  - Easier backup and migration of configuration

  <ins>4. obtaining Withings authorization:</ins>
 
  ```bash
  $ docker compose pull
  [+] Pulling 13/13
  ✔ withings-sync Pulled                                                           56.0s
    ✔ cb8611c9fe51 Pull complete                                                    4.2s
    ✔ 52e189a1282f Pull complete                                                    6.4s
    ✔ 95e68cb0cebc Pull complete                                                   19.0s
    ✔ c3ba8bc06a4d Pull complete                                                   19.3s
    ✔ fc2b9c85008a Pull complete                                                   21.6s
    ✔ 0376fca350d9 Pull complete                                                   21.7s
    ✔ 4f4fb700ef54 Pull complete                                                   21.9s
    ✔ c749d618f51d Pull complete                                                   43.0s
    ✔ 86d00088bd8d Pull complete                                                   43.2s
    ✔ 98dec7b84387 Pull complete                                                   52.8s
    ✔ 8825309bd8c2 Pull complete                                                   53.1s
    ✔ 7747652082d6 Pull complete                                                   53.3s
  ```

  First start to ensure the script can start successfully:

  ```bash
  $ docker compose run -it --remove-orphans --entrypoint "poetry run withings-sync" withings-sync
  [+] Creating 1/1
  ✔ Network stack_default  Created                                                  0.5s
  2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json
  2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings!
  2024-12-01 01:29:02,603 - withings - WARNING -
  2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
  2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity)
  2024-12-01 01:29:02,604 - withings - WARNING -
  2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html&
  2024-12-01 01:29:02,604 - withings - INFO -

  Token : <PASTE TOKEN>

  2024-12-01 01:31:07,832 - withings - INFO - Get Access Token
  2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token
  2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59
  2024-12-01 01:31:09,406 - withings - INFO - Get Measurements
  2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified
  ```
  You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt.

  This is one-time activity and it will not be needed to repeat.

  <ins>5. running the container:</ins>

  Subsequent runs will use the saved access tokens in `~/.withings_user.json`

  ```bash
  $ docker compose run -it --remove-orphans withings-sync                           0.5s
  [+] Creating 1/1
  ✔ Container stack-withings-sync-run-3f24bc7ec7f9  Removed
  2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token
  2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59
  2024-12-01 01:37:42,563 - withings - INFO - Get Measurements
  2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified
  ```

  <ins>6. updating to a newer version:</ins>
 
  ```bash
  $ docker compose pull
  $ docker compose run -it --remove-orphans withings-sync
  ```

  This will ensure that both Withings and Garmin session files persist across container restarts in the `/config` directory.
</details>

### 1.3 Installation of withings-sync with docker compose (using supercronic)
> This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently.
<details>
  <summary>Expand to show installation steps.</summary>

  <ins>1. create the following file/directory structure:</ins>
 
  > Make sure to create the directories (`mkdir`) & files (`touch`) upfront or docker will create them as root.
  ```bash
  .                                          # STACK_PATH
  ./.env                                     # .env file containing your variables
  ./docker-compose.yml                       # docker-compose file
  ./config/withings-sync/                    # config directory for withings-sync (will be created automatically)
  ./config/withings-sync/entrypoint.sh       # entrypoint.sh file containing your cmd & arguments
  ```

  <ins>2. contents of an example `.env` file:</ins>
 
  ```bash
  TZ=Europe/Kyiv
  STACK_PATH=/home/youruser/withings-sync
  GARMIN_USERNAME="your.name@domain.ext"
  GARMIN_PASSWORD="YourPasswordHere"
  ```

  <ins>3. contents of an example `entrypoint.sh` file (updated for --config-folder):</ins>
 
  ```bash
  #!/bin/sh
  echo "$(( $RANDOM % 59 +0 )) */3 * * * * * poetry run withings-sync --config-folder /config --features BLOOD_PRESSURE" > /home/withings-sync/cronjob
  supercronic -debug -passthrough-logs /home/withings-sync/cronjob
  ```
 
  <ins>4. contents of an example `docker-compose.yml` file (simplified with --config-folder):</ins>
 
  ```yaml
  services:
  withings-sync:
    image: ghcr.io/jaroslawhartman/withings-sync:latest
    container_name: withings-sync
    stdin_open: true # docker run -i
    tty: true        # docker run -t
    environment:
      - TZ=${TZ:?err}
      - GARMIN_USERNAME=${GARMIN_USERNAME:?err}
      - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${STACK_PATH:?err}/config/withings-sync:/config
      - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh
    entrypoint: "sh /home/withings-sync/entrypoint.sh"
    restart: unless-stopped
  ```

  **Note**: Using `--config-folder` simplifies the setup by storing both Withings and Garmin session files in the `/config` directory, eliminating the need for multiple volume mounts.

  <ins>5. obtaining Withings authorization:</ins>
 
  ```bash
  $ docker compose pull
  [+] Pulling 13/13
  ✔ withings-sync Pulled                                                           56.0s
    ✔ cb8611c9fe51 Pull complete                                                    4.2s
    ✔ 52e189a1282f Pull complete                                                    6.4s
    ✔ 95e68cb0cebc Pull complete                                                   19.0s
    ✔ c3ba8bc06a4d Pull complete                                                   19.3s
    ✔ fc2b9c85008a Pull complete                                                   21.6s
    ✔ 0376fca350d9 Pull complete                                                   21.7s
    ✔ 4f4fb700ef54 Pull complete                                                   21.9s
    ✔ c749d618f51d Pull complete                                                   43.0s
    ✔ 86d00088bd8d Pull complete                                                   43.2s
    ✔ 98dec7b84387 Pull complete                                                   52.8s
    ✔ 8825309bd8c2 Pull complete                                                   53.1s
    ✔ 7747652082d6 Pull complete                                                   53.3s
  ```

  First start to ensure the container can start successfully:

  ```bash
  $ docker compose run -it --remove-orphans --entrypoint "poetry run withings-sync" withings-sync
  [+] Creating 1/1
  ✔ Network stack_default  Created                                                  0.5s
  2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json
  2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings!
  2024-12-01 01:29:02,603 - withings - WARNING -
  2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
  2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity)
  2024-12-01 01:29:02,604 - withings - WARNING -
  2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html&
  2024-12-01 01:29:02,604 - withings - INFO -

  Token : <PASTE TOKEN>

  2024-12-01 01:31:07,832 - withings - INFO - Get Access Token
  2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token
  2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59
  2024-12-01 01:31:09,406 - withings - INFO - Get Measurements
  2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified
  ```

  <ins>6. running the container:</ins>

  And for subsequent runs we start docker compose and let the container run in the background.
  Subsequent runs will use the saved access tokens in `~/.withings_user.json`

  ```bash
  $ docker compose up -d --remove-orphans
  [+] Running 1/1
  ✔ Container withings-sync                         Started                         1.5s
  ```

  <ins>7. logging:</ins>

  ```bash
  $ docker compose logs withings-sync
  withings-sync  | WARN[2024-12-24T09:23:55+01:00] process reaping disabled, not pid 1
  withings-sync  | INFO[2024-12-24T09:23:55+01:00] read crontab: /home/withings-sync/cronjob
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] try parse (7 fields): '53 */3 * * * poetry run'
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] failed to parse (7 fields): '53 */3 * * * poetry run': failed: syntax error in day-of-week field: 'poetry'
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] try parse (6 fields): '53 */3 * * * poetry'
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] failed to parse (6 fields): '53 */3 * * * poetry': failed: syntax error in year field: 'poetry'
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] try parse (5 fields): '53 */3 * * *'
  withings-sync  | DEBU[2024-12-24T09:23:55+01:00] job will run next at 2024-12-24 09:53:00 +0100 CET  job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *"
  withings-sync  | INFO[2024-12-24T09:53:00+01:00] starting                      iteration=0 job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *"
  withings-sync  | 2024-12-24 09:53:29,177 - withings - INFO - Refresh Access Token
  withings-sync  | 2024-12-24 09:53:29,380 - root - INFO - Fetching measurements from 2024-12-22 18:52 to 2024-12-24 23:59
  withings-sync  | 2024-12-24 09:53:29,662 - withings - INFO - Get Measurements
  withings-sync  | 2024-12-24 09:53:29,866 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure.  Not syncing...
  withings-sync  | 2024-12-24 09:53:29,868 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure.  Not syncing...
  withings-sync  | 2024-12-24 09:53:29,870 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure.  Not syncing...
  withings-sync  | 2024-12-24 09:53:29,878 - root - INFO - No blood pressure data to sync for FIT file
  withings-sync  | 2024-12-24 09:53:29,880 - root - INFO - No TrainerRoad username or a new measurement - skipping sync
  withings-sync  | 2024-12-24 09:53:33,665 - root - INFO - Fit file with weight information uploaded to Garmin Connect
  withings-sync  | 2024-12-24 09:53:33,666 - withings - INFO - Saving Last Sync
  withings-sync  | INFO[2024-12-24T09:53:34+01:00] job succeeded                 iteration=0 job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *"
  withings-sync  | DEBU[2024-12-24T09:53:34+01:00] job will run next at 2024-12-24 12:53:00 +0100 CET  job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *"
  ```

  <ins>8. updating to a newer version:</ins>
 
  ```bash
  $ docker compose pull
  $ docker compose down
  $ docker compose up -d --remove-orphans
  $ docker image prune -f
  ```

  This will ensure that both Withings and Garmin session files persist across container restarts in the `/config` directory.
</details>

### 1.4 Installation of withings-sync with docker (not compose)
> This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
<details>
  <summary>Expand to show installation steps.</summary>

```bash
$ docker pull ghcr.io/jaroslawhartman/withings-sync:latest
```

First start to ensure the script can start successfully (using --config-folder for simplified session management):

Obtaining Withings authorisation:

```bash
# Create a local config directory
mkdir -p ./config

# Run with config folder mounted
$ docker run -v ./config:/config --interactive --tty --name withings-sync ghcr.io/jaroslawhartman/withings-sync:latest --config-folder /config --garmin-username=<username> --garmin-password=<password>

Can't read config file /config/.withings_user.json
User interaction needed to get Authentification Code from Withings!

Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP!
(This is one-time activity)

https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html&

Token : <token>
Withings: Get Access Token
Withings: Refresh Access Token
Withings: Get Measurements
   Measurements received
JaHa.WAW.PL
Garmin Connect User Name: JaHa.WAW.PL
Fit file uploaded to Garmin Connect
```

And for subsequent runs (both session files persist in ./config):

```bash
$ docker start -i withings-sync
Withings: Refresh Access Token
Withings: Get Measurements
   Measurements received
JaHa.WAW.PL
Garmin Connect User Name: JaHa.WAW.PL
Fit file uploaded to Garmin Connect
```

**Note**: Using `--config-folder /config` stores both Withings and Garmin session files in the mounted directory, making them persist across container restarts.
</details>

## 2. Usage Instructions

```
usage: withings-sync [-h] [--version] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE] [--todate DATE] [--to-fit] [--to-json]
                     [--output BASENAME] [--no-upload] [--features BLOOD_PRESSURE [BLOOD_PRESSURE ...]] [--verbose | --silent] [--dump-raw] [--config-folder PATH]

A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string.

options:
  -h, --help            show this help message and exit
  --version, -V         show program's version number and exit
  --garmin-username GARMIN_USERNAME, --gu GARMIN_USERNAME
                        Username to log in to Garmin Connect.
  --garmin-password GARMIN_PASSWORD, --gp GARMIN_PASSWORD
                        Password to log in to Garmin Connect.
  --trainerroad-username TRAINERROAD_USERNAME, --tu TRAINERROAD_USERNAME
                        Username to log in to TrainerRoad.
  --trainerroad-password TRAINERROAD_PASSWORD, --tp TRAINERROAD_PASSWORD
                        Password to log in to TrainerRoad.
  --fromdate DATE, -f DATE
                        Date to start syncing from. Ex: 2023-12-20
  --todate DATE, -t DATE
                        Date for the last sync. Ex: 2023-12-30
  --to-fit, -F          Write output file in FIT format.
  --to-json, -J         Write output file in JSON format.
  --output BASENAME, -o BASENAME
                        Write downloaded measurements to file.
  --no-upload           Won't upload to Garmin Connect or TrainerRoad.
  --features BLOOD_PRESSURE [BLOOD_PRESSURE ...]
                        Enable Features like BLOOD_PRESSURE.
  --verbose, -v         Run verbosely.
  --silent, -s          Run silently (suppress INFO messages).
  --dump-raw, -R        Dump the raw Withings API JSON for the selected date range to file. If --output is provided, the file will be BASENAME.withings_raw.json. Otherwise, a default filename with the date range will be used.
  --config-folder PATH, -c PATH
                        Path to config folder for session files (if not specified, uses legacy paths in home directory).
```

## 3. Providing credentials
### 3.1 Providing credentials via environment variables

You can use the following environment variables for providing the Garmin and/or Trainerroad credentials:

- `GARMIN_USERNAME`
- `GARMIN_PASSWORD`
- `TRAINERROAD_USERNAME`
- `TRAINERROAD_PASSWORD`

The CLI also uses python-dotenv to populate the variables above. Therefore setting the environment variables
has the same effect as placing the variables in a `.env` file in the working directory.

### 3.2 Providing credentials via secrets files

You can also populate the following 'secrets' files to provide the Garmin and/or Trainerroad credentials:

- `/run/secrets/garmin_username`
- `/run/secrets/garmin_password`
- `/run/secrets/trainerroad_username`
- `/run/secrets/trainerroad_password`

Secrets are useful in an orchestrated container context — see the [Docker Swarm](https://docs.docker.com/engine/swarm/secrets/) or [Rancher](https://rancher.com/docs/rancher/v1.6/en/cattle/secrets/) docs for more information on how to securely inject secrets into a container.

### 3.3 Order of priority for credentials

In the case of credentials being available via multiple means (e.g. [environment variables](#providing-credentials-via-environment-variables) and [secrets files](#providing-credentials-via-secrets-files)), the order of resolution for determining which credentials to use is as follows, with later methods overriding credentials supplied by an earlier method:

1. Read secrets file(s)
2. Read environment variable(s), variables set explicitly take precedence over values from a `.env` file.
3. Use command invocation argument(s)

## 4. Tips

### 4.1 Garmin SSO errors

Some users reported errors raised by the Garmin SSO login:

```
withings_sync.garmin.APIException: SSO error 401
```

or

```
withings_sync.garmin.APIException: SSO error 403
```

These errors are raised if a user tries to login too frequently.
E.g. by running the script every 10 minutes.

**We recommend to run the script around 8-10 times per day (every 2-3 hours).**

See also: https://github.com/jaroslawhartman/withings-sync/issues/31

### 4.2 Garmin auth

You can configure the location of the garmin session file with the variabe `GARMIN_SESSION`.

Note: If you specify both `--config-folder` and the `GARMIN_SESSION` environment variable, the `--config-folder` option takes precedence.

### 4.3 Config folder

By default, withings-sync stores session files in your home directory:
- `~/.withings_user.json` for Withings authentication
- `~/.garmin_session` for Garmin authentication

You can use the `--config-folder` or `-c` argument to store all session files in a custom folder:

```bash
# Store session files in ~/.withings-sync
withings-sync --config-folder ~/.withings-sync

# Store session files in a custom location
withings-sync --config-folder /path/to/my/config
```

When using a custom config folder:
- The folder will be created automatically if it doesn't exist
- If you have existing session files in the legacy locations, the script will show you where to copy them from
- This is useful for Docker installations or for keeping your home directory clean

### 4.4 Run a periodic Kubernetes job

1. Create the secret using kubectl in the command line.
```
export GARMIN_USERNAME="user@username.com"
export GARMIN_PASSWORD="superPassword"
export TRAINERROAD_USERNAME: "name@name.com"
export TRAINERROAD_PASSWORD: "superPassword"
kubectl create secret generic withings-secret --from-literal=GARMIN_USERNAME=$GARMIN_USERNAME --from-literal=GARMIN_PASSWORD=$GARMIN_PASSWORD
--from-literal=TRAINERROAD_USERNAME=$TRAINERROAD_USERNAME --from-literal=TRAINERROAD_PASSWORD=$TRAINERROAD_PASSWORD
```

2. Create the PVC.
```
kubectl apply -f k8s-pvc.yaml
```

3. Run the bootstrap pod, which attaches to the PVC for storing credentials. 
```
kubectl apply -f k8s-bootstrap.yaml
```
The bootstrap pod stays on indefinitely to allow time for you to exec in, and 
generate the credentials.

4. Exec into the bootstrap pod, generate credentials and store them in the PVC.
```
kubectl exec -it bootstrap-withings-sync -- sh
```
From _within_ the bootstrap pod:
```
# Create a config directory in the PVC
mkdir -p /data/config

# Run with config folder to store all session files
poetry run withings-sync --config-folder /data/config --fromdate=<RECORDED_DATE>
```
It is important that this run includes a date that has a record, as a record is required for the program to attempt an upload to garmin in order to create the session files for garmin.
The command above will allow entering the withings token and the MFA code for garmin. 
After successful auth, the credentials will be automatically stored in `/data/config/`:
- `/data/config/.withings_user.json`
- `/data/config/.garmin_session`

5. Create the cron job. Update the cron job command to use the config folder:
```
kubectl apply -f k8s-job.yaml
```

Note: Update your k8s-job.yaml to include `--config-folder /data/config` in the container command.

## 5 For advanced users - registering own Withings application
> Instead of using the provided Withings application tokens you can register your own app with Withings and use that one instead. 
<details>
  <summary>If you are not sure you need this, you most likely won't.</summary>

The script has been registered as a Withings application and got assigned `Client ID` and `Consumer Secret`. If you wish to create your own application - feel free!


* First you need a Withings account. [Sign up here](https://account.withings.com/connectionuser/account_create).
* Then you need a Withings developer app registered. [Create your app here](https://account.withings.com/partner/add_oauth2).

Note, registering it is quite cumbersome, as you need to have a callback URL and an Icon. Anyway, when done, you should have the following identifiers:

| Identfier       |  Example                                                           |
|-----------------|--------------------------------------------------------------------|
| Client ID       | `183e03.................765c98c10e8f1aa647a37067a1......baf491626` |
| Consumer Secret | `a75d65.................4c16719ef7bd69fa7c5d3fd0ea......ed48f1765` |
| Callback URI    | `https://jhartman.pl/withings/notify`                              |

Configure them in `config/withings_app.json`, for example:

```
{
    "callback_url": "https://wieloryb.uk.to/withings/withings.html",
    "client_id": "183e0******0b3551f96765c98c1******b64bbbaf491626",
    "consumer_secret": "a75d65******1df1514c16719ef7bd69fa7*****2e2b0ed48f1765"
}
```

For the callback URL you will need to setup a webserver hosting `contrib/withings.html`.

To do this in a Docker installation, you can use the environment variable `WITHINGS_APP` to point to a mounted `withings_app.json`

Example docker-compose:
```
  withings-sync:
    container_name: withings-sync
    image: ghcr.io/jaroslawhartman/withings-sync:latest
    volumes:
      - "withings-sync:/root"
      - "/etc/localtime:/etc/localtime:ro"
    environment:
      WITHINGS_APP: /home/withings-sync/withings_app.json
(...)
```
You can then add the app-config in `withings-sync/withings_app.json`

</details>

## 6. Release

Release works via the GitHub [Draft a new Release](https://github.com/jaroslawhartman/withings-sync/releases/new) 
function.
The `version` key in `pyproject.toml` will be bumped automatically (Version will be written to pyproject.toml file).

### Docker Image

Container images are created automagically by GitHub Action and published 
to [ghcr](https://github.com/jaroslawhartman/withings-sync/pkgs/container/withings-sync).

### Pypi & GitHub

Will be conducted automatically within the Github-Release cycle.
This needs the permission on the [pypi-project](https://pypi.org/project/withings-sync/).
The python packages are added to the GitHub releases by a GitHub Action.

## 7. References

* SSO authorization derived from https://github.com/cpfair/tapiriik
* TrainerRoad API from https://github.com/stuwilkins/python-trainerroad

## 8. Credits / Authors

* Based on [withings-garmin](https://github.com/ikasamah/withings-garmin) by Masayuki Hamasaki, improved to support SSO authorization in Garmin Connect 2.
* Based on [withings-garmin-v2](https://github.com/jaroslawhartman/withings-garmin-v2) by Jarek Hartman, improved Python 3 compatability, code-style and setuptools packaging, Kubernetes and Docker support.