Found live 2026-09-03: after reload-only deploys, webhook-triggered automations (doorman ingest, deploy fast path) silently stop firing while the endpoint still returns HTTP 200. Proven: core restart restores them. Sha-watch now restarts core on every new master HEAD (same semantics as the webhook fast path for packages/** changes). https://projects.knownelement.com/issues/345
374 lines
13 KiB
YAML
374 lines
13 KiB
YAML
- id: pfv_plant_ups_on_battery
|
|
alias: PFV plant - UPS on battery
|
|
triggers:
|
|
- trigger: state
|
|
entity_id:
|
|
- sensor.tsys1_ups_status
|
|
- sensor.tsys9_ups_status
|
|
conditions:
|
|
- condition: template
|
|
value_template: '{{ ''On Battery'' in states(trigger.entity_id) }}'
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: POWER OUTAGE
|
|
message: '{{ state_attr(trigger.entity_id, ''friendly_name'') }} lost utility
|
|
power and is ON BATTERY at {{ states(''sensor.'' ~ trigger.entity_id.split(''.'')[1]
|
|
| replace(''_status'', ''_battery_charge'')) }}% charge.'
|
|
mode: single
|
|
- id: pfv_plant_ups_battery_low
|
|
alias: PFV plant - UPS battery low
|
|
triggers:
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.tsys1_ups_battery_charge
|
|
below: 40
|
|
conditions:
|
|
- condition: template
|
|
value_template: '{{ ''On Battery'' in states(trigger.entity_id | replace(''_battery_charge'',
|
|
''_status'')) }}'
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: UPS BATTERY LOW
|
|
message: '{{ trigger.entity_id }} at {{ states(trigger.entity_id) }}% while
|
|
on battery. Start graceful shutdown of VMs before hypervisors (#455 ordering).'
|
|
mode: single
|
|
- id: pfv_plant_ups_comm_lost
|
|
alias: PFV plant - UPS communication lost
|
|
triggers:
|
|
- trigger: state
|
|
entity_id:
|
|
- sensor.tsys1_ups_status
|
|
- sensor.tsys9_ups_status
|
|
to: unavailable
|
|
for: 00:05:00
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: UPS COMMS LOST
|
|
message: '{{ trigger.entity_id }} unreachable for 5 minutes (nut-server or network
|
|
path down; tsys1 also hosts pfv-bms itself).'
|
|
mode: single
|
|
- id: pfv_plant_ups_runtime_low
|
|
alias: PFV plant - UPS runtime low while on battery
|
|
description: On battery and runtime burned below 10 min — the graceful
|
|
shutdown window is closing. VMs before hypervisors per #455.
|
|
triggers:
|
|
- trigger: numeric_state
|
|
entity_id: sensor.pfv_ups_runtime_minutes
|
|
below: 10
|
|
conditions:
|
|
- condition: template
|
|
value_template: "{{ states('sensor.tsys1_ups_status') not in ['Online', 'unknown', 'unavailable'] }}"
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: UPS RUNTIME LOW
|
|
message: On battery {{ states('sensor.pfv_ups_runtime_minutes') }} min left — start graceful shutdown now (#455 ordering).
|
|
mode: single
|
|
- id: pfv_plant_ups_power_restored
|
|
alias: PFV plant - utility power restored
|
|
triggers:
|
|
- trigger: state
|
|
entity_id:
|
|
- sensor.tsys1_ups_status
|
|
- sensor.tsys9_ups_status
|
|
conditions:
|
|
- condition: template
|
|
value_template: "{{ trigger.from_state is not none and 'On Battery' in trigger.from_state.state\n
|
|
\ and 'On Battery' not in states(trigger.entity_id) }}"
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: Power restored
|
|
message: '{{ state_attr(trigger.entity_id, ''friendly_name'') }} is back ONLINE.'
|
|
mode: single
|
|
- id: pfv_plant_high_temperature
|
|
alias: PFV plant - high temperature
|
|
triggers:
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys3_nvme_temperature
|
|
- sensor.pfv_tsys5_nvme_temperature
|
|
above: 150
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys4_dimm_18_temperature
|
|
- sensor.pfv_tsys4_dimm_19_temperature
|
|
- sensor.pfv_tsys4_dimm_1a_temperature
|
|
- sensor.pfv_tsys4_dimm_1b_temperature
|
|
above: 140
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys1_drive_temperature
|
|
- sensor.pfv_tsys4_drive_0_0_temperature
|
|
- sensor.pfv_tsys4_drive_0_10_temperature
|
|
- sensor.pfv_tsys4_drive_1_0_temperature
|
|
- sensor.pfv_tsys4_drive_1_10_temperature
|
|
- sensor.pfv_tsys5_drive_1_0_temperature
|
|
- sensor.pfv_tsys5_drive_4_10_temperature
|
|
- sensor.pfv_tsys5_drive_4_20_temperature
|
|
- sensor.pfv_tsys5_drive_4_30_temperature
|
|
- sensor.pfv_tsys9_drive_temperature
|
|
above: 140
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys3_gpu_temperature
|
|
- sensor.pfv_jetson_gpu_temperature
|
|
above: 185
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys3_pch_temperature
|
|
- sensor.pfv_tsys9_pch_temperature
|
|
above: 160
|
|
# CPU-SoC silicon tier: idle silicon routinely exceeds the
|
|
# ambient 104F threshold (subopi3 SoC idles at ~117F); 176F = 80C
|
|
# pre-throttle territory for Pi-Jetson SoCs and server CPUs [#733]
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_tsys3_cpu_temperature
|
|
- sensor.pfv_tsys6_cpu_temperature
|
|
- sensor.pfv_tsys7_cpu_temperature
|
|
- sensor.pfv_tsys9_cpu_temperature
|
|
- sensor.pfv_tsys7_drac_cpu1
|
|
- sensor.pfv_tsys7_drac_cpu2
|
|
- sensor.pfv_jetson_temperature
|
|
- sensor.pfv_svrpi_temperature
|
|
- sensor.pfv_subopi3_temperature
|
|
- sensor.pfv_subopi_dev_3_temperature
|
|
- sensor.pfv_subopi_dev_4_temperature
|
|
above: 176
|
|
- trigger: numeric_state
|
|
entity_id:
|
|
- sensor.pfv_artroom_temperature
|
|
- sensor.pfv_garage_temperature
|
|
- sensor.pfv_tsys1_rack_temperature
|
|
- sensor.pfv_tsys4_ambient_temperature
|
|
- sensor.pfv_tsys5_ambient_temperature
|
|
- sensor.pfv_tsys6_drac_ambient
|
|
- sensor.pfv_tsys7_drac_inlet
|
|
- sensor.pfv_tsys7_drac_exhaust
|
|
above: 104
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: HIGH TEMPERATURE
|
|
message: '{{ trigger.entity_id }} is {{ states(trigger.entity_id) }}°F (class
|
|
threshold exceeded). Check cooling/airflow.'
|
|
mode: single
|
|
- id: pfv_plant_sensor_watchdog
|
|
alias: PFV plant - sensor watchdog (unavailable sensors)
|
|
description: HA is the environmental brain (#344) — a silent sensor is an outage.
|
|
Re-alert suppressed by 4h cooldown via last_notified input_text if present.
|
|
triggers:
|
|
- trigger: time_pattern
|
|
minutes: /30
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: SENSOR WATCHDOG
|
|
message: >-
|
|
{% set fleet = states.sensor | selectattr('entity_id', 'match', 'sensor\.pfv_')
|
|
| list %}
|
|
{% set dead = fleet | selectattr('state', 'in', ['unavailable', 'unknown']) | list %}
|
|
{% if dead | length > 0 %}
|
|
{{ dead | length }} of {{ fleet | length }} fleet sensors silent:
|
|
{{ dead | map(attribute='entity_id') | join(', ') }}
|
|
{% else %}
|
|
all {{ fleet | length }} fleet sensors OK
|
|
{% endif %}
|
|
mode: single
|
|
- id: pfv_plant_temp_rising_fast
|
|
alias: PFV plant - temperature rising fast (rate-of-change)
|
|
description: Catches dead AC minutes before the 104F threshold trips.
|
|
triggers:
|
|
- trigger: state
|
|
entity_id:
|
|
- binary_sensor.server_room_temp_rising
|
|
- binary_sensor.tsys6_ambient_rising
|
|
- binary_sensor.tsys7_inlet_rising
|
|
- binary_sensor.garage_temp_rising
|
|
to: "on"
|
|
for:
|
|
minutes: 2
|
|
actions:
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: TEMP RISING FAST
|
|
message: '{{ trigger.entity_id }} climbing ~10F/15min — check cooling
|
|
before the 104F threshold. (rate-of-change early warning)'
|
|
# Deploy pipeline [#734]: gitea repo webhook (master pushes) -> forced pull
|
|
# -> minimal reload. Core restarts ONLY when configuration.yaml or a
|
|
# packages platform-sensor file changed; everything else reloads with no
|
|
# bounce. The git_pull add-on's auto_restart is disabled - this webhook
|
|
# owns apply timing (no more 5-min-timer surprise restarts).
|
|
- id: pfv_deploy_git_webhook
|
|
alias: PFV deploy - git push to pull + minimal reload
|
|
description: >-
|
|
Gitea webhook on master pushes. Forces a git pull now, then
|
|
homeassistant.reload_all unless files that require a core restart
|
|
changed. Replaces the git_pull add-on timer auto-restart [#734].
|
|
mode: queued
|
|
max_exceeded: silent
|
|
triggers:
|
|
- trigger: webhook
|
|
webhook_id: !secret deploy_webhook_id
|
|
allowed_methods:
|
|
- POST
|
|
local_only: false
|
|
conditions: []
|
|
actions:
|
|
- if:
|
|
- condition: template
|
|
value_template: "{{ trigger.json.ref | default('') != 'refs/heads/master' }}"
|
|
then:
|
|
- stop: "deploy webhook: non-master ref {{ trigger.json.ref | default('') }} ignored"
|
|
- variables:
|
|
changed_files: >-
|
|
{{ (trigger.json.commits | default([]) | map(attribute='added') | flatten | list)
|
|
+ (trigger.json.commits | default([]) | map(attribute='removed') | flatten | list)
|
|
+ (trigger.json.commits | default([]) | map(attribute='modified') | flatten | list) }}
|
|
need_restart: >-
|
|
{{ changed_files | select('match', '^(configuration\.yaml|packages/.+)') | list | count > 0 }}
|
|
push_message: >-
|
|
Git push ({{ trigger.json.sender.login | default('unknown') }}):
|
|
{{ trigger.json.commits | default([]) | map(attribute='message') | join(' | ') | truncate(120) }}.
|
|
{{ 'Core restart required' if need_restart else 'Reload only - no core restart' }}.
|
|
- action: persistent_notification.create
|
|
data:
|
|
title: Deploy started
|
|
message: "{{ push_message }} Pulling now."
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: PFV deploy started
|
|
message: "{{ push_message }}"
|
|
- action: hassio.addon_restart
|
|
data:
|
|
addon: core_git_pull
|
|
- delay:
|
|
hours: 0
|
|
minutes: 0
|
|
seconds: 25
|
|
- if:
|
|
- condition: template
|
|
value_template: "{{ need_restart }}"
|
|
then:
|
|
- action: persistent_notification.create
|
|
data:
|
|
title: Deploy - restarting core
|
|
message: "Pull done; restarting core for {{ changed_files | join(', ') }}. Back in ~1 min."
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: PFV deploy - core restarting
|
|
message: "Pull done; restarting core for {{ changed_files | join(', ') }}."
|
|
- delay:
|
|
hours: 0
|
|
minutes: 0
|
|
seconds: 3
|
|
- action: homeassistant.restart
|
|
else:
|
|
- action: homeassistant.reload_all
|
|
- action: persistent_notification.create
|
|
data:
|
|
title: Deploy complete
|
|
message: "Pulled + reloaded YAML config at {{ now().strftime('%H:%M:%S') }} (no core restart). Files: {{ changed_files | join(', ') | truncate(180) }}"
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: PFV deploy complete
|
|
message: "Pulled + reloaded YAML at {{ now().strftime('%H:%M:%S') }} - no core restart. Files: {{ changed_files | join(', ') | truncate(120) }}"
|
|
# Deploy pipeline safety net [#734]: polls gitea master HEAD (rest sensor
|
|
# in packages/deploy_pipeline.yaml) and applies pulls reload-only. The
|
|
# gitea webhook is the fast path once gitea's SSRF allowlist permits
|
|
# pfv-bms.knel.net; until then this net catches every push within 5 min
|
|
# with NO core restart. Restarts for new platform sensors stay manual
|
|
# (22222 shell) until the webhook path is live.
|
|
- id: pfv_deploy_sha_watch
|
|
alias: PFV deploy - gitea sha watch safety net
|
|
description: >-
|
|
Master HEAD sha changed -> pull + CORE RESTART. Interim deploy
|
|
trigger while gitea webhook delivery is blocked by its SSRF host
|
|
allowlist [#734]. Restart (not reload) is REQUIRED: automation
|
|
reload loses webhook-trigger registration - found live 2026-09-03
|
|
(#345): after any reload-only deploy, webhook automations (doorman
|
|
scan ingest, this pipeline's own fast path) silently stop firing
|
|
while the endpoint still returns HTTP 200. Core restart
|
|
re-registers them. Accepted trade-off: ~1 min HA outage per
|
|
master deploy.
|
|
mode: single
|
|
max_exceeded: silent
|
|
triggers:
|
|
- trigger: state
|
|
entity_id: sensor.pfv_deploy_gitea_master_head
|
|
conditions:
|
|
- condition: template
|
|
value_template: >-
|
|
{{ trigger.from_state is not none
|
|
and trigger.from_state.state not in ['unknown', 'unavailable']
|
|
and trigger.to_state is not none
|
|
and trigger.from_state.state != trigger.to_state.state }}
|
|
actions:
|
|
- action: hassio.addon_restart
|
|
data:
|
|
addon: core_git_pull
|
|
- delay:
|
|
hours: 0
|
|
minutes: 0
|
|
seconds: 20
|
|
- action: notify.send_message
|
|
target:
|
|
entity_id:
|
|
- notify.ultix_sidecar_new
|
|
- notify.allthes_ipad
|
|
data:
|
|
title: PFV deploy (sha watch)
|
|
message: >-
|
|
New master HEAD {{ states('sensor.pfv_deploy_gitea_master_head')[:7] }}
|
|
pulled. Restarting HA core (webhook re-registration) - back in
|
|
~1 min.
|
|
- action: homeassistant.restart
|