Capture current Ansible control plane state

Commit the accumulated infrastructure work that was living only in the
working tree: monitoring stack, emergency access/bot, gyro allocator,
grimmory, adguard, backup audit and the OpenCode agent definitions.

Also ignore Python bytecode, local archives and Nix/direnv artifacts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
This commit is contained in:
Dmitry
2026-08-26 21:39:28 +03:00
co-authored by Claude Opus 5
parent 4bafa7d09e
commit c676be81ec
126 changed files with 10583 additions and 44 deletions
@@ -0,0 +1,40 @@
global:
resolve_timeout: 5m
route:
receiver: telegram-warning
group_by: [alertname, instance]
group_wait: 30s
group_interval: 10m
repeat_interval: 6h
routes:
- matchers:
- severity="critical"
receiver: telegram-critical
repeat_interval: 2h
inhibit_rules:
- source_matchers:
- alertname="ExporterDown"
target_matchers:
- alertname="LanServiceUnavailable"
equal: [instance]
receivers:
- name: telegram-warning
telegram_configs:
- bot_token: "{{ monitoring_telegram_bot_token }}"
chat_id: {{ monitoring_telegram_chat_id }}
send_resolved: true
message: |-
[{{ "{{" }} .Status | toUpper {{ "}}" }}] {{ "{{" }} .CommonLabels.alertname {{ "}}" }}
{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}
- name: telegram-critical
telegram_configs:
- bot_token: "{{ monitoring_telegram_bot_token }}"
chat_id: {{ monitoring_telegram_chat_id }}
send_resolved: true
message: |-
[{{ "{{" }} .Status | toUpper {{ "}}" }}] CRITICAL: {{ "{{" }} .CommonLabels.alertname {{ "}}" }}
{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}
@@ -0,0 +1,134 @@
groups:
- name: homelab-availability
rules:
- alert: ExporterDown
expr: up{job=~"node|smartctl|pve"} == 0
for: 10m
labels:
severity: warning
annotations:
summary: "Exporter unavailable: {{ '{{' }} $labels.job {{ '}}' }} {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: CriticalServiceUnavailable
expr: probe_success{job="blackbox-vps"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "External or VPN check failed: {{ '{{' }} $labels.probe_name {{ '}}' }}"
- alert: RuVpsMonitoringStale
expr: time() - homelab_vps_probe_timestamp_seconds > 5 * 60
for: 5m
labels:
severity: critical
annotations:
summary: "ru-vps has not pushed monitoring metrics through OpenVPN"
- alert: RuVpsMonitoringMissing
expr: absent(homelab_vps_probe_timestamp_seconds{job="blackbox-vps"})
for: 10m
labels:
severity: critical
annotations:
summary: "ru-vps has no monitoring metrics in Pushgateway"
- alert: LanServiceUnavailable
expr: probe_success{job=~"blackbox-lan-http|blackbox-lan-tcp"} == 0
for: 10m
labels:
severity: warning
annotations:
summary: "LAN service unavailable: {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: TLSCertificateExpiringSoon
expr: (probe_ssl_earliest_cert_expiry - time()) < 14 * 24 * 3600
for: 1h
labels:
severity: warning
annotations:
summary: "TLS certificate expires within 14 days: {{ '{{' }} $labels.instance {{ '}}' }}"
- name: homelab-capacity
rules:
- alert: FilesystemAlmostFull
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) < 0.15
for: 15m
labels:
severity: warning
annotations:
summary: "Filesystem is more than 85% full: {{ '{{' }} $labels.instance {{ '}}' }} {{ '{{' }} $labels.mountpoint {{ '}}' }}"
- alert: FilesystemWillFillSoon
expr: predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 7 * 24 * 3600) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "Filesystem may fill within seven days: {{ '{{' }} $labels.instance {{ '}}' }} {{ '{{' }} $labels.mountpoint {{ '}}' }}"
- alert: HostMemoryPressure
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) < 0.1
for: 20m
labels:
severity: warning
annotations:
summary: "Memory pressure on {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: SMARTDeviceFailed
expr: homelab_smart_device_healthy == 0
for: 5m
labels:
severity: critical
annotations:
summary: "SMART health check failed: {{ '{{' }} $labels.instance {{ '}}' }} {{ '{{' }} $labels.device {{ '}}' }}"
- alert: NVMECriticalWarning
expr: homelab_smart_nvme_critical_warning > 0
for: 5m
labels:
severity: critical
annotations:
summary: "NVMe critical warning: {{ '{{' }} $labels.instance {{ '}}' }} {{ '{{' }} $labels.device {{ '}}' }}"
- name: homelab-backups
rules:
- alert: BackupAuditFailed
expr: homelab_backup_audit_success == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Backup audit failed: {{ '{{' }} $labels.profile {{ '}}' }} on {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: BackupAuditStale
expr: time() - homelab_backup_audit_timestamp_seconds > 48 * 3600
for: 15m
labels:
severity: critical
annotations:
summary: "Backup audit result is stale: {{ '{{' }} $labels.profile {{ '}}' }} on {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: BackupAuditMissing
expr: absent(homelab_backup_audit_timestamp_seconds{profile="pbs"}) or absent(homelab_backup_audit_timestamp_seconds{profile="gitea"}) or absent(homelab_backup_audit_timestamp_seconds{profile="vaultwarden"}) or absent(homelab_backup_audit_timestamp_seconds{profile="grimmory"})
for: 12h
labels:
severity: critical
annotations:
summary: "Expected backup audit metric has not been published"
- alert: ResticBackupTooOld
expr: homelab_backup_audit_snapshot_age_hours{profile=~"gitea|vaultwarden|grimmory"} > 36
for: 15m
labels:
severity: critical
annotations:
summary: "Restic backup is older than 36 hours: {{ '{{' }} $labels.profile {{ '}}' }}"
- alert: PBSBackupTooOld
expr: homelab_backup_audit_snapshot_age_hours{profile="pbs"} > 48
for: 15m
labels:
severity: critical
annotations:
summary: "PBS backup is older than 48 hours: VMID {{ '{{' }} $labels.vmid {{ '}}' }}"
@@ -0,0 +1,10 @@
modules:
http_2xx:
prober: http
timeout: 10s
http:
preferred_ip_protocol: ip4
valid_status_codes: [200, 301, 302]
tcp_connect:
prober: tcp
timeout: 10s
@@ -0,0 +1,63 @@
services:
prometheus:
image: prom/prometheus:v3.2.1
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time={{ monitoring_prometheus_retention }}
- --storage.tsdb.retention.size={{ monitoring_prometheus_retention_size }}
- --web.enable-lifecycle
volumes:
- ./prometheus:/etc/prometheus:ro
- ./prometheus/data:/prometheus
restart: unless-stopped
alertmanager:
image: prom/alertmanager:v0.28.0
user: "0:0"
command:
- --config.file=/etc/alertmanager/alertmanager.yml
- --storage.path=/alertmanager
volumes:
- ./alertmanager:/etc/alertmanager:ro
restart: unless-stopped
grafana:
image: grafana/grafana:11.5.1
environment:
GF_SECURITY_ADMIN_USER: {{ monitoring_grafana_admin_user | to_json }}
GF_SECURITY_ADMIN_PASSWORD: {{ monitoring_grafana_admin_password | to_json }}
GF_USERS_ALLOW_SIGN_UP: "false"
ports:
- "192.168.1.30:3000:3000"
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/etc/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
restart: unless-stopped
blackbox-exporter:
image: prom/blackbox-exporter:v0.25.0
command:
- --config.file=/config/blackbox.yml
volumes:
- ./blackbox.yml:/config/blackbox.yml:ro
restart: unless-stopped
pve-exporter:
image: prompve/prometheus-pve-exporter:3.5.5
user: "0:0"
command:
- --config.file=/etc/pve-exporter/pve.yml
volumes:
- ./pve-exporter/pve.yml:/etc/pve-exporter/pve.yml:ro
restart: unless-stopped
pushgateway:
image: prom/pushgateway:v1.11.0
ports:
- "192.168.1.30:9091:9091"
restart: unless-stopped
volumes:
grafana-data:
@@ -0,0 +1,94 @@
global:
scrape_interval: 30s
evaluation_interval: 30s
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
rule_files:
- /etc/prometheus/alerts.yml
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: node
static_configs:
- targets:
- "192.168.1.5:9100"
- "192.168.1.10:9100"
- "192.168.1.20:9100"
- "192.168.1.23:9100"
- "192.168.1.24:9100"
- "192.168.1.25:9100"
- "192.168.1.26:9100"
- "192.168.1.27:9100"
- "192.168.1.28:9100"
- "192.168.1.30:9100"
- "192.168.1.34:9100"
- job_name: pve
metrics_path: /pve
params:
module: [default]
static_configs:
- targets:
- "192.168.1.5"
- "192.168.1.10"
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: pve-exporter:9221
- job_name: blackbox-lan-http
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- "http://192.168.1.24"
- "http://192.168.1.25:3000"
- "http://192.168.1.27:8080"
- "http://192.168.1.28"
- "http://192.168.1.34:6060/api/v1/healthcheck"
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
- target_label: probe_location
replacement: lan
- job_name: blackbox-lan-tcp
metrics_path: /probe
params:
module: [tcp_connect]
static_configs:
- targets:
- "192.168.1.5:8006"
- "192.168.1.10:8006"
- "192.168.1.20:8007"
- "192.168.1.25:2222"
- "192.168.1.27:9090"
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
- target_label: probe_location
replacement: lan
- job_name: pushgateway
honor_labels: true
static_configs:
- targets: ["pushgateway:9091"]
@@ -0,0 +1,5 @@
default:
user: "{{ monitoring_pve_api_user }}"
token_name: "{{ monitoring_pve_api_token_id }}"
token_value: "{{ monitoring_pve_api_token_secret }}"
verify_ssl: false