Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .docker/data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# Except
!.gitignore
!README.md
26 changes: 26 additions & 0 deletions .docker/data/README.md
Original file line number Diff line number Diff line change
@@ -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/<container-name>`.

## 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/"
```
24 changes: 24 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
COMPOSE_PROJECT_NAME=quantumleap
COMPOSE_DOMAIN=quantumleap.local.itkdev.dk
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.local
*.local.yml
.htpasswd
22 changes: 22 additions & 0 deletions .markdownlint.jsonc
Original file line number Diff line number Diff line change
@@ -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"]
}
}
12 changes: 12 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -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/
11 changes: 11 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions 01-init.sql
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,111 @@
# IOTLab QuantumLeap

Based on <https://quantumleap.readthedocs.io/>.

Start the show:

``` shell name=start
task compose -- pull
task 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
task compose -- exec --no-TTY orion curl --silent --show-error localhost:1026/v2/subscriptions --header 'content-type: application/json' --data @- <<EOF
{
"description": "Test subscription",
"subject": {
"entities": [
{
"idPattern": ".*",
"type": "Room"
}
]
},
"notification": {
"http": {
"url": "http://quantumleap:8668/v2/notify"
},
"metadata": ["dateCreated", "dateModified"]
},
"throttling": 5
}
EOF
```

``` shell name=orion-subscriptions-get
task compose -- exec --no-TTY orion curl --silent --show-error localhost:1026/v2/subscriptions | jq
```

``` shell name=orion-entity-create
# https://fiware-orion.readthedocs.io/en/master/user/walkthrough_apiv2.html#entity-creation
task compose -- exec --no-TTY orion curl --silent --show-error localhost:1026/v2/entities --header 'content-type: application/json' --data @- <<EOF
{
"id": "Room1",
"type": "Room",
"temperature": {
"value": 23,
"type": "Float"
},
"pressure": {
"value": 720,
"type": "Integer"
}
}
EOF
```


``` shell name=orion-entity-update substitutions="{«temperature.value»: 87, «pressure.value»: 42}"
# https://fiware-orion.readthedocs.io/en/master/user/walkthrough_apiv2.html#update-entity
task compose -- exec --no-TTY orion curl --silent --show-error localhost:1026/v2/entities/Room1/attrs --header 'content-type: application/json' --data @- <<EOF
{
"temperature": {
"value": «temperature.value»,
"type": "Float"
},
"pressure": {
"value": «pressure.value»,
"type": "Float"
}
}
EOF
```

Talk to the timescale database:

``` shell name=timescale-query
task compose -- exec timescale psql quantumleap quantumleap --command '\dt'
task compose -- exec timescale psql quantumleap quantumleap --command 'SELECT * FROM etroom'
```

Generate some random data:

``` shell name=generate-random-data
while true; do
temperature=$((-20 + $RANDOM % 50))
pressure=$(($RANDOM % 1024))
echo "temperature: $temperature; pressure: $pressure"
markdown-code-runner run orion-entity-update --substitutions "{«temperature.value»: $temperature, «pressure.value»: $pressure}"
sleep 1
done
```


``` shell
markdown-code-runner run orion-subscription-create orion-subscriptions-get
markdown-code-runner run orion-entity-create generate-random-data
```

## Production

Create/edit `.env.docker.local`:

``` dotenv
COMPOSE_PROJECT_NAME=quantumleap
COMPOSE_DOMAIN=quantumleap.srvitkiotlab.itkdev.dk
COMPOSE_FILES=docker-compose.yml,docker-compose.prod.yml
```
49 changes: 49 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# yaml-language-server: $schema=https://taskfile.dev/schema.json

version: '3'

# https://taskfile.dev/usage/#env-files
dotenv:
- .env.docker.local
- .env.local
- .env

vars:
DOCKER_COMPOSE: '{{.TASK_DOCKER_COMPOSE | default "docker compose"}}'

tasks:
grafana:reset-database:
desc: Reset Grafana database
prompt: Really?
cmds:
- git restore docker-compose.quantumleap.yml
- |
git apply <<'EOF'
diff --git a/docker-compose.quantumleap.yml b/docker-compose.quantumleap.yml
index 92329be..641c646 100644
--- a/docker-compose.quantumleap.yml
+++ b/docker-compose.quantumleap.yml
@@ -122,4 +122,4 @@ services:
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/provisioning:/etc/grafana/provisioning
# Cf. environment variable GF_PATHS_DATA
- - ./.docker/data/grafana/grafana.db:/var/lib/grafana/grafana.db
+ # - ./.docker/data/grafana/grafana.db:/var/lib/grafana/grafana.db
EOF
- mkdir -p .docker/data/grafana/
- rm .docker/data/grafana/grafana.db || true
- task: compose
vars:
TASK_ARGS: up --detach --wait grafana
- task: compose
vars:
TASK_ARGS: cp grafana:/var/lib/grafana/grafana.db .docker/data/grafana/grafana.db
- git restore docker-compose.quantumleap.yml

- task: compose
vars:
TASK_ARGS: up --detach

compose:
cmds:
- '{{.DOCKER_COMPOSE}} {{.TASK_ARGS}} {{.CLI_ARGS}}'
51 changes: 51 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
services:
orion:
restart: "${ORION_RESTART:-unless-stopped}"
labels:
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "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"

# 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}"

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.${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"
Loading