Repository navigation
Expand file tree
/
Copy pathtugtainer_updates.yaml
More file actions
334 lines (328 loc) · 17.5 KB
/
Copy pathtugtainer_updates.yaml
File metadata and controls
334 lines (328 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Tugtainer Updates - Container update notifications via webhook
# Related Issue: 1561
# Receives Apprise JSON payloads from Tugtainer and posts outcome alerts.
# -------------------------------------------------------------------
# Notes: Expects JSON with title/message/type from the Tugtainer template.
# Notes: Available-only reports stay silent in HA; Updated, Failed, and Rolled-back reports remain visible.
# Notes: Fires update events independently when a report has both Available and Updated sections.
# Notes: Fires `tugtainer_home_assistant_core_updated` when `### Updated:` includes Home Assistant.
# Notes: Every Available report dispatches Joanna; include still-pending available(notified) items in the Tugtainer notification template.
# Notes: A daily 07:30 check dispatches Joanna on the appliance to verify rollout completion and protected control-plane updates.
# Notes: Home Assistant changelog dispatch uses core-YYYY.M URL format from parsed/fallback version.
# Notes: Home Assistant is auto-update eligible after its Tugtainer pre-update config check; no per-release approval is required.
# Notes: Portainer image-update entities are auto-disabled because Tugtainer/Compose own container rollouts.
# - Video: https://youtu.be/4NNOkXzUyYw
# - Blog: https://www.vcloudinfo.com/2026/07/home-assistant-tugtainer-docker-updates.html
# Notes: Changelog digest follow-up https://www.vcloudinfo.com/2026/04/joanna-home-assistant-changelog-digest-tugtainer.html
# Notes: Prior background post https://www.vcloudinfo.com/2026/02/tugtainer-docker-updates-home-assistant-notifications.html
######################################################################
input_datetime:
tugtainer_last_update:
name: "Tugtainer last update"
has_date: true
has_time: true
automation:
- alias: "Tugtainer - Disable Duplicate Portainer Image Updates"
id: tugtainer_disable_duplicate_portainer_image_updates
description: "Keep Portainer's per-container image-update entities disabled while preserving its health telemetry."
mode: queued
trigger:
- platform: event
event_type: entity_registry_updated
event_data:
action: create
- platform: homeassistant
event: start
variables:
portainer_update_entities: >-
{% set found = namespace(entities=[]) %}
{% if trigger.platform == 'event' %}
{% set candidate = trigger.event.data.entity_id | default('', true) | string %}
{% if candidate.startswith('update.') and
candidate.endswith('_image_update_available') and
config_entry_attr(config_entry_id(candidate), 'domain') == 'portainer' %}
{% set found.entities = [candidate] %}
{% endif %}
{% else %}
{% for candidate in states.update | map(attribute='entity_id') | list %}
{% if candidate.endswith('_image_update_available') and
config_entry_attr(config_entry_id(candidate), 'domain') == 'portainer' %}
{% set found.entities = found.entities + [candidate] %}
{% endif %}
{% endfor %}
{% endif %}
{{ found.entities }}
condition:
- condition: template
value_template: "{{ portainer_update_entities | count > 0 }}"
action:
- repeat:
for_each: "{{ portainer_update_entities }}"
sequence:
- service: homeassistant.disable_entity
data:
entity_id: "{{ repeat.item }}"
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Disabled {{ portainer_update_entities | count }} duplicate Portainer
image-update {{ 'entity' if portainer_update_entities | count == 1 else 'entities' }};
Tugtainer and Compose remain the rollout owners.
- alias: "Tugtainer Update Report"
id: tugtainer_update_report
description: "Receive Tugtainer reports, suppress Available-only alerts, and dispatch update work."
mode: queued
trigger:
- platform: webhook
webhook_id: !secret tugtainer_updates_webhook
allowed_methods:
- POST
local_only: true
variables:
payload: "{{ trigger.json | default({}) }}"
title: "{{ payload.title | default('Tugtainer update') }}"
message: "{{ payload.message | default('Update event received') }}"
event_type: "{{ payload.type | default('info') }}"
has_available_section: "{{ '### available:' in (message | lower) }}"
has_failed_section: "{{ '### failed:' in (message | lower) }}"
has_rolled_back_section: "{{ '### rolled-back:' in (message | lower) }}"
has_failure_section: "{{ has_failed_section or has_rolled_back_section }}"
updated_section: >-
{% set sections = message | regex_findall('(?is)###\\s*updated:\\s*(.*?)(?:\\n###\\s|$)') %}
{{ sections[0] if sections | count > 0 else '' }}
has_updated_section: "{{ updated_section | trim != '' }}"
show_persistent_notification: >-
{{ not has_available_section or has_updated_section or has_failure_section }}
ha_updated_line: >-
{% set matches = updated_section | regex_findall('(?im)^.*(?:home-assistant|ghcr\\.io/home-assistant/home-assistant).*$') %}
{{ matches[0] if matches | count > 0 else '' }}
ha_core_update_detected: "{{ has_updated_section and (ha_updated_line | trim != '') }}"
ha_version_from_updated_line: >-
{% set arrow_match = ha_updated_line | regex_findall('(?i)(?:->|→|to)\\s*v?([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{% if arrow_match | count > 0 %}
{{ arrow_match[-1] }}
{% else %}
{% set any_match = ha_updated_line | regex_findall('(?i)v?([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{{ any_match[-1] if any_match | count > 0 else '' }}
{% endif %}
ha_sensor_version: "{{ states('sensor.ha_installed_version') | string | trim }}"
ha_core_version_full: >-
{% set parsed = ha_version_from_updated_line | trim %}
{% if parsed != '' %}
{{ parsed }}
{% else %}
{% set sensor_matches = ha_sensor_version | regex_findall('([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{{ sensor_matches[0] if sensor_matches | count > 0 else '' }}
{% endif %}
ha_core_version_minor: >-
{% set minor = ha_core_version_full | trim | regex_findall('^([0-9]{4}\\.[0-9]+)') %}
{{ minor[0] if minor | count > 0 else '' }}
ha_core_changelog_url: >-
{% if ha_core_version_minor | trim != '' %}
https://www.home-assistant.io/changelogs/core-{{ ha_core_version_minor | trim }}
{% else %}
{% endif %}
ha_report_excerpt: >-
{% set excerpt = ha_updated_line | trim %}
{{ excerpt if excerpt != '' else (message | truncate(280, true)) }}
full_message: >-
{{ message }}{% if event_type %} ({{ event_type | upper }}){% endif %}
action:
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.tugtainer_last_update
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- if:
- condition: template
value_template: "{{ show_persistent_notification }}"
then:
- service: persistent_notification.create
data:
title: "{{ title }}"
message: "{{ full_message }}"
- if:
- condition: template
value_template: "{{ has_available_section }}"
then:
- event: tugtainer_available_detected
event_data:
title: "{{ title }}"
event_type: "{{ event_type }}"
message: "{{ message }}"
- if:
- condition: template
value_template: "{{ has_failure_section }}"
then:
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Tugtainer reported failed or rolled-back container updates.
Joanna dispatch requested.
- service: script.joanna_dispatch
data:
trigger_context: "HA automation tugtainer_update_report (Tugtainer Update Report)"
source: "home_assistant_automation.tugtainer_update_report"
summary: "Tugtainer reported failed or rolled-back container updates"
entity_ids:
- "input_datetime.tugtainer_last_update"
diagnostics: >-
title={{ title }},
event_type={{ event_type }},
failed_section={{ has_failed_section }},
rolled_back_section={{ has_rolled_back_section }},
message={{ message }}
request: >-
Investigate this Tugtainer failure report. Inspect the owning Docker
host, Tugtainer logs, and live container state before changing
anything. Recover failed or rolled-back services through their
Compose definitions when safe, verify application health, and
reconcile Tugtainer state. For a Home Assistant failure, use its
validated configuration, retained prior image, HTTP healthcheck,
and owning Compose definition to recover it without waiting for
per-release approval. Report what was fixed and any remaining
blockers.
- if:
- condition: template
value_template: "{{ ha_core_update_detected and (ha_core_version_minor | trim != '') }}"
then:
- event: tugtainer_home_assistant_core_updated
event_data:
title: "{{ title }}"
event_type: "{{ event_type }}"
message: "{{ message }}"
core_version_full: "{{ ha_core_version_full | trim }}"
core_version_minor: "{{ ha_core_version_minor | trim }}"
changelog_url: "{{ ha_core_changelog_url | trim }}"
report_excerpt: "{{ ha_report_excerpt | trim }}"
- alias: "Tugtainer - Dispatch Joanna For Available Updates"
id: tugtainer_dispatch_joanna_for_available_updates
description: "Dispatch appliance Joanna for Available reports and daily post-rollout verification."
mode: queued
trigger:
- platform: event
event_type: tugtainer_available_detected
id: available_report
- platform: time
at: "07:30:00"
id: post_rollout
variables:
post_rollout: "{{ trigger.id | default('available_report') == 'post_rollout' }}"
title: "{{ 'Tugtainer post-rollout verification' if post_rollout else trigger.event.data.title | default('Tugtainer update') }}"
message: >-
{{ 'Verify the completed 07:00 rollout and every still-pending protected updater component from live inventory.'
if post_rollout else trigger.event.data.message | default('Update event received') }}
event_type: "{{ 'scheduled_verification' if post_rollout else trigger.event.data.event_type | default('info') }}"
trigger_context: "HA automation tugtainer_dispatch_joanna_for_available_updates (Tugtainer - Dispatch Joanna For Available Updates)"
action:
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
{{ 'Tugtainer daily post-rollout verification' if post_rollout else 'Tugtainer reported Available container updates' }}.
Appliance Joanna dispatch requested.
- service: script.joanna_dispatch
data:
trigger_context: "{{ trigger_context }}"
source: "home_assistant_automation.tugtainer_dispatch_joanna_for_available_updates"
summary: >-
{{ 'Verify Tugtainer rollout completion and protected updater maintenance'
if post_rollout else 'Tugtainer reported container updates scheduled for automatic rollout' }}
entity_ids:
- "input_datetime.tugtainer_last_update"
diagnostics: >-
title={{ title }},
event_type={{ event_type }},
post_rollout={{ post_rollout }},
message={{ message }}
request: >-
Review the report and the complete live Tugtainer inventory, including
pending items omitted from the report because they were already announced.
Reconcile registered enabled hosts against the current environment's
live Docker hosts, including docker_13; identify missing agents or
untracked containers rather than checking registered hosts alone.
Eligible containers, including Home Assistant core, are scheduled for
Tugtainer's 07:00 automatic rollout and do not require per-release
approval. When Home Assistant is listed, confirm its pre-update config
check, HTTP healthcheck, and rollback safeguards remain configured;
do not race the scheduled rollout with an early manual recreation.
Protected Tugtainer control-plane containers cannot self-update.
Inspect the manager, every agent, and socket proxies independently of
report contents; update pending components one host at a time through
their owning Compose definitions when low-risk. Retain prior images and
verify compatible manager/agent APIs and application health afterward.
{% if post_rollout %}
This is the daily post-rollout check. Verify the actual terminal update
jobs, database readiness, backup exporters, and affected application
endpoints. Investigate every remaining pending item; distinguish an
intentional opt-out or delay from a failed, rolled-back, or missed
rollout. Healthy updates first detected after today's rollout belong
to the next scheduled window; do not treat them as missed updates.
Recover eligible failed services through their owning Compose
definitions when safe, without racing any active update job. Preserve
database major versions, volumes, local-build opt-outs, and Home
Assistant's config-check, HTTP healthcheck, and rollback gate.
{% endif %}
Refresh Tugtainer's real inventory after manual updates. If an
authenticated refresh API is unavailable, use its native check worker
after loading the complete model registry and SettingsStorage, without
resending notifications and retriggering this dispatch; do not
silently leave stale update flags. Report newly repaired failures and
verified blockers; keep unchanged healthy results internal.
- alias: "Tugtainer - Dispatch Joanna For Home Assistant Core Digest"
id: tugtainer_dispatch_joanna_for_home_assistant_core_digest
description: "Dispatch Joanna after Home Assistant core is updated so relevant changelog notes reach the morning digest."
mode: queued
trigger:
- platform: event
event_type: tugtainer_home_assistant_core_updated
variables:
report_title: "{{ trigger.event.data.title | default('Tugtainer update') }}"
report_message: "{{ trigger.event.data.message | default('Update event received') }}"
report_event_type: "{{ trigger.event.data.event_type | default('info') }}"
core_version_full: "{{ trigger.event.data.core_version_full | default('', true) | string | trim }}"
core_version_minor: "{{ trigger.event.data.core_version_minor | default('', true) | string | trim }}"
changelog_url: "{{ trigger.event.data.changelog_url | default('', true) | string | trim }}"
report_excerpt: "{{ trigger.event.data.report_excerpt | default('', true) | string | replace('\n', ' ') | replace('\r', ' ') | trim }}"
trigger_context: "HA automation tugtainer_dispatch_joanna_for_home_assistant_core_digest (Tugtainer - Dispatch Joanna For Home Assistant Core Digest)"
structured_request: |-
HA_CORE_UPDATE_DIGEST_REQUEST
core_version_full={{ core_version_full }}
core_version_minor={{ core_version_minor }}
changelog_url={{ changelog_url }}
report_title={{ report_title | string | replace('\n', ' ') | trim }}
report_event_type={{ report_event_type }}
report_excerpt={{ report_excerpt }}
condition:
- condition: template
value_template: "{{ core_version_minor != '' and changelog_url != '' }}"
action:
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Home Assistant core update detected via Tugtainer ({{ core_version_full }}).
Joanna digest dispatch requested.
- service: script.joanna_dispatch
data:
trigger_context: "{{ trigger_context }}"
source: "home_assistant_automation.tugtainer_home_assistant_core_digest"
summary: >-
Home Assistant core updated to {{ core_version_full }} via Tugtainer.
Queue a concise changelog note for the morning digest.
entity_ids:
- "input_datetime.tugtainer_last_update"
diagnostics: >-
report_title={{ report_title }},
report_event_type={{ report_event_type }},
core_version_full={{ core_version_full }},
core_version_minor={{ core_version_minor }},
changelog_url={{ changelog_url }}
request: "{{ structured_request }}"