I have added a lot of changes from various ai sessions
This commit is contained in:
parent
68345648d9
commit
15e802bebc
|
|
@ -122,6 +122,9 @@ kube_proxy_mode: iptables
|
||||||
# Moritz: changed to iptables for tegra
|
# Moritz: changed to iptables for tegra
|
||||||
#kube_proxy_mode: ipvs
|
#kube_proxy_mode: ipvs
|
||||||
|
|
||||||
|
# Expose kube-proxy metrics on all interfaces so Prometheus can scrape port 10249
|
||||||
|
kube_proxy_metrics_bind_address: "0.0.0.0:10249"
|
||||||
|
|
||||||
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
|
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
|
||||||
# must be set to true for MetalLB to work
|
# must be set to true for MetalLB to work
|
||||||
kube_proxy_strict_arp: false
|
kube_proxy_strict_arp: false
|
||||||
|
|
|
||||||
|
|
@ -121,19 +121,24 @@ Backup tool. See chart [README](https://github.com/vmware-tanzu/helm-charts/blob
|
||||||
```sh
|
```sh
|
||||||
helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
||||||
helm repo update
|
helm repo update
|
||||||
helm upgrade --install --create-namespace --namespace velero -f ./velero/velero.secret.yaml velero vmware-tanzu/velero --version 5.3.0
|
# Apply CRDs first (required on upgrades across major versions)
|
||||||
kubectl create secret generic rclone-config --from-file=./velero/rclone.secret
|
helm show crds vmware-tanzu/velero --version 12.1.0 | kubectl apply -f -
|
||||||
kubectl apply -f velero/dropbox_sync.yml
|
helm upgrade --install --create-namespace --namespace velero -f ./velero/velero.secret.yaml velero vmware-tanzu/velero --version 12.1.0
|
||||||
# #
|
# Secrets for rclone (minio + gdrive)
|
||||||
|
kubectl create secret generic rclone-config --from-file=./velero/rclone.secret -n velero
|
||||||
|
kubectl create secret generic gdrive-sa-key --from-file=gdrive_sa.json.secret=./velero/gdrive_sa.json.secret -n velero
|
||||||
|
# Deploy GDrive sync cronjob
|
||||||
|
kubectl apply -f velero/gdrive_sync.yml
|
||||||
|
# Teardown
|
||||||
helm delete velero -n velero
|
helm delete velero -n velero
|
||||||
kubectl delete ns velero
|
kubectl delete ns velero
|
||||||
```
|
```
|
||||||
|
|
||||||
A manual backup may be created executing the following command. **Note: Keep backuped namespaces in sync with config from helm chart!!!**
|
A manual backup may be created executing the following command. **Note: Keep backed-up namespaces in sync with config from helm chart!!!**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
DATE=$( date +%Y%m%d )
|
DATE=$( date +%Y%m%d )
|
||||||
velero backup create $DATE --include-namespaces datalab,development,nextcloud,tt-rss,mailu --wait
|
velero backup create $DATE --include-namespaces coder,home-assistant,n8n,influxdb --wait
|
||||||
```
|
```
|
||||||
|
|
||||||
## coder
|
## coder
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,4 @@ spec:
|
||||||
solvers:
|
solvers:
|
||||||
- http01:
|
- http01:
|
||||||
ingress:
|
ingress:
|
||||||
class: nginx
|
ingressClassName: traefik
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,4 @@ spec:
|
||||||
solvers:
|
solvers:
|
||||||
- http01:
|
- http01:
|
||||||
ingress:
|
ingress:
|
||||||
class: nginx
|
ingressClassName: traefik
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
||||||
|
# this token has been generated to enable AI to access HASS
|
||||||
|
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2NDMyZTI1MDIzZjU0ZmFiYTU3OTc0ODYyMzg3M2ExZSIsImlhdCI6MTc4MjczMTY4NiwiZXhwIjoyMDk4MDkxNjg2fQ.u8kFHoYL2kdhVenfe3BiWcNkN_en3yn91MRGSVJvbmw
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
# kind: ConfigMap
|
|
||||||
# metadata:
|
|
||||||
# creationTimestamp: null
|
|
||||||
# name: rclone-dropbox-config
|
|
||||||
# namespace: mailu
|
|
||||||
# apiVersion: v1
|
|
||||||
# data:
|
|
||||||
# rclone.conf: |
|
|
||||||
# [dropbox]
|
|
||||||
# type = dropbox
|
|
||||||
# token = {"access_token":"17hvEArIh3cAAAAAAAGTXUfwkGdz8EC_rDcEseLmSRltQoUz_AVceuUewx0sGMQ1","token_type":"bearer","expiry":"0001-01-01T00:00:00Z"}
|
|
||||||
# ---
|
|
||||||
# apiVersion: batch/v1beta1
|
|
||||||
# kind: CronJob
|
|
||||||
# metadata:
|
|
||||||
# name: backup-mailu
|
|
||||||
# namespace: mailu
|
|
||||||
# spec:
|
|
||||||
# schedule: "30 4 * * *"
|
|
||||||
# jobTemplate:
|
|
||||||
# spec:
|
|
||||||
# template:
|
|
||||||
# spec:
|
|
||||||
# containers:
|
|
||||||
# - name: backup-mailu
|
|
||||||
# image: iptizer/swiss
|
|
||||||
# volumeMounts:
|
|
||||||
# - name: rclone-dropbox-config
|
|
||||||
# mountPath: /rclone.conf
|
|
||||||
# subPath: "rclone.conf"
|
|
||||||
# - mountPath: /data/
|
|
||||||
# name: data
|
|
||||||
# command:
|
|
||||||
# - "/bin/bash"
|
|
||||||
# - "-c"
|
|
||||||
# args:
|
|
||||||
# - "cd / && tar zcvf - /data/ | /usr/bin/rclone --config /rclone.conf -v rcat dropbox:server_backup/$( /bin/expr $( date +%m ) % 3 )_$( /bin/expr $( date +%j ) % 3 )_mailu.tar.gz"
|
|
||||||
# volumes:
|
|
||||||
# - name: rclone-dropbox-config
|
|
||||||
# configMap:
|
|
||||||
# name: rclone-dropbox-config
|
|
||||||
# - name: data
|
|
||||||
# persistentVolumeClaim:
|
|
||||||
# claimName: mailu-storage
|
|
||||||
# restartPolicy: Never
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
# DO NOT USE MASTER!!!!
|
|
||||||
#mailuVersion: master
|
|
||||||
# DO NOT USE MASTER!!!
|
|
||||||
|
|
||||||
hostnames:
|
|
||||||
- mail.moritzgraf.de
|
|
||||||
domain: moritzgraf.de
|
|
||||||
secretKey: fa5faeD9aegietaesahbiequ5Pe9au
|
|
||||||
subnet: 10.233.0.0/16
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: nginx
|
|
||||||
|
|
||||||
clamav:
|
|
||||||
enabled: false
|
|
||||||
front:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 10Mi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 200Mi
|
|
||||||
cpu: 200m
|
|
||||||
admin:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 20Mi
|
|
||||||
cpu: 50m
|
|
||||||
limits:
|
|
||||||
memory: 500Mi
|
|
||||||
cpu: 500m
|
|
||||||
|
|
||||||
dovecot:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 50Mi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 500Mi
|
|
||||||
cpu: 500m
|
|
||||||
postfix:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 1Gi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 2Gi
|
|
||||||
cpu: 500m
|
|
||||||
|
|
||||||
redis:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 20Mi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 300Mi
|
|
||||||
cpu: 200m
|
|
||||||
roundcube:
|
|
||||||
enabled: true
|
|
||||||
logLevel: INFO
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 10Mi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 200Mi
|
|
||||||
cpu: 200m
|
|
||||||
rspamd:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: 10Mi
|
|
||||||
cpu: 10m
|
|
||||||
limits:
|
|
||||||
memory: 2000Mi
|
|
||||||
cpu: 600m
|
|
||||||
certmanager:
|
|
||||||
issuerType: ClusterIssuer
|
|
||||||
issuerName: "letsencrypt-prod"
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
persistence:
|
|
||||||
storageClass: openebs-hostpath
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
creationTimestamp: null
|
|
||||||
labels:
|
|
||||||
run: restore
|
|
||||||
name: restore
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: iptizer/swiss
|
|
||||||
name: restore
|
|
||||||
resources: {}
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /data/
|
|
||||||
name: data
|
|
||||||
command:
|
|
||||||
- "sleep"
|
|
||||||
- "3600"
|
|
||||||
dnsPolicy: ClusterFirst
|
|
||||||
restartPolicy: Never
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: mailu-storage
|
|
||||||
status: {}
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,164 @@
|
||||||
|
# AGENTS.md — Monitoring Folder Guide for AI Agents
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This folder manages the **kube-prometheus-stack** deployment and **Grafana dashboards as code** for the `haumdaucher` Kubernetes cluster. The monitoring stack runs in the `monitoring` namespace.
|
||||||
|
|
||||||
|
## Folder Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
monitoring/
|
||||||
|
├── AGENTS.md ← You are here
|
||||||
|
├── README.md ← Human-oriented deployment guide
|
||||||
|
├── DASHBOARD_CREATION.md ← How to create/update dashboards (read this!)
|
||||||
|
├── prometheus-operator.secret.yml ← Helm values for kube-prometheus-stack
|
||||||
|
├── alertmanagerconfig.secret.yaml ← Alertmanager config (Telegram alerts)
|
||||||
|
├── servicemonitor.secret.yml ← ServiceMonitor examples (commented out)
|
||||||
|
├── tankerkoenig.yml ← Tankerkoenig fuel price exporter
|
||||||
|
├── dashboards/ ← Grafana dashboards as ConfigMaps
|
||||||
|
│ ├── grafana-dashboard-home-climate.yaml
|
||||||
|
│ ├── grafana-dashboard-energy-monitor.yaml
|
||||||
|
│ ├── grafana-dashboard-heating-gas.yaml
|
||||||
|
│ └── grafana-dashboard-outdoor-weather.yaml
|
||||||
|
└── scripts/ ← Discovery & helper scripts
|
||||||
|
├── discover-influxdb.sh ← Query InfluxDB schema
|
||||||
|
└── discover-home-assistant.sh ← Query Home Assistant entities
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Infrastructure Context
|
||||||
|
|
||||||
|
### Namespaces & Services
|
||||||
|
|
||||||
|
| Namespace | Service | Purpose |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| `monitoring` | kube-prometheus-stack | Prometheus, Alertmanager, Grafana |
|
||||||
|
| `influxdb` | InfluxDB2 | Time-series storage for Home Assistant sensor data |
|
||||||
|
| `home-assistant` | Home Assistant | Smart home hub, writes sensor data to InfluxDB |
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Home Assistant sensors → InfluxDB (bucket: default, org: influxdata)
|
||||||
|
↓
|
||||||
|
Grafana (Flux queries via datasource)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grafana Datasources
|
||||||
|
|
||||||
|
| Name | UID | Type | URL |
|
||||||
|
|------|-----|------|-----|
|
||||||
|
| InfluxDB Home Assistant | `P2AB959DC95E5519F` | influxdb (Flux) | `http://influxdb-influxdb2.influxdb.svc.cluster.local:80` |
|
||||||
|
| InfluxDB Home Assistant InfluxQL | `P86455D1023ECC470` | influxdb (InfluxQL) | same |
|
||||||
|
| Prometheus | `prometheus` | prometheus | internal |
|
||||||
|
|
||||||
|
> **⚠️ IMPORTANT**: The datasource UIDs above are the **current** values. If the Grafana deployment is recreated, these UIDs will change. Always re-discover UIDs via `kubectl -n monitoring exec deploy/prometheus-operator-grafana -c grafana -- curl -s -u "admin:<password>" http://localhost:3000/api/datasources` before creating new dashboards.
|
||||||
|
|
||||||
|
### Grafana Admin Credentials
|
||||||
|
|
||||||
|
- URL: `https://grafana.haumdaucher.de`
|
||||||
|
- User: `admin`
|
||||||
|
- Password: stored in `prometheus-operator.secret.yml` under `grafana.adminPassword`
|
||||||
|
|
||||||
|
### Home Assistant API
|
||||||
|
|
||||||
|
- URL: `https://hass.moritzgraf.de`
|
||||||
|
- Token: stored in `../home-assistant/tmp_long_lived_token.secret.yml`
|
||||||
|
|
||||||
|
### InfluxDB API
|
||||||
|
|
||||||
|
- Internal URL: `http://influxdb-influxdb2.influxdb.svc.cluster.local:80`
|
||||||
|
- Org: `influxdata`
|
||||||
|
- Bucket: `default`
|
||||||
|
- Token: stored in `prometheus-operator.secret.yml` under `grafana.additionalDataSources[0].secureJsonData.token`
|
||||||
|
|
||||||
|
## Dashboard Architecture
|
||||||
|
|
||||||
|
Dashboards are managed as **Kubernetes ConfigMaps** with the label `grafana_dashboard: "1"`. The kube-prometheus-stack's **Grafana Dashboard Sidecar** (`grafana-sc-dashboard` container) watches for these ConfigMaps and auto-loads them into Grafana.
|
||||||
|
|
||||||
|
### Key Design Decisions
|
||||||
|
|
||||||
|
1. **One file per dashboard** — Each ConfigMap YAML contains the dashboard JSON embedded directly in `data`. No separate JSON files (avoids duplication).
|
||||||
|
2. **Strict GitOps** — Dashboards are `editable: false` in Grafana UI. All changes must be made to the YAML files and re-applied.
|
||||||
|
3. **Namespace-scoped** — Sidecar only watches the `monitoring` namespace (`searchNamespace: monitoring`).
|
||||||
|
4. **Folder annotation** — Use `grafana_dashboard_folder` annotation to organize dashboards in Grafana folders.
|
||||||
|
|
||||||
|
### ConfigMap Template
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-<name>
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: "1"
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: "🏠 Home"
|
||||||
|
data:
|
||||||
|
<name>.json: |
|
||||||
|
{
|
||||||
|
"uid": "<unique-uid>",
|
||||||
|
"title": "<Dashboard Title>",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## InfluxDB Data Schema
|
||||||
|
|
||||||
|
Home Assistant writes sensor data to InfluxDB with this structure:
|
||||||
|
|
||||||
|
- **Measurement name** = unit of measurement (e.g., `°C`, `%`, `W`, `kWh`, `m³`, `hPa`, `km/h`, `mm/h`)
|
||||||
|
- **Fields**: `value` (numeric), plus metadata fields (`friendly_name_str`, `device_class_str`, `id_str`, etc.)
|
||||||
|
- **Tags**: `entity_id` (e.g., `home_wohnzimmer_temperature` — **NOTE: no `sensor.` prefix!**), `domain` (e.g., `sensor`)
|
||||||
|
|
||||||
|
### Flux Query Pattern (MUST follow this exactly)
|
||||||
|
|
||||||
|
**Exact match on entity_id:**
|
||||||
|
```flux
|
||||||
|
from(bucket: "default")
|
||||||
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "°C")
|
||||||
|
|> filter(fn: (r) => r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r.entity_id == "home_wandthermostat_bad_temperature")
|
||||||
|
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||||
|
|> yield(name: "mean")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regex match on entity_id** (use `=~` operator with `/pattern/`):
|
||||||
|
```flux
|
||||||
|
from(bucket: "default")
|
||||||
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "°C")
|
||||||
|
|> filter(fn: (r) => r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r.entity_id =~ /^home_/)
|
||||||
|
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||||
|
|> yield(name: "mean")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical rules:**
|
||||||
|
- Always use bracket notation: `r["_measurement"]`, `r["_field"]` (fields starting with `_`)
|
||||||
|
- Always use dynamic time: `range(start: v.timeRangeStart, stop: v.timeRangeStop)` and `aggregateWindow(every: v.windowPeriod, ...)`
|
||||||
|
- Always end with `|> yield(name: "mean")`
|
||||||
|
- **entity_id in InfluxDB does NOT have the `sensor.` prefix** — use `home_wohnzimmer_temperature`, NOT `sensor.home_wohnzimmer_temperature`
|
||||||
|
- For "current value" stat panels, use `range(start: -15m)` with `last()` instead of aggregateWindow
|
||||||
|
- Regex operator is `=~` with `/pattern/` syntax (e.g., `=~ /^home_/` matches all entities starting with `home_`)
|
||||||
|
|
||||||
|
## Common Pitfalls (Lessons Learned)
|
||||||
|
|
||||||
|
1. **Entity IDs have no `sensor.` prefix in InfluxDB** — HA stores domain separately. Use `home_wohnzimmer_temperature`, not `sensor.home_wohnzimmer_temperature`. This is the #1 cause of "no data".
|
||||||
|
2. **Dashboard JSON must NOT be wrapped in `"dashboard"` key** — Grafana file provisioning expects top-level properties (`uid`, `title`, `panels`, etc.). The `"dashboard"` wrapper is only used in the HTTP API.
|
||||||
|
3. **Datasource reference must use actual UID** — Discover via `kubectl exec` + Grafana API. Current UID: `P2AB959DC95E5519F`.
|
||||||
|
4. **Don't add `uid` to helm datasource config** — Adding `uid` to existing datasources in `additionalDataSources` causes Grafana provisioning to fail with "data source not found". Let UIDs be auto-generated.
|
||||||
|
5. **Always use bracket notation for `_measurement` and `_field`** — Use `r["_measurement"]` and `r["_field"]`, not dot notation. Fields starting with underscore may not resolve with dot notation in Flux.
|
||||||
|
6. **Always use Grafana time variables** — Never hardcode `range(start: -24h)` or `aggregateWindow(every: 5m)`. Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod`.
|
||||||
|
7. **Always add `yield(name: "mean")`** — Required for Grafana to properly parse Flux results.
|
||||||
|
8. **InfluxDB has multiple fields per measurement** — Always filter `r["_field"] == "value"` to get numeric data.
|
||||||
|
9. **Sidecar container name is `grafana-sc-dashboard`** (singular), not `grafana-sc-dashboards`.
|
||||||
|
10. **Use Wandthermostat entities for temperature/humidity** — Query HA to filter by device type. Wandthermostat entities have `wandthermostat` or `thermostat` in their entity_id.
|
||||||
|
|
||||||
|
## Related Files Outside This Folder
|
||||||
|
|
||||||
|
- `../home-assistant/home-assistant.secret.yaml` — HA helm values (InfluxDB integration config)
|
||||||
|
- `../home-assistant/tmp_long_lived_token.secret.yml` — HA API token for discovery
|
||||||
|
- `../influxdb/influxdb2.secret.yml` — InfluxDB helm values
|
||||||
|
- `../../kochbuch/plans/grafana-dashboards-as-code.md` — Original architecture plan
|
||||||
|
|
@ -0,0 +1,326 @@
|
||||||
|
# DASHBOARD_CREATION.md — How to Create & Update Grafana Dashboards
|
||||||
|
|
||||||
|
> **Linked from**: [AGENTS.md](AGENTS.md)
|
||||||
|
|
||||||
|
This document captures the exact process used to create the Grafana dashboards in this folder, including all pitfalls encountered and lessons learned. Use this as a reference when creating new dashboards or updating existing ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step-by-Step Dashboard Creation Process
|
||||||
|
|
||||||
|
### Step 1: Discovery — Find Available Metrics
|
||||||
|
|
||||||
|
Before creating any dashboard, discover what data actually exists:
|
||||||
|
|
||||||
|
#### 1a. Query Home Assistant for Entity Metadata
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get all entities with their friendly names, units, and device classes
|
||||||
|
curl -s -H "Authorization: Bearer <HA_TOKEN>" \
|
||||||
|
https://hass.moritzgraf.de/api/states | \
|
||||||
|
jq '[.[] | select(.attributes.unit_of_measurement != null) | {
|
||||||
|
entity_id,
|
||||||
|
unit: .attributes.unit_of_measurement,
|
||||||
|
friendly_name: (.attributes.friendly_name // ""),
|
||||||
|
device_class: (.attributes.device_class // "")
|
||||||
|
}] | group_by(.unit)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the helper script: `./scripts/discover-home-assistant.sh`
|
||||||
|
|
||||||
|
#### 1b. Query InfluxDB for Measurements and Schema
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Port-forward to InfluxDB (no ingress configured)
|
||||||
|
kubectl -n influxdb port-forward svc/influxdb-influxdb2 8086:80
|
||||||
|
|
||||||
|
# List measurements with data in last 6 months
|
||||||
|
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||||
|
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||||
|
-H "Content-Type: application/vnd.flux" \
|
||||||
|
-d 'from(bucket: "default")
|
||||||
|
|> range(start: -6mo)
|
||||||
|
|> keep(columns: ["_measurement"])
|
||||||
|
|> distinct()
|
||||||
|
|> sort()'
|
||||||
|
|
||||||
|
# For a specific measurement, get fields and tags
|
||||||
|
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||||
|
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||||
|
-H "Content-Type: application/vnd.flux" \
|
||||||
|
-d 'import "influxdata/influxdb/schema"
|
||||||
|
schema.measurementFieldKeys(bucket: "default", measurement: "°C")'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use: `./scripts/discover-influxdb.sh`
|
||||||
|
|
||||||
|
#### 1c. Verify Data Exists
|
||||||
|
|
||||||
|
Always test a sample query before building dashboards:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||||
|
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||||
|
-H "Content-Type: application/vnd.flux" \
|
||||||
|
-d 'from(bucket: "default")
|
||||||
|
|> range(start: -1h)
|
||||||
|
|> filter(fn: (r) => r._measurement == "°C" and r._field == "value")
|
||||||
|
|> limit(n: 5)'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Discover Grafana Datasource UIDs
|
||||||
|
|
||||||
|
Datasource UIDs change when Grafana is redeployed. Always discover the current UIDs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n monitoring exec deploy/prometheus-operator-grafana -c grafana -- \
|
||||||
|
curl -s -u "admin:<GRAFANA_PASSWORD>" \
|
||||||
|
http://localhost:3000/api/datasources | \
|
||||||
|
jq '.[] | {name, uid, type}'
|
||||||
|
```
|
||||||
|
|
||||||
|
The Grafana admin password is in [`prometheus-operator.secret.yml`](prometheus-operator.secret.yml) under `grafana.adminPassword`.
|
||||||
|
|
||||||
|
### Step 3: Select the Right Entities
|
||||||
|
|
||||||
|
**Important**: Not all sensors of the same type are equal. For example:
|
||||||
|
- Use **Wandthermostat** (wall thermostat) entities for temperature/humidity, not raw sensor entities. Wandthermostats provide more reliable, calibrated readings.
|
||||||
|
- Query Home Assistant to filter by entity name pattern or device type.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find all Wandthermostat temperature sensors
|
||||||
|
curl -s -H "Authorization: Bearer <HA_TOKEN>" \
|
||||||
|
https://hass.moritzgraf.de/api/states | \
|
||||||
|
jq '[.[] | select(.attributes.unit_of_measurement == "°C" and
|
||||||
|
(.entity_id | test("wandthermostat|thermostat"; "i")))]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Create the Dashboard ConfigMap YAML
|
||||||
|
|
||||||
|
Create a new file in `dashboards/` following this template:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-<name>
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: "1"
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: "🏠 Home"
|
||||||
|
data:
|
||||||
|
<name>.json: |
|
||||||
|
{
|
||||||
|
"uid": "<unique-uid>",
|
||||||
|
"title": "<Dashboard Title>",
|
||||||
|
"tags": ["home", "<category>", "auto-generated"],
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 1,
|
||||||
|
"refresh": "5m",
|
||||||
|
"editable": false,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"time": { "from": "now-24h", "to": "now" },
|
||||||
|
"panels": [ ... ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Critical JSON Structure Rules
|
||||||
|
|
||||||
|
**✅ CORRECT** (top-level properties, no wrapper):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uid": "my-dashboard",
|
||||||
|
"title": "My Dashboard",
|
||||||
|
"panels": [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ WRONG** (wrapped in "dashboard" key — causes "Dashboard title cannot be empty"):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dashboard": {
|
||||||
|
"uid": "my-dashboard",
|
||||||
|
"title": "My Dashboard",
|
||||||
|
"panels": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"dashboard"` wrapper is **only** used in the Grafana HTTP API (`/api/dashboards/db`). For file-based provisioning via the sidecar, properties must be at the top level.
|
||||||
|
|
||||||
|
#### Datasource Reference
|
||||||
|
|
||||||
|
**✅ CORRECT** (by actual UID):
|
||||||
|
```json
|
||||||
|
"datasource": { "type": "influxdb", "uid": "P2AB959DC95E5519F" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ WRONG** (by name — won't resolve):
|
||||||
|
```json
|
||||||
|
"datasource": { "type": "influxdb", "uid": "InfluxDB Home Assistant" }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Flux Query Template
|
||||||
|
|
||||||
|
**Exact match on entity_id:**
|
||||||
|
```flux
|
||||||
|
from(bucket: "default")
|
||||||
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r.entity_id == "<ENTITY_ID>")
|
||||||
|
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||||
|
|> yield(name: "mean")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regex match on entity_id** (for panels showing multiple entities matching a pattern):
|
||||||
|
```flux
|
||||||
|
from(bucket: "default")
|
||||||
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r.entity_id =~ /^home_/)
|
||||||
|
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||||
|
|> yield(name: "mean")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Current value** (for stat panels showing last reading):
|
||||||
|
```flux
|
||||||
|
from(bucket: "default")
|
||||||
|
|> range(start: -15m)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r.entity_id == "<ENTITY_ID>")
|
||||||
|
|> last()
|
||||||
|
|> yield(name: "mean")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points**:
|
||||||
|
- Use bracket notation `r["_measurement"]`, `r["_field"]` for fields starting with underscore.
|
||||||
|
- **entity_id in InfluxDB has NO `sensor.` prefix** — HA stores domain in a separate `domain` tag. Use `home_wohnzimmer_temperature`, not `sensor.home_wohnzimmer_temperature`.
|
||||||
|
- Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod` — never hardcode time values.
|
||||||
|
- Always end with `|> yield(name: "mean")` — required for Grafana to parse Flux results.
|
||||||
|
- Use regex `=~ /pattern/` for multi-entity panels (e.g., `=~ /^home_/` matches all home entities).
|
||||||
|
- Always filter `r["_field"] == "value"` — InfluxDB stores metadata strings in other fields.
|
||||||
|
|
||||||
|
### Step 5: Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply all dashboards
|
||||||
|
kubectl apply -f dashboards/
|
||||||
|
|
||||||
|
# Apply a single dashboard
|
||||||
|
kubectl apply -f dashboards/grafana-dashboard-<name>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check ConfigMaps are labeled correctly
|
||||||
|
kubectl get configmaps -n monitoring -l grafana_dashboard=1
|
||||||
|
|
||||||
|
# Check sidecar logs (container name is grafana-sc-dashboard, singular!)
|
||||||
|
kubectl -n monitoring logs -l "app.kubernetes.io/name=grafana" \
|
||||||
|
-c grafana-sc-dashboard --tail=20
|
||||||
|
|
||||||
|
# Check Grafana main logs for errors
|
||||||
|
kubectl -n monitoring logs -l "app.kubernetes.io/name=grafana" \
|
||||||
|
-c grafana --tail=30 | grep -i "error\|failed"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update Process (Modifying an Existing Dashboard)
|
||||||
|
|
||||||
|
1. Edit the YAML file in `dashboards/`
|
||||||
|
2. Increment the `version` field in the dashboard JSON
|
||||||
|
3. Run `kubectl apply -f dashboards/grafana-dashboard-<name>.yaml`
|
||||||
|
4. The sidecar will detect the change and reload within ~60 seconds
|
||||||
|
5. Verify in Grafana UI
|
||||||
|
|
||||||
|
**Never edit dashboards in the Grafana UI** — they are `editable: false`. All changes must go through the YAML files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Removing a Dashboard
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete configmap -n monitoring grafana-dashboard-<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
The sidecar will remove it from Grafana within ~60 seconds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitfalls Encountered in This Conversation
|
||||||
|
|
||||||
|
### Pitfall 1: Entity IDs lack `sensor.` prefix in InfluxDB
|
||||||
|
**Problem**: Used `sensor.home_wohnzimmer_temperature` in queries — no data appeared. Home Assistant stores the domain (`sensor`) in a separate `domain` tag, not in `entity_id`.
|
||||||
|
**Fix**: Use `home_wohnzimmer_temperature` (without prefix). Always query InfluxDB directly to verify entity_id format before building queries.
|
||||||
|
|
||||||
|
### Pitfall 2: Hardcoded time ranges
|
||||||
|
**Problem**: Used `range(start: -24h)` and `aggregateWindow(every: 5m)` — dashboard time picker had no effect, data not loaded properly.
|
||||||
|
**Fix**: Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod`. These Grafana variables adapt to the dashboard time picker.
|
||||||
|
|
||||||
|
### Pitfall 3: Missing `yield()`
|
||||||
|
**Problem**: Flux queries missing `yield(name: "mean")` — Grafana couldn't parse results.
|
||||||
|
**Fix**: Always end Flux queries with `|> yield(name: "mean")`.
|
||||||
|
|
||||||
|
### Pitfall 4: Dot notation for underscore fields
|
||||||
|
**Problem**: Used `r._measurement` and `r._field` — Flux may not resolve fields starting with `_` via dot notation.
|
||||||
|
**Fix**: Use bracket notation: `r["_measurement"]`, `r["_field"]`.
|
||||||
|
|
||||||
|
### Pitfall 5: Datasource UID vs Name
|
||||||
|
**Problem**: Used datasource name as UID (`"uid": "InfluxDB Home Assistant"`). Dashboards loaded but showed "no data".
|
||||||
|
**Fix**: Discover actual UID via `kubectl exec` + Grafana API. Current: `P2AB959DC95E5519F`.
|
||||||
|
|
||||||
|
### Pitfall 6: `"dashboard"` JSON Wrapper
|
||||||
|
**Problem**: Dashboard JSON wrapped in `{"dashboard": {...}}`. Grafana showed "Dashboard title cannot be empty".
|
||||||
|
**Fix**: Top-level properties only. The `"dashboard"` wrapper is for HTTP API, not file provisioning.
|
||||||
|
|
||||||
|
### Pitfall 7: Adding `uid` to Helm Datasource Config
|
||||||
|
**Problem**: Added `uid: influxdb-ha-flux` to helm values — Grafana pod crashed.
|
||||||
|
**Fix**: Let Grafana auto-generate UIDs. Never add `uid` to existing datasources.
|
||||||
|
|
||||||
|
### Pitfall 8: Multi-entity panels using `or` in filter
|
||||||
|
**Problem**: Used `filter(fn: (r) => r.entity_id == "A" or r.entity_id == "B")` — Flux `or` inside a single filter call doesn't reliably work.
|
||||||
|
**Fix**: Use regex: `filter(fn: (r) => r.entity_id =~ /^home_/)` for matching multiple entities.
|
||||||
|
|
||||||
|
### Pitfall 9: Wrong Sensor Selection
|
||||||
|
**Problem**: Used raw temperature sensors instead of Wandthermostat sensors.
|
||||||
|
**Fix**: Query HA to filter entities with `wandthermostat` or `thermostat` in entity_id.
|
||||||
|
|
||||||
|
### Pitfall 10: Sidecar Container Name
|
||||||
|
**Problem**: `kubectl logs -c grafana-sc-dashboards` (plural).
|
||||||
|
**Fix**: Container name is `grafana-sc-dashboard` (singular).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dashboard Design Guidelines
|
||||||
|
|
||||||
|
1. **Use semantic thresholds with colors**: Blue=cold, green=comfort, orange=warm, red=hot for temperature; reverse for grid import (green=low, red=high).
|
||||||
|
2. **Use Stat panels with sparklines** for current values — they show the 24h mini-trend.
|
||||||
|
3. **Use BarGauge for day comparisons** (today vs yesterday vs avg).
|
||||||
|
4. **Use Gauge for ranged values** (battery SoC 0-100%, cloud cover 0-100%).
|
||||||
|
5. **Color-code panel backgrounds** with `"colorMode": "background"` for at-a-glance status.
|
||||||
|
6. **Set `"editable": false`** for strict GitOps.
|
||||||
|
7. **Use emoji in dashboard and panel titles** for visual appeal (🏠 ⚡ 🔥 🌡️ 💧).
|
||||||
|
8. **Add `"auto-generated"` tag** to distinguish from manually created dashboards.
|
||||||
|
9. **Include calcs in legend** (`mean`, `max`, `min`, `lastNotNull`) for quick statistics.
|
||||||
|
10. **Use `"spanNulls": true`** in timeseries to handle gaps in sensor data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## InfluxDB Measurement Reference
|
||||||
|
|
||||||
|
| Measurement | Unit | Typical Entities |
|
||||||
|
|-------------|------|------------------|
|
||||||
|
| `°C` | celsius | Wandthermostat temperatures, outdoor temp, battery temp |
|
||||||
|
| `%` | percent | Humidity, battery levels, heating valve position, cloud cover |
|
||||||
|
| `W` | watt | Solar production, self-consumption, grid import/export, house consumption |
|
||||||
|
| `kWh` | kilowatt-hour | Daily energy totals (production, consumption, import, export) |
|
||||||
|
| `m³` | cubic meter | Gas volume counter |
|
||||||
|
| `m³/h` | cubic meter/hour | Current gas flow rate |
|
||||||
|
| `hPa` | hectopascal | Barometric pressure |
|
||||||
|
| `km/h` | kilometer/hour | Wind speed |
|
||||||
|
| `mm/h` | millimeter/hour | Precipitation rate |
|
||||||
|
| `EUR` | euro | Gas cost, electricity cost |
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Grafana Dashboards as Code
|
||||||
|
|
||||||
|
This directory contains Grafana dashboards managed as Kubernetes ConfigMaps. Dashboards are automatically loaded into Grafana via the dashboard sidecar.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
dashboards/*.yaml (ConfigMap with embedded JSON)
|
||||||
|
│
|
||||||
|
│ kubectl apply
|
||||||
|
▼
|
||||||
|
Kubernetes ConfigMap (label: grafana_dashboard=1)
|
||||||
|
│
|
||||||
|
│ Sidecar watches for ConfigMaps
|
||||||
|
▼
|
||||||
|
Grafana Dashboard (read-only, GitOps managed)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply all dashboards
|
||||||
|
kubectl apply -f dashboards/
|
||||||
|
|
||||||
|
# Apply a single dashboard
|
||||||
|
kubectl apply -f dashboards/grafana-dashboard-home-climate.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check ConfigMaps are created and labeled correctly
|
||||||
|
kubectl get configmaps -n monitoring -l grafana_dashboard=1
|
||||||
|
|
||||||
|
# Check sidecar logs to confirm dashboards were loaded
|
||||||
|
kubectl logs -n monitoring deployment/kube-prometheus-stack-grafana -c grafana-sc-dashboards --tail=50
|
||||||
|
|
||||||
|
# List dashboards via Grafana API
|
||||||
|
kubectl -n monitoring exec deploy/kube-prometheus-stack-grafana -- \
|
||||||
|
curl -s -u admin:$(kubectl -n monitoring get secret kube-prometheus-stack-grafana -o jsonpath='{.data.admin-password}' | base64 -d) \
|
||||||
|
http://localhost:3000/api/search?type=dash-db | jq '.[].title'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Before generating new dashboards, discover what metrics are available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Discover InfluxDB measurements, fields, and tags
|
||||||
|
./scripts/discover-influxdb.sh
|
||||||
|
|
||||||
|
# Discover Home Assistant entities and their metadata
|
||||||
|
./scripts/discover-home-assistant.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard Structure
|
||||||
|
|
||||||
|
Each dashboard is a single YAML file containing a Kubernetes ConfigMap with the dashboard JSON embedded:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-<name>
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: "1"
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: "<Folder Name>"
|
||||||
|
data:
|
||||||
|
<name>.json: |
|
||||||
|
{
|
||||||
|
"dashboard": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a New Dashboard
|
||||||
|
|
||||||
|
1. Run discovery scripts to understand available metrics
|
||||||
|
2. Create a new ConfigMap YAML in `dashboards/`
|
||||||
|
3. Apply: `kubectl apply -f dashboards/grafana-dashboard-<name>.yaml`
|
||||||
|
4. Verify the dashboard appears in Grafana at https://grafana.haumdaucher.de
|
||||||
|
|
||||||
|
## Removing a Dashboard
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete configmap -n monitoring grafana-dashboard-<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
The sidecar will automatically remove the dashboard from Grafana within ~60 seconds.
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,92 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-energy-monitor
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: '1'
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: 🏠 Home
|
||||||
|
data:
|
||||||
|
energy-monitor.json: '{"uid": "energy-monitor", "title": "⚡ Energy Monitor", "tags": ["home", "energy", "solar", "power", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh":
|
||||||
|
"5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "⚡ Live Power", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed": false},
|
||||||
|
{"title": "☀️ Solar", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals":
|
||||||
|
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#FFD700", "value": 100}, {"color": "#73BF69", "value": 500}, {"color": "#37872D", "value": 2000}]}, "color":
|
||||||
|
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemleistung\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🏠 Self-Use", "type": "stat", "datasource": {"type": "influxdb",
|
||||||
|
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value":
|
||||||
|
null}, {"color": "#73BF69", "value": 100}, {"color": "#FFD700", "value": 1000}, {"color": "#FF7383", "value": 3000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode":
|
||||||
|
"background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigene_leistung\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔌 Grid Import", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||||
|
"gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700",
|
||||||
|
"value": 500}, {"color": "#FF7383", "value": 2000}, {"color": "#C4162A", "value": 5000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode":
|
||||||
|
"auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r)
|
||||||
|
=> r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_bezugsleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty:
|
||||||
|
false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "↗️ Grid Export", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4,
|
||||||
|
"w": 4, "x": 12, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#5794F2", "value": 100},
|
||||||
|
{"color": "#1F60C4", "value": 500}, {"color": "#3274D9", "value": 2000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions":
|
||||||
|
{"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_einspeiseleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||||
|
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔋 Battery SoC", "type": "gauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x":
|
||||||
|
16, "y": 1}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "min": 0, "max": 100, "thresholds": {"mode": "absolute", "steps": [{"color": "#C4162A", "value": null}, {"color": "#FF7383",
|
||||||
|
"value": 20}, {"color": "#FFD700", "value": 40}, {"color": "#73BF69", "value": 60}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation":
|
||||||
|
"auto", "showThresholdLabels": true, "showThresholdMarkers": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||||
|
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"ladeaufstellung_soc\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🏡 Total House", "type":
|
||||||
|
"stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 20, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode":
|
||||||
|
"absolute", "steps": [{"color": "#555", "value": null}, {"color": "#73BF69", "value": 200}, {"color": "#FFD700", "value": 1000}, {"color": "#FF7383", "value": 3000}, {"color": "#C4162A", "value": 5000}]},
|
||||||
|
"color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query":
|
||||||
|
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||||
|
== \"home_strommesserat_currentpowerconsumption\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title":
|
||||||
|
"📈 Power Flow", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "Power Timeline", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||||
|
"gridPos": {"h": 10, "w": 24, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "watt", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher":
|
||||||
|
{"id": "byName", "options": "Solar"}, "properties": [{"id": "color", "value": {"fixedColor": "#FFD700", "mode": "fixed"}}, {"id": "custom.fillOpacity", "value": 15}]}, {"matcher": {"id": "byName", "options":
|
||||||
|
"Self-Use"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Import"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||||
|
"#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Export"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}, {"id": "custom.lineStyle",
|
||||||
|
"value": {"fill": "dash", "dash": [8, 4]}}]}, {"matcher": {"id": "byName", "options": "Local Load"}, "properties": [{"id": "color", "value": {"fixedColor": "#C4162A", "mode": "fixed"}}]}]}, "options":
|
||||||
|
{"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["max", "mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "SOLAR", "query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemleistung\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Solar\")", "resultFormat": "time_series"}, {"refId": "SELF", "query":
|
||||||
|
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||||
|
== \"garten_bzp3n7g1b0_eigene_leistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Self-Use\")", "resultFormat":
|
||||||
|
"time_series"}, {"refId": "IMPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"]
|
||||||
|
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_bezugsleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||||
|
set(key: \"source\", value: \"Grid Import\")", "resultFormat": "time_series"}, {"refId": "EXPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_einspeiseleistung\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Grid Export\")", "resultFormat": "time_series"}, {"refId": "LOAD", "query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_lokale_lastleistung\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Local Load\")", "resultFormat": "time_series"}]}, {"title": "📊 Energy
|
||||||
|
Summary (Today)", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 16}, "collapsed": false}, {"title": "☀️ Solar Produced", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||||
|
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color":
|
||||||
|
"#FFD700", "value": 5}, {"color": "#73BF69", "value": 15}, {"color": "#37872D", "value": 30}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]},
|
||||||
|
"orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\"
|
||||||
|
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemerzeugung_heute\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title":
|
||||||
|
"🏠 Self-Consumed", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 6, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals":
|
||||||
|
1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#73BF69", "value": 5}, {"color": "#FFD700", "value": 15}, {"color": "#FF7383", "value": 30}]},
|
||||||
|
"color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query":
|
||||||
|
"from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigenverbrauch_heute\")\n |>
|
||||||
|
last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔌 Grid Imported", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h":
|
||||||
|
4, "w": 6, "x": 12, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700",
|
||||||
|
"value": 2}, {"color": "#FF7383", "value": 10}, {"color": "#C4162A", "value": 20}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation":
|
||||||
|
"horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"]
|
||||||
|
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_netzbezug_heute\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "↗️ Grid Exported",
|
||||||
|
"type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 18, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min":
|
||||||
|
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#5794F2", "value": 2}, {"color": "#1F60C4", "value": 10}, {"color": "#3274D9", "value": 20}]}, "color": {"mode":
|
||||||
|
"thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"export_ins_netz_heute\")\n |> last()\n |>
|
||||||
|
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Energy Trend", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 21}, "collapsed": false}, {"title": "Daily Energy", "type":
|
||||||
|
"timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 24, "x": 0, "y": 22}, "fieldConfig": {"defaults": {"unit": "kwatth", "custom": {"lineWidth": 2,
|
||||||
|
"fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Solar"}, "properties": [{"id": "color", "value": {"fixedColor": "#FFD700", "mode":
|
||||||
|
"fixed"}}]}, {"matcher": {"id": "byName", "options": "Self-Use"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid
|
||||||
|
Import"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Export"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||||
|
"#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["sum", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId":
|
||||||
|
"SOLAR", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |>
|
||||||
|
filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemerzeugung_heute\")\n |> aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value:
|
||||||
|
\"Solar\")", "resultFormat": "time_series"}, {"refId": "SELF", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigenverbrauch_heute\")\n |> aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name:
|
||||||
|
\"mean\") |> set(key: \"type\", value: \"Self-Use\")", "resultFormat": "time_series"}, {"refId": "IMPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_netzbezug_heute\")\n |> aggregateWindow(every: 1d, fn: mean,
|
||||||
|
createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value: \"Grid Import\")", "resultFormat": "time_series"}, {"refId": "EXPORT", "query": "from(bucket: \"default\")\n |> range(start:
|
||||||
|
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"export_ins_netz_heute\")\n |>
|
||||||
|
aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value: \"Grid Export\")", "resultFormat": "time_series"}]}]}'
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-heating-gas
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: '1'
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: 🏠 Home
|
||||||
|
data:
|
||||||
|
heating-gas.json: '{"uid": "heating-gas", "title": "🔥 Heating & Gas", "tags": ["home", "heating", "gas", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh": "5m", "editable":
|
||||||
|
false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "⛽ Gas Overview", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed": false}, {"title":
|
||||||
|
"Current Gas Flow", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit": "cms", "decimals":
|
||||||
|
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 0.5}, {"color": "#FF7383", "value": 1.5}, {"color": "#C4162A", "value": 3}]}, "color":
|
||||||
|
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³/h\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||||
|
\"home_gaszahler_currentgasflow\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Total Gas Volume",
|
||||||
|
"type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 6, "y": 1}, "fieldConfig": {"defaults": {"unit": "cms", "decimals": 0, "thresholds":
|
||||||
|
{"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 500}, {"color": "#FF7383", "value": 1000}, {"color": "#C4162A", "value": 2000}]}, "color": {"mode":
|
||||||
|
"thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_gaszahler_gasvolume\")\n |> last()\n |> yield(name:
|
||||||
|
\"mean\")", "resultFormat": "time_series"}]}, {"title": "Gas Cost (EUR)", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 12, "y": 1},
|
||||||
|
"fieldConfig": {"defaults": {"unit": "currencyEUR", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 200}, {"color": "#FF7383",
|
||||||
|
"value": 500}, {"color": "#C4162A", "value": 1000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||||
|
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"EUR\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||||
|
(r) => r.entity_id == \"home_gaszahler_gasvolume_cost\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Electricity Cost (EUR)", "type": "stat", "datasource":
|
||||||
|
{"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 18, "y": 1}, "fieldConfig": {"defaults": {"unit": "currencyEUR", "decimals": 0, "thresholds": {"mode": "absolute",
|
||||||
|
"steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 100}, {"color": "#FF7383", "value": 300}, {"color": "#C4162A", "value": 500}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||||
|
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||||
|
-15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"EUR\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_strommesserat_energy_counter_usage_high_tariff_cost\")\n |>
|
||||||
|
last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Gas Consumption Trend", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title":
|
||||||
|
"Gas Flow", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 16, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "cms", "custom":
|
||||||
|
{"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Gas Flow"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||||
|
"#FF7383", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId":
|
||||||
|
"FLOW", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³/h\" and r[\"_field\"] == \"value\")\n |>
|
||||||
|
filter(fn: (r) => r.entity_id == \"home_gaszahler_currentgasflow\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value:
|
||||||
|
\"Gas Flow\")", "resultFormat": "time_series"}]}, {"title": "Gas Volume", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 8, "x": 16, "y":
|
||||||
|
6}, "fieldConfig": {"defaults": {"unit": "cms", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Gas Volume"},
|
||||||
|
"properties": [{"id": "color", "value": {"fixedColor": "#C4162A", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip":
|
||||||
|
{"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "VOL", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"m³\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_gaszahler_gasvolume\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name:
|
||||||
|
\"mean\") |> set(key: \"metric\", value: \"Gas Volume\")", "resultFormat": "time_series"}]}, {"title": "🏠 Heating Activity", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 14}, "collapsed":
|
||||||
|
false}, {"title": "Heating Valve Position", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 24, "x": 0, "y": 15}, "fieldConfig": {"defaults":
|
||||||
|
{"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Wohnzimmer"}, "properties": [{"id":
|
||||||
|
"color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Wohnzimmer #2"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF9830", "mode": "fixed"}}]},
|
||||||
|
{"matcher": {"id": "byName", "options": "Arbeitszimmer"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Schlafzimmer"},
|
||||||
|
"properties": [{"id": "color", "value": {"fixedColor": "#8F3BB8", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Küche"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69",
|
||||||
|
"mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Bad"}, "properties": [{"id": "color", "value": {"fixedColor": "#1F60C4", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table",
|
||||||
|
"placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||||
|
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_heating\")\n |> aggregateWindow(every:
|
||||||
|
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Wohnzimmer\")", "resultFormat": "time_series"}, {"refId": "Wo", "query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_heating\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Wohnzimmer #2\")", "resultFormat": "time_series"}, {"refId": "Ar",
|
||||||
|
"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||||
|
(r) => r.entity_id == \"home_arbeitszimmer_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Arbeitszimmer\")",
|
||||||
|
"resultFormat": "time_series"}, {"refId": "Sc", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||||
|
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_schlafzimmer_og_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |>
|
||||||
|
set(key: \"room\", value: \"Schlafzimmer\")", "resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||||
|
(r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_vorne_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty:
|
||||||
|
false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Küche\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||||
|
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_badezimmer_thermostat_heating\")\n |> aggregateWindow(every:
|
||||||
|
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Bad\")", "resultFormat": "time_series"}]}]}'
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-home-climate
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: '1'
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: 🏠 Home
|
||||||
|
data:
|
||||||
|
home-climate.json: '{"uid": "home-climate", "title": "🏠 Home Climate", "tags": ["home", "climate", "temperature", "humidity", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version":
|
||||||
|
1, "refresh": "5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "🌡️ Current Temperatures", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0,
|
||||||
|
"y": 0}, "collapsed": false}, {"title": "Wohnzimmer", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults":
|
||||||
|
{"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red",
|
||||||
|
"value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets":
|
||||||
|
[{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||||
|
(r) => r.entity_id == \"home_wall_mounted_thermostat_pro_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]},
|
||||||
|
{"title": "Wohnzimmer #2", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius",
|
||||||
|
"decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color":
|
||||||
|
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||||
|
\"home_wohnzimmer_2_thermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Arbeitszimmer",
|
||||||
|
"type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds":
|
||||||
|
{"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}},
|
||||||
|
"options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_arbeitszimmer_thermostat_temperature\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Küche", "type": "stat", "datasource": {"type": "influxdb",
|
||||||
|
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue",
|
||||||
|
"value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background",
|
||||||
|
"textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Bad", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos":
|
||||||
|
{"h": 4, "w": 4, "x": 16, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value":
|
||||||
|
18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions":
|
||||||
|
{"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||||
|
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Temperature Trends", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "All Rooms Temperature",
|
||||||
|
"type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 16, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth":
|
||||||
|
2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull", "min", "max"]}, "tooltip": {"mode":
|
||||||
|
"multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] ==
|
||||||
|
\"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||||
|
set(key: \"room\", value: \"Wohnzimmer\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Wo2", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop:
|
||||||
|
v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_temperature\")\n |> aggregateWindow(every:
|
||||||
|
v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer #2\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Ar", "query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||||
|
\"home_arbeitszimmer_thermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Arbeitszimmer\")\n |> yield(name: \"mean\")",
|
||||||
|
"resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\"
|
||||||
|
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Küche\")\n |> yield(name:
|
||||||
|
\"mean\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_temperature\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Bad\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Indoor vs Outdoor", "type": "timeseries", "datasource":
|
||||||
|
{"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 8, "x": 16, "y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls":
|
||||||
|
true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Outdoor"}, "properties": [{"id": "color", "value": {"fixedColor": "light-blue", "mode": "fixed"}}, {"id": "custom.lineStyle",
|
||||||
|
"value": {"fill": "dash", "dash": [10, 5]}}]}, {"matcher": {"id": "byName", "options": "Indoor Avg"}, "properties": [{"id": "color", "value": {"fixedColor": "orange", "mode": "fixed"}}]}]}, "options":
|
||||||
|
{"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "OUT", "query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |>
|
||||||
|
aggregateWindow(every: 1h, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"location\", value: \"Outdoor\")", "resultFormat": "time_series"}, {"refId": "IN", "query": "from(bucket:
|
||||||
|
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id =~
|
||||||
|
/^home_/)\n |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> group()\n |> mean()\n |> set(key: \"location\", value: \"Indoor Avg\")", "resultFormat":
|
||||||
|
"time_series"}]}, {"title": "💧 Humidity", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 16}, "collapsed": false}, {"title": "Wohnzimmer", "type": "stat", "datasource": {"type": "influxdb",
|
||||||
|
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red",
|
||||||
|
"value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode":
|
||||||
|
"area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||||
|
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_humidity\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Arbeitszimmer", "type": "stat", "datasource": {"type":
|
||||||
|
"influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color":
|
||||||
|
"red", "value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||||
|
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||||
|
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_arbeitszimmer_thermostat_humidity\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Wohnzimmer #2", "type": "stat", "datasource": {"type":
|
||||||
|
"influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color":
|
||||||
|
"red", "value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||||
|
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||||
|
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_humidity\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Bad", "type": "stat", "datasource": {"type": "influxdb",
|
||||||
|
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red",
|
||||||
|
"value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode":
|
||||||
|
"area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||||
|
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_humidity\")\n |> aggregateWindow(every:
|
||||||
|
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Küche", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||||
|
"gridPos": {"h": 4, "w": 4, "x": 16, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red", "value": null}, {"color":
|
||||||
|
"orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode":
|
||||||
|
"background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Humidity Trends", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||||
|
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 20}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}}, "options": {"legend":
|
||||||
|
{"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |>
|
||||||
|
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_humidity\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Ar", "query":
|
||||||
|
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||||
|
== \"home_arbeitszimmer_thermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Arbeitszimmer\")\n |> yield(name: \"mean\")",
|
||||||
|
"resultFormat": "time_series"}, {"refId": "Wo2", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||||
|
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer #2\")\n |> yield(name:
|
||||||
|
\"mean\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||||
|
filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_humidity\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||||
|
fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Bad\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start:
|
||||||
|
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_humidity\")\n |>
|
||||||
|
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Küche\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}]}'
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: grafana-dashboard-outdoor-weather
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: '1'
|
||||||
|
annotations:
|
||||||
|
grafana_dashboard_folder: 🏠 Home
|
||||||
|
data:
|
||||||
|
outdoor-weather.json: '{"uid": "outdoor-weather", "title": "🌡️ Outdoor & Weather", "tags": ["home", "weather", "outdoor", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh":
|
||||||
|
"5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "🌤️ Current Conditions", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed":
|
||||||
|
false}, {"title": "🌡️ Temperature", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit":
|
||||||
|
"celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "#5794F2", "value": null}, {"color": "#73BF69", "value": 0}, {"color": "#FFD700", "value": 15}, {"color": "#FF7383",
|
||||||
|
"value": 25}, {"color": "#C4162A", "value": 30}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||||
|
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"]
|
||||||
|
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat":
|
||||||
|
"time_series"}]}, {"title": "💧 Humidity", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit":
|
||||||
|
"percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#FF7383", "value": null}, {"color": "#FFD700", "value": 30}, {"color": "#73BF69", "value": 40}, {"color": "#FFD700",
|
||||||
|
"value": 70}, {"color": "#5794F2", "value": 90}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||||
|
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"]
|
||||||
|
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_luftfeuchtigkeit\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||||
|
"resultFormat": "time_series"}]}, {"title": "🔵 Pressure", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig":
|
||||||
|
{"defaults": {"unit": "pressurehpa", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#FF7383", "value": null}, {"color": "#FFD700", "value": 990}, {"color": "#73BF69", "value":
|
||||||
|
1010}, {"color": "#FFD700", "value": 1030}, {"color": "#FF7383", "value": 1040}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto",
|
||||||
|
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"hPa\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_druck\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name:
|
||||||
|
\"mean\")", "resultFormat": "time_series"}]}, {"title": "💨 Wind", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 1}, "fieldConfig":
|
||||||
|
{"defaults": {"unit": "velocitykmh", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 20}, {"color": "#FF7383", "value":
|
||||||
|
40}, {"color": "#C4162A", "value": 60}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs":
|
||||||
|
["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"km/h\" and r[\"_field\"]
|
||||||
|
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_windgeschwindigkeit\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||||
|
"resultFormat": "time_series"}]}, {"title": "🌧️ Rain", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 16, "y": 1}, "fieldConfig": {"defaults":
|
||||||
|
{"unit": "mm/h", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#5794F2", "value": 0.1}, {"color": "#1F60C4", "value": 1}, {"color": "#3274D9",
|
||||||
|
"value": 5}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets":
|
||||||
|
[{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"mm/h\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||||
|
(r) => r.entity_id == \"regensburg_regensburg_niederschlag\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]},
|
||||||
|
{"title": "☁️ Cloud Cover", "type": "gauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 20, "y": 1}, "fieldConfig": {"defaults": {"unit": "percent",
|
||||||
|
"decimals": 0, "min": 0, "max": 100, "thresholds": {"mode": "absolute", "steps": [{"color": "#5794F2", "value": null}, {"color": "#73BF69", "value": 20}, {"color": "#FFD700", "value": 50}, {"color":
|
||||||
|
"#808080", "value": 80}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "auto", "showThresholdLabels": true, "showThresholdMarkers":
|
||||||
|
true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||||
|
== \"regensburg_regensburg_bewolkungsgrad\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Temperature & Humidity Trend", "type": "row", "gridPos": {"h":
|
||||||
|
1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "Temperature", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 0,
|
||||||
|
"y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options":
|
||||||
|
"Temperature"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["min", "max",
|
||||||
|
"mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "T", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r)
|
||||||
|
=> r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty:
|
||||||
|
false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Temperature\")", "resultFormat": "time_series"}]}, {"title": "Humidity", "type": "timeseries", "datasource": {"type": "influxdb",
|
||||||
|
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 12, "y": 6}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints":
|
||||||
|
"never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Humidity"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode":
|
||||||
|
"table", "placement": "bottom", "calcs": ["min", "max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "H", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||||
|
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_luftfeuchtigkeit\")\n |> aggregateWindow(every:
|
||||||
|
30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Humidity\")", "resultFormat": "time_series"}]}, {"title": "📊 Pressure & Wind", "type": "row", "gridPos":
|
||||||
|
{"h": 1, "w": 24, "x": 0, "y": 14}, "collapsed": false}, {"title": "Pressure", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x":
|
||||||
|
0, "y": 15}, "fieldConfig": {"defaults": {"unit": "pressurehpa", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options":
|
||||||
|
"Pressure"}, "properties": [{"id": "color", "value": {"fixedColor": "#8F3BB8", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["min", "max", "mean"]},
|
||||||
|
"tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "P", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||||
|
== \"hPa\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_druck\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||||
|
set(key: \"metric\", value: \"Pressure\")", "resultFormat": "time_series"}]}, {"title": "Wind Speed", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos":
|
||||||
|
{"h": 8, "w": 12, "x": 12, "y": 15}, "fieldConfig": {"defaults": {"unit": "velocitykmh", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher":
|
||||||
|
{"id": "byName", "options": "Wind"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs":
|
||||||
|
["max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "W", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||||
|
(r) => r[\"_measurement\"] == \"km/h\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_windgeschwindigkeit\")\n |> aggregateWindow(every: 30m, fn: mean,
|
||||||
|
createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Wind\")", "resultFormat": "time_series"}]}, {"title": "🌧️ Precipitation & Cloud", "type": "row", "gridPos": {"h": 1,
|
||||||
|
"w": 24, "x": 0, "y": 23}, "collapsed": false}, {"title": "Rain", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 0, "y": 24},
|
||||||
|
"fieldConfig": {"defaults": {"unit": "mm/h", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Rain"}, "properties":
|
||||||
|
[{"id": "color", "value": {"fixedColor": "#3274D9", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode":
|
||||||
|
"multi", "sort": "desc"}}, "targets": [{"refId": "R", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"mm/h\"
|
||||||
|
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_niederschlag\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||||
|
set(key: \"metric\", value: \"Rain\")", "resultFormat": "time_series"}]}, {"title": "Cloud Cover", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h":
|
||||||
|
8, "w": 12, "x": 12, "y": 24}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id":
|
||||||
|
"byName", "options": "Cloud Cover"}, "properties": [{"id": "color", "value": {"fixedColor": "#808080", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs":
|
||||||
|
["mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "C", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||||
|
(r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_bewolkungsgrad\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty:
|
||||||
|
false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Cloud Cover\")", "resultFormat": "time_series"}]}]}'
|
||||||
|
|
@ -7,12 +7,11 @@ kubeControllerManager:
|
||||||
serviceMonitor:
|
serviceMonitor:
|
||||||
https: true
|
https: true
|
||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
# proxy not working, svc not exposed by port
|
|
||||||
kubeProxy:
|
kubeProxy:
|
||||||
service:
|
service:
|
||||||
targetPort: 10249
|
targetPort: 10249
|
||||||
serviceMonitor:
|
serviceMonitor:
|
||||||
https: true
|
https: false
|
||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
|
@ -61,6 +60,7 @@ prometheus:
|
||||||
storage: 50Gi
|
storage: 50Gi
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
hosts:
|
hosts:
|
||||||
- prometheus.haumdaucher.de
|
- prometheus.haumdaucher.de
|
||||||
tls:
|
tls:
|
||||||
|
|
@ -68,7 +68,7 @@ prometheus:
|
||||||
- "prometheus.haumdaucher.de"
|
- "prometheus.haumdaucher.de"
|
||||||
secretName: prometheus-haumdaucher-de
|
secretName: prometheus-haumdaucher-de
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: "nginx"
|
kubernetes.io/ingress.class: "traefik"
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
||||||
|
|
@ -89,6 +89,7 @@ alertmanager:
|
||||||
enabled: true
|
enabled: true
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
hosts:
|
hosts:
|
||||||
- alertmanager.haumdaucher.de
|
- alertmanager.haumdaucher.de
|
||||||
tls:
|
tls:
|
||||||
|
|
@ -96,7 +97,7 @@ alertmanager:
|
||||||
- "alertmanager.haumdaucher.de"
|
- "alertmanager.haumdaucher.de"
|
||||||
secretName: alertmanager-haumdaucher-de
|
secretName: alertmanager-haumdaucher-de
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: "nginx"
|
kubernetes.io/ingress.class: "traefik"
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
||||||
|
|
@ -126,8 +127,31 @@ grafana:
|
||||||
imageRenderer:
|
imageRenderer:
|
||||||
enabled: true
|
enabled: true
|
||||||
adminPassword: kohkohh5sah1Do3ize2x
|
adminPassword: kohkohh5sah1Do3ize2x
|
||||||
|
additionalDataSources:
|
||||||
|
- name: InfluxDB Home Assistant
|
||||||
|
type: influxdb
|
||||||
|
access: proxy
|
||||||
|
url: http://influxdb-influxdb2.influxdb.svc.cluster.local:80
|
||||||
|
jsonData:
|
||||||
|
version: Flux
|
||||||
|
organization: influxdata
|
||||||
|
defaultBucket: default
|
||||||
|
httpMode: POST
|
||||||
|
secureJsonData:
|
||||||
|
token: enaiY9yaiWi6ahv0phoph3FaiphoGh
|
||||||
|
- name: InfluxDB Home Assistant InfluxQL
|
||||||
|
type: influxdb
|
||||||
|
access: proxy
|
||||||
|
url: http://influxdb-influxdb2.influxdb.svc.cluster.local:80
|
||||||
|
jsonData:
|
||||||
|
dbName: default
|
||||||
|
httpHeaderName1: 'Authorization'
|
||||||
|
httpMode: POST
|
||||||
|
secureJsonData:
|
||||||
|
httpHeaderValue1: 'Token enaiY9yaiWi6ahv0phoph3FaiphoGh'
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
hosts:
|
hosts:
|
||||||
- grafana.haumdaucher.de
|
- grafana.haumdaucher.de
|
||||||
tls:
|
tls:
|
||||||
|
|
@ -135,7 +159,7 @@ grafana:
|
||||||
- "grafana.haumdaucher.de"
|
- "grafana.haumdaucher.de"
|
||||||
secretName: grafana-haumdaucher-de
|
secretName: grafana-haumdaucher-de
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: "nginx"
|
kubernetes.io/ingress.class: "traefik"
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
env:
|
env:
|
||||||
|
|
@ -149,4 +173,22 @@ grafana:
|
||||||
# interval_day: "MMM YYYY"
|
# interval_day: "MMM YYYY"
|
||||||
# interval_month: "YYYY-MM"
|
# interval_month: "YYYY-MM"
|
||||||
# interval_year: "YYYY"
|
# interval_year: "YYYY"
|
||||||
|
sidecar:
|
||||||
|
dashboards:
|
||||||
|
enabled: true
|
||||||
|
label: grafana_dashboard
|
||||||
|
labelValue: "1"
|
||||||
|
searchNamespace: monitoring
|
||||||
|
folder: /tmp/dashboards
|
||||||
|
defaultFolderName: null
|
||||||
|
provider:
|
||||||
|
name: sidecarProvider
|
||||||
|
orgId: 1
|
||||||
|
folder: ""
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
editable: false
|
||||||
|
allowUiUpdates: false
|
||||||
|
options:
|
||||||
|
path: /tmp/dashboards
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# discover-home-assistant.sh — Query Home Assistant REST API for entity metadata
|
||||||
|
# Usage: ./discover-home-assistant.sh
|
||||||
|
# Requires: curl, jq
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HA_URL="https://hass.moritzgraf.de"
|
||||||
|
HA_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2NDMyZTI1MDIzZjU0ZmFiYTU3OTc0ODYyMzg3M2ExZSIsImlhdCI6MTc4MjczMTY4NiwiZXhwIjoyMDk4MDkxNjg2fQ.u8kFHoYL2kdhVenfe3BiWcNkN_en3yn91MRGSVJvbmw"
|
||||||
|
|
||||||
|
echo "=== Home Assistant Entity Discovery ==="
|
||||||
|
echo "URL: $HA_URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get all entities
|
||||||
|
echo "→ Fetching all entities..."
|
||||||
|
ENTITIES=$(curl -s -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/states")
|
||||||
|
|
||||||
|
if [ -z "$ENTITIES" ] || echo "$ENTITIES" | jq -e '. == null' >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: Failed to fetch entities. Check token and connectivity."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Entity Summary ==="
|
||||||
|
echo "$ENTITIES" | jq -r '
|
||||||
|
[.[] | {
|
||||||
|
entity_id,
|
||||||
|
state,
|
||||||
|
friendly_name: (.attributes.friendly_name // ""),
|
||||||
|
unit: (.attributes.unit_of_measurement // ""),
|
||||||
|
device_class: (.attributes.device_class // ""),
|
||||||
|
domain: (.entity_id | split(".")[0])
|
||||||
|
}]
|
||||||
|
| sort_by(.domain, .entity_id)
|
||||||
|
| .[]
|
||||||
|
| "\(.entity_id) | state=\(.state) | name=\(.friendly_name) | unit=\(.unit) | class=\(.device_class) | domain=\(.domain)"
|
||||||
|
'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Domains (entity types) ==="
|
||||||
|
echo "$ENTITIES" | jq -r '[.[].entity_id | split(".")[0]] | unique | .[]'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Entities with unit_of_measurement (likely sensor data in InfluxDB) ==="
|
||||||
|
echo "$ENTITIES" | jq -r '
|
||||||
|
[.[] | select(.attributes.unit_of_measurement != null) | {
|
||||||
|
entity_id,
|
||||||
|
unit: .attributes.unit_of_measurement,
|
||||||
|
friendly_name: (.attributes.friendly_name // ""),
|
||||||
|
device_class: (.attributes.device_class // "")
|
||||||
|
}]
|
||||||
|
| sort_by(.unit, .entity_id)
|
||||||
|
| .[]
|
||||||
|
| "\(.entity_id) | \(.unit) | \(.device_class) | \(.friendly_name)"
|
||||||
|
'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Device classes ==="
|
||||||
|
echo "$ENTITIES" | jq -r '[.[].attributes.device_class | select(. != null)] | unique | .[]'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Discovery complete ==="
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# discover-influxdb.sh — Query InfluxDB to discover measurements, fields, and tags
|
||||||
|
# Usage: ./discover-influxdb.sh [namespace]
|
||||||
|
# Requires: kubectl, curl, jq
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NAMESPACE="${1:-influxdb}"
|
||||||
|
INFLUXDB_TOKEN="enaiY9yaiWi6ahv0phoph3FaiphoGh"
|
||||||
|
INFLUXDB_ORG="influxdata"
|
||||||
|
INFLUXDB_BUCKET="default"
|
||||||
|
LOCAL_PORT="8086"
|
||||||
|
|
||||||
|
echo "=== InfluxDB Discovery ==="
|
||||||
|
echo "Namespace: $NAMESPACE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Start port-forward in background
|
||||||
|
echo "→ Starting port-forward to influxdb-influxdb2..."
|
||||||
|
kubectl -n "$NAMESPACE" port-forward "svc/influxdb-influxdb2" "$LOCAL_PORT:80" &>/dev/null &
|
||||||
|
PF_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Ensure cleanup on exit
|
||||||
|
cleanup() {
|
||||||
|
kill "$PF_PID" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
API="http://localhost:$LOCAL_PORT/api/v2/query?org=$INFLUXDB_ORG"
|
||||||
|
AUTH="Authorization: Token $INFLUXDB_TOKEN"
|
||||||
|
CT="Content-Type: application/vnd.flux"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Measurements (tables) in bucket '$INFLUXDB_BUCKET' ==="
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "import \"influxdata/influxdb/schema\"
|
||||||
|
schema.measurements(bucket: \"$INFLUXDB_BUCKET\")" \
|
||||||
|
| jq -r '.[] | ._value' 2>/dev/null || echo "(no results or parse error)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Measurements with data in last 6 months ==="
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "from(bucket: \"$INFLUXDB_BUCKET\")
|
||||||
|
|> range(start: -6mo)
|
||||||
|
|> keep(columns: [\"_measurement\"])
|
||||||
|
|> distinct()
|
||||||
|
|> sort()" \
|
||||||
|
| jq -r '.[] | ._value' 2>/dev/null || echo "(no results or parse error)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== For each measurement: fields, tags, and sample count ==="
|
||||||
|
|
||||||
|
# Get measurements
|
||||||
|
MEASUREMENTS=$(curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "import \"influxdata/influxdb/schema\"
|
||||||
|
schema.measurements(bucket: \"$INFLUXDB_BUCKET\")" \
|
||||||
|
| jq -r '.[] | ._value' 2>/dev/null)
|
||||||
|
|
||||||
|
for m in $MEASUREMENTS; do
|
||||||
|
echo ""
|
||||||
|
echo "--- Measurement: $m ---"
|
||||||
|
|
||||||
|
# Fields
|
||||||
|
echo " Fields:"
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "import \"influxdata/influxdb/schema\"
|
||||||
|
schema.measurementFieldKeys(bucket: \"$INFLUXDB_BUCKET\", measurement: \"$m\")" \
|
||||||
|
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||||
|
|
||||||
|
# Tags
|
||||||
|
echo " Tags:"
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "import \"influxdata/influxdb/schema\"
|
||||||
|
schema.measurementTagKeys(bucket: \"$INFLUXDB_BUCKET\", measurement: \"$m\")" \
|
||||||
|
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||||
|
|
||||||
|
# Tag values for entity_id (most common tag in HA data)
|
||||||
|
echo " entity_id values (sample):"
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "import \"influxdata/influxdb/schema\"
|
||||||
|
schema.tagValues(bucket: \"$INFLUXDB_BUCKET\", tag: \"entity_id\")
|
||||||
|
|> filter(fn: (r) => r._measurement == \"$m\")
|
||||||
|
|> limit(n: 20)" \
|
||||||
|
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||||
|
|
||||||
|
# Sample count in last 6 months
|
||||||
|
echo " Data points (last 6 months):"
|
||||||
|
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||||
|
-d "from(bucket: \"$INFLUXDB_BUCKET\")
|
||||||
|
|> range(start: -6mo)
|
||||||
|
|> filter(fn: (r) => r._measurement == \"$m\")
|
||||||
|
|> count()" \
|
||||||
|
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Discovery complete ==="
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
rootUser:
|
|
||||||
password: cah6eepai1Saepho8tho
|
|
||||||
forcePassword: true
|
|
||||||
db:
|
|
||||||
name: tt-rss
|
|
||||||
user: tt-rss
|
|
||||||
password: AhS5cai7oph3ahN4Aile
|
|
||||||
forcePassword: true
|
|
||||||
replication:
|
|
||||||
enabled: false
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
db:
|
|
||||||
host: tt-rss-mariadb
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
hosts:
|
|
||||||
- path: "/"
|
|
||||||
tls: true
|
|
||||||
name: "tt-rss.phpmyadmin.haumdaucher.de"
|
|
||||||
tlsSecret: "tt-rss-phpmyadmin-haumdaucher-de"
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
env:
|
|
||||||
# -- External URL you use to connect to the RSS (the one you enter in your browser)
|
|
||||||
TTRSS_SELF_URL_PATH: "tt-rss.moritzgraf.de"
|
|
||||||
# -- Postgres database hostname
|
|
||||||
# @default -- internal postgresql URL
|
|
||||||
TTRSS_DB_HOST: "tt-rss-mariadb"
|
|
||||||
# -- Postgres database port.
|
|
||||||
TTRSS_DB_PORT: "3306"
|
|
||||||
# -- Postgres database user name
|
|
||||||
# @default -- postgresql.postgresqlUsername value
|
|
||||||
TTRSS_DB_USER: "tt-rss"
|
|
||||||
# -- Postgres database password
|
|
||||||
# @default -- postgresql.postgresqlPassword value
|
|
||||||
TTRSS_DB_PASS: "AhS5cai7oph3ahN4Aile"
|
|
||||||
# -- Postgres database password
|
|
||||||
# @default -- postgresql.postgresqlDatabase value
|
|
||||||
TTRSS_DB_NAME: "tt-rss"
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
main:
|
|
||||||
enabled: true
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: "nginx"
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- "tt-rss.moritzgraf.de"
|
|
||||||
secretName: tt-rss-moritzgraf-de
|
|
||||||
postgresql:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,29 +0,0 @@
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: CronJob
|
|
||||||
metadata:
|
|
||||||
name: dropbox-sync
|
|
||||||
namespace: velero
|
|
||||||
spec:
|
|
||||||
schedule: "0 5 * * *"
|
|
||||||
jobTemplate:
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
serviceAccountName: default
|
|
||||||
containers:
|
|
||||||
- name: dopybox-sync
|
|
||||||
image: iptizer/swiss
|
|
||||||
volumeMounts:
|
|
||||||
- name: rclone-config
|
|
||||||
mountPath: /rclone.secret
|
|
||||||
subPath: "rclone.secret"
|
|
||||||
command:
|
|
||||||
- "devbox"
|
|
||||||
- "run"
|
|
||||||
args:
|
|
||||||
- "rclone --config /rclone.secret -v copy minio:backup/ dropbox:server_backup/velero/"
|
|
||||||
volumes:
|
|
||||||
- name: rclone-config
|
|
||||||
secret:
|
|
||||||
secretName: rclone-config
|
|
||||||
restartPolicy: Never
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,39 @@
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: gdrive-sync
|
||||||
|
namespace: velero
|
||||||
|
spec:
|
||||||
|
schedule: "0 5 * * *"
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: default
|
||||||
|
containers:
|
||||||
|
- name: gdrive-sync
|
||||||
|
image: rclone/rclone:latest
|
||||||
|
volumeMounts:
|
||||||
|
- name: rclone-config
|
||||||
|
mountPath: /rclone.secret
|
||||||
|
subPath: "rclone.secret"
|
||||||
|
- name: gdrive-sa-key
|
||||||
|
mountPath: /gdrive_sa.json.secret
|
||||||
|
subPath: "gdrive_sa.json.secret"
|
||||||
|
command:
|
||||||
|
- "rclone"
|
||||||
|
args:
|
||||||
|
- "--config"
|
||||||
|
- "/rclone.secret"
|
||||||
|
- "-v"
|
||||||
|
- "sync"
|
||||||
|
- "minio:backup/"
|
||||||
|
- "gdrivemoritz:"
|
||||||
|
volumes:
|
||||||
|
- name: rclone-config
|
||||||
|
secret:
|
||||||
|
secretName: rclone-config
|
||||||
|
- name: gdrive-sa-key
|
||||||
|
secret:
|
||||||
|
secretName: gdrive-sa-key
|
||||||
|
restartPolicy: Never
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: gdrive-sync-manual
|
||||||
|
namespace: velero
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: default
|
||||||
|
containers:
|
||||||
|
- name: gdrive-sync
|
||||||
|
image: rclone/rclone:latest
|
||||||
|
volumeMounts:
|
||||||
|
- name: rclone-config
|
||||||
|
mountPath: /rclone.secret
|
||||||
|
subPath: "rclone.secret"
|
||||||
|
- name: gdrive-sa-key
|
||||||
|
mountPath: /gdrive_sa.json.secret
|
||||||
|
subPath: "gdrive_sa.json.secret"
|
||||||
|
command:
|
||||||
|
- "rclone"
|
||||||
|
args:
|
||||||
|
- "--config"
|
||||||
|
- "/rclone.secret"
|
||||||
|
- "-v"
|
||||||
|
- "sync"
|
||||||
|
- "minio:backup/"
|
||||||
|
- "gdrivemoritz:"
|
||||||
|
volumes:
|
||||||
|
- name: rclone-config
|
||||||
|
secret:
|
||||||
|
secretName: rclone-config
|
||||||
|
- name: gdrive-sa-key
|
||||||
|
secret:
|
||||||
|
secretName: gdrive-sa-key
|
||||||
|
restartPolicy: Never
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,828 @@
|
||||||
|
##
|
||||||
|
## Configuration settings related to Velero installation namespace
|
||||||
|
##
|
||||||
|
|
||||||
|
# Labels settings in namespace
|
||||||
|
namespace:
|
||||||
|
labels: {}
|
||||||
|
# Enforce Pod Security Standards with Namespace Labels
|
||||||
|
# https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
|
||||||
|
# pod-security.kubernetes.io/enforce: privileged
|
||||||
|
# pod-security.kubernetes.io/enforce-version: latest
|
||||||
|
# pod-security.kubernetes.io/audit: privileged
|
||||||
|
# pod-security.kubernetes.io/audit-version: latest
|
||||||
|
# pod-security.kubernetes.io/warn: privileged
|
||||||
|
# pod-security.kubernetes.io/warn-version: latest
|
||||||
|
|
||||||
|
##
|
||||||
|
## End of namespace-related settings.
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Configuration settings that directly affect the Velero deployment YAML.
|
||||||
|
##
|
||||||
|
|
||||||
|
# Details of the container image to use in the Velero deployment & daemonset (if
|
||||||
|
# enabling node-agent). Required.
|
||||||
|
image:
|
||||||
|
repository: docker.io/velero/velero
|
||||||
|
tag: v1.18.1
|
||||||
|
# Digest value example: sha256:73266e6bd7e2fe3f65fb20677d36c4a8df76d417d9ba76bd9932e0d983a776ec.
|
||||||
|
# If used, it will take precedence over the image.tag.
|
||||||
|
# digest:
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# One or more secrets to be used when pulling images
|
||||||
|
imagePullSecrets: []
|
||||||
|
# - registrySecretName
|
||||||
|
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
# Annotations to add to the Velero deployment's. Optional.
|
||||||
|
#
|
||||||
|
# If you are using reloader use the following annotation with your VELERO_SECRET_NAME
|
||||||
|
annotations: {}
|
||||||
|
# secret.reloader.stakater.com/reload: "<VELERO_SECRET_NAME>"
|
||||||
|
|
||||||
|
# Annotations to add to secret
|
||||||
|
secretAnnotations: {}
|
||||||
|
|
||||||
|
# Labels to add to the Velero deployment's. Optional.
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
# Annotations to add to the Velero deployment's pod template. Optional.
|
||||||
|
#
|
||||||
|
# If using kube2iam or kiam, use the following annotation with your AWS_ACCOUNT_ID
|
||||||
|
# and VELERO_ROLE_NAME filled in:
|
||||||
|
podAnnotations: {}
|
||||||
|
# iam.amazonaws.com/role: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<VELERO_ROLE_NAME>"
|
||||||
|
|
||||||
|
# Additional pod labels for Velero deployment's template. Optional
|
||||||
|
# ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||||
|
podLabels: {}
|
||||||
|
|
||||||
|
# Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)
|
||||||
|
# revisionHistoryLimit: 1
|
||||||
|
|
||||||
|
# Resource requests/limits to specify for the Velero deployment.
|
||||||
|
# https://velero.io/docs/v1.6/customize-installation/#customize-resource-requests-and-limits
|
||||||
|
resources: {}
|
||||||
|
# requests:
|
||||||
|
# cpu: 500m
|
||||||
|
# memory: 128Mi
|
||||||
|
# limits:
|
||||||
|
# cpu: 1000m
|
||||||
|
# memory: 512Mi
|
||||||
|
|
||||||
|
# Container resize policy for the Velero deployment.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/
|
||||||
|
resizePolicy: []
|
||||||
|
# - resourceName: cpu
|
||||||
|
# restartPolicy: NotRequired
|
||||||
|
# - resourceName: memory
|
||||||
|
# restartPolicy: RestartContainer
|
||||||
|
|
||||||
|
# Configure hostAliases for Velero deployment. Optional
|
||||||
|
# For more information, check: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
|
||||||
|
hostAliases: []
|
||||||
|
# - ip: "127.0.0.1"
|
||||||
|
# hostnames:
|
||||||
|
# - "foo.local"
|
||||||
|
# - "bar.local"
|
||||||
|
|
||||||
|
# Resource requests/limits to specify for the upgradeCRDs job pod. Need to be adjusted by user accordingly.
|
||||||
|
upgradeJobResources: {}
|
||||||
|
# requests:
|
||||||
|
# cpu: 50m
|
||||||
|
# memory: 128Mi
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 256Mi
|
||||||
|
upgradeCRDsJob:
|
||||||
|
# Extra volumes for the Upgrade CRDs Job. Optional.
|
||||||
|
extraVolumes: []
|
||||||
|
# Extra volumeMounts for the Upgrade CRDs Job. Optional.
|
||||||
|
extraVolumeMounts: []
|
||||||
|
# Additional values to be used as environment variables. Optional.
|
||||||
|
extraEnvVars: []
|
||||||
|
# Simple value
|
||||||
|
# - name: SIMPLE_VAR
|
||||||
|
# value: "simple-value"
|
||||||
|
|
||||||
|
# FieldRef example
|
||||||
|
# - name: MY_POD_LABEL
|
||||||
|
# valueFrom:
|
||||||
|
# fieldRef:
|
||||||
|
# fieldPath: metadata.labels['my_label']
|
||||||
|
|
||||||
|
# Configure if API credential for Service Account is automounted.
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
# Configure the shell cmd in case you are using custom image
|
||||||
|
# shellCmd: /tmp/sh
|
||||||
|
# updateCmd: /velero install --crds-only --dry-run -o yaml | /tmp/kubectl apply -f -
|
||||||
|
|
||||||
|
# Configure the dnsPolicy of the Velero deployment
|
||||||
|
# See: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
|
||||||
|
# Init containers to add to the Velero deployment's pod spec. At least one plugin provider image is required.
|
||||||
|
# If the value is a string then it is evaluated as a template.
|
||||||
|
initContainers:
|
||||||
|
# - name: velero-plugin-for-aws
|
||||||
|
# image: velero/velero-plugin-for-aws:v1.13.1
|
||||||
|
# imagePullPolicy: IfNotPresent
|
||||||
|
# volumeMounts:
|
||||||
|
# - mountPath: /target
|
||||||
|
# name: plugins
|
||||||
|
|
||||||
|
# SecurityContext to use for the Velero deployment. Optional.
|
||||||
|
# Set fsGroup for `AWS IAM Roles for Service Accounts`
|
||||||
|
# see more informations at: https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 1337
|
||||||
|
|
||||||
|
# Container Level Security Context for the 'velero' container of the Velero deployment. Optional.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||||
|
containerSecurityContext: {}
|
||||||
|
# allowPrivilegeEscalation: false
|
||||||
|
# capabilities:
|
||||||
|
# drop: ["ALL"]
|
||||||
|
# add: []
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
|
||||||
|
# Container Lifecycle Hooks to use for the Velero deployment. Optional.
|
||||||
|
lifecycle: {}
|
||||||
|
|
||||||
|
# Pod priority class name to use for the Velero deployment. Optional.
|
||||||
|
priorityClassName: ""
|
||||||
|
|
||||||
|
# Pod runtime class name to use for the Velero deployment. Optional.
|
||||||
|
runtimeClassName: ""
|
||||||
|
|
||||||
|
# The number of seconds to allow for graceful termination of the pod. Optional.
|
||||||
|
terminationGracePeriodSeconds: 3600
|
||||||
|
|
||||||
|
# Liveness probe of the pod
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: http-monitoring
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 5
|
||||||
|
|
||||||
|
# Readiness probe of the pod
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /metrics
|
||||||
|
port: http-monitoring
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 5
|
||||||
|
|
||||||
|
# Tolerations to use for the Velero deployment. Optional.
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
# Affinity to use for the Velero deployment. Optional.
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
# Node selector to use for the Velero deployment. Optional.
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
# DNS configuration to use for the Velero deployment. Optional.
|
||||||
|
dnsConfig: {}
|
||||||
|
|
||||||
|
# Pod Disruption Budget for the Velero deployment. Optional.
|
||||||
|
# Velero runs a single replica with a Recreate strategy by default, so this is
|
||||||
|
# disabled by default. Enable it when running Velero with multiple replicas.
|
||||||
|
podDisruptionBudget:
|
||||||
|
enabled: false
|
||||||
|
# minAvailable and maxUnavailable are mutually exclusive; set only one.
|
||||||
|
minAvailable: 1
|
||||||
|
maxUnavailable: ""
|
||||||
|
# See https://kubernetes.io/docs/tasks/run-application/configure-pdb/
|
||||||
|
# unhealthyPodEvictionPolicy: IfHealthyBudget
|
||||||
|
annotations: {}
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
# Extra volumes for the Velero deployment. Optional.
|
||||||
|
extraVolumes: []
|
||||||
|
|
||||||
|
# Extra volumeMounts for the Velero deployment. Optional.
|
||||||
|
extraVolumeMounts: []
|
||||||
|
|
||||||
|
# Extra K8s manifests to deploy
|
||||||
|
extraObjects: []
|
||||||
|
# - apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||||
|
# kind: SecretProviderClass
|
||||||
|
# metadata:
|
||||||
|
# name: velero-secrets-store
|
||||||
|
# spec:
|
||||||
|
# provider: aws
|
||||||
|
# parameters:
|
||||||
|
# objects: |
|
||||||
|
# - objectName: "velero"
|
||||||
|
# objectType: "secretsmanager"
|
||||||
|
# jmesPath:
|
||||||
|
# - path: "access_key"
|
||||||
|
# objectAlias: "access_key"
|
||||||
|
# - path: "secret_key"
|
||||||
|
# objectAlias: "secret_key"
|
||||||
|
# secretObjects:
|
||||||
|
# - data:
|
||||||
|
# - key: access_key
|
||||||
|
# objectName: client-id
|
||||||
|
# - key: client-secret
|
||||||
|
# objectName: client-secret
|
||||||
|
# secretName: velero-secrets-store
|
||||||
|
# type: Opaque
|
||||||
|
|
||||||
|
# Settings for Velero's prometheus metrics. Enabled by default.
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
scrapeInterval: 30s
|
||||||
|
scrapeTimeout: 10s
|
||||||
|
|
||||||
|
# service metdata if metrics are enabled
|
||||||
|
service:
|
||||||
|
annotations: {}
|
||||||
|
type: ClusterIP
|
||||||
|
labels: {}
|
||||||
|
nodePort: null
|
||||||
|
|
||||||
|
# External/Internal traffic policy setting (Cluster, Local)
|
||||||
|
# https://kubernetes.io/docs/reference/networking/virtual-ips/#traffic-policies
|
||||||
|
externalTrafficPolicy: ""
|
||||||
|
internalTrafficPolicy: ""
|
||||||
|
|
||||||
|
# the IP family policy for the metrics Service to be able to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services).
|
||||||
|
ipFamilyPolicy: ""
|
||||||
|
# a list of IP families for the metrics Service that should be supported, in the order in which they should be applied to ClusterIP. Can be "IPv4" and/or "IPv6".
|
||||||
|
ipFamilies: []
|
||||||
|
|
||||||
|
# Pod annotations for Prometheus
|
||||||
|
podAnnotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8085"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
|
||||||
|
serviceMonitor:
|
||||||
|
autodetect: true
|
||||||
|
enabled: false
|
||||||
|
annotations: {}
|
||||||
|
additionalLabels: {}
|
||||||
|
|
||||||
|
# metrics.serviceMonitor.metricRelabelings Specify Metric Relabelings to add to the scrape endpoint
|
||||||
|
# ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig
|
||||||
|
# metricRelabelings: []
|
||||||
|
# metrics.serviceMonitor.relabelings [array] Prometheus relabeling rules
|
||||||
|
# relabelings: []
|
||||||
|
# ServiceMonitor namespace. Default to Velero namespace.
|
||||||
|
# namespace:
|
||||||
|
# ServiceMonitor connection scheme. Defaults to HTTP.
|
||||||
|
# scheme: ""
|
||||||
|
# ServiceMonitor connection tlsConfig. Defaults to {}.
|
||||||
|
# tlsConfig: {}
|
||||||
|
nodeAgentPodMonitor:
|
||||||
|
autodetect: true
|
||||||
|
enabled: false
|
||||||
|
annotations: {}
|
||||||
|
additionalLabels: {}
|
||||||
|
# metrics.nodeAgentPodMonitor.metricRelabelings Specify Metric Relabelings to add to the scrape endpoint
|
||||||
|
# ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig
|
||||||
|
# metricRelabelings: []
|
||||||
|
# metrics.nodeAgentPodMonitor.relabelings [array] Prometheus relabeling rules
|
||||||
|
# relabelings: []
|
||||||
|
# PodMonitor namespace. Default to Velero namespace.
|
||||||
|
# namespace:
|
||||||
|
# PodMonitor connection scheme. Defaults to HTTP.
|
||||||
|
# scheme: ""
|
||||||
|
# PodMonitor connection tlsConfig. Defaults to {}.
|
||||||
|
# tlsConfig: {}
|
||||||
|
|
||||||
|
prometheusRule:
|
||||||
|
autodetect: true
|
||||||
|
enabled: false
|
||||||
|
# Additional labels to add to deployed PrometheusRule
|
||||||
|
additionalLabels: {}
|
||||||
|
# PrometheusRule namespace. Defaults to Velero namespace.
|
||||||
|
# namespace: ""
|
||||||
|
# Rules to be deployed
|
||||||
|
spec: []
|
||||||
|
# - alert: VeleroBackupFailed
|
||||||
|
# annotations:
|
||||||
|
# message: Velero backup {{ $labels.schedule }} has failed
|
||||||
|
# expr: |-
|
||||||
|
# velero_backup_last_status{schedule!=""} != 1
|
||||||
|
# for: 15m
|
||||||
|
# labels:
|
||||||
|
# severity: warning
|
||||||
|
# - alert: VeleroBackupFailing
|
||||||
|
# annotations:
|
||||||
|
# message: Velero backup {{ $labels.schedule }} has been failing for the last 12h
|
||||||
|
# expr: |-
|
||||||
|
# velero_backup_last_status{schedule!=""} != 1
|
||||||
|
# for: 12h
|
||||||
|
# labels:
|
||||||
|
# severity: critical
|
||||||
|
# - alert: VeleroNoNewBackup
|
||||||
|
# annotations:
|
||||||
|
# message: Velero backup {{ $labels.schedule }} has not run successfully in the last 25h
|
||||||
|
# expr: |-
|
||||||
|
# (
|
||||||
|
# (time() - velero_backup_last_successful_timestamp{schedule!=""}) >bool (25 * 3600)
|
||||||
|
# or
|
||||||
|
# absent(velero_backup_last_successful_timestamp{schedule!=""})
|
||||||
|
# ) == 1
|
||||||
|
# for: 1h
|
||||||
|
# labels:
|
||||||
|
# severity: critical
|
||||||
|
# - alert: VeleroBackupPartialFailures
|
||||||
|
# annotations:
|
||||||
|
# message: Velero backup {{ $labels.schedule }} has {{ $value | humanizePercentage }} partialy failed backups
|
||||||
|
# expr: |-
|
||||||
|
# rate(velero_backup_partial_failure_total{schedule!=""}[25m])
|
||||||
|
# / rate(velero_backup_attempt_total{schedule!=""}[25m]) > 0.5
|
||||||
|
# for: 15m
|
||||||
|
# labels:
|
||||||
|
# severity: warning
|
||||||
|
|
||||||
|
kubectl:
|
||||||
|
image:
|
||||||
|
repository: registry.k8s.io/kubectl
|
||||||
|
# Digest value example: sha256:c7bf8cf6e79f474fda2623a7b98572a1b909c4d912b05062b444ffe79883fdbe.
|
||||||
|
# If used, it will take precedence over the kubectl.image.tag.
|
||||||
|
# digest:
|
||||||
|
# kubectl image tag. If used, it will take precedence over the cluster Kubernetes version.
|
||||||
|
# tag: v1.34.5
|
||||||
|
# Container Level Security Context for the 'kubectl' container of the crd jobs. Optional.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||||
|
containerSecurityContext: {}
|
||||||
|
# Resource requests/limits to specify for the upgrade/cleanup job. Optional
|
||||||
|
resources: {}
|
||||||
|
# Annotations to set for the upgrade/cleanup job. Optional.
|
||||||
|
annotations: {}
|
||||||
|
# Labels to set for the upgrade/cleanup job. Optional.
|
||||||
|
labels: {}
|
||||||
|
# Extra volumes for the upgrade/cleanup job. Optional.
|
||||||
|
extraVolumes: []
|
||||||
|
# Extra volumeMounts for the upgrade/cleanup job. Optional.
|
||||||
|
extraVolumeMounts: []
|
||||||
|
|
||||||
|
# This job upgrades the CRDs.
|
||||||
|
upgradeCRDs: true
|
||||||
|
|
||||||
|
# This job is meant primarily for cleaning up CRDs on CI systems.
|
||||||
|
# Using this on production systems, especially those that have multiple releases of Velero, will be destructive.
|
||||||
|
cleanUpCRDs: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## End of deployment-related settings.
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Parameters for the `default` BackupStorageLocation and VolumeSnapshotLocation,
|
||||||
|
## and additional server settings.
|
||||||
|
##
|
||||||
|
configuration:
|
||||||
|
# Parameters for the BackupStorageLocation(s). Configure multiple by adding other element(s) to the backupStorageLocation slice.
|
||||||
|
# See https://velero.io/docs/v1.6/api-types/backupstoragelocation/
|
||||||
|
backupStorageLocation:
|
||||||
|
# name is the name of the backup storage location where backups should be stored. If a name is not provided,
|
||||||
|
# a backup storage location will be created with the name "default". Optional.
|
||||||
|
- name:
|
||||||
|
# provider is the name for the backup storage location provider.
|
||||||
|
provider: ""
|
||||||
|
# bucket is the name of the bucket to store backups in. Required.
|
||||||
|
bucket: ""
|
||||||
|
# caCert defines a base64 encoded CA bundle to use when verifying TLS connections to the provider. Optional.
|
||||||
|
caCert:
|
||||||
|
# prefix is the directory under which all Velero data should be stored within the bucket. Optional.
|
||||||
|
prefix:
|
||||||
|
# default indicates this location is the default backup storage location. Optional.
|
||||||
|
default: false
|
||||||
|
# validationFrequency defines how frequently Velero should validate the object storage. Optional.
|
||||||
|
validationFrequency:
|
||||||
|
# backupSyncPeriod defines how frequently Velero should synchronize backups in object storage. Optional.
|
||||||
|
backupSyncPeriod:
|
||||||
|
# accessMode determines if velero can write to this backup storage location. Optional.
|
||||||
|
# default to ReadWrite, ReadOnly is used during migrations and restores.
|
||||||
|
accessMode: ReadWrite
|
||||||
|
credential:
|
||||||
|
# name of the secret used by this backupStorageLocation.
|
||||||
|
name:
|
||||||
|
# name of key that contains the secret data to be used.
|
||||||
|
key:
|
||||||
|
# Additional provider-specific configuration. See link above
|
||||||
|
# for details of required/optional fields for your provider.
|
||||||
|
config: {}
|
||||||
|
# region:
|
||||||
|
# s3ForcePathStyle:
|
||||||
|
# s3Url:
|
||||||
|
# kmsKeyId:
|
||||||
|
# resourceGroup:
|
||||||
|
# The ID of the subscription containing the storage account, if different from the cluster’s subscription. (Azure only)
|
||||||
|
# subscriptionId:
|
||||||
|
# storageAccount:
|
||||||
|
# publicUrl:
|
||||||
|
# Name of the GCP service account to use for this backup storage location. Specify the
|
||||||
|
# service account here if you want to use workload identity instead of providing the key file.(GCP only)
|
||||||
|
# serviceAccount:
|
||||||
|
# Option to skip certificate validation or not if insecureSkipTLSVerify is set to be true, the client side should set the
|
||||||
|
# flag. For Velero client Command like velero backup describe, velero backup logs needs to add the flag --insecure-skip-tls-verify
|
||||||
|
# insecureSkipTLSVerify:
|
||||||
|
|
||||||
|
# annotations allows adding arbitrary annotations to this BackupStorageLocation resource. Optional.
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# Parameters for the VolumeSnapshotLocation(s). Configure multiple by adding other element(s) to the volumeSnapshotLocation slice.
|
||||||
|
# See https://velero.io/docs/v1.6/api-types/volumesnapshotlocation/
|
||||||
|
volumeSnapshotLocation:
|
||||||
|
# name is the name of the volume snapshot location where snapshots are being taken. If a name is not provided,
|
||||||
|
# a volume snapshot location will be created with the name "default". Optional.
|
||||||
|
- name:
|
||||||
|
# provider is the name for the volume snapshot provider.
|
||||||
|
provider: ""
|
||||||
|
credential:
|
||||||
|
# name of the secret used by this volumeSnapshotLocation.
|
||||||
|
name:
|
||||||
|
# name of key that contains the secret data to be used.
|
||||||
|
key:
|
||||||
|
# Additional provider-specific configuration. See link above
|
||||||
|
# for details of required/optional fields for your provider.
|
||||||
|
config: {}
|
||||||
|
# region:
|
||||||
|
# apiTimeout:
|
||||||
|
# resourceGroup:
|
||||||
|
# The ID of the subscription where volume snapshots should be stored, if different from the cluster’s subscription. If specified, also requires `configuration.volumeSnapshotLocation.config.resourceGroup`to be set. (Azure only)
|
||||||
|
# subscriptionId:
|
||||||
|
# incremental:
|
||||||
|
# snapshotLocation:
|
||||||
|
# project:
|
||||||
|
|
||||||
|
# annotations allows adding arbitrary annotations to this VolumeSnapshotLocation resource. Optional.
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# These are server-level settings passed as CLI flags to the `velero server` command. Velero
|
||||||
|
# uses default values if they're not passed in, so they only need to be explicitly specified
|
||||||
|
# here if using a non-default value. The `velero server` default values are shown in the
|
||||||
|
# comments below.
|
||||||
|
# --------------------
|
||||||
|
# `velero server` default: kopia
|
||||||
|
uploaderType:
|
||||||
|
# `velero server` default: 1m
|
||||||
|
backupSyncPeriod:
|
||||||
|
# `velero server` default: 4h
|
||||||
|
fsBackupTimeout:
|
||||||
|
# `velero server` default: 30
|
||||||
|
clientBurst:
|
||||||
|
# `velero server` default: 500
|
||||||
|
clientPageSize:
|
||||||
|
# `velero server` default: 20.0
|
||||||
|
clientQPS:
|
||||||
|
# Name of the default backup storage location. Default: default
|
||||||
|
defaultBackupStorageLocation:
|
||||||
|
# The default duration any single item operation can take before timing out, especially important for large volume schedules. Default 4h
|
||||||
|
defaultItemOperationTimeout:
|
||||||
|
# How long to wait by default before backups can be garbage collected. Default: 72h
|
||||||
|
defaultBackupTTL:
|
||||||
|
# Name of the default volume snapshot location.
|
||||||
|
defaultVolumeSnapshotLocations:
|
||||||
|
# `velero server` default: empty
|
||||||
|
disableControllers:
|
||||||
|
# `velero server` default: false
|
||||||
|
disableInformerCache: false
|
||||||
|
# `velero server` default: 1h
|
||||||
|
garbageCollectionFrequency:
|
||||||
|
# `velero server` default: 1
|
||||||
|
itemBlockWorkerCount:
|
||||||
|
# Set log-format for Velero pod. Default: text. Other option: json.
|
||||||
|
logFormat:
|
||||||
|
# Set log-level for Velero pod. Default: info. Other options: debug, warning, error, fatal, panic.
|
||||||
|
logLevel:
|
||||||
|
# The address to expose prometheus metrics. Default: :8085
|
||||||
|
metricsAddress:
|
||||||
|
# Directory containing Velero plugins. Default: /plugins
|
||||||
|
pluginDir:
|
||||||
|
# The address to expose the pprof profiler. Default: localhost:6060
|
||||||
|
profilerAddress:
|
||||||
|
# `velero server` default: false
|
||||||
|
restoreOnlyMode:
|
||||||
|
# `velero server` default: customresourcedefinitions,namespaces,storageclasses,volumesnapshotclass.snapshot.storage.k8s.io,volumesnapshotcontents.snapshot.storage.k8s.io,volumesnapshots.snapshot.storage.k8s.io,persistentvolumes,persistentvolumeclaims,secrets,configmaps,serviceaccounts,limitranges,pods,replicasets.apps,clusterclasses.cluster.x-k8s.io,clusters.cluster.x-k8s.io,clusterresourcesets.addons.cluster.x-k8s.io
|
||||||
|
restoreResourcePriorities:
|
||||||
|
# `velero server` default: 1m
|
||||||
|
storeValidationFrequency:
|
||||||
|
# How long to wait on persistent volumes and namespaces to terminate during a restore before timing out. Default: 10m
|
||||||
|
terminatingResourceTimeout:
|
||||||
|
# Bool flag to configure Velero server to move data by default for all snapshots supporting data movement. Default: false
|
||||||
|
defaultSnapshotMoveData:
|
||||||
|
# Comma separated list of velero feature flags. default: empty
|
||||||
|
# features: EnableCSI
|
||||||
|
features:
|
||||||
|
# Configures the timeout for provisioning the volume created from the CSI snapshot. Default: 30m
|
||||||
|
dataMoverPrepareTimeout:
|
||||||
|
# Resource requests/limits to specify for the repository-maintenance job. Optional.
|
||||||
|
# https://velero.io/docs/v1.14/repository-maintenance/#resource-limitation
|
||||||
|
repositoryMaintenanceJob:
|
||||||
|
# Per-repository resource settings ConfigMap
|
||||||
|
# This ConfigMap allows specifying different settings for different repositories
|
||||||
|
# See: https://velero.io/docs/main/repository-maintenance/
|
||||||
|
repositoryConfigData:
|
||||||
|
# Name of the ConfigMap to create. If not provided, will use "velero-repo-maintenance"
|
||||||
|
name: "velero-repo-maintenance"
|
||||||
|
# Global configuration applied to all repositories
|
||||||
|
# This configuration is used when no specific repository configuration is found
|
||||||
|
# global:
|
||||||
|
# podResources:
|
||||||
|
# cpuRequest: "100m"
|
||||||
|
# cpuLimit: "200m"
|
||||||
|
# memoryRequest: "100Mi"
|
||||||
|
# memoryLimit: "200Mi"
|
||||||
|
# keepLatestMaintenanceJobs: 1
|
||||||
|
# loadAffinity:
|
||||||
|
# - nodeSelector:
|
||||||
|
# matchExpressions:
|
||||||
|
# - key: "cloud.google.com/machine-family"
|
||||||
|
# operator: "In"
|
||||||
|
# values: ["e2"]
|
||||||
|
# - nodeSelector:
|
||||||
|
# matchExpressions:
|
||||||
|
# - key: "topology.kubernetes.io/zone"
|
||||||
|
# operator: "In"
|
||||||
|
# values: ["us-central1-a", "us-central1-b", "us-central1-c"]
|
||||||
|
# priorityClassName: "low-priority" # Note: priorityClassName is only supported in global configuration
|
||||||
|
global:
|
||||||
|
keepLatestMaintenanceJobs: 3
|
||||||
|
# Repository-specific configurations
|
||||||
|
# Repository keys are formed as: "{namespace}-{storageLocation}-{repositoryType}"
|
||||||
|
# For example: "default-default-kopia" or "prod-s3-backup-kopia"
|
||||||
|
# Note: priorityClassName is NOT supported in repository-specific configurations
|
||||||
|
# repositories:
|
||||||
|
# "kibishii-default-kopia":
|
||||||
|
# podResources:
|
||||||
|
# cpuRequest: "200m"
|
||||||
|
# cpuLimit: "400m"
|
||||||
|
# memoryRequest: "200Mi"
|
||||||
|
# memoryLimit: "400Mi"
|
||||||
|
# keepLatestMaintenanceJobs: 2
|
||||||
|
repositories: {}
|
||||||
|
# `velero server` default: velero
|
||||||
|
namespace:
|
||||||
|
# additional command-line arguments that will be passed to the `velero server`
|
||||||
|
# e.g.: extraArgs: ["--foo=bar"]
|
||||||
|
extraArgs: []
|
||||||
|
|
||||||
|
# Additional values to be used as environment variables. Optional.
|
||||||
|
extraEnvVars: []
|
||||||
|
# Simple value
|
||||||
|
# - name: SIMPLE_VAR
|
||||||
|
# value: "simple-value"
|
||||||
|
|
||||||
|
# FieldRef example
|
||||||
|
# - name: MY_POD_LABEL
|
||||||
|
# valueFrom:
|
||||||
|
# fieldRef:
|
||||||
|
# fieldPath: metadata.labels['my_label']
|
||||||
|
|
||||||
|
# Set true for backup all pod volumes without having to apply annotation on the pod when used file system backup Default: false.
|
||||||
|
defaultVolumesToFsBackup:
|
||||||
|
|
||||||
|
# How often repository maintain is run for repositories by default.
|
||||||
|
defaultRepoMaintainFrequency:
|
||||||
|
|
||||||
|
##
|
||||||
|
## End of backup/snapshot location settings.
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Settings for additional Velero resources.
|
||||||
|
##
|
||||||
|
|
||||||
|
rbac:
|
||||||
|
# Whether to create the Velero role and role binding to give all permissions to the namespace to Velero.
|
||||||
|
create: true
|
||||||
|
# Whether to create the cluster role binding to give administrator permissions to Velero
|
||||||
|
clusterAdministrator: true
|
||||||
|
# Name of the ClusterRole.
|
||||||
|
clusterAdministratorName: cluster-admin
|
||||||
|
|
||||||
|
# Information about the Kubernetes service account Velero uses.
|
||||||
|
serviceAccount:
|
||||||
|
server:
|
||||||
|
create: true
|
||||||
|
name:
|
||||||
|
annotations:
|
||||||
|
labels:
|
||||||
|
imagePullSecrets: []
|
||||||
|
# - registrySecretName
|
||||||
|
# Configure if API credential for Service Account is automounted.
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
|
||||||
|
# Info about the secret to be used by the Velero deployment, which
|
||||||
|
# should contain credentials for the cloud provider IAM account you've
|
||||||
|
# set up for Velero.
|
||||||
|
credentials:
|
||||||
|
# Whether a secret should be used. Set to false if, for examples:
|
||||||
|
# - using kube2iam or kiam to provide AWS IAM credentials instead of providing the key file. (AWS only)
|
||||||
|
# - using workload identity instead of providing the key file. (Azure/GCP only)
|
||||||
|
useSecret: true
|
||||||
|
# Name of the secret to create if `useSecret` is true and `existingSecret` is empty
|
||||||
|
name:
|
||||||
|
# Name of a pre-existing secret (if any) in the Velero namespace
|
||||||
|
# that should be used to get IAM account credentials. Optional.
|
||||||
|
existingSecret:
|
||||||
|
# Data to be stored in the Velero secret, if `useSecret` is true and `existingSecret` is empty.
|
||||||
|
# As of the current Velero release, Velero only uses one secret key/value at a time.
|
||||||
|
# The key must be named `cloud`, and the value corresponds to the entire content of your IAM credentials file.
|
||||||
|
# Note that the format will be different for different providers, please check their documentation.
|
||||||
|
# Here is a list of documentation for plugins maintained by the Velero team:
|
||||||
|
# [AWS] https://github.com/vmware-tanzu/velero-plugin-for-aws/blob/main/README.md
|
||||||
|
# [GCP] https://github.com/vmware-tanzu/velero-plugin-for-gcp/blob/main/README.md
|
||||||
|
# [Azure] https://github.com/vmware-tanzu/velero-plugin-for-microsoft-azure/blob/main/README.md
|
||||||
|
secretContents: {}
|
||||||
|
# cloud: |
|
||||||
|
# [default]
|
||||||
|
# aws_access_key_id=<REDACTED>
|
||||||
|
# aws_secret_access_key=<REDACTED>
|
||||||
|
# additional key/value pairs to be used as environment variables such as "DIGITALOCEAN_TOKEN: <your-key>". Values will be stored in the secret.
|
||||||
|
extraEnvVars: {}
|
||||||
|
# Name of a pre-existing secret (if any) in the Velero namespace
|
||||||
|
# that will be used to load environment variables into velero and node-agent.
|
||||||
|
# Secret should be in format - https://kubernetes.io/docs/concepts/configuration/secret/#use-case-as-container-environment-variables
|
||||||
|
extraSecretRef: ""
|
||||||
|
|
||||||
|
# Whether to create backupstoragelocation crd, if false => do not create a default backup location
|
||||||
|
backupsEnabled: true
|
||||||
|
# Whether to create volumesnapshotlocation crd, if false => disable snapshot feature
|
||||||
|
snapshotsEnabled: true
|
||||||
|
|
||||||
|
# Whether to deploy the node-agent daemonset.
|
||||||
|
deployNodeAgent: false
|
||||||
|
|
||||||
|
nodeAgent:
|
||||||
|
# Disables Host Path volumes for node-agent.
|
||||||
|
disableHostPath: false
|
||||||
|
podVolumePath: /var/lib/kubelet/pods
|
||||||
|
pluginVolumePath: /var/lib/kubelet/plugins
|
||||||
|
# Pod priority class name to use for the node-agent daemonset. Optional.
|
||||||
|
priorityClassName: ""
|
||||||
|
# Pod runtime class name to use for the node-agent daemonset. Optional.
|
||||||
|
runtimeClassName: ""
|
||||||
|
# Resource requests/limits to specify for the node-agent daemonset deployment. Optional.
|
||||||
|
# https://velero.io/docs/v1.6/customize-installation/#customize-resource-requests-and-limits
|
||||||
|
resources: {}
|
||||||
|
# requests:
|
||||||
|
# cpu: 500m
|
||||||
|
# memory: 512Mi
|
||||||
|
# limits:
|
||||||
|
# cpu: 1000m
|
||||||
|
# memory: 1024Mi
|
||||||
|
# Container resize policy for the node-agent daemonset.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/
|
||||||
|
resizePolicy: []
|
||||||
|
# - resourceName: cpu
|
||||||
|
# restartPolicy: NotRequired
|
||||||
|
# - resourceName: memory
|
||||||
|
# restartPolicy: RestartContainer
|
||||||
|
|
||||||
|
# Tolerations to use for the node-agent daemonset. Optional.
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
# Annotations to set for the node-agent daemonset. Optional.
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# labels to set for the node-agent daemonset. Optional.
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
# Additional pod labels for the node-agent daemonset. Optional
|
||||||
|
# ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||||
|
podLabels: {}
|
||||||
|
|
||||||
|
# will map /scratch to emptyDir. Set to false and specify your own volume
|
||||||
|
# via extraVolumes and extraVolumeMounts that maps to /scratch
|
||||||
|
# if you don't want to use emptyDir.
|
||||||
|
useScratchEmptyDir: true
|
||||||
|
|
||||||
|
# Extra volumes for the node-agent daemonset. Optional.
|
||||||
|
extraVolumes: []
|
||||||
|
|
||||||
|
# Extra volumeMounts for the node-agent daemonset. Optional.
|
||||||
|
extraVolumeMounts: []
|
||||||
|
|
||||||
|
# Additional values to be used as environment variables for node-agent daemonset. Optional.
|
||||||
|
extraEnvVars: []
|
||||||
|
# Simple key/value
|
||||||
|
# - name: SIMPLE_VAR
|
||||||
|
# value: "simple-value"
|
||||||
|
|
||||||
|
# FieldRef example
|
||||||
|
# - name: MY_POD_LABEL
|
||||||
|
# valueFrom:
|
||||||
|
# fieldRef:
|
||||||
|
# fieldPath: metadata.labels['my_label']
|
||||||
|
|
||||||
|
# Additional command-line arguments that will be passed to the node-agent. Optional.
|
||||||
|
# e.g.: extraArgs: ["--foo=bar"]
|
||||||
|
extraArgs: []
|
||||||
|
|
||||||
|
# Configure the dnsPolicy of the node-agent daemonset
|
||||||
|
# See: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
|
||||||
|
# Configure hostAliases for node-agent daemonset. Optional
|
||||||
|
# For more information, check: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
|
||||||
|
hostAliases: []
|
||||||
|
# - ip: "127.0.0.1"
|
||||||
|
# hostnames:
|
||||||
|
# - "foo.local"
|
||||||
|
# - "bar.local"
|
||||||
|
|
||||||
|
# SecurityContext to use for the Velero deployment. Optional.
|
||||||
|
# Set fsGroup for `AWS IAM Roles for Service Accounts`
|
||||||
|
# see more informations at: https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
|
||||||
|
podSecurityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
# fsGroup: 1337
|
||||||
|
|
||||||
|
# Container Level Security Context for the 'node-agent' container of the node-agent daemonset. Optional.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||||
|
containerSecurityContext: {}
|
||||||
|
|
||||||
|
# Container Lifecycle Hooks to use for the node-agent daemonset. Optional.
|
||||||
|
lifecycle: {}
|
||||||
|
|
||||||
|
# Node selector to use for the node-agent daemonset. Optional.
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
# Affinity to use with node-agent daemonset. Optional.
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
# DNS configuration to use for the node-agent daemonset. Optional.
|
||||||
|
dnsConfig: {}
|
||||||
|
|
||||||
|
# Update strategy to use for the node-agent daemonset. Optional.
|
||||||
|
updateStrategy: {}
|
||||||
|
|
||||||
|
# Backup schedules to create.
|
||||||
|
# Eg:
|
||||||
|
# schedules:
|
||||||
|
# mybackup:
|
||||||
|
# disabled: false
|
||||||
|
# labels:
|
||||||
|
# myenv: foo
|
||||||
|
# annotations:
|
||||||
|
# myenv: foo
|
||||||
|
# schedule: "0 0 * * *"
|
||||||
|
# useOwnerReferencesInBackup: false
|
||||||
|
# paused: false
|
||||||
|
# skipImmediately: false
|
||||||
|
# template:
|
||||||
|
# ttl: "240h"
|
||||||
|
# storageLocation: default
|
||||||
|
# includedNamespaces:
|
||||||
|
# - foo
|
||||||
|
# # See: https://velero.io/docs/v1.14/resource-filtering/#excludes
|
||||||
|
# excludedNamespaceScopedResources:
|
||||||
|
# - persistentVolumeClaims
|
||||||
|
# excludedClusterScopedResources:
|
||||||
|
# - persistentVolumes
|
||||||
|
schedules: {}
|
||||||
|
|
||||||
|
# Velero ConfigMaps.
|
||||||
|
# Eg:
|
||||||
|
# configMaps:
|
||||||
|
# See: https://velero.io/docs/v1.11/file-system-backup/
|
||||||
|
# fs-restore-action-config:
|
||||||
|
# labels:
|
||||||
|
# velero.io/plugin-config: ""
|
||||||
|
# velero.io/pod-volume-restore: RestoreItemAction
|
||||||
|
# data:
|
||||||
|
# image: velero/velero:v1.17.1
|
||||||
|
# cpuRequest: 200m
|
||||||
|
# memRequest: 128Mi
|
||||||
|
# cpuLimit: 200m
|
||||||
|
# memLimit: 128Mi
|
||||||
|
# secCtx: |
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# add: []
|
||||||
|
# allowPrivilegeEscalation: false
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsUser: 1001
|
||||||
|
# runAsGroup: 999
|
||||||
|
configMaps: {}
|
||||||
|
|
||||||
|
##
|
||||||
|
## End of additional Velero resource settings.
|
||||||
|
##
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
||||||
# This file is maintained automatically by "tofu init".
|
# This file is maintained automatically by "terraform init".
|
||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/ovh/ovh" {
|
provider "registry.opentofu.org/ovh/ovh" {
|
||||||
|
|
@ -21,3 +21,84 @@ provider "registry.opentofu.org/ovh/ovh" {
|
||||||
"zh:f953ef37e4a1b3cac22f851c3269e7376704018ffa642b4768869cc227ffccc9",
|
"zh:f953ef37e4a1b3cac22f851c3269e7376704018ffa642b4768869cc227ffccc9",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/google" {
|
||||||
|
version = "5.45.2"
|
||||||
|
constraints = "~> 5.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:y2hf6zus1eKA5vpAfoyYkNBKDBQTVqmx4OVh3iRBaRo=",
|
||||||
|
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
|
||||||
|
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
|
||||||
|
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
|
||||||
|
"zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392",
|
||||||
|
"zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca",
|
||||||
|
"zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf",
|
||||||
|
"zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9",
|
||||||
|
"zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4",
|
||||||
|
"zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3",
|
||||||
|
"zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8",
|
||||||
|
"zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd",
|
||||||
|
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/local" {
|
||||||
|
version = "2.9.0"
|
||||||
|
constraints = "~> 2.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:oF4vw1rikPMqQtMMBoUjc/pERp50DlWwO+C2I6/sNI8=",
|
||||||
|
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
|
||||||
|
"zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64",
|
||||||
|
"zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90",
|
||||||
|
"zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c",
|
||||||
|
"zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8",
|
||||||
|
"zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8",
|
||||||
|
"zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b",
|
||||||
|
"zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9",
|
||||||
|
"zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195",
|
||||||
|
"zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3",
|
||||||
|
"zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/random" {
|
||||||
|
version = "3.9.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:o0s5Mk9NXMP60nlheO1r0LsDGGratFb3oL0t7bD2QnM=",
|
||||||
|
"zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1",
|
||||||
|
"zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",
|
||||||
|
"zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f",
|
||||||
|
"zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0",
|
||||||
|
"zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61",
|
||||||
|
"zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc",
|
||||||
|
"zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e",
|
||||||
|
"zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef",
|
||||||
|
"zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b",
|
||||||
|
"zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257",
|
||||||
|
"zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/ovh/ovh" {
|
||||||
|
version = "2.15.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:Y6pQdKjn1OR4wNlxRpy+nI7Dvap/bKzFb68fynMxTmI=",
|
||||||
|
"zh:22d361f99caa70b07532fce13208fce8b84072cf83eaf6ddfcd9096df88b57ea",
|
||||||
|
"zh:497f88936649d3c352fdb639ba5ebdabfec8ec7a59d67bf619013e3afbbaefb4",
|
||||||
|
"zh:62e49208e2950daf366b396694dc7de5bba31a8b7a10aac7c021f2c78fd64f70",
|
||||||
|
"zh:97e06f7494706df30c136f4809314d92a25129738cf080e22c5c9e47ec70c4cf",
|
||||||
|
"zh:be23cf1ecb2eb76c60dad72eb4f9d005c6184cae6e2cf9f13b0462030133644c",
|
||||||
|
"zh:bf60c1af6db5ca51ff4372fee8e64a21462134d281ae9cb3968ca5b209338c4b",
|
||||||
|
"zh:c9e5ad5c063249786595c5b6c3f59791bd21f5157ff0572a4ed2d52ca8141c50",
|
||||||
|
"zh:d2bf2603741df6fc29a88384f7026fb8b0e982b8a270796e672037d287b41e83",
|
||||||
|
"zh:d944de8032fc01d8488087755bf7fb813e7b8c259e23a7b68ed654905ad19b56",
|
||||||
|
"zh:dcf2a7620abac38e9d8f0a88f2a79de0fddd917ba0facba1108f6581eb1b7f6f",
|
||||||
|
"zh:e7c7905954f69f0d5b6cf9bd87572101924e128aa33311856dbbfedd711361d2",
|
||||||
|
"zh:f6a9977d744ed879a0b3c28a5a800b6a1f49aabe8d71e87ca1d02fe6a77a31ba",
|
||||||
|
"zh:fd0e48de826df04e18489d33b0ff35bd776cb6e7704188f67dad66cbb041e5af",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
variable "gcp_project_id" {
|
||||||
|
type = string
|
||||||
|
description = "The GCP Project ID where the Service Account should be created. If empty, a new project named 'velero-backup-xxxx' will be created."
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_id" "project_suffix" {
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project" "backup_project" {
|
||||||
|
count = var.gcp_project_id == "" ? 1 : 0
|
||||||
|
name = "velero-backup"
|
||||||
|
project_id = "velero-backup-${random_id.project_suffix.hex}"
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
project_id = var.gcp_project_id == "" ? google_project.backup_project[0].project_id : var.gcp_project_id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_service" "drive_api" {
|
||||||
|
project = local.project_id
|
||||||
|
service = "drive.googleapis.com"
|
||||||
|
disable_on_destroy = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_organization_policy" "allow_key_creation" {
|
||||||
|
project = local.project_id
|
||||||
|
constraint = "iam.disableServiceAccountKeyCreation"
|
||||||
|
|
||||||
|
boolean_policy {
|
||||||
|
enforced = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_service_account" "velero_backup_sa" {
|
||||||
|
project = local.project_id
|
||||||
|
account_id = "velero-backup-sa"
|
||||||
|
display_name = "Velero GDrive Backup Service Account"
|
||||||
|
depends_on = [google_project_service.drive_api]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_service_account_key" "velero_backup_sa_key" {
|
||||||
|
service_account_id = google_service_account.velero_backup_sa.name
|
||||||
|
depends_on = [google_project_organization_policy.allow_key_creation]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "local_file" "gdrive_sa_key_file" {
|
||||||
|
content = base64decode(google_service_account_key.velero_backup_sa_key.private_key)
|
||||||
|
filename = "${path.module}/../k8s/velero/gdrive_sa.json.secret"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "velero_backup_sa_email" {
|
||||||
|
value = google_service_account.velero_backup_sa.email
|
||||||
|
description = "The email address of the Google Cloud Service Account. Share your Google Drive backup folder with this email address and grant it Editor access."
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -3,6 +3,14 @@ terraform {
|
||||||
ovh = {
|
ovh = {
|
||||||
source = "ovh/ovh"
|
source = "ovh/ovh"
|
||||||
}
|
}
|
||||||
|
google = {
|
||||||
|
source = "hashicorp/google"
|
||||||
|
version = "~> 5.0"
|
||||||
|
}
|
||||||
|
local = {
|
||||||
|
source = "hashicorp/local"
|
||||||
|
version = "~> 2.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
required_version = ">= 0.13"
|
required_version = ">= 0.13"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue