diff --git a/config/README.md b/config/README.md index f303f98a..b01a14c9 100755 --- a/config/README.md +++ b/config/README.md @@ -34,7 +34,7 @@ Live view of the `config/` directory my production Home Assistant instance loads - **Packages (`packages/`)** – complete subsystems that bundle sensors, switches, automations, scripts, and lovelace assets for a single feature (alarm, garage, water shutoff, etc.). - **Container updates** – `packages/tugtainer_updates.yaml` logs container updates from Tugtainer into Home Assistant persistent notifications. - **Automations (`automation/`)** – event-driven YAML broken out by area or device; the legacy `automations.yaml` remains for UI-created flows. -- **Scripts & scenes (`script/`, `scene/`)** – curated lighting and ambiance logic used by presence, holiday, and seasonal routines. +- **Scripts & scenes (`script/`, `scene/`)** – curated lighting, notification, and AGENT engineer handoff helpers used by presence, holiday, seasonal, and infrastructure routines. - **Templates (`templates/`)** – Jinja helpers and speech templates reused by the notify/speech engines. - **Dashboards (`dashboards/`)** – YAML-managed Lovelace dashboards and UI resources (generated from storage, then maintained as code). - **www/ + custom components** – branding assets, floorplans, and any custom components the core install depends on. @@ -44,7 +44,7 @@ Live view of the `config/` directory my production Home Assistant instance loads | --- | --- | --- | | Packages | Self-contained subsystems that highlight patterns like combined alerts + actions. | [packages/alarm.yaml](packages/alarm.yaml), [packages/garadget.yaml](packages/garadget.yaml), [packages/powerwall.yaml](packages/powerwall.yaml) | | Automations | Real-world triggers that tie Zwave, MQTT, and REST sensors into the rest of the house. | [automation/garage_entry_light.yaml](automation/garage_entry_light.yaml), [automation/dark_rainy_day.yaml](automation/dark_rainy_day.yaml), [automation/dash_buttons.yaml](automation/dash_buttons.yaml) | -| Scripts | Reusable building blocks for lighting, notifications, and safety responses. | [script/monthly_color_scene.yaml](script/monthly_color_scene.yaml), [script/notify_engine.yaml](script/notify_engine.yaml), [script/speech_engine.yaml](script/speech_engine.yaml) | +| Scripts | Reusable building blocks for lighting, notifications, safety responses, and Joanna/BearClaw remediation dispatch. | [script/joanna_dispatch.yaml](script/joanna_dispatch.yaml), [script/notify_engine.yaml](script/notify_engine.yaml), [script/speech_engine.yaml](script/speech_engine.yaml) | | Scenes | Seasonal and ambiance presets that the scripts and automations call into. | [scene/monthly_colors.yaml](scene/monthly_colors.yaml), [scene/living_room.yaml](scene/living_room.yaml) | | Templates & Speech | Human-friendly voice briefings and templated responses. | [templates/speech/briefing.yaml](templates/speech/briefing.yaml) | | Dashboards & Media | UI chrome, floorplans, sound bites, and automation assets. | [www/custom_ui/floorplan/images/branding/Bear-Stone-Docker-Diagram.jpg](www/custom_ui/floorplan/images/branding/Bear-Stone-Docker-Diagram.jpg), [media/](media) | diff --git a/config/packages/README.md b/config/packages/README.md index a432f8a4..55c92cc6 100755 --- a/config/packages/README.md +++ b/config/packages/README.md @@ -49,10 +49,12 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this | [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching telemetry + container/stack Repairs automation, 20-minute Joanna escalation for persistent container outages using stable configured monitor membership, and weekly scheduled prune actions across docker_10/14/17/69. | `sensor.docker_*_apt_status`, `binary_sensor.*_stack_status`, `sensor.docker_stacks_down_count`, `repairs.create`, `script.joanna_dispatch` | | [github_watched_repo_scout.yaml](github_watched_repo_scout.yaml) | Nightly Joanna dispatch that reviews unread notifications from watched GitHub repos, recommends HA-config ideas, refreshes strong-candidate issues, and marks processed watched-repo notifications read. | `automation.github_watched_repo_scout_nightly`, `script.joanna_dispatch`, `script.send_to_logbook` | | [proxmox.yaml](proxmox.yaml) | Proxmox runtime and disk pressure monitoring with Repairs for node degradations plus nightly Frigate reboot. | `binary_sensor.proxmox*_runtime_healthy`, `sensor.proxmox*_disk_used_percentage`, `repairs.create`, `button.qemu_docker2_101_reboot` | +| [synology_dsm.yaml](synology_dsm.yaml) | Synology DSM integration health normalization for Carlo-NAS01 and Carlo-NVR, with Repairs + Joanna dispatch on sustained integration, security, or storage problems. | `binary_sensor.carlo_*_synology_problem`, `sensor.carlo_*_synology_problem_summary`, `repairs.create`, `script.joanna_dispatch` | | [infrastructure_observability.yaml](infrastructure_observability.yaml) | Normalized WAN/DNS/backup/domain/cert health + website uptime/latency SLO signals for Infrastructure dashboards. | `binary_sensor.infra_website_uptime_slo_breach`, `binary_sensor.infra_website_latency_degraded`, `binary_sensor.infra_*` | | [onenote_indexer.yaml](onenote_indexer.yaml) | OneNote indexer health/status monitoring for Joanna, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful` | | [mqtt_status.yaml](mqtt_status.yaml) | Command-line MQTT broker reachability probe with Spook Repairs escalation and Joanna troubleshooting dispatch on outage. | `binary_sensor.mqtt_status_raw`, `binary_sensor.mqtt_broker_problem`, `repairs.create`, `rest_command.bearclaw_command` | | [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` | +| [processmonitor.yaml](processmonitor.yaml) | Root filesystem disk-pressure monitoring with early Joanna review at 80% and Repairs + urgent dispatch at 90%. | `sensor.disk_use_percent`, `repairs.create`, `script.joanna_dispatch`, `tts.clear_cache` | | [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts, plus event-based Joanna dispatch when reports include `### Available:` (24h cooldown via `mode: single` + delay, no new helpers). | `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` | | [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance, include LLM-first routing context for freeform text, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch plus QMD/memory-index sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `binary_sensor.joanna_*`, `automation.bearclaw_*`, `script.send_to_logbook` | | [telegram_bot.yaml](telegram_bot.yaml) | Legacy Telegram transport marker for BearClaw; the shared `joanna_send_telegram` helper now forwards through the codex_appliance direct Telegram API. | `rest_command.bearclaw_telegram_send`, `script.joanna_send_telegram` | diff --git a/config/packages/processmonitor.yaml b/config/packages/processmonitor.yaml index c4ad6190..f71aa852 100755 --- a/config/packages/processmonitor.yaml +++ b/config/packages/processmonitor.yaml @@ -1,59 +1,129 @@ -#------------------------------------------- -# @CCOSTAN -# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig -# Process Monitor - Track HA-related services and processes. -#------------------------------------------- ###################################################################### -## Process status sensors and notifications. +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Process Monitor - Disk pressure alerting + Joanna dispatch +# Tracks Home Assistant root filesystem usage from the System Monitor integration. +# ------------------------------------------------------------------- +# - Blog: https://www.vcloudinfo.com/2026/04/joanna-agent-engineer-home-assistant-infrastructure-dispatch.html +# Notes: Uses `sensor.disk_use_percent` for the root (`/`) filesystem. +# Notes: 80% usage triggers cleanup-oriented notification + Joanna review. +# Notes: 90% usage opens a Repairs issue and dispatches Joanna for urgent triage. ###################################################################### -# homeassistant: -# customize: -# sensor.process_mosquitto: -# friendly_name: 'Mosquitto' -# -# Uses SYSTEMMONITOR integration -#------------------------------------------- - - -#------------------------------------------- -############################################################################## -### Automations - Detect when things are not right. Like any Good Watchdog. -############################################################################## automation: - - alias: "Self Heal Disk Use Alarm" id: b16f2155-4688-4c0f-9cf8-b382e294a029 + description: "Warn on elevated root disk usage and request Joanna review before it becomes critical." + mode: single trigger: - platform: numeric_state entity_id: sensor.disk_use_percent above: 80 + variables: + mount_path: "/" + disk_use: "{{ states('sensor.disk_use_percent') | float(0) | round(1) }}" + trigger_context: "HA automation b16f2155-4688-4c0f-9cf8-b382e294a029 (Self Heal Disk Use Alarm)" action: - service: script.notify_engine data: - value1: 'Hard Drive Monitor:' - value2: "Your harddrive is running out of Space! /dev/root:{{ states.sensor.disk_use_percent.state }}%!" - value3: 'Attempting to clean' - who: 'carlo' + value1: "Hard Drive Monitor:" + value2: "Your harddrive is running out of Space! {{ mount_path }}:{{ disk_use }}%!" + value3: "Attempting to clean" + who: "carlo" - service: script.send_to_logbook data: topic: "SYSTEM" - message: "Disk usage exceeded 80% (/dev/root: {{ states.sensor.disk_use_percent.state }}%). Attempting to clean." + message: "Disk usage exceeded 80% ({{ mount_path }}: {{ disk_use }}%). Attempting to clean." - service: tts.clear_cache + - condition: template + value_template: "{{ disk_use | float(0) < 90 }}" + - service: script.joanna_dispatch + data: + trigger_context: "{{ trigger_context }}" + source: "home_assistant_automation.self_heal_disk_use_alarm" + summary: "Home Assistant root disk usage exceeded 80%" + entity_ids: + - "sensor.disk_use_percent" + diagnostics: >- + mount_path={{ mount_path }}, + disk_use={{ disk_use }}, + threshold=80 + request: >- + Review Home Assistant disk growth and recommend safe cleanup actions. + Check recorder/database size, logs, cache, backups, and temporary files. + Do not restart Home Assistant or remove data unless explicitly requested. - alias: "Disk Use Alarm" id: 1ce3cb43-0e27-4c53-acdd-d672396f3559 + description: "Open a Repairs issue and dispatch Joanna when root disk usage becomes critical." + mode: single trigger: - platform: numeric_state entity_id: sensor.disk_use_percent above: 90 + variables: + issue_id: "processmonitor_disk_use_critical" + mount_path: "/" + disk_use: "{{ states('sensor.disk_use_percent') | float(0) | round(1) }}" + trigger_context: "HA automation 1ce3cb43-0e27-4c53-acdd-d672396f3559 (Disk Use Alarm)" action: - service: script.notify_engine data: - value1: 'Hard Drive Monitor:' - value2: "Your harddrive is running out of Space! /dev/root:{{ states.sensor.disk_use_percent.state }}%!" - who: 'carlo' + value1: "Hard Drive Monitor:" + value2: "Your harddrive is running out of Space! {{ mount_path }}:{{ disk_use }}%!" + who: "carlo" + - service: script.send_to_logbook + data: + topic: "SYSTEM" + message: >- + Disk usage exceeded 90% ({{ mount_path }}: {{ disk_use }}%). + Repair {{ issue_id }} opened and Joanna investigation requested. + - service: repairs.create + data: + issue_id: "{{ issue_id }}" + title: "Home Assistant disk usage critical" + severity: "error" + persistent: true + description: >- + Home Assistant detected critical disk pressure on {{ mount_path }}. + + disk_use: {{ disk_use }}% + entity_id: sensor.disk_use_percent + - service: script.joanna_dispatch + data: + trigger_context: "{{ trigger_context }}" + source: "home_assistant_automation.disk_use_alarm" + summary: "Home Assistant root disk usage exceeded 90%" + entity_ids: + - "sensor.disk_use_percent" + diagnostics: >- + issue_id={{ issue_id }}, + mount_path={{ mount_path }}, + disk_use={{ disk_use }}, + threshold=90 + request: >- + Investigate critical Home Assistant disk usage and recommend or perform safe remediation if available. + Check recorder/database size, logs, cache, backups, and temporary files first. + Do not restart Home Assistant or prune/delete data unless explicitly requested. + + - alias: "Disk Use Alarm Recovery" + id: processmonitor_disk_use_alarm_recovery + description: "Clear the disk pressure repair once root filesystem usage has recovered." + mode: single + trigger: + - platform: numeric_state + entity_id: sensor.disk_use_percent + below: 85 + for: + minutes: 10 + action: + - service: repairs.remove + continue_on_error: true + data: + issue_id: "processmonitor_disk_use_critical" - service: script.send_to_logbook data: topic: "SYSTEM" - message: "Disk usage exceeded 90% (/dev/root: {{ states.sensor.disk_use_percent.state }}%)." + message: "Disk usage recovered below 85% on /. Repair processmonitor_disk_use_critical cleared." diff --git a/config/packages/synology_dsm.yaml b/config/packages/synology_dsm.yaml new file mode 100644 index 00000000..3f8e0870 --- /dev/null +++ b/config/packages/synology_dsm.yaml @@ -0,0 +1,486 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Synology DSM Monitoring - NAS health normalization + Joanna dispatch +# Reviews Synology DSM integration health and escalates sustained NAS problems. +# ------------------------------------------------------------------- +# Notes: Uses native `synology_dsm` entities for Carlo-NAS01 and Carlo-NVR. +# Notes: Joanna dispatches are reserved for integration/security/storage problems, not routine reboot/shutdown controls. +# Notes: DSM update availability stays diagnostic context only; it does not trigger remediation by itself. +###################################################################### + +template: + - binary_sensor: + - name: "Carlo-NAS01 Synology Integration Problem" + unique_id: carlo_nas01_synology_integration_problem + device_class: problem + state: >- + {% set ids = [ + 'binary_sensor.carlo_nas01_security_status', + 'sensor.carlo_nas01_volume_1_status', + 'sensor.carlo_nas01_volume_1_volume_used', + 'sensor.carlo_nas01_drive_1_status', + 'sensor.carlo_nas01_drive_2_status', + 'sensor.carlo_nas01_drive_3_status', + 'binary_sensor.carlo_nas01_drive_1_below_min_remaining_life', + 'binary_sensor.carlo_nas01_drive_2_below_min_remaining_life', + 'binary_sensor.carlo_nas01_drive_3_below_min_remaining_life', + 'binary_sensor.carlo_nas01_drive_1_exceeded_max_bad_sectors', + 'binary_sensor.carlo_nas01_drive_2_exceeded_max_bad_sectors', + 'binary_sensor.carlo_nas01_drive_3_exceeded_max_bad_sectors', + 'update.carlo_nas01_dsm_update' + ] %} + {% set ns = namespace(problem=false) %} + {% for id in ids %} + {% if states(id) in ['unknown', 'unavailable'] %} + {% set ns.problem = true %} + {% endif %} + {% endfor %} + {{ ns.problem }} + + - name: "Carlo-NAS01 Synology Security Problem" + unique_id: carlo_nas01_synology_security_problem + device_class: problem + state: >- + {{ is_state('binary_sensor.carlo_nas01_security_status', 'on') }} + + - name: "Carlo-NAS01 Synology Storage Problem" + unique_id: carlo_nas01_synology_storage_problem + device_class: problem + state: >- + {% set volume_status = states('sensor.carlo_nas01_volume_1_status') | lower %} + {% set volume_used_raw = states('sensor.carlo_nas01_volume_1_volume_used') %} + {% set volume_used = volume_used_raw | float(0) if volume_used_raw not in ['unknown', 'unavailable', 'none', ''] else 0 %} + {{ volume_status not in ['normal', 'unknown', 'unavailable', 'none', ''] or + volume_used >= 85 or + states('sensor.carlo_nas01_drive_1_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nas01_drive_2_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nas01_drive_3_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + is_state('binary_sensor.carlo_nas01_drive_1_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_2_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_3_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_1_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nas01_drive_2_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nas01_drive_3_exceeded_max_bad_sectors', 'on') }} + + - name: "Carlo-NAS01 Synology Problem" + unique_id: carlo_nas01_synology_problem + device_class: problem + state: >- + {{ is_state('binary_sensor.carlo_nas01_synology_integration_problem', 'on') or + is_state('binary_sensor.carlo_nas01_synology_security_problem', 'on') or + is_state('binary_sensor.carlo_nas01_synology_storage_problem', 'on') }} + + - name: "Carlo-NVR Synology Integration Problem" + unique_id: carlo_nvr_synology_integration_problem + device_class: problem + state: >- + {% set ids = [ + 'binary_sensor.carlo_nvr_security_status', + 'sensor.carlo_nvr_volume_1_status', + 'sensor.carlo_nvr_volume_1_volume_used', + 'sensor.carlo_nvr_drive_1_status', + 'sensor.carlo_nvr_drive_2_status', + 'binary_sensor.carlo_nvr_drive_1_below_min_remaining_life', + 'binary_sensor.carlo_nvr_drive_2_below_min_remaining_life', + 'binary_sensor.carlo_nvr_drive_1_exceeded_max_bad_sectors', + 'binary_sensor.carlo_nvr_drive_2_exceeded_max_bad_sectors', + 'update.carlo_nvr_dsm_update' + ] %} + {% set ns = namespace(problem=false) %} + {% for id in ids %} + {% if states(id) in ['unknown', 'unavailable'] %} + {% set ns.problem = true %} + {% endif %} + {% endfor %} + {{ ns.problem }} + + - name: "Carlo-NVR Synology Security Problem" + unique_id: carlo_nvr_synology_security_problem + device_class: problem + state: >- + {{ is_state('binary_sensor.carlo_nvr_security_status', 'on') }} + + - name: "Carlo-NVR Synology Storage Problem" + unique_id: carlo_nvr_synology_storage_problem + device_class: problem + state: >- + {% set volume_status = states('sensor.carlo_nvr_volume_1_status') | lower %} + {% set volume_used_raw = states('sensor.carlo_nvr_volume_1_volume_used') %} + {% set volume_used = volume_used_raw | float(0) if volume_used_raw not in ['unknown', 'unavailable', 'none', ''] else 0 %} + {{ volume_status not in ['normal', 'unknown', 'unavailable', 'none', ''] or + volume_used >= 85 or + states('sensor.carlo_nvr_drive_1_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nvr_drive_2_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + is_state('binary_sensor.carlo_nvr_drive_1_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nvr_drive_2_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nvr_drive_1_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nvr_drive_2_exceeded_max_bad_sectors', 'on') }} + + - name: "Carlo-NVR Synology Problem" + unique_id: carlo_nvr_synology_problem + device_class: problem + state: >- + {{ is_state('binary_sensor.carlo_nvr_synology_integration_problem', 'on') or + is_state('binary_sensor.carlo_nvr_synology_security_problem', 'on') or + is_state('binary_sensor.carlo_nvr_synology_storage_problem', 'on') }} + + - sensor: + - name: "Carlo-NAS01 Synology Problem Severity" + unique_id: carlo_nas01_synology_problem_severity + state: >- + {% set volume_status = states('sensor.carlo_nas01_volume_1_status') | lower %} + {% set volume_used_raw = states('sensor.carlo_nas01_volume_1_volume_used') %} + {% set volume_used = volume_used_raw | float(0) if volume_used_raw not in ['unknown', 'unavailable', 'none', ''] else 0 %} + {% set hard_storage = volume_status not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nas01_drive_1_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nas01_drive_2_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nas01_drive_3_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + is_state('binary_sensor.carlo_nas01_drive_1_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_2_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_3_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nas01_drive_1_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nas01_drive_2_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nas01_drive_3_exceeded_max_bad_sectors', 'on') or + volume_used >= 92 %} + {% if is_state('binary_sensor.carlo_nas01_synology_integration_problem', 'on') or + is_state('binary_sensor.carlo_nas01_synology_security_problem', 'on') or + hard_storage %} + error + {% elif is_state('binary_sensor.carlo_nas01_synology_storage_problem', 'on') %} + warning + {% else %} + none + {% endif %} + + - name: "Carlo-NAS01 Synology Problem Summary" + unique_id: carlo_nas01_synology_problem_summary + state: >- + {% set ns = namespace(items=[]) %} + {% if states('binary_sensor.carlo_nas01_security_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['security sensor unavailable'] %} + {% elif is_state('binary_sensor.carlo_nas01_security_status', 'on') %} + {% set ns.items = ns.items + ['security advisor reported an unsafe state'] %} + {% endif %} + {% set volume_status = states('sensor.carlo_nas01_volume_1_status') | lower %} + {% if volume_status in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['volume status unavailable'] %} + {% elif volume_status not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['volume status=' ~ volume_status] %} + {% endif %} + {% set volume_used = states('sensor.carlo_nas01_volume_1_volume_used') %} + {% if volume_used in ['unknown', 'unavailable', 'none', ''] %} + {% set ns.items = ns.items + ['volume used unavailable'] %} + {% elif volume_used | float(0) >= 85 %} + {% set ns.items = ns.items + ['volume used=' ~ (volume_used | float(0) | round(1)) ~ '%'] %} + {% endif %} + {% if states('sensor.carlo_nas01_drive_1_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['drive 1 status unavailable'] %} + {% elif states('sensor.carlo_nas01_drive_1_status') | lower not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['drive 1 status=' ~ (states('sensor.carlo_nas01_drive_1_status') | lower)] %} + {% endif %} + {% if states('sensor.carlo_nas01_drive_2_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['drive 2 status unavailable'] %} + {% elif states('sensor.carlo_nas01_drive_2_status') | lower not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['drive 2 status=' ~ (states('sensor.carlo_nas01_drive_2_status') | lower)] %} + {% endif %} + {% if states('sensor.carlo_nas01_drive_3_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['drive 3 status unavailable'] %} + {% elif states('sensor.carlo_nas01_drive_3_status') | lower not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['drive 3 status=' ~ (states('sensor.carlo_nas01_drive_3_status') | lower)] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_1_below_min_remaining_life', 'on') %} + {% set ns.items = ns.items + ['drive 1 below remaining life threshold'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_2_below_min_remaining_life', 'on') %} + {% set ns.items = ns.items + ['drive 2 below remaining life threshold'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_3_below_min_remaining_life', 'on') %} + {% set ns.items = ns.items + ['drive 3 below remaining life threshold'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_1_exceeded_max_bad_sectors', 'on') %} + {% set ns.items = ns.items + ['drive 1 exceeded max bad sectors'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_2_exceeded_max_bad_sectors', 'on') %} + {% set ns.items = ns.items + ['drive 2 exceeded max bad sectors'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nas01_drive_3_exceeded_max_bad_sectors', 'on') %} + {% set ns.items = ns.items + ['drive 3 exceeded max bad sectors'] %} + {% endif %} + {{ ns.items | join('; ') if ns.items else 'ok' }} + + - name: "Carlo-NVR Synology Problem Severity" + unique_id: carlo_nvr_synology_problem_severity + state: >- + {% set volume_status = states('sensor.carlo_nvr_volume_1_status') | lower %} + {% set volume_used_raw = states('sensor.carlo_nvr_volume_1_volume_used') %} + {% set volume_used = volume_used_raw | float(0) if volume_used_raw not in ['unknown', 'unavailable', 'none', ''] else 0 %} + {% set hard_storage = volume_status not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nvr_drive_1_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + states('sensor.carlo_nvr_drive_2_status') | lower not in ['normal', 'unknown', 'unavailable', 'none', ''] or + is_state('binary_sensor.carlo_nvr_drive_1_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nvr_drive_2_below_min_remaining_life', 'on') or + is_state('binary_sensor.carlo_nvr_drive_1_exceeded_max_bad_sectors', 'on') or + is_state('binary_sensor.carlo_nvr_drive_2_exceeded_max_bad_sectors', 'on') or + volume_used >= 92 %} + {% if is_state('binary_sensor.carlo_nvr_synology_integration_problem', 'on') or + is_state('binary_sensor.carlo_nvr_synology_security_problem', 'on') or + hard_storage %} + error + {% elif is_state('binary_sensor.carlo_nvr_synology_storage_problem', 'on') %} + warning + {% else %} + none + {% endif %} + + - name: "Carlo-NVR Synology Problem Summary" + unique_id: carlo_nvr_synology_problem_summary + state: >- + {% set ns = namespace(items=[]) %} + {% if states('binary_sensor.carlo_nvr_security_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['security sensor unavailable'] %} + {% elif is_state('binary_sensor.carlo_nvr_security_status', 'on') %} + {% set ns.items = ns.items + ['security advisor reported an unsafe state'] %} + {% endif %} + {% set volume_status = states('sensor.carlo_nvr_volume_1_status') | lower %} + {% if volume_status in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['volume status unavailable'] %} + {% elif volume_status not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['volume status=' ~ volume_status] %} + {% endif %} + {% set volume_used = states('sensor.carlo_nvr_volume_1_volume_used') %} + {% if volume_used in ['unknown', 'unavailable', 'none', ''] %} + {% set ns.items = ns.items + ['volume used unavailable'] %} + {% elif volume_used | float(0) >= 85 %} + {% set ns.items = ns.items + ['volume used=' ~ (volume_used | float(0) | round(1)) ~ '%'] %} + {% endif %} + {% if states('sensor.carlo_nvr_drive_1_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['drive 1 status unavailable'] %} + {% elif states('sensor.carlo_nvr_drive_1_status') | lower not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['drive 1 status=' ~ (states('sensor.carlo_nvr_drive_1_status') | lower)] %} + {% endif %} + {% if states('sensor.carlo_nvr_drive_2_status') in ['unknown', 'unavailable'] %} + {% set ns.items = ns.items + ['drive 2 status unavailable'] %} + {% elif states('sensor.carlo_nvr_drive_2_status') | lower not in ['normal', 'none', ''] %} + {% set ns.items = ns.items + ['drive 2 status=' ~ (states('sensor.carlo_nvr_drive_2_status') | lower)] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nvr_drive_1_below_min_remaining_life', 'on') %} + {% set ns.items = ns.items + ['drive 1 below remaining life threshold'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nvr_drive_2_below_min_remaining_life', 'on') %} + {% set ns.items = ns.items + ['drive 2 below remaining life threshold'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nvr_drive_1_exceeded_max_bad_sectors', 'on') %} + {% set ns.items = ns.items + ['drive 1 exceeded max bad sectors'] %} + {% endif %} + {% if is_state('binary_sensor.carlo_nvr_drive_2_exceeded_max_bad_sectors', 'on') %} + {% set ns.items = ns.items + ['drive 2 exceeded max bad sectors'] %} + {% endif %} + {{ ns.items | join('; ') if ns.items else 'ok' }} + +automation: + - id: synology_dsm_open_repair_and_dispatch + alias: "Synology DSM - Open Repair And Dispatch" + description: "Open a Repairs issue and dispatch Joanna when a Synology problem stays active." + mode: queued + trigger: + - platform: state + entity_id: + - binary_sensor.carlo_nas01_synology_problem + - binary_sensor.carlo_nvr_synology_problem + to: "on" + for: "00:10:00" + variables: + host_name: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + Carlo-NAS01 + {% else %} + Carlo-NVR + {% endif %} + issue_id: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + synology_carlo_nas01_problem + {% else %} + synology_carlo_nvr_problem + {% endif %} + source: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + home_assistant_automation.synology_dsm_open_repair_and_dispatch.carlo_nas01 + {% else %} + home_assistant_automation.synology_dsm_open_repair_and_dispatch.carlo_nvr + {% endif %} + ssh_alias: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + nas11 + {% else %} + nas12 + {% endif %} + dsm_url: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + http://192.168.10.11:5000 + {% else %} + https://192.168.10.12:5001 + {% endif %} + severity_sensor: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + sensor.carlo_nas01_synology_problem_severity + {% else %} + sensor.carlo_nvr_synology_problem_severity + {% endif %} + summary_sensor: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + sensor.carlo_nas01_synology_problem_summary + {% else %} + sensor.carlo_nvr_synology_problem_summary + {% endif %} + security_entity: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + binary_sensor.carlo_nas01_security_status + {% else %} + binary_sensor.carlo_nvr_security_status + {% endif %} + volume_status_entity: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + sensor.carlo_nas01_volume_1_status + {% else %} + sensor.carlo_nvr_volume_1_status + {% endif %} + volume_used_entity: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + sensor.carlo_nas01_volume_1_volume_used + {% else %} + sensor.carlo_nvr_volume_1_volume_used + {% endif %} + update_entity: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + update.carlo_nas01_dsm_update + {% else %} + update.carlo_nvr_dsm_update + {% endif %} + entity_ids: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + binary_sensor.carlo_nas01_synology_problem, + binary_sensor.carlo_nas01_synology_integration_problem, + binary_sensor.carlo_nas01_synology_security_problem, + binary_sensor.carlo_nas01_synology_storage_problem, + sensor.carlo_nas01_synology_problem_severity, + sensor.carlo_nas01_synology_problem_summary, + binary_sensor.carlo_nas01_security_status, + sensor.carlo_nas01_volume_1_status, + sensor.carlo_nas01_volume_1_volume_used, + sensor.carlo_nas01_drive_1_status, + sensor.carlo_nas01_drive_2_status, + sensor.carlo_nas01_drive_3_status, + binary_sensor.carlo_nas01_drive_1_below_min_remaining_life, + binary_sensor.carlo_nas01_drive_2_below_min_remaining_life, + binary_sensor.carlo_nas01_drive_3_below_min_remaining_life, + binary_sensor.carlo_nas01_drive_1_exceeded_max_bad_sectors, + binary_sensor.carlo_nas01_drive_2_exceeded_max_bad_sectors, + binary_sensor.carlo_nas01_drive_3_exceeded_max_bad_sectors, + update.carlo_nas01_dsm_update + {% else %} + binary_sensor.carlo_nvr_synology_problem, + binary_sensor.carlo_nvr_synology_integration_problem, + binary_sensor.carlo_nvr_synology_security_problem, + binary_sensor.carlo_nvr_synology_storage_problem, + sensor.carlo_nvr_synology_problem_severity, + sensor.carlo_nvr_synology_problem_summary, + binary_sensor.carlo_nvr_security_status, + sensor.carlo_nvr_volume_1_status, + sensor.carlo_nvr_volume_1_volume_used, + sensor.carlo_nvr_drive_1_status, + sensor.carlo_nvr_drive_2_status, + binary_sensor.carlo_nvr_drive_1_below_min_remaining_life, + binary_sensor.carlo_nvr_drive_2_below_min_remaining_life, + binary_sensor.carlo_nvr_drive_1_exceeded_max_bad_sectors, + binary_sensor.carlo_nvr_drive_2_exceeded_max_bad_sectors, + update.carlo_nvr_dsm_update + {% endif %} + problem_severity: "{{ states(severity_sensor) }}" + problem_summary: "{{ states(summary_sensor) }}" + security_state: "{{ states(security_entity) }}" + volume_status: "{{ states(volume_status_entity) }}" + volume_used: "{{ states(volume_used_entity) }}" + dsm_update_state: "{{ states(update_entity) }}" + trigger_context: "HA automation synology_dsm_open_repair_and_dispatch (Synology DSM - Open Repair And Dispatch)" + action: + - service: repairs.create + data: + issue_id: "{{ issue_id }}" + title: "{{ host_name }} Synology health issue" + severity: "{{ 'error' if problem_severity == 'error' else 'warning' }}" + persistent: true + description: >- + Home Assistant detected a sustained Synology DSM issue for {{ host_name }}. + + summary: {{ problem_summary }} + security_state: {{ security_state }} + volume_status: {{ volume_status }} + volume_used: {{ volume_used }} + dsm_update: {{ dsm_update_state }} + ssh_alias: {{ ssh_alias }} + dsm_url: {{ dsm_url }} + - service: script.send_to_logbook + data: + topic: "SYNOLOGY" + message: >- + {{ host_name }} reported a Synology DSM problem for 10 minutes. + Repair {{ issue_id }} opened and Joanna investigation requested. + Summary: {{ problem_summary }}. + - service: script.joanna_dispatch + data: + trigger_context: "{{ trigger_context }}" + source: "{{ source }}" + summary: "{{ host_name }} Synology DSM problem detected" + entity_ids: "{{ entity_ids }}" + diagnostics: >- + issue_id={{ issue_id }}, + severity={{ problem_severity }}, + problem_sensor={{ trigger.entity_id }}, + problem_summary={{ problem_summary }}, + security_state={{ security_state }}, + volume_status={{ volume_status }}, + volume_used={{ volume_used }}, + dsm_update={{ dsm_update_state }}, + ssh_alias={{ ssh_alias }}, + dsm_url={{ dsm_url }} + request: >- + Investigate {{ host_name }} using the Home Assistant Synology DSM entities first, then DSM or SSH if needed. + Review security status, drive health, volume health, and integration availability. + Do not reboot or shut down the NAS unless explicitly requested. + + - id: synology_dsm_clear_repair_on_recovery + alias: "Synology DSM - Clear Repair On Recovery" + description: "Clear the Synology Repairs issue once the monitored problem has recovered." + mode: queued + trigger: + - platform: state + entity_id: + - binary_sensor.carlo_nas01_synology_problem + - binary_sensor.carlo_nvr_synology_problem + to: "off" + for: "00:05:00" + variables: + host_name: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + Carlo-NAS01 + {% else %} + Carlo-NVR + {% endif %} + issue_id: >- + {% if trigger.entity_id == 'binary_sensor.carlo_nas01_synology_problem' %} + synology_carlo_nas01_problem + {% else %} + synology_carlo_nvr_problem + {% endif %} + action: + - service: repairs.remove + continue_on_error: true + data: + issue_id: "{{ issue_id }}" + - service: script.send_to_logbook + data: + topic: "SYNOLOGY" + message: "{{ host_name }} Synology DSM health recovered. Repair {{ issue_id }} cleared." diff --git a/config/script/README.md b/config/script/README.md index af92d6b8..f9752504 100755 --- a/config/script/README.md +++ b/config/script/README.md @@ -16,7 +16,7 @@ -Reusable scripts that other automations call for notifications, lighting, and safety responses. Pass variables in; let the script do the heavy lifting. +Reusable scripts that other automations call for notifications, lighting, safety responses, and Joanna/BearClaw AGENT engineer handoffs. Pass variables in; let the script do the heavy lifting. ### Quick navigation - You are here: `config/script/` (scripts library) @@ -30,30 +30,43 @@ Reusable scripts that other automations call for notifications, lighting, and sa | [notify_engine.yaml](notify_engine.yaml) | Single entrypoint for rich push notifications. | | [notify_live_activity.yaml](notify_live_activity.yaml) | Shared helper for tagged live activity/live update pushes and clear commands. | | [send_to_logbook.yaml](send_to_logbook.yaml) | Generic `logbook.log` helper for Activity feed entries (Issue #1550). | -| [joanna_dispatch.yaml](joanna_dispatch.yaml) | Shared BearClaw/Joanna dispatch schema for automation remediation requests. | +| [joanna_dispatch.yaml](joanna_dispatch.yaml) | Shared AGENT engineer dispatch contract that routes HA-detected issues into Joanna/BearClaw remediation. | | [speech_engine.yaml](speech_engine.yaml) | TTS/announcement orchestration with templated speech. | | [monthly_color_scene.yaml](monthly_color_scene.yaml) | Seasonal lighting scenes used across automations. | -| [interior_off.yaml](interior_off.yaml) | One-call �all interior lights off� helper. | +| [interior_off.yaml](interior_off.yaml) | One-call "all interior lights off" helper. | -### Joanna + BearClaw automated resolution flow -`script.joanna_dispatch` is the shared handoff contract from Home Assistant automations to BearClaw/Joanna. +### Joanna + BearClaw AGENT engineer handoff +`script.joanna_dispatch` is the shared handoff contract from Home Assistant automations into Joanna/BearClaw when Home Assistant detects something worth investigating or fixing. Why we use it: - Keeps one message schema for remediation context (`trigger_context`, `source`, `summary`, `entity_ids`, `diagnostics`, `request`). - Avoids repeating direct `rest_command.bearclaw_command` payload formatting in multiple packages. +- Lets Home Assistant stay focused on detection, timing, and routing while Joanna acts as the AGENT engineer for infrastructure triage and recommended remediation. - Makes resolution-trigger automations easier to review, update, and audit. +What the helper normalizes before the BearClaw intake call: +- `trigger_context`, `source`, and `summary` so every dispatch has traceable origin details. +- `entity_ids` from either a YAML list or a comma-delimited string. +- `diagnostics` from either free text or structured mappings/sequences. +- `request` guardrails so Joanna defaults to investigation/recommendation, not blind resets or power-cycles. + Current automations that kick off automated resolutions (via `script.joanna_dispatch`): | Automation ID | Alias | File | | --- | --- | --- | +| `github_watched_repo_scout_nightly` | GitHub Watched Repo Scout - Nightly Joanna Review | [../packages/github_watched_repo_scout.yaml](../packages/github_watched_repo_scout.yaml) | | `mqtt_open_repair_on_failure` | MQTT - Open Repair On Failure | [../packages/mqtt_status.yaml](../packages/mqtt_status.yaml) | | `onenote_indexer_daily_delete_maintenance` | OneNote Indexer - Daily Delete Maintenance Request | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) | | `onenote_indexer_failure_open_repair` | OneNote Indexer - Open Repair On Failure | [../packages/onenote_indexer.yaml](../packages/onenote_indexer.yaml) | +| `infra_backup_nightly_verification` | Infrastructure - Backup Nightly Verification | [../packages/infrastructure_observability.yaml](../packages/infrastructure_observability.yaml) | | `docker_state_sync_repairs_dynamic` | Docker State Sync - Repairs (Dynamic) | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) | +| `docker_group_reconcile_weekly_joanna_review` | Docker Group Reconcile - Weekly Joanna Review | [../packages/docker_infrastructure.yaml](../packages/docker_infrastructure.yaml) | | `unifi_ap_no_clients_repair_combined` | Unifi AP Create Repair Issue after 5m of 0 Clients | [../packages/wireless.yaml](../packages/wireless.yaml) | +| `synology_dsm_open_repair_and_dispatch` | Synology DSM - Open Repair And Dispatch | [../packages/synology_dsm.yaml](../packages/synology_dsm.yaml) | +| `b16f2155-4688-4c0f-9cf8-b382e294a029` | Self Heal Disk Use Alarm | [../packages/processmonitor.yaml](../packages/processmonitor.yaml) | +| `1ce3cb43-0e27-4c53-acdd-d672396f3559` | Disk Use Alarm | [../packages/processmonitor.yaml](../packages/processmonitor.yaml) | ### Tips -- Keep scripts generic�route data via `data:`/`variables:` and reuse everywhere. +- Keep scripts generic, route data via `data:`/`variables:`, and reuse everywhere. - If you copy a script, rename any `alias` and `id` fields to avoid duplicates. **All of my configuration files are tested against the most stable version of home-assistant.**