diff --git a/codex_skills/homeassistant-yaml-dry-verifier/README.md b/codex_skills/homeassistant-yaml-dry-verifier/README.md index cef39c0d..25355101 100644 --- a/codex_skills/homeassistant-yaml-dry-verifier/README.md +++ b/codex_skills/homeassistant-yaml-dry-verifier/README.md @@ -31,6 +31,7 @@ This directory contains the `homeassistant-yaml-dry-verifier` skill and the CLI - Detects duplicate entries within a single block (`INTRA`). - Detects package-defined scripts called from multiple files (`CENTRAL_SCRIPT`). - Collapses noisy ENTRY reports when they are already fully explained by an identical `FULL_BLOCK` finding. +- Adds workflow guardrails for automation refactors that rename/remove entity references or introduce cleanup behavior: stale-reference checks, dry-run/preview expectations, explicit confirmation, and audit/backup output. ## CLI Usage @@ -70,6 +71,8 @@ Exit codes: - This verifier intentionally keeps text output and a small CLI surface. - It does not implement suppression files, severity scoring, JSON output, or diff-only mode. +- It is not an orphan entity cleaner and should not delete Home Assistant registry entries during normal DRY runs. +- Treat generic `unavailable`, disabled, or no-`config_entry_id` entities as audit signals only; YAML, helper, REST, command-line, MQTT, finance, YouTube, and local infrastructure telemetry can be intentional. - Use it as a fast pre-refactor signal and pair with Home Assistant config validation before restart/reload. **All of my configuration files are tested against the most stable version of home-assistant.** diff --git a/codex_skills/homeassistant-yaml-dry-verifier/SKILL.md b/codex_skills/homeassistant-yaml-dry-verifier/SKILL.md index 28ca6399..a5fed600 100644 --- a/codex_skills/homeassistant-yaml-dry-verifier/SKILL.md +++ b/codex_skills/homeassistant-yaml-dry-verifier/SKILL.md @@ -1,6 +1,6 @@ --- name: homeassistant-yaml-dry-verifier -description: "Verify Home Assistant YAML for DRY and efficiency issues by detecting redundant trigger/condition/action/sequence structures and repeated blocks across automations, scripts, and packages. Use when creating, reviewing, or refactoring YAML in config/packages, config/automations, config/scripts, or dashboard-related YAML where duplication risk is high." +description: "Verify Home Assistant YAML for DRY and efficiency issues by detecting redundant trigger/condition/action/sequence structures and repeated blocks across automations, scripts, and packages. Use when creating, reviewing, or refactoring YAML in config/packages, config/automations, config/scripts, or dashboard-related YAML where duplication risk is high. Include a read-only entity/reference safety pass when automation changes rename/remove entities or introduce maintenance cleanup behavior." --- # Home Assistant YAML DRY Verifier @@ -13,13 +13,15 @@ Use this skill to lint Home Assistant YAML for repeat logic before or after edit - Resolve the findings in the same task by refactoring YAML to remove duplication. - Re-run the verifier after refactoring and iterate until targeted findings are cleared. - If a finding cannot be safely resolved, explicitly document the blocker and the smallest safe follow-up. +- If touched YAML performs cleanup, registry hygiene, purge, deletion, or other destructive maintenance, require preview/dry-run behavior, explicit confirmation, and audit/backup output before any destructive action is considered complete. ## Quick Start 1. Run the verifier script on the file(s) you edited. 2. Review repeated block findings first (highest confidence). 3. Refactor into shared scripts/helpers/templates where appropriate. -4. Re-run the verifier and then run your normal Home Assistant config check. +4. If the change renames/removes entity references or adds maintenance cleanup behavior, perform the read-only entity/reference safety pass. +5. Re-run the verifier and then run your normal Home Assistant config check. ```bash python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.py config/packages/life360.yaml --strict @@ -53,15 +55,25 @@ python codex_skills/homeassistant-yaml-dry-verifier/scripts/verify_ha_yaml_dry.p - Repeated triggers: consolidate where behavior is equivalent, or split by intent if readability improves. - For cooldown/throttle behavior, prefer automation-local `this.attributes.last_triggered` with custom event handoff before adding new helper entities, unless shared persistent state is required across automations. -5. Validate after edits: +5. Entity/Registry Safety Pass: +- Keep this pass read-only during normal DRY work. Report possible stale references or risky cleanup behavior; do not delete Home Assistant registry entries as part of this skill. +- When refactors rename, remove, or consolidate automations, scripts, helpers, entities, service calls, or dashboard targets, search touched and adjacent YAML for stale references before closing the task. +- Use live Home Assistant context or registry/state evidence when available and in scope, especially before changing entity IDs or automations that depend on device/integration state. +- Do not treat generic `unavailable`, disabled, or no-`config_entry_id` entities as safe deletion candidates. In YAML-heavy setups these are often intentional. +- Treat these platforms as common false-positive sources unless stronger evidence proves otherwise: `automation`, `script`, `scene`, `template`, helpers (`input_*`, `group`, `timer`, `counter`, `schedule`, `zone`, `person`, `tag`), `command_line`, `rest`, `mqtt`, `yahoofinance`, `youtube`, and local infrastructure telemetry. +- For cleanup or maintenance automations, prefer a preview/report action first, persistent ignore rules where repeated noise is expected, and an audit artifact that records what would change or did change. +- Destructive cleanup must be gated by explicit operator confirmation and should have backup/audit output. A dry-run-only recommendation is acceptable when the evidence is not strong enough. + +6. Validate after edits: - Re-run this verifier. - Run Home Assistant config validation before reload/restart. -6. Enforce closure: +7. Enforce closure: - Treat unresolved `FULL_BLOCK`/`ENTRY` findings in touched files as incomplete work unless a blocker is documented. - Prefer consolidating duplicated automation triggers/conditions/actions into shared logic or a single branching automation. - Treat unresolved `CENTRAL_SCRIPT` findings in touched scope as incomplete unless documented as deferred-with-blocker. - Move shared package scripts to `config/script/.yaml` when they are used cross-file. +- Treat unresolved stale-reference or cleanup-safety concerns in touched scope as incomplete unless documented as `deferred-with-blocker`. ## Dashboard Designer Integration @@ -77,6 +89,8 @@ Always report: - Script caller detection should include direct `service: script.` and `script.turn_on`-style entity targeting when present. - Concrete refactor recommendation per group. - Resolution status for each finding (`resolved`, `deferred-with-blocker`). +- Entity/reference hygiene status when this pass is in scope (`checked`, `not-in-scope`, or `deferred-with-blocker`). +- For cleanup or destructive maintenance YAML, preview/dry-run, confirmation, and audit/backup status. Strict behavior: - `--strict` returns non-zero for any reported finding (`FULL_BLOCK`, `ENTRY`, `INTRA`, `CENTRAL_SCRIPT`). diff --git a/config/dashboards/infrastructure/partials/proxmox_sections.yaml b/config/dashboards/infrastructure/partials/proxmox_sections.yaml index 7df52b13..4219aa79 100644 --- a/config/dashboards/infrastructure/partials/proxmox_sections.yaml +++ b/config/dashboards/infrastructure/partials/proxmox_sections.yaml @@ -120,10 +120,6 @@ name: HASS CPU - entity: sensor.qemu_carlo_hass_105_memory_used_percentage name: HASS MEM - - entity: sensor.qemu_wireguard_104_cpu_used - name: WireGuard CPU - - entity: sensor.qemu_wireguard_104_memory_used_percentage - name: WireGuard MEM - type: custom:vertical-stack-in-card card_mod: diff --git a/config/dashboards/kiosk/partials/locator_sections.yaml b/config/dashboards/kiosk/partials/locator_sections.yaml index f82a6db9..18dbfc3f 100644 --- a/config/dashboards/kiosk/partials/locator_sections.yaml +++ b/config/dashboards/kiosk/partials/locator_sections.yaml @@ -53,35 +53,28 @@ - entity: person.carlo state_not: home row: - type: attribute - entity: sensor.carlo_place + entity: sensor.carlo_location_display name: Carlo Location - type: conditional conditions: - entity: person.stacey state_not: home row: - type: attribute - entity: sensor.stacey_place - attribute: place_name + entity: sensor.stacey_location_display name: Stacey Location - type: conditional conditions: - entity: person.justin state_not: home row: - type: attribute - entity: sensor.justin_place - attribute: place_name + entity: sensor.justin_location_display name: Justin Location - type: conditional conditions: - entity: person.paige state_not: home row: - type: attribute - entity: sensor.paige_place - attribute: place_name + entity: sensor.paige_location_display name: Paige Location - show_state: true show_name: true diff --git a/config/dashboards/overview/partials/home_sections.yaml b/config/dashboards/overview/partials/home_sections.yaml index 396af704..b17c2850 100644 --- a/config/dashboards/overview/partials/home_sections.yaml +++ b/config/dashboards/overview/partials/home_sections.yaml @@ -66,36 +66,28 @@ - entity: person.carlo state_not: home row: - type: attribute - entity: sensor.carlo_place - attribute: formatted_place + entity: sensor.carlo_location_display name: Carlo Location - type: conditional conditions: - entity: person.stacey state_not: home row: - type: attribute - entity: sensor.stacey_place - attribute: formatted_place + entity: sensor.stacey_location_display name: Stacey Location - type: conditional conditions: - entity: person.justin state_not: home row: - type: attribute - entity: sensor.justin_place - attribute: formatted_place + entity: sensor.justin_location_display name: Justin Location - type: conditional conditions: - entity: person.paige state_not: home row: - type: attribute - entity: sensor.paige_place - attribute: formatted_place + entity: sensor.paige_location_display name: Paige Location state_color: false - type: custom:mushroom-template-card diff --git a/config/packages/docker_infrastructure.yaml b/config/packages/docker_infrastructure.yaml index dfa813f8..249e0d54 100644 --- a/config/packages/docker_infrastructure.yaml +++ b/config/packages/docker_infrastructure.yaml @@ -7,9 +7,9 @@ # Related Issue: 1632, 1584 # APT results and container down repairs. # ------------------------------------------------------------------- -# Notes: Hosts run weekly Wed 12:00 APT job and POST JSON to webhooks. +# Notes: Hosts run daily read-only APT pending checks plus Mon/Thu 12:00 APT jobs. # Notes: Reboots are handled directly on each host by apt_weekly.sh. -# Notes: Reboot staggering: docker_14 first, docker_69 second, docker_10 third. +# Notes: Reboot staggering: docker_14, docker_69, docker_17, docker_10. # Notes: Container monitoring is dynamic with binary_sensor status preferred over switch state. # Notes: Weekly Joanna reconcile checks discovered container switches vs configured group members. # Notes: Includes Portainer stack status repairs, 20-minute Joanna dispatch for persistent container outages, and scheduled image prune. @@ -899,6 +899,7 @@ automation: updated: "{{ payload.get('updated', false) | bool }}" reboot_required: "{{ payload.get('reboot_required', false) | bool }}" packages: "{{ payload.get('packages', 0) | int(0) }}" + security_packages: "{{ payload.get('security_packages', 0) | int(0) }}" message: "{{ payload.get('message', '') | string }}" helpers: docker_10: @@ -919,17 +920,20 @@ automation: last_result: input_text.apt_docker_69_last_result host_helpers: "{{ helpers[host_id] if host_id in helpers else none }}" result: >- + {% set security = ' (' ~ security_packages ~ ' SEC)' if security_packages > 0 else '' %} {% if not success %} ERROR{% if (message | trim) != '' %}: {{ message | trim }}{% endif %} {% elif updated %} - UPDATED {{ packages }} PKGS{% if reboot_required %} (REBOOT REQ){% endif %} + UPDATED {{ packages }} PKGS{{ security }}{% if reboot_required %} (REBOOT REQ){% endif %} + {% elif packages > 0 %} + PENDING {{ packages }} PKGS{{ security }}{% if reboot_required %} (REBOOT REQ){% endif %} {% elif reboot_required %} NO UPDATES (REBOOT REQ) {% else %} NO UPDATES {% endif %} log_message: >- - {{ host_id }} updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %}; reboot required{% endif %}. + {{ host_id }} updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if security_packages > 0 %} ({{ security_packages }} security){% endif %}{% if reboot_required %}; reboot required{% endif %}. condition: - condition: template value_template: "{{ host_helpers is not none }}" diff --git a/config/packages/infrastructure.yaml b/config/packages/infrastructure.yaml index 563cd22f..70ac75f7 100644 --- a/config/packages/infrastructure.yaml +++ b/config/packages/infrastructure.yaml @@ -9,7 +9,7 @@ # Related Issue: 1584 # Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts. # Notes: Domain warning threshold is <30 days; critical threshold is <14 days. -# Notes: Nightly Duplicati verification is performed by codex_appliance after the Duplicati retry window because HA backup entities are not available. +# Notes: Nightly Duplicati verification runs at 08:00 after the 05:30 Duplicati job and docker_14 reboot window. # Notes: Monthly HA log hygiene review requests Telegram + GitHub issue follow-up only; Joanna must wait for approval before any changes. # Notes: Numeric WAN telemetry exposes state_class so recorder can keep long-term statistics. # Notes: Docker host root disk usage uses Glances-backed normalized sensors; raw Glances sensors are recorder/logbook-filtered. @@ -578,7 +578,7 @@ automation: mode: single trigger: - platform: time - at: "06:45:00" + at: "08:00:00" action: - variables: trigger_context: "HA automation infra_backup_nightly_verification (Infrastructure - Backup Nightly Verification)" @@ -612,6 +612,10 @@ automation: continue_on_error: true data: issue_id: infra_duplicati_backup_failure + - service: repairs.remove + continue_on_error: true + data: + issue_id: user_infra_duplicati_backup_failure default: - service: repairs.create data: @@ -633,7 +637,7 @@ automation: entity_ids: - "switch.duplicati_container" diagnostics: >- - scheduled_time=06:45:00, + scheduled_time=08:00:00, duplicati_container={{ duplicati_state }}, verifier_http_status={{ verify_http_status }}, verifier_status={{ verify_status }}, diff --git a/config/packages/powerwall.yaml b/config/packages/powerwall.yaml index dc99cadc..45f8d69d 100755 --- a/config/packages/powerwall.yaml +++ b/config/packages/powerwall.yaml @@ -12,6 +12,7 @@ # Notes: Read more https://www.vcloudinfo.com/2018/01/going-green-to-save-some-green-in-2018.html | Existing Issue #272 # Tesla Powerwall added via UI Integration # Notes: Live outage tracking derives its chronometer anchor from binary_sensor.powerwall_grid_status.last_changed. +# Notes: Camera PoE restore requires grid online and Powerwall charge at least 50%. ###################################################################### # Binary Sensors: # - binary_sensor.powerwall_charging ............. battery_charging (on=charging) @@ -375,7 +376,7 @@ automation: - alias: "Restore PoE ports when grid returns" id: 1ae8b5c5-8627-4a44-8c8a-5bf8ca5e1bf5 - description: "Turn camera PoE ports back on after grid has been stable" + description: "Turn camera PoE ports back on after grid has returned and battery is at least 50%" mode: single trigger: - platform: state @@ -386,7 +387,7 @@ automation: minutes: 60 - platform: numeric_state entity_id: sensor.powerwall_charge - above: 90 + above: 49.9 condition: - condition: or @@ -403,9 +404,12 @@ automation: - condition: state entity_id: switch.poe_garage_port_6_poe state: 'off' + - condition: state + entity_id: binary_sensor.powerwall_grid_status + state: 'on' - condition: numeric_state entity_id: sensor.powerwall_charge - above: 90 + above: 49.9 action: - service: switch.turn_on target: @@ -417,7 +421,7 @@ automation: - service: script.notify_engine data: title: "Grid restored - PoE ports re-enabled" - value1: "Power is stable. Camera PoE ports 3-6 were turned back on automatically." + value1: "Grid is online and Powerwall charge is at least 50%. Camera PoE ports 3-6 were turned back on automatically." who: 'family' group: 'information' diff --git a/config/recorder.yaml b/config/recorder.yaml index 9ba69e5e..0b57292c 100755 --- a/config/recorder.yaml +++ b/config/recorder.yaml @@ -49,7 +49,6 @@ exclude: - sensor.*_since - sensor.*uptime* - sensor.sun_next_* - - sensor.vpn_client_* - sensor.*_linkquality - sensor.*_link_quality - sensor.*_lqi diff --git a/config/shell_scripts/README.md b/config/shell_scripts/README.md index 96994ee7..e7e3a628 100755 --- a/config/shell_scripts/README.md +++ b/config/shell_scripts/README.md @@ -27,7 +27,8 @@ Longer-running shell helpers referenced by automations, packages, or cron. Anyth | File | Why it matters | | --- | --- | | [HAUpdate.sh](HAUpdate.sh) | One-command Home Assistant update helper. | -| [apt_weekly.sh](apt_weekly.sh) | Weekly APT updater that posts webhook status and can schedule reboot when needed. | +| [apt_pending_check.sh](apt_pending_check.sh) | Read-only APT pending-count reporter for the Docker host maintenance dashboard. | +| [apt_weekly.sh](apt_weekly.sh) | Twice-weekly APT updater that posts webhook status and can schedule reboot when needed. | | [apt_reboot_report.sh](apt_reboot_report.sh) | Boot-time webhook status reporter that clears/keeps reboot-required state in HA. | | [gitupdate.sh](gitupdate.sh) | Pull the latest config changes on demand. | | [basketball.yaml](basketball.yaml) | ESPN stat scraping helper used by sensors. | diff --git a/config/shell_scripts/apt_pending_check.service.sample b/config/shell_scripts/apt_pending_check.service.sample new file mode 100644 index 00000000..b245f7a2 --- /dev/null +++ b/config/shell_scripts/apt_pending_check.service.sample @@ -0,0 +1,8 @@ +[Unit] +Description=Daily APT pending check (Home Assistant webhook) +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/apt_pending_check.sh "http://YOUR_HOME_ASSISTANT:8123/api/webhook/YOUR_APT_WEBHOOK" "docker_10" diff --git a/config/shell_scripts/apt_pending_check.sh b/config/shell_scripts/apt_pending_check.sh new file mode 100644 index 00000000..b477bfab --- /dev/null +++ b/config/shell_scripts/apt_pending_check.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Read-only APT pending check for Docker hosts. +# Posts current package counts to the same Home Assistant webhook as apt_weekly.sh. + +WEBHOOK_URL="$1" +HOST_NAME="${2:-$(hostname -s)}" + +if [[ -z "$WEBHOOK_URL" ]]; then + echo "Usage: $0 [host_name]" >&2 + exit 1 +fi + +post_result() { + local success="$1" + local packages="$2" + local security_packages="$3" + local reboot_required="$4" + local message="${5:-}" + + payload=$(cat </dev/null | tail -n +2 | wc -l) +UPGRADABLE="$(apt list --upgradable 2>/dev/null | tail -n +2 || true)" +PACKAGES=0 +SECURITY_PACKAGES=0 +if [[ -n "$UPGRADABLE" ]]; then + PACKAGES="$(printf '%s\n' "$UPGRADABLE" | sed '/^[[:space:]]*$/d' | wc -l)" + SECURITY_PACKAGES="$(printf '%s\n' "$UPGRADABLE" | grep -Ec '(^|,|-)(security|esm-apps-security|esm-infra-security)(,|/|[[:space:]]|$)' || true)" +fi if [[ "$PACKAGES" -gt 0 ]]; then log "Applying upgrades ($PACKAGES pending)" - if apt-get -y upgrade --with-new-pkgs; then + if apt-get "${APT_OPTS[@]}" -y upgrade --with-new-pkgs; then UPDATED=true else MESSAGE="apt-get upgrade failed" @@ -61,6 +68,7 @@ payload=$(cat </dev/null 2>&1 || true if [[ "$REBOOT_DELAY_MINUTES" -eq 0 ]]; then log "Reboot required; rebooting immediately." - shutdown -r now "APT weekly maintenance reboot" + shutdown -r now "APT maintenance reboot" else log "Reboot required; scheduling reboot in ${REBOOT_DELAY_MINUTES} minute(s)." - shutdown -r +"$REBOOT_DELAY_MINUTES" "APT weekly maintenance reboot" + shutdown -r +"$REBOOT_DELAY_MINUTES" "APT maintenance reboot" fi fi diff --git a/config/shell_scripts/apt_weekly.timer.sample b/config/shell_scripts/apt_weekly.timer.sample new file mode 100644 index 00000000..bff12068 --- /dev/null +++ b/config/shell_scripts/apt_weekly.timer.sample @@ -0,0 +1,10 @@ +[Unit] +Description=Run APT maintenance twice weekly + +[Timer] +OnCalendar=Mon,Thu *-*-* 12:00:00 +Persistent=true +Unit=apt_weekly.service + +[Install] +WantedBy=timers.target