###################################################################### # @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; Tugtainer owns report scheduling and de-duplication. # 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 Joanna for every Tugtainer Available report." mode: queued trigger: - platform: event event_type: tugtainer_available_detected variables: title: "{{ trigger.event.data.title | default('Tugtainer update') }}" message: "{{ trigger.event.data.message | default('Update event received') }}" event_type: "{{ 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 reported Available container updates. Joanna dispatch requested. - service: script.joanna_dispatch data: trigger_context: "{{ trigger_context }}" source: "home_assistant_automation.tugtainer_dispatch_joanna_for_available_updates" summary: "Tugtainer reported container updates scheduled for automatic rollout" entity_ids: - "input_datetime.tugtainer_last_update" diagnostics: >- title={{ title }}, event_type={{ event_type }}, message={{ message }} request: >- Review the Tugtainer report for containers listed under Available. 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; update those one host at a time through their owning Compose definitions when low-risk. Verify the scheduled and manual updates, reconcile Tugtainer's inventory, and report any failures or rollbacks. - 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 }}"