From 725e3b012588f37aa596c8f08a6e45380cf3c536 Mon Sep 17 00:00:00 2001 From: Micha Date: Sun, 21 Jun 2026 22:32:41 +0200 Subject: [PATCH] Add Dawarich stack --- .gitignore | 3 +- apps/dawarich/.env.example | 13 + apps/dawarich/README.md | 152 ++++++++ apps/dawarich/docker-compose.yml | 271 +++++++++++++ apps/dawarich/grafana/dashboard-dawarich.json | 355 ++++++++++++++++++ apps/dawarich/grafana/datasource-dawarich.yml | 17 + .../homeassistant-dawarich.example.yaml | 47 +++ .../postgres/initdb/20-grafana-readonly.sh | 35 ++ apps/dawarich/prometheus-scrape.snippet.yml | 18 + .../dawarich_grafana_ro_password.txt.example | 1 + .../dawarich_metrics_password.txt.example | 1 + .../dawarich_postgres_password.txt.example | 1 + .../dawarich_redis_password.txt.example | 1 + .../dawarich_secret_key_base.txt.example | 1 + docs/SECRETS_MAP.md | 10 + docs/SERVICE_CATALOG.md | 3 + monitoring/docker-compose.yml | 12 + monitoring/grafana/dashboards/dawarich.json | 355 ++++++++++++++++++ .../provisioning/datasources/dawarich.yml | 17 + monitoring/prometheus/prometheus.yml | 10 + 20 files changed, 1322 insertions(+), 1 deletion(-) create mode 100644 apps/dawarich/.env.example create mode 100644 apps/dawarich/README.md create mode 100644 apps/dawarich/docker-compose.yml create mode 100644 apps/dawarich/grafana/dashboard-dawarich.json create mode 100644 apps/dawarich/grafana/datasource-dawarich.yml create mode 100644 apps/dawarich/homeassistant-dawarich.example.yaml create mode 100644 apps/dawarich/postgres/initdb/20-grafana-readonly.sh create mode 100644 apps/dawarich/prometheus-scrape.snippet.yml create mode 100644 apps/dawarich/secrets/dawarich_grafana_ro_password.txt.example create mode 100644 apps/dawarich/secrets/dawarich_metrics_password.txt.example create mode 100644 apps/dawarich/secrets/dawarich_postgres_password.txt.example create mode 100644 apps/dawarich/secrets/dawarich_redis_password.txt.example create mode 100644 apps/dawarich/secrets/dawarich_secret_key_base.txt.example create mode 100644 monitoring/grafana/dashboards/dawarich.json create mode 100644 monitoring/grafana/provisioning/datasources/dawarich.yml diff --git a/.gitignore b/.gitignore index 0fcceb1..31e6df0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,8 @@ !**/stack.env.example # Secrets and certificate material -**/secrets/ +**/secrets/* +!**/secrets/*.example **/letsencrypt/ **/acme.json **/*.key diff --git a/apps/dawarich/.env.example b/apps/dawarich/.env.example new file mode 100644 index 0000000..97225f2 --- /dev/null +++ b/apps/dawarich/.env.example @@ -0,0 +1,13 @@ +COMPOSE_PROJECT_NAME=dawarich + +TZ=Europe/Berlin +DAWARICH_HOST=dawarich.kaleschke.info +APPLICATION_HOSTS=dawarich.kaleschke.info + +POSTGRES_USER=dawarich +POSTGRES_DB=dawarich_production +GRAFANA_DB_USER=dawarich_grafana_ro + +METRICS_USERNAME=prometheus +BACKGROUND_PROCESSING_CONCURRENCY=5 +RAILS_MAX_THREADS=10 diff --git a/apps/dawarich/README.md b/apps/dawarich/README.md new file mode 100644 index 0000000..073d56b --- /dev/null +++ b/apps/dawarich/README.md @@ -0,0 +1,152 @@ +# Dawarich Stack + +Produktionsvorlage fuer Dawarich im KalliLab-Homelab mit GitOps ueber Gitea und Komodo. + +## Gepruefter Stand + +- Dawarich Release: `1.8.1` (GitHub latest am 2026-06-11) +- Docker Image: `freikin/dawarich:1.8.1` +- Hinweis: `freika/dawarich` existiert auf Docker Hub nicht; das offizielle Image aus dem Upstream-Compose ist `freikin/dawarich`. +- Dawarich Tracking-Endpoint fuer OwnTracks: `/api/v1/owntracks/points?api_key=` +- Dawarich Prometheus ab 1.7.7: `dawarich_app:3000/metrics`; Port `9394` ist intern fuer Sidekiq-Metriken. + +Quellen: + +- https://github.com/Freika/dawarich/releases/tag/1.8.1 +- https://dawarich.app/docs/getting-started/track-your-location/ +- https://dawarich.app/docs/self-hosting/monitoring/prometheus/ + +## Dateien + +```text +apps/dawarich/ +|-- docker-compose.yml +|-- .env.example +|-- prometheus-scrape.snippet.yml +|-- homeassistant-dawarich.example.yaml +|-- grafana/ +| |-- datasource-dawarich.yml +| `-- dashboard-dawarich.json +|-- postgres/initdb/20-grafana-readonly.sh +`-- secrets/*.txt.example +``` + +## Setup-Reihenfolge + +1. Stack-Verzeichnis nach Komodo/Gitea uebernehmen: `apps/dawarich`. +2. `.env.example` als nicht versionierte Stack-`.env` oder Komodo Stack Environment anlegen. +3. Secret-Dateien auf dem Unraid-Host erstellen: + +```bash +install -d -m 700 /mnt/user/appdata/secrets +openssl rand -base64 48 > /mnt/user/appdata/secrets/dawarich_postgres_password.txt +openssl rand -base64 48 | tr -dc 'A-Za-z0-9._~-' | head -c 48 > /mnt/user/appdata/secrets/dawarich_redis_password.txt +openssl rand -hex 64 > /mnt/user/appdata/secrets/dawarich_secret_key_base.txt +openssl rand -base64 48 > /mnt/user/appdata/secrets/dawarich_metrics_password.txt +openssl rand -base64 48 > /mnt/user/appdata/secrets/dawarich_grafana_ro_password.txt +chmod 600 /mnt/user/appdata/secrets/dawarich_*.txt +``` + +4. Bind-Volume-Zielpfade vor dem ersten Deploy anlegen: + +```bash +install -d -m 750 \ + /mnt/user/appdata/dawarich/postgres17 \ + /mnt/user/appdata/dawarich/redis \ + /mnt/user/appdata/dawarich/shared \ + /mnt/user/appdata/dawarich/public \ + /mnt/user/appdata/dawarich/watched \ + /mnt/user/appdata/dawarich/storage +``` + +5. In Komodo als Compose-Stack deployen. `frontend_net` und `backend_net` muessen bereits existieren. +6. Ersten Login in Dawarich durchfuehren und den API-Key im Account-Bereich erzeugen. +7. Home Assistant `homeassistant-dawarich.example.yaml` in das Smart-Home-Fachrepo uebernehmen und `device_tracker.your_phone` ersetzen. + +## Traefik und Authelia + +Die UI liegt auf `https://dawarich.kaleschke.info` und nutzt `authelia@file,secure-headers@file`. + +Die Tracking-API-Routen fuer OwnTracks, Overland und Traccar sind separat und priorisiert ohne Authelia geroutet, weil diese Clients per Dawarich-API-Key authentifizieren und keine Browser-ForwardAuth-Challenge verarbeiten koennen. + +## Prometheus + +`prometheus-scrape.snippet.yml` ist die dienstnahe Referenz. Produktiv ist der Job bereits in `monitoring/prometheus/prometheus.yml` eingetragen. + +Der Monitoring-Stack ist dafuer bereits vorbereitet: + +- `prometheus` haengt an `backend_net`, damit `dawarich_app` erreichbar ist. +- `/mnt/user/appdata/secrets/dawarich_metrics_password.txt` ist als Docker Secret eingebunden. + +Nicht `dawarich_app:9394` scrapen: das ist nach aktueller Dawarich-Doku veraltet. Der Web-Service aggregiert App- und Sidekiq-Metriken unter `dawarich_app:3000/metrics`. + +## Grafana + +Der Read-only-User `dawarich_grafana_ro` wird beim ersten DB-Init durch `postgres/initdb/20-grafana-readonly.sh` angelegt. + +Bei einer bereits initialisierten DB das Script einmal manuell im DB-Container ausfuehren: + +```bash +docker exec dawarich_db /docker-entrypoint-initdb.d/20-grafana-readonly.sh +``` + +Die produktive Provisionierung ist bereits in den vorhandenen Monitoring-Stack integriert: + +- Datasource: `monitoring/grafana/provisioning/datasources/dawarich.yml` +- Dashboard: `monitoring/grafana/dashboards/dawarich.json` +- Grafana haengt an `backend_net`, damit `dawarich_db:5432` erreichbar ist. +- `DAWARICH_GRAFANA_RO_PASSWORD` wird beim Grafana-Start aus `/mnt/user/appdata/secrets/dawarich_grafana_ro_password.txt` exportiert. + +## Home Assistant + +Dawarich akzeptiert OwnTracks-kompatible Location-Punkte per: + +```text +https://dawarich.kaleschke.info/api/v1/owntracks/points?api_key= +``` + +`homeassistant-dawarich.example.yaml` enthaelt: + +- `rest_command.dawarich_push_owntracks` +- Automation fuer `device_tracker`-State-Changes +- API-Key aus HA `secrets.yaml` als `dawarich_api_key` + +Alternativ existiert eine HACS-Integration `dawarich-home-assistant`; die YAML-Variante hier bleibt absichtlich transparent und GitOps-lesbar. + +## Backup mit Borg + +Borg-relevante Daten liegen unter: + +```text +/mnt/user/appdata/dawarich/postgres17 +/mnt/user/appdata/dawarich/redis +/mnt/user/appdata/dawarich/shared +/mnt/user/appdata/dawarich/public +/mnt/user/appdata/dawarich/watched +/mnt/user/appdata/dawarich/storage +/mnt/user/appdata/secrets/dawarich_*.txt +``` + +Primaerer Restore-Weg fuer die DB sollte ein logischer Dump plus Appdaten sein. Raw-Postgres-Verzeichnisse sind nur fuer gleiches Major/PostGIS-Image und sauberen Shutdown geeignet. + +Empfohlener Dump vor Borg: + +```bash +docker exec dawarich_db pg_dump -U dawarich -d dawarich_production -Fc > /mnt/user/backups/borg/dumps/latest/dawarich.dump +``` + +## Updates + +- Kein `latest` verwenden. +- Vor jedem Update Release Notes lesen, besonders bei Dawarich und PostGIS. +- Dawarich App und Sidekiq muessen immer dasselbe Image-Tag nutzen. +- PostGIS-Major-/Minor-Wechsel getrennt planen und vorher Dump plus Restore-Probe erstellen. +- Image-Digests nach Review bewusst aktualisieren. + +## Rollback + +1. Komodo Stack stoppen. +2. Vorherigen Git-Commit mit altem Image-Tag/Digest deployen. +3. Falls nur App-Code gewechselt wurde: Stack starten und Healthchecks pruefen. +4. Falls DB-Migrationen gelaufen sind: DB aus `dawarich.dump` in einen frischen PostGIS-17-Container restoren; kein blindes Zurueckkopieren eines Live-Postgres-Verzeichnisses. +5. Dawarich UI, `/api/v1/health`, Prometheus-Scrape und HA-Push testen. diff --git a/apps/dawarich/docker-compose.yml b/apps/dawarich/docker-compose.yml new file mode 100644 index 0000000..a6dce10 --- /dev/null +++ b/apps/dawarich/docker-compose.yml @@ -0,0 +1,271 @@ +name: dawarich + +x-dawarich-image: &dawarich_image freikin/dawarich:1.8.1@sha256:7c70f2169e848ed77ae1cec01dd10ec4a73a70a785d4e4d248db1735c0bc25ed + +services: + dawarich_db: + image: postgis/postgis:17-3.5-alpine@sha256:fc07e7a034e013d50ada575673b798ca6277e000b8364e39e217f612d94bd9a5 + container_name: dawarich_db + restart: unless-stopped + shm_size: 1G + environment: + TZ: ${TZ} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD_FILE: /run/secrets/dawarich_postgres_password + GRAFANA_DB_USER: ${GRAFANA_DB_USER} + PGDATA: /var/lib/postgresql/data + volumes: + - dawarich_db_data:/var/lib/postgresql/data + - dawarich_shared:/var/shared + - ./postgres/initdb:/docker-entrypoint-initdb.d:ro + networks: + - backend_net + secrets: + - dawarich_postgres_password + - dawarich_grafana_ro_password + expose: + - "5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 10s + retries: 5 + start_period: 30s + security_opt: + - no-new-privileges:true + + dawarich_redis: + image: redis:7-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99 + container_name: dawarich_redis + restart: unless-stopped + command: + - /bin/sh + - -lc + - | + exec redis-server \ + --save 900 1 \ + --save 300 10 \ + --appendonly no \ + --requirepass "$$(cat /run/secrets/dawarich_redis_password)" + volumes: + - dawarich_redis_data:/data + networks: + - backend_net + secrets: + - dawarich_redis_password + expose: + - "6379" + healthcheck: + test: ["CMD-SHELL", "redis-cli -a \"$$(cat /run/secrets/dawarich_redis_password)\" --raw incr ping >/dev/null"] + interval: 10s + timeout: 10s + retries: 5 + start_period: 30s + security_opt: + - no-new-privileges:true + + dawarich_app: + image: *dawarich_image + container_name: dawarich_app + restart: unless-stopped + stdin_open: true + tty: true + entrypoint: + - /bin/sh + - -lc + command: + - | + export DATABASE_PASSWORD="$$(cat /run/secrets/dawarich_postgres_password)" + export REDIS_URL="redis://:$$(cat /run/secrets/dawarich_redis_password)@dawarich_redis:6379/0" + export SECRET_KEY_BASE="$$(cat /run/secrets/dawarich_secret_key_base)" + export METRICS_PASSWORD="$$(cat /run/secrets/dawarich_metrics_password)" + exec web-entrypoint.sh bin/rails server -p 3000 -b :: + environment: + TZ: ${TZ} + RAILS_ENV: production + DATABASE_HOST: dawarich_db + DATABASE_PORT: "5432" + DATABASE_USERNAME: ${POSTGRES_USER} + DATABASE_NAME: ${POSTGRES_DB} + APPLICATION_HOSTS: ${APPLICATION_HOSTS} + APPLICATION_PROTOCOL: https + TIME_ZONE: ${TZ} + SELF_HOSTED: "true" + STORE_GEODATA: "true" + RAILS_LOG_TO_STDOUT: "true" + PROMETHEUS_EXPORTER_ENABLED: "true" + METRICS_USERNAME: ${METRICS_USERNAME} + SIDEKIQ_METRICS_URL: http://dawarich_sidekiq:9394/metrics + BACKGROUND_PROCESSING_CONCURRENCY: ${BACKGROUND_PROCESSING_CONCURRENCY} + RAILS_MAX_THREADS: ${RAILS_MAX_THREADS} + volumes: + - dawarich_public:/var/app/public + - dawarich_watched:/var/app/tmp/imports/watched + - dawarich_storage:/var/app/storage + - dawarich_db_data:/dawarich_db_data:ro + networks: + - frontend_net + - backend_net + secrets: + - dawarich_postgres_password + - dawarich_redis_password + - dawarich_secret_key_base + - dawarich_metrics_password + expose: + - "3000" + healthcheck: + test: ["CMD-SHELL", "wget -qO - http://127.0.0.1:3000/api/v1/health | grep -q '\"status\"[[:space:]]*:[[:space:]]*\"ok\"'"] + interval: 10s + timeout: 10s + retries: 30 + start_period: 30s + depends_on: + dawarich_db: + condition: service_healthy + dawarich_redis: + condition: service_healthy + security_opt: + - no-new-privileges:true + labels: + - traefik.enable=true + - traefik.docker.network=frontend_net + + # Public API-key endpoints for mobile apps and Home Assistant pushes. + - traefik.http.routers.dawarich-api.rule=Host(`${DAWARICH_HOST}`) && (Path(`/api/v1/owntracks/points`) || Path(`/api/v1/overland/batches`) || Path(`/api/v1/traccar/points`)) + - traefik.http.routers.dawarich-api.entrypoints=websecure + - traefik.http.routers.dawarich-api.tls=true + - traefik.http.routers.dawarich-api.tls.certresolver=le + - traefik.http.routers.dawarich-api.priority=100 + - traefik.http.routers.dawarich-api.middlewares=secure-headers@file + - traefik.http.routers.dawarich-api.service=dawarich + + # UI and all other routes require Authelia ForwardAuth. + - traefik.http.routers.dawarich.rule=Host(`${DAWARICH_HOST}`) + - traefik.http.routers.dawarich.entrypoints=websecure + - traefik.http.routers.dawarich.tls=true + - traefik.http.routers.dawarich.tls.certresolver=le + - traefik.http.routers.dawarich.priority=10 + - traefik.http.routers.dawarich.middlewares=authelia@file,secure-headers@file + - traefik.http.routers.dawarich.service=dawarich + - traefik.http.services.dawarich.loadbalancer.server.port=3000 + + dawarich_sidekiq: + image: *dawarich_image + container_name: dawarich_sidekiq + restart: unless-stopped + stdin_open: true + tty: true + entrypoint: + - /bin/sh + - -lc + command: + - | + export DATABASE_PASSWORD="$$(cat /run/secrets/dawarich_postgres_password)" + export REDIS_URL="redis://:$$(cat /run/secrets/dawarich_redis_password)@dawarich_redis:6379/0" + export SECRET_KEY_BASE="$$(cat /run/secrets/dawarich_secret_key_base)" + export METRICS_PASSWORD="$$(cat /run/secrets/dawarich_metrics_password)" + exec sidekiq-entrypoint.sh sidekiq + environment: + TZ: ${TZ} + RAILS_ENV: production + DATABASE_HOST: dawarich_db + DATABASE_PORT: "5432" + DATABASE_USERNAME: ${POSTGRES_USER} + DATABASE_NAME: ${POSTGRES_DB} + APPLICATION_HOSTS: ${APPLICATION_HOSTS} + APPLICATION_PROTOCOL: https + TIME_ZONE: ${TZ} + SELF_HOSTED: "true" + STORE_GEODATA: "true" + RAILS_LOG_TO_STDOUT: "true" + PROMETHEUS_EXPORTER_ENABLED: "true" + PROMETHEUS_EXPORTER_PORT: "9394" + METRICS_USERNAME: ${METRICS_USERNAME} + BACKGROUND_PROCESSING_CONCURRENCY: ${BACKGROUND_PROCESSING_CONCURRENCY} + RAILS_MAX_THREADS: ${RAILS_MAX_THREADS} + volumes: + - dawarich_public:/var/app/public + - dawarich_watched:/var/app/tmp/imports/watched + - dawarich_storage:/var/app/storage + networks: + - frontend_net + - backend_net + secrets: + - dawarich_postgres_password + - dawarich_redis_password + - dawarich_secret_key_base + - dawarich_metrics_password + expose: + - "9394" + healthcheck: + test: ["CMD-SHELL", "pgrep -f sidekiq >/dev/null"] + interval: 10s + timeout: 10s + retries: 30 + start_period: 30s + depends_on: + dawarich_db: + condition: service_healthy + dawarich_redis: + condition: service_healthy + dawarich_app: + condition: service_healthy + security_opt: + - no-new-privileges:true + +networks: + frontend_net: + external: true + backend_net: + external: true + +volumes: + dawarich_db_data: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/postgres17 + dawarich_redis_data: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/redis + dawarich_shared: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/shared + dawarich_public: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/public + dawarich_watched: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/watched + dawarich_storage: + driver: local + driver_opts: + type: none + o: bind + device: /mnt/user/appdata/dawarich/storage + +secrets: + dawarich_postgres_password: + file: /mnt/user/appdata/secrets/dawarich_postgres_password.txt + dawarich_redis_password: + file: /mnt/user/appdata/secrets/dawarich_redis_password.txt + dawarich_secret_key_base: + file: /mnt/user/appdata/secrets/dawarich_secret_key_base.txt + dawarich_metrics_password: + file: /mnt/user/appdata/secrets/dawarich_metrics_password.txt + dawarich_grafana_ro_password: + file: /mnt/user/appdata/secrets/dawarich_grafana_ro_password.txt diff --git a/apps/dawarich/grafana/dashboard-dawarich.json b/apps/dawarich/grafana/dashboard-dawarich.json new file mode 100644 index 0000000..cb9ed9a --- /dev/null +++ b/apps/dawarich/grafana/dashboard-dawarich.json @@ -0,0 +1,355 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 16, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.55, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 4, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "latitude": "latitude", + "longitude": "longitude", + "mode": "coords" + }, + "name": "Location points", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "fit", + "lat": 51, + "lon": 10, + "zoom": 5 + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\n to_timestamp(timestamp) AS \"time\",\n ST_Y(lonlat::geometry) AS latitude,\n ST_X(lonlat::geometry) AS longitude,\n accuracy,\n tracker_id\nFROM points\nWHERE $__unixEpochFilter(timestamp)\n AND lonlat IS NOT NULL\nORDER BY timestamp DESC\nLIMIT 20000;", + "refId": "A" + } + ], + "title": "Location Points", + "type": "geomap" + }, + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "km" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT\n make_date(year, month, 1)::timestamp AS \"time\",\n round((distance::numeric / 1000.0), 2) AS \"km\"\nFROM stats\nWHERE make_date(year, month, 1)::timestamp BETWEEN $__timeFrom() AND $__timeTo()\nORDER BY 1;", + "refId": "A" + } + ], + "title": "Kilometers per Month", + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT\n date_trunc('day', to_timestamp(timestamp)) AS \"time\",\n count(*) AS \"points\"\nFROM points\nWHERE $__unixEpochFilter(timestamp)\nGROUP BY 1\nORDER BY 1;", + "refId": "A" + } + ], + "title": "Points per Day", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "dawarich", + "location" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Dawarich", + "uid": "dawarich", + "version": 1, + "weekStart": "" +} diff --git a/apps/dawarich/grafana/datasource-dawarich.yml b/apps/dawarich/grafana/datasource-dawarich.yml new file mode 100644 index 0000000..54f499f --- /dev/null +++ b/apps/dawarich/grafana/datasource-dawarich.yml @@ -0,0 +1,17 @@ +apiVersion: 1 + +datasources: + - name: Dawarich PostgreSQL + uid: dawarich-postgres + type: postgres + access: proxy + url: dawarich_db:5432 + database: dawarich_production + user: dawarich_grafana_ro + editable: false + jsonData: + sslmode: disable + postgresVersion: 1700 + timescaledb: false + secureJsonData: + password: $DAWARICH_GRAFANA_RO_PASSWORD diff --git a/apps/dawarich/homeassistant-dawarich.example.yaml b/apps/dawarich/homeassistant-dawarich.example.yaml new file mode 100644 index 0000000..b9b9106 --- /dev/null +++ b/apps/dawarich/homeassistant-dawarich.example.yaml @@ -0,0 +1,47 @@ +# Add `dawarich_api_key` to Home Assistant `secrets.yaml`. +# The endpoint is the current OwnTracks-compatible Dawarich endpoint: +# https:///api/v1/owntracks/points?api_key= + +rest_command: + dawarich_push_owntracks: + url: "https://dawarich.kaleschke.info/api/v1/owntracks/points?api_key={{ api_key }}" + method: POST + content_type: "application/json" + payload: >- + { + "_type": "location", + "lat": {{ latitude }}, + "lon": {{ longitude }}, + "tst": {{ timestamp }}, + "acc": {{ accuracy | default(0) }}, + "alt": {{ altitude | default(0) }}, + "batt": {{ battery | default(0) }}, + "tid": "{{ tracker_id[:2] }}" + } + +automation: + - id: dawarich_push_device_tracker_location + alias: Dawarich - push device tracker location + mode: queued + max: 20 + trigger: + - platform: state + entity_id: + - device_tracker.your_phone + condition: + - condition: template + value_template: >- + {{ trigger.to_state is not none + and state_attr(trigger.entity_id, 'latitude') is number + and state_attr(trigger.entity_id, 'longitude') is number }} + action: + - service: rest_command.dawarich_push_owntracks + data: + api_key: !secret dawarich_api_key + tracker_id: "{{ trigger.entity_id.split('.')[1] }}" + latitude: "{{ state_attr(trigger.entity_id, 'latitude') }}" + longitude: "{{ state_attr(trigger.entity_id, 'longitude') }}" + accuracy: "{{ state_attr(trigger.entity_id, 'gps_accuracy') | default(0, true) }}" + altitude: "{{ state_attr(trigger.entity_id, 'altitude') | default(0, true) }}" + battery: "{{ state_attr(trigger.entity_id, 'battery_level') | default(0, true) }}" + timestamp: "{{ as_timestamp(trigger.to_state.last_updated) | int }}" diff --git a/apps/dawarich/postgres/initdb/20-grafana-readonly.sh b/apps/dawarich/postgres/initdb/20-grafana-readonly.sh new file mode 100644 index 0000000..2654090 --- /dev/null +++ b/apps/dawarich/postgres/initdb/20-grafana-readonly.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -eu + +GRAFANA_USER="${GRAFANA_DB_USER:-dawarich_grafana_ro}" +GRAFANA_PASSWORD="$(cat /run/secrets/dawarich_grafana_ro_password)" + +sql_ident() { + printf '"%s"' "$(printf '%s' "$1" | sed 's/"/""/g')" +} + +sql_literal() { + printf "'%s'" "$(printf '%s' "$1" | sed "s/'/''/g")" +} + +DB_IDENT="$(sql_ident "$POSTGRES_DB")" +USER_IDENT="$(sql_ident "$GRAFANA_USER")" +USER_LITERAL="$(sql_literal "$GRAFANA_USER")" +PASSWORD_LITERAL="$(sql_literal "$GRAFANA_PASSWORD")" + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < InfluxDB | Datasource Token | `/mnt/user/appdata/secrets/monitoring_grafana_influxdb_token.txt` -> Docker Secret `/run/secrets/monitoring_grafana_influxdb_token` | aktiv | | Grafana OIDC (Authelia) | Client Secret | `/mnt/user/appdata/secrets/grafana_oidc_client_secret` (Klartext, chmod 600) -> Docker Secret `/run/secrets/grafana_oidc_client_secret` -> `GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE`. Zugehoeriger pbkdf2-Hash liegt im Authelia-Host-Config-Client `grafana` (kein Wert im Repo) | aktiv (2026-06-06) | | Mealie OIDC (Authelia) | Client Secret | Stack-ENV `${MEALIE_OIDC_CLIENT_SECRET}` in `/mnt/user/services/stacks/mealie/apps/mealie/.env` (Komodo-Stack-ENV); pbkdf2-Hash im Authelia-Host-Config-Client `mealie` (kein Wert im Repo) | aktiv (2026-06-06) | +| Dawarich | DB Password | `/mnt/user/appdata/secrets/dawarich_postgres_password.txt` -> Docker Secret `/run/secrets/dawarich_postgres_password`; Postgres nutzt `POSTGRES_PASSWORD_FILE`, App/Sidekiq lesen per Entrypoint-Export | geplant | +| Dawarich | Redis Password | `/mnt/user/appdata/secrets/dawarich_redis_password.txt` -> Docker Secret `/run/secrets/dawarich_redis_password`; Redis `--requirepass`, App/Sidekiq `REDIS_URL` | geplant | +| Dawarich | Rails `SECRET_KEY_BASE` | `/mnt/user/appdata/secrets/dawarich_secret_key_base.txt` -> Docker Secret `/run/secrets/dawarich_secret_key_base` | geplant | +| Dawarich Metrics | Basic-Auth Password | `/mnt/user/appdata/secrets/dawarich_metrics_password.txt` -> Docker Secret `/run/secrets/dawarich_metrics_password`; Prometheus `password_file` | geplant | +| Grafana -> Dawarich | Read-only DB Password | `/mnt/user/appdata/secrets/dawarich_grafana_ro_password.txt` -> Docker Secret `/run/secrets/dawarich_grafana_ro_password`; Grafana-Env `DAWARICH_GRAFANA_RO_PASSWORD` | geplant | | Renovate Bot | Gitea Service-Account PAT | `/mnt/user/appdata/secrets/renovate_token.txt` -> Host-Datei (chmod 600), gelesen von `ops/renovate/run-renovate.sh` und an Renovate-Container als `RENOVATE_TOKEN` weitergegeben | aktiv nach Operator-Setup (siehe `docs/RENOVATE.md`) | | n8n | Encryption Key fuer interne Credential-Verschluesselung | `/mnt/user/appdata/secrets/n8n_encryption_key.txt` (chmod 600) -> Komodo Stack ENV `${N8N_ENCRYPTION_KEY}`; kein `_FILE`-Support im Upstream-Image | aktiv | | n8n | GMX IMAP Login (Mail-Trigger Workflow) | n8n Credentials Store (Typ `imap`), nur in `/mnt/user/appdata/n8n/data` mit `N8N_ENCRYPTION_KEY` verschluesselt | aktiv | @@ -108,6 +113,11 @@ Dieses Dokument listet sensible Daten, deren Ablageorte und die vorgesehene Einb |-- ha_token_codex |-- filebrowser_admin_password.txt |-- homelab_smtp_password.txt +|-- dawarich_postgres_password.txt +|-- dawarich_redis_password.txt +|-- dawarich_secret_key_base.txt +|-- dawarich_metrics_password.txt +|-- dawarich_grafana_ro_password.txt `-- vaultwarden_admin_token.txt ``` diff --git a/docs/SERVICE_CATALOG.md b/docs/SERVICE_CATALOG.md index bc2b478..d49fc05 100644 --- a/docs/SERVICE_CATALOG.md +++ b/docs/SERVICE_CATALOG.md @@ -43,6 +43,9 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und | `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default`, `immich_egress` | `model-cache` | rebuildbar | nein | keine Traefik-Route; `immich_egress` (nicht-internal) nur fuer Modell-Download zu huggingface, sonst scheitert Smart Search/Gesichtserkennung an DNS | | `mealie` | Rezeptverwaltung | `apps/mealie/docker-compose.yml` | `https://mealie.kaleschke.info` | `mealie-postgres`, Traefik | `/mnt/user/appdata/mealie/data` | Tier 2, Borg + `mealie.dump` | ja | App + DB in internem Netz getrennt | | `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 | +| `dawarich_app` | Standort-Historie / Google-Timeline-Ersatz | `apps/dawarich/docker-compose.yml` | `https://dawarich.kaleschke.info` | eigene PostGIS-DB, eigene Redis, Traefik + Authelia, optional Home Assistant Push | `/mnt/user/appdata/dawarich/{postgres17,redis,shared,public,watched,storage}`, `dawarich_*.txt` Secrets | Tier 2, Borg + `dawarich.dump` | ja + Authelia | UI hinter Authelia; API-Key-Tracking-Endpunkte fuer OwnTracks/Overland/Traccar ohne ForwardAuth priorisiert. App und Sidekiq nutzen `freikin/dawarich:1.8.1`; Prometheus-Scrape nach aktueller Dawarich-Doku ueber `dawarich_app:3000/metrics`, Sidekiq-Metriken intern ueber `:9394`. | +| `dawarich_db` | Dawarich PostGIS-Datenbank | `apps/dawarich/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/dawarich/postgres17`, `dawarich_postgres_password.txt`, `dawarich_grafana_ro_password.txt` | Dump `dawarich.dump`; raw DB nur bei gleichem PG/PostGIS und sauberem Shutdown | nein | PostGIS 17-3.5 Alpine; Grafana-Read-only-User `dawarich_grafana_ro` per Init-Script | +| `dawarich_redis` | Dawarich Cache/Queue-Backend | `apps/dawarich/docker-compose.yml` | intern | `backend_net` | `/mnt/user/appdata/dawarich/redis`, `dawarich_redis_password.txt` | Teil von Dawarich-Restore, aber aus DB/Appdaten rekonstruierbar | nein | Redis 7 Alpine, keine Host-Ports | | `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht; Dump-Dateiname behaelt den historischen Cluster-Namen | | `nextcloud` | Datei-/Cloud-Dienst | `apps/nextcloud/docker-compose.yml` | `https://cloud.kaleschke.info` | eigene PostgreSQL, eigene Redis, Traefik | `/mnt/user/appdata/nextcloud/html`, `/mnt/user/documents/nextcloud-data` | Tier 2, `nextcloud.dump` + Share | ja | native App-Auth ohne zentrale ForwardAuth; WebDAV/CardDAV beachten | | `nextcloud-postgres` | Nextcloud-Datenbank | `apps/nextcloud/docker-compose.yml` | intern | `nextcloud_internal` | `/mnt/user/appdata/nextcloud/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/nextcloud-postgres17`, `nextcloud_postgres_password.txt` | `nextcloud.dump`, raw DB nicht primaerer Restore-Weg | nein | interne DB; PostgreSQL 18 | diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 8840c9b..72e19bb 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -17,8 +17,11 @@ services: - prometheus_data:/prometheus networks: - monitoring_net + - backend_net expose: - "9090" + secrets: + - dawarich_metrics_password security_opt: - no-new-privileges:true depends_on: @@ -165,6 +168,7 @@ services: - -c - | export GRAFANA_INFLUXDB_TOKEN="$$(cat /run/secrets/monitoring_grafana_influxdb_token)" + export DAWARICH_GRAFANA_RO_PASSWORD="$$(cat /run/secrets/dawarich_grafana_ro_password)" exec /run.sh volumes: - grafana_data:/var/lib/grafana @@ -173,10 +177,12 @@ services: networks: - monitoring_net - frontend_net + - backend_net secrets: - monitoring_grafana_admin_password - monitoring_grafana_influxdb_token - grafana_oidc_client_secret + - dawarich_grafana_ro_password expose: - "3000" security_opt: @@ -390,6 +396,8 @@ networks: driver: bridge frontend_net: external: true + backend_net: + external: true dns_net: external: true @@ -409,3 +417,7 @@ secrets: file: /mnt/user/appdata/secrets/grafana_oidc_client_secret influxdb3_admin_token: file: /mnt/user/appdata/secrets/influxdb3_admin_token.json + dawarich_metrics_password: + file: /mnt/user/appdata/secrets/dawarich_metrics_password.txt + dawarich_grafana_ro_password: + file: /mnt/user/appdata/secrets/dawarich_grafana_ro_password.txt diff --git a/monitoring/grafana/dashboards/dawarich.json b/monitoring/grafana/dashboards/dawarich.json new file mode 100644 index 0000000..cb9ed9a --- /dev/null +++ b/monitoring/grafana/dashboards/dawarich.json @@ -0,0 +1,355 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 16, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.55, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 4, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "latitude": "latitude", + "longitude": "longitude", + "mode": "coords" + }, + "name": "Location points", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "fit", + "lat": 51, + "lon": 10, + "zoom": 5 + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT\n to_timestamp(timestamp) AS \"time\",\n ST_Y(lonlat::geometry) AS latitude,\n ST_X(lonlat::geometry) AS longitude,\n accuracy,\n tracker_id\nFROM points\nWHERE $__unixEpochFilter(timestamp)\n AND lonlat IS NOT NULL\nORDER BY timestamp DESC\nLIMIT 20000;", + "refId": "A" + } + ], + "title": "Location Points", + "type": "geomap" + }, + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "km" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT\n make_date(year, month, 1)::timestamp AS \"time\",\n round((distance::numeric / 1000.0), 2) AS \"km\"\nFROM stats\nWHERE make_date(year, month, 1)::timestamp BETWEEN $__timeFrom() AND $__timeTo()\nORDER BY 1;", + "refId": "A" + } + ], + "title": "Kilometers per Month", + "type": "timeseries" + }, + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "13.0.2", + "targets": [ + { + "datasource": { + "type": "postgres", + "uid": "dawarich-postgres" + }, + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT\n date_trunc('day', to_timestamp(timestamp)) AS \"time\",\n count(*) AS \"points\"\nFROM points\nWHERE $__unixEpochFilter(timestamp)\nGROUP BY 1\nORDER BY 1;", + "refId": "A" + } + ], + "title": "Points per Day", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "dawarich", + "location" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Dawarich", + "uid": "dawarich", + "version": 1, + "weekStart": "" +} diff --git a/monitoring/grafana/provisioning/datasources/dawarich.yml b/monitoring/grafana/provisioning/datasources/dawarich.yml new file mode 100644 index 0000000..54f499f --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/dawarich.yml @@ -0,0 +1,17 @@ +apiVersion: 1 + +datasources: + - name: Dawarich PostgreSQL + uid: dawarich-postgres + type: postgres + access: proxy + url: dawarich_db:5432 + database: dawarich_production + user: dawarich_grafana_ro + editable: false + jsonData: + sslmode: disable + postgresVersion: 1700 + timescaledb: false + secureJsonData: + password: $DAWARICH_GRAFANA_RO_PASSWORD diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml index 7798b16..160f418 100644 --- a/monitoring/prometheus/prometheus.yml +++ b/monitoring/prometheus/prometheus.yml @@ -36,6 +36,16 @@ scrape_configs: - targets: - traefik:8082 + - job_name: dawarich + metrics_path: /metrics + basic_auth: + username: prometheus + password_file: /run/secrets/dawarich_metrics_password + static_configs: + # Dawarich >= 1.7.7 serves aggregated web + Sidekiq metrics here. + - targets: + - dawarich_app:3000 + - job_name: blackbox-http metrics_path: /probe params: