From d422242078a0641b5261b15a10c5ad097bc0a987 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 4 Mar 2026 15:59:14 +0100 Subject: [PATCH 1/4] QuantumLeap --- .docker/data/.gitignore | 5 ++ .docker/data/README.md | 26 ++++++++ .editorconfig | 24 +++++++ .env | 2 + .gitignore | 2 + .markdownlint.jsonc | 22 +++++++ .markdownlintignore | 12 ++++ .prettierrc.yaml | 11 ++++ 01-init.sql | 9 +++ CHANGELOG.md | 10 +++ README.md | 110 +++++++++++++++++++++++++++++++++ docker-compose.prod.yml | 38 ++++++++++++ docker-compose.quantumleap.yml | 99 +++++++++++++++++++++++++++++ docker-compose.yml | 26 ++++++++ ql-config.yaml | 2 + 15 files changed, 398 insertions(+) create mode 100644 .docker/data/.gitignore create mode 100644 .docker/data/README.md create mode 100644 .editorconfig create mode 100644 .env create mode 100644 .gitignore create mode 100644 .markdownlint.jsonc create mode 100644 .markdownlintignore create mode 100644 .prettierrc.yaml create mode 100644 01-init.sql create mode 100644 CHANGELOG.md create mode 100644 docker-compose.prod.yml create mode 100644 docker-compose.quantumleap.yml create mode 100644 docker-compose.yml create mode 100644 ql-config.yaml diff --git a/.docker/data/.gitignore b/.docker/data/.gitignore new file mode 100644 index 0000000..4ce1020 --- /dev/null +++ b/.docker/data/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except +!.gitignore +!README.md diff --git a/.docker/data/README.md b/.docker/data/README.md new file mode 100644 index 0000000..8895d7b --- /dev/null +++ b/.docker/data/README.md @@ -0,0 +1,26 @@ +# .docker/data + +Please map persistent volumes to this directory on the servers. + +If a container needs to persist data between restarts you can map the relevant files in the container to ``docker/data/`. + +## RabbitMQ example +If you are using RabbitMQ running in a container as a message broker you need to configure a persistent volume for RabbitMQs data directory to avoid losing message on container restarts. + +```yaml +# docker-compose.server.override.yml + +services: + rabbit: + image: rabbitmq:3.9-management-alpine + hostname: "${COMPOSE_PROJECT_NAME}" + networks: + - app + - frontend + environment: + - "RABBITMQ_DEFAULT_USER=${RABBITMQ_USER}" + - "RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}" + - "RABBITMQ_ERLANG_COOKIE=${RABBITMQ_ERLANG_COOKIE}" + volumes: + - ".docker/data/rabbitmq:/var/lib/rabbitmq/mnesia/" +``` diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f37feb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# This file is copied from config/symfony/.editorconfig in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = LF +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,css,scss}] +indent_size = 2 + +[*.{yml,yaml}] +indent_size = 2 + +[config/**/*.{yml,yaml}] +indent_size = 4 diff --git a/.env b/.env new file mode 100644 index 0000000..d70a1c9 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +COMPOSE_PROJECT_NAME=quantumleap +COMPOSE_DOMAIN=quantumleap.local.itkdev.dk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..551e742 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.local +.htpasswd diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..0253096 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,22 @@ +// This file is copied from config/markdown/.markdownlint.jsonc in https://github.com/itk-dev/devops_itkdev-docker. +// Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +// markdownlint-cli configuration file (cf. https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration) +{ + "default": true, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false + }, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + "no-duplicate-heading": { + "siblings_only": true + }, + // https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md033.md + "no-inline-html": { + "allowed_elements": ["details", "summary"] + } +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..d143ace --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,12 @@ +# This file is copied from config/markdown/.markdownlintignore in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#ignoring-files +vendor/ +node_modules/ +LICENSE.md +# Drupal +web/*.md +web/core/ +web/libraries/ +web/*/contrib/ diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..12e0898 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,11 @@ +# This file is copied from config/symfony/yaml/.prettierrc.yaml in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# https://prettier.io/docs/configuration +overrides: + # Symfony config + - files: + - "config/**/*.{yml,yaml}" + options: + tabWidth: 4 + singleQuote: true diff --git a/01-init.sql b/01-init.sql new file mode 100644 index 0000000..0e25c81 --- /dev/null +++ b/01-init.sql @@ -0,0 +1,9 @@ +-- # @todo Reqrite to use environment variables (cf. https://stackoverflow.com/a/70976611) +CREATE ROLE quantumleap LOGIN PASSWORD '*'; + +CREATE DATABASE quantumleap OWNER quantumleap ENCODING 'UTF8'; + +\connect quantumleap + +CREATE EXTENSION IF NOT EXISTS postgis CASCADE; +CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..832c88e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/itk-dev/iotlab-broker diff --git a/README.md b/README.md index 5cd350f..3f7f6c4 100644 --- a/README.md +++ b/README.md @@ -1 +1,111 @@ # IOTLab QuantumLeap + +Based on . + +Start the show: + +``` shell name=start +docker compose pull +docker compose up --detach --wait + +open https://orion.quantumleap.local.itkdev.dk +open https://grafana.quantumleap.local.itkdev.dk +``` + +``` shell name=orion-subscription-create +# https://quantumleap.readthedocs.io/en/latest/user/using/#orion-subscription +docker compose exec --no-TTY orion curl --silent --show-error localhost:1026/v2/subscriptions --header 'content-type: application/json' --data @- < Date: Wed, 4 Mar 2026 16:48:01 +0100 Subject: [PATCH 2/4] Hep! --- docker-compose.prod.yml | 20 +++++++++---------- docker-compose.quantumleap.yml | 19 +++++++++++------- grafana/provisioning/datasources/default.yaml | 7 +++++++ .../provisioning/datasources/timescale.yaml | 15 ++++++++++++++ 4 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 grafana/provisioning/datasources/default.yaml create mode 100644 grafana/provisioning/datasources/timescale.yaml diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 585357b..ba6cdd7 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,11 +3,11 @@ services: restart: "${ORION_RESTART:-unless-stopped}" labels: - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}-http.entrypoints=web" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}-http.middlewares=redirect-to-https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}.entrypoints=websecure" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}_orion.rule=Host(`orion.${COMPOSE_DOMAIN?}`)" - - "traefik.http.services.${COMPOSE_PROJECT_NAME?}_orion.loadbalancer.server.port=3000" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.entrypoints=web" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.middlewares=redirect-to-https" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion.rule=Host(`orion.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_orion.loadbalancer.server.port=1026" networks: # Database for orion @@ -31,8 +31,8 @@ services: restart: "${GRAFANA_RESTART:-unless-stopped}" labels: - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}-http.entrypoints=web" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}-http.middlewares=redirect-to-https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME?}.entrypoints=websecure" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}_grafana.rule=Host(`grafana.${COMPOSE_DOMAIN?}`)" - - "traefik.http.services.${COMPOSE_PROJECT_NAME?}_grafana.loadbalancer.server.port=3000" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.entrypoints=web" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.middlewares=redirect-to-https" + - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana.rule=Host(`grafana.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_grafana.loadbalancer.server.port=3000" diff --git a/docker-compose.quantumleap.yml b/docker-compose.quantumleap.yml index 44bcb2a..f98bbe4 100644 --- a/docker-compose.quantumleap.yml +++ b/docker-compose.quantumleap.yml @@ -17,10 +17,10 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=frontend" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}_orion.rule=Host(`orion.${COMPOSE_DOMAIN?}`)" - - "traefik.http.services.${COMPOSE_PROJECT_NAME?}_orion.loadbalancer.server.port=1026" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion.rule=Host(`orion.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_orion.loadbalancer.server.port=1026" # HTTPS config - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}.middlewares=redirect-to-https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}.middlewares=redirect-to-https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" networks: - quantumleap @@ -70,7 +70,7 @@ services: - quantumleap timescale: - image: timescale/timescaledb-ha:${TIMESCALE_VERSION:-pg16-all-oss} + image: timescale/timescaledb-ha:${TIMESCALE_VERSION:-pg17.9-ts2.25.2-oss} volumes: # https://www.w3tutorials.net/blog/how-to-create-user-database-in-script-for-docker-postgres/ - ./01-init.sql:/docker-entrypoint-initdb.d/01-init.sql @@ -89,11 +89,16 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=frontend" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}_grafana.rule=Host(`grafana.${COMPOSE_DOMAIN?}`)" - - "traefik.http.services.${COMPOSE_PROJECT_NAME?}_grafana.loadbalancer.server.port=3000" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana.rule=Host(`grafana.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_grafana.loadbalancer.server.port=3000" # HTTPS config - - "traefik.http.routers.${COMPOSE_PROJECT_NAME?}.middlewares=redirect-to-https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}.middlewares=redirect-to-https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" networks: - quantumleap - frontend + volumes: + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-docker/ + #- ./grafana/grafana.ini://etc/grafana/grafana.ini + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/ + - ./grafana/provisioning:/etc/grafana/provisioning diff --git a/grafana/provisioning/datasources/default.yaml b/grafana/provisioning/datasources/default.yaml new file mode 100644 index 0000000..79b1f0b --- /dev/null +++ b/grafana/provisioning/datasources/default.yaml @@ -0,0 +1,7 @@ +# https://grafana.com/tutorials/provision-dashboards-and-data-sources/#provision-a-data-source + +apiVersion: 1 + +datasources: + - name: TestData + type: testdata diff --git a/grafana/provisioning/datasources/timescale.yaml b/grafana/provisioning/datasources/timescale.yaml new file mode 100644 index 0000000..aea7849 --- /dev/null +++ b/grafana/provisioning/datasources/timescale.yaml @@ -0,0 +1,15 @@ +# https://grafana.com/tutorials/provision-dashboards-and-data-sources/#provision-a-data-source + +apiVersion: 1 + +datasources: + - name: "timescale" + type: "grafana-postgresql-datasource" + url: "timescale:5432" + user: "quantumleap" + jsonData: + database: "quantumleap" + sslmode: "disable" + # https://grafana.com/docs/grafana/latest/administration/provisioning/#use-environment-variables + secureJsonData: + password: "*" From 3e5575b937a88bd7d35e2cbbe6d00174fbfd342b Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 5 Mar 2026 08:17:39 +0100 Subject: [PATCH 3/4] More config --- docker-compose.prod.yml | 37 +++++++++++++++++++++++----------- docker-compose.quantumleap.yml | 16 +++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ba6cdd7..a81415e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,21 +3,32 @@ services: restart: "${ORION_RESTART:-unless-stopped}" labels: - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.entrypoints=web" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.middlewares=redirect-to-https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion-http.middlewares=redirect-to-https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion.entrypoints=websecure" - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion.rule=Host(`orion.${COMPOSE_DOMAIN:?}`)" - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_orion.loadbalancer.server.port=1026" - networks: - # Database for orion + # https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/basicauth/#users-usersfile + - "traefik.http.middlewares.orion-auth.basicauth.users=${ORION_BASICAUTH_USERS:?}" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_orion.middlewares=orion-auth" + mongo: restart: "${ORION_RESTART:-unless-stopped}" - # ---------------------------------------------------------------------------- - quantumleap: restart: "${QUANTUMLEAP_RESTART:-unless-stopped}" + labels: + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap-http.middlewares=redirect-to-https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap.rule=Host(`quantumleap.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_quantumleap.loadbalancer.server.port=8668" + + # https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/basicauth/#users-usersfile + - "traefik.http.middlewares.quantumleap-auth.basicauth.users=${QUANTUMLEAP_BASICAUTH_USERS:?}" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap.middlewares=quantumleap-auth" redis: restart: "${QUANTUMLEAP_RESTART:-unless-stopped}" @@ -25,14 +36,16 @@ services: timescale: restart: "${QUANTUMLEAP_RESTART:-unless-stopped}" - # ---------------------------------------------------------------------------- - grafana: restart: "${GRAFANA_RESTART:-unless-stopped}" labels: - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.entrypoints=web" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}-http.middlewares=redirect-to-https" - - "traefik.http.routers.scorpio_${COMPOSE_PROJECT_NAME:?}.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana-http.middlewares=redirect-to-https" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana.entrypoints=websecure" - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana.rule=Host(`grafana.${COMPOSE_DOMAIN:?}`)" - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_grafana.loadbalancer.server.port=3000" + + # https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/basicauth/#users-usersfile + - "traefik.http.middlewares.grafana-auth.basicauth.users=${GRAFANA_BASICAUTH_USERS:?}" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_grafana.middlewares=grafana-auth" diff --git a/docker-compose.quantumleap.yml b/docker-compose.quantumleap.yml index f98bbe4..c2f0ef1 100644 --- a/docker-compose.quantumleap.yml +++ b/docker-compose.quantumleap.yml @@ -40,6 +40,14 @@ services: quantumleap: image: orchestracities/quantumleap:${QL_VERSION:-latest} + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_quantumleap.rule=Host(`quantumleap.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_quantumleap.loadbalancer.server.port=8668" + # HTTPS config + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}.middlewares=redirect-to-https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" environment: LOGLEVEL: DEBUG POSTGRES_HOST: timescale @@ -58,6 +66,7 @@ services: - ./ql-config.yaml:/data/config/ql-config.yaml networks: - quantumleap + - frontend depends_on: - redis - timescale @@ -97,6 +106,13 @@ services: networks: - quantumleap - frontend + environment: + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables + # https://grafana.com/tutorials/run-grafana-behind-a-proxy/ + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#server + # - GF_SERVER_PROTOCOL=https + - GF_SERVER_DOMAIN=grafana.${COMPOSE_DOMAIN:?} + # - GF_SERVER_HTTP_PORT=80 volumes: # https://grafana.com/docs/grafana/latest/setup-grafana/configure-docker/ #- ./grafana/grafana.ini://etc/grafana/grafana.ini From e58570dbeb11eb843d2f58042493870981473ac8 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 5 Mar 2026 13:13:40 +0100 Subject: [PATCH 4/4] =?UTF-8?q?Hmm=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 16 +++++------ Taskfile.yml | 49 ++++++++++++++++++++++++++++++++++ docker-compose.quantumleap.yml | 5 ++++ 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 Taskfile.yml diff --git a/.gitignore b/.gitignore index 551e742..efc3fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.local +*.local.yml .htpasswd diff --git a/README.md b/README.md index 3f7f6c4..e14c0ae 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Based on . Start the show: ``` shell name=start -docker compose pull -docker compose up --detach --wait +task compose -- pull +task compose -- up --detach --wait open https://orion.quantumleap.local.itkdev.dk open https://grafana.quantumleap.local.itkdev.dk @@ -14,7 +14,7 @@ open https://grafana.quantumleap.local.itkdev.dk ``` shell name=orion-subscription-create # https://quantumleap.readthedocs.io/en/latest/user/using/#orion-subscription -docker compose exec --no-TTY orion curl --silent --show-error localhost:1026/v2/subscriptions --header 'content-type: application/json' --data @- <