From 0f845033a6b5d5a24ea347cb0a17a5b7908d7d16 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:19:34 +0100 Subject: [PATCH 1/6] Telegram support --- .../EdwardTFN/auto_update_scheduled.yaml | 1857 ++++++++++------- 1 file changed, 1158 insertions(+), 699 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index a6b8f39..88fc520 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -1,3 +1,26 @@ +#################################################################################################### +##### Home Assistant Auto-update on Schedule ##### +##### Repository: https://github.com/edwardtfn/ha_auto_update_scheduled ##### +#################################################################################################### +##### Purpose: YAML blueprint to automatically update Home Assistant and its components when ##### +##### updates are available, following a schedule and with configurable options. ##### +#################################################################################################### +##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo ##### +#################################################################################################### +##### NOTE: ##### +##### - Home Assistant may restart automatically as part of the update process. ##### +##### - Use under your own risk! Please review Home Assistant community discussions about ##### +##### the risks involved in auto-updating the system. ##### +##### - SECURITY WARNING: ##### +##### - Ensure you have reliable backups before enabling automatic updates ##### +##### - Auto-updates in production environments may lead to system instability ##### +##### - Updates may affect connected devices and integrations ##### +##### - For discussions: ##### +##### https://community.home-assistant.io/t/459281 ##### +##### - For support, bug reports and new ideas: ##### +##### https://github.com/edwardtfn/ha_auto_update_scheduled/issues ##### +#################################################################################################### +--- blueprint: name: Home Assistant Auto-update on a schedule base author: Edward Firmo (https://github.com/edwardtfn) @@ -8,17 +31,27 @@ blueprint: Update Home Assistant automatically when a new update is available. - v2024.9.30.0 + ## v2025.1.0 - Attention: + ## Attention: - * Home Assistant may restart automatically as part of the update process. + - Home Assistant may restart automatically as part of the update process. - * **Use under your own risk!** Please see the discussions on Home Assistant community around the risks involved on auto-updating the system. + - **Use under your own risk!** Please see the discussions on Home Assistant community around the risks involved on auto-updating the system. - For questions and suggestions, please use [this thread in Home Assistant Community portal](https://community.home-assistant.io/t/scheduled-auto-update-for-home-assistant/459281). + - Ensure you have reliable backups before enabling automatic updates. + + - Auto-updates in production environments may lead to system instability. + + - Updates may affect connected devices and integrations. + + ## For discussions: + - [Home Assistant Community portal](https://community.home-assistant.io/t/459281) + + ## For support, bug reports and new ideas: + - [GitHub Issues](https://github.com/edwardtfn/ha_auto_update_scheduled/issues) domain: automation - source_url: https://raw.githubusercontent.com/edwardtfn/HomeAssistant-Config/main/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml + source_url: https://raw.githubusercontent.com/edwardtfn/ha_auto_update_scheduled/main/auto_update_scheduled.yaml input: when_section: name: When (Schedule, etc.) @@ -28,23 +61,30 @@ blueprint: input: schedule_entity: name: Schedule entity - description: 'You can create an [Schedule](https://www.home-assistant.io/integrations/schedule) under [Settings > Devices & Services > Helpers](https://my.home-assistant.io/redirect/config_flow_start/?domain=schedule). + description: > + You can create a [Schedule](https://www.home-assistant.io/integrations/schedule) under + [Settings > Devices & Services > Helpers](https://my.home-assistant.io/redirect/config_flow_start/?domain=schedule). - Note => The schedule windows will define when an update will start. It is possible that a backup, an update or a restart process finishes after the schedule window, but new updates won''t stars outside the schedule windows.' + Note => The schedule windows will define when an update will start. + It is possible that a backup, an update or a restart process finishes after the schedule window, + but new updates won't stars outside the schedule windows. default: [] selector: entity: multiple: false domain: schedule - schedule_monthday: - name: Earliest day in the month to update Home Assistant (optional) + schedule_monthday_earliest: + name: Earliest day in the month to run the update process description: > Usually a new major version of Home Assistant is available on the begining of every month. - Some people consider those releases as not stable enough and prefer to avoid those versions, not updating the system until the mid of the month (day 15). + Some people consider those releases as not stable enough and prefer to avoid those versions, + not updating the system until the mid of the month (day 15). - Note => If you select a day higher than 28 the updates won't run every month. - default: 1 + Notes: + - If you select a day higher than 28 the updates won't run every month. + - Selecting `0` will disable the check. + default: 0 selector: number: min: 1 @@ -52,10 +92,26 @@ blueprint: step: 1 mode: slider + schedule_monthday_last: + name: Latest day in the month to run the update process + description: > + Specify the latest day in month, on which this update can run. + + Note: + - Selecting `0` or a date lower than the "Earliest Day" will disable the check. + default: 0 + selector: + number: + min: 0 + max: 31 + step: 1 + mode: slider + update_out_of_schedule: name: Out of schedule entities (optional) description: - You can select update entities which will be updated as soon an update is detected, even if outside the schedule windows. + You can select update entities which will be updated as soon an update is detected, + even if outside the schedule windows. default: [] selector: entity: @@ -65,9 +121,12 @@ blueprint: pause_entities: name: Pause update entities (optional) - description: 'You can select one or more entities to pause the updates. If any of the selected entities is "On" or "True" the system won''t be updated on the schedule time. + description: > + You can select one or more entities to pause the updates. + If any of the selected entities is "On" or "True" the system won't be updated on the schedule time. - You can use this to hold your updates when you have a party at home, or when you are on vacations and don''t want to be concerned about updates on Home Assistant.' + You can use this to hold your updates when you have a party at home, + or when you are on vacations and don't want to be concerned about updates on Home Assistant. default: [] selector: entity: @@ -142,6 +201,61 @@ blueprint: default: "all" selector: *update_mode-selector + update_inclusion_mode: + name: Entity Inclusion Mode + description: >- + Please select the entity inclusion mode. + * All: + Just build the update entity list from all available update.* entities, which are in state 'on'. + * Specified/Specified-Single: + Build the update entity list from the specified entity list, filtering for entities in 'on' state. + - Specified: Updates all matching entities + - Specified-Single: Updates only the first matching entity + * Searchfilter: + This works as a wildcard filter. + Examples: + 'update.zigbee2mqtt_' => all update entities from zigbee2mqtt addon + 'update.esphome_' => all update entities from esphome devices if their names start with 'esphome_' + * Searchfilter-Single: + Just like the searchfilter, but only the first entity. (Sorted alphabetically) + default: "all" + selector: + select: + multiple: false + options: + - label: All + value: "all" + - label: Specified + value: "specified" + - label: Specified-Single + value: "specified-single" + - label: Searchfilter + value: "searchfilter" + - label: Searchfilter-Single + value: "searchfilter-single" + + update_inclusion_entity_list: + name: Entity Inclusion List + description: >- + => Important: This entity list will only be used when the Update Inclusion mode is set to "Specified" or "Specified-Single". + Select the items which should be updated. + default: [] + selector: + entity: + multiple: true + domain: update + + update_inclusion_entity_searchfilter: + name: Entity Inclusion Searchfilter + description: >- + => Important: This searchfilter will only be used when the Update Inclusion mode is set to "Searchstring". + This defines the Searchfilter for finding the right update entities. + This enables wildcard-like searching for a group of entities, aka "all zigbee entities" or "everything from Sonoff" etc. + default: "" + selector: + text: + multiline: false + update_exclusions: name: Exclusions (optional) description: 'Select the items that should NOT be included on the automated updates. @@ -165,20 +279,21 @@ blueprint: default: true selector: boolean: - backup_wait_time: - name: Backup wait time - description: - Select how long the system should wait for the backup to be completed after it is triggered. - Please make sure the schedule covers the waiting time, otherwise the update will never be triggered after a backup. + backup_timeout: + name: Backup Timeout + description: >- + Specify how much time the automation waits for the backup process. + We can't check wether the backup was successful. + Per default we wait 1 hour to finish the backup. default: 60 selector: number: - min: 0 - max: 3600 - step: 10 mode: box + min: 1 + max: 600 unit_of_measurement: minutes + max_backup_age: name: Maximum backup age (or zero, to disable) (optional) description: @@ -196,6 +311,16 @@ blueprint: duration: enable_day: true + backup_location: + name: Backup Location + description: >- + You can create a new backup location under [Settings > System > Storage](https://my.home-assistant.io/redirect/storage/). + + Specify where to store the backup for this autoupdate process. + default: "/backup" + selector: + backup_location: + actions_section: name: Actions (optional) icon: mdi:shoe-print @@ -251,6 +376,95 @@ blueprint: selector: action: {} + telegram_section: + name: Telegram notifications (optional) + icon: mdi:bell + description: Here you can setup an optional notification to Telegram + collapsed: true + input: + notification_telegram_enable: + name: Enable telegram notifications + default: false + selector: + boolean: + + notification_telegram_target_id: + name: Number of Telegram Target (Chat or Group ID) + description: > + This specifies the Telegram Target (Chat or group ID) aka the target to send the message to. + + Example: -1111111111111 + default: "" + selector: + text: + + notification_telegram_disable_notification: + name: Disable the Telegram notification from the Telegram client. + description: > + This switches to a 'silent' message without a notification on other devices with a running telegram client. + default: false + selector: + boolean: + + notification_telegram_message_title: + name: Telegram notification title + description: | + `Optional` + + Please select a title to be used on each Telegram notification message. + + You can type a custom text. + default: + - friendly_name + selector: + select: + custom_value: true + multiple: false + options: + - label: Automation's friendly name + value: friendly_name + - label: Automation's entity Id + value: entity_id + - label: Blank (no title) + value: blank + + notification_telegram_select_notifications: + name: Select what to notify via Telegram + description: | + Select which steps of the backup should be notified via Telegram. + default: + - starting + - list_of_updates + - pre_update_actions + - backup + - update_progress + - remaining_updates + - restart + - post_update_actions + - done + selector: + select: + multiple: true + options: + - label: (Re-)Starting message + value: starting + - label: List of updates + value: list_of_updates + - label: Pre-update actions + value: pre_update_actions + - label: Backup + value: backup + - label: Update progress + value: update_progress + - label: Remaining updates + value: remaining_updates + - label: Restart + value: restart + - label: Post-update actions + value: post_update_actions + - label: Done message + value: done + general_settings: name: General settings (optional) icon: mdi:cog @@ -259,7 +473,10 @@ blueprint: input: restart_type: name: Force Home Assistant to restart if required by any update? - description: This won't affect updates where a restart is automatic, but for updates requiring a manual restart (quite common on HACS) this blueprint can automatically force a restart by the end of the updates. + description: > + This won't affect updates where a restart is automatic, + but for updates requiring a manual restart (quite common on HACS) + this blueprint can automatically force a restart by the end of the updates. default: "no-restart" selector: select: @@ -271,30 +488,69 @@ blueprint: value: "core" - label: "Restart Home Assistant host" value: "host" + verbose_logging_bool: name: Log addon progress with verbosity to the logbook? (optional) default: false selector: boolean: {} + update_process_started_entity: + name: '"Update Process Started" Toggle Entity (optional)' + description: > + You can create a [Toggle Helper](https://www.home-assistant.io/integrations/input_boolean) under + [Settings > Devices & Services > Helpers](https://my.home-assistant.io/redirect/config_flow_start/?domain=input_boolean). + + This entity will be used to determine if we still have to do a backup + on automation start - so it is for inner workings of this automation. + + This saves you from getting multiple backups in the same update window. + default: [] + selector: + entity: + multiple: false + domain: input_boolean + + update_timeout: + name: Update Timeout + description: | + Maximum time to wait for each update to complete. + + This protects the automation of waiting forever for an update that fails. + + Usually updates don't take that very long, + so feel free to ajust this time to something that fits better your environment. + + The default is 600 seconds (10 minutes). + You can select any value between 10 seconds and 7200 seconds (2 hours). + + If an update times out: + - The automation will log an error message + - The automation will continue with the next update + - The timed-out update may still complete in the background + default: 600 + selector: + number: + min: 10 + max: 7200 + step: 10 + unit_of_measurement: seconds + mode: box + mode: single max_exceeded: warning trigger_variables: + input_pause_entities: !input pause_entities input_update_exclusions: !input update_exclusions input_update_out_of_schedule: !input update_out_of_schedule - updates_available: > - {% set updates_available_obj = - states.update - | default([]) - | selectattr("state", "eq", "on") - | rejectattr("entity_id", "in", input_update_exclusions) - %} - {{ - updates_available_obj | map(attributes="entity_id") | list - if (updates_available_obj | list | count | int(0) > 0) - else [] - }} + schedule_monthday_earliest: !input schedule_monthday_earliest + schedule_monthday_last: !input schedule_monthday_last + input_schedule_monthday_earliest: '{{ schedule_monthday_earliest | int(0) }}' + input_schedule_monthday_last: '{{ schedule_monthday_last | int(0) }}' + input_update_inclusion_entity_list: !input update_inclusion_entity_list + input_update_inclusion_entity_searchfilter: !input update_inclusion_entity_searchfilter + input_update_process_started_entity: !input update_process_started_entity trigger: - id: HA Schedule based @@ -314,8 +570,16 @@ trigger: | list | count | int(0) > 0 }} - id: New day - platform: time - at: "00:00:01" + platform: template + value_template: > + {{ + (now().day >= input_schedule_monthday_earliest or input_schedule_monthday_earliest == 0) and + ( + now().day <= input_schedule_monthday_last or + input_schedule_monthday_last == 0 or + input_schedule_monthday_last < input_schedule_monthday_earliest + ) + }} - id: Automations reloaded platform: event event_type: automation_reloaded @@ -324,36 +588,37 @@ trigger: entity_id: !input pause_entities condition: - - condition: template - value_template: >- - {{ - states.update - | default([]) - | selectattr("state", "eq", "on") - | rejectattr("entity_id", "in", input_update_exclusions) - | list | count | int(0) > 0 - }} - - condition: or conditions: - condition: and conditions: - - condition: template - value_template: > - {{ now().day >= ((input_schedule_monthday) | int(0)) }} - condition: state entity_id: !input schedule_entity + state: 'on' + - condition: template + value_template: >- + {{ + states.update + | default([]) + | selectattr("state", "eq", "on") + | rejectattr("entity_id", "in", input_update_exclusions) + | list | count | int(0) > 0 + }} + - condition: and + conditions: + - condition: trigger + id: "HA Start" + - '{{ input_update_process_started_entity | default([]) | count == 1 }}' + - condition: state + entity_id: !input update_process_started_entity state: "on" - - condition: template - value_template: > - {% set ns = namespace(found=false) %} - {% for item in input_update_out_of_schedule %} - {% if item in updates_available %} - {% set ns.found = true %} - {% break %} - {% endif %} - {% endfor %} - {{ ns.found }} + + - condition: template + value_template: > + {{ + (now().day >= input_schedule_monthday_earliest or input_schedule_monthday_earliest == 0) and + (now().day <= input_schedule_monthday_last or input_schedule_monthday_last == 0) + }} - condition: or conditions: @@ -363,705 +628,899 @@ condition: state: 'off' variables: - input_schedule_monthday: !input schedule_monthday - input_pause_entities: !input pause_entities - temp_input_update_exclusions: !input update_exclusions input_verbose_logging_bool: !input verbose_logging_bool - input_backup_bool: !input backup_bool - input_restart_type: !input restart_type - - temp_max_backup_age: !input max_backup_age - input_max_backup_age_seconds: > - {{ - timedelta( - days=temp_max_backup_age.days, - hours=temp_max_backup_age.hours, - minutes=temp_max_backup_age.minutes, - seconds=temp_max_backup_age.seconds - ).total_seconds() - }} - input_core_os_update_mode: !input core_os_update_mode input_firmware_update_mode: !input firmware_update_mode input_general_update_mode: !input general_update_mode - updates_list: [] - pending_updates_list: [] - + input_update_inclusion_mode: !input update_inclusion_mode + input_notification_telegram_enable: !input notification_telegram_enable + input_notification_telegram_target_id: !input notification_telegram_target_id + input_notification_telegram_disable_notification: !input notification_telegram_disable_notification + input_notification_telegram_select_notifications: !input notification_telegram_select_notifications + input_notification_telegram_message_title: !input notification_telegram_message_title + core_update_entity: > + {% for u in integration_entities('hassio') | select('search', '^update[.]') + if (device_attr(u, 'identifiers') | first)[1] == 'core' %} + {{ u }} + {% endfor %} + os_update_entity: > + {% for u in integration_entities('hassio') | select('search', '^update[.]') + if (device_attr(u, 'identifiers') | first)[1] == 'OS' %} + {{ u }} + {% endfor %} + supervisor_update_entity: > + {% for u in integration_entities('hassio') | select('search', '^update[.]') + if (device_attr(u, 'identifiers') | first)[1] == 'supervisor' %} + {{ u }} + {% endfor %} + is_continuing_from_a_restart: > + {{ + input_update_process_started_entity | default([]) | count == 1 and + is_state(input_update_process_started_entity | first, "on") + }} + friendly_name: | + {% set friendly_name_tmp = state_attr(this.entity_id, "friendly_name") | default("") %} + {{ + friendly_name_tmp + if friendly_name_tmp is string and friendly_name_tmp | length > 0 + else "Auto-update" + }} + telegram_title: | + {% if (input_notification_telegram_message_title == "friendly_name") %} + {{ friendly_name }} + {% elif (input_notification_telegram_message_title == "entity_id") %} + {{ this.entity_id }} + {% elif (input_notification_telegram_message_title != "blank" and + input_notification_telegram_message_title is string and + input_notification_telegram_message_title | length > 0) %} + {{ input_notification_telegram_message_title }} + {% else %} + {{ "" }} + {% endif %} action: - ########## Refresh update list ########## + - alias: Refresh update entities + action: homeassistant.update_entity + data: + entity_id: | + {{ + states.update + | default([]) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + continue_on_error: true + - &recalc_update_list variables: - core_update_entity: > - {% for u in integration_entities('hassio') | select('search', '^update[.]') - if (device_attr(u, 'identifiers') | first)[1] == 'core' %} - {{ u }} - {% endfor %} - os_update_entity: > - {% for u in integration_entities('hassio') | select('search', '^update[.]') - if (device_attr(u, 'identifiers') | first)[1] == 'OS' %} - {{ u }} - {% endfor %} - supervisor_update_entity: > - {% for u in integration_entities('hassio') | select('search', '^update[.]') - if (device_attr(u, 'identifiers') | first)[1] == 'supervisor' %} - {{ u }} - {% endfor %} firmware_update_entities: > - {{ states.update - | default([]) - | selectattr('attributes.device_class', 'defined') - | selectattr('attributes.device_class', 'eq', 'firmware') - | map(attribute='entity_id') - | list - }} + {{ + states.update + | default([]) + | selectattr("state", "eq", "on") + | selectattr('attributes.device_class', 'defined') + | selectattr('attributes.device_class', 'eq', 'firmware') + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} general_update_entities: > - {{ states.update + {% if input_update_inclusion_mode == "all" %} + {{ + states.update + | default([]) + | selectattr('state', 'eq', 'on') + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', supervisor_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + {% elif input_update_inclusion_mode == "specified" %} + {{ + states.update + | default([]) + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', input_update_inclusion_entity_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', supervisor_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + {% elif input_update_inclusion_mode == "specified-single" %} + {{ + states.update + | default([]) + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', input_update_inclusion_entity_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', supervisor_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + | first + }} + {% elif input_update_inclusion_mode == "searchstring" %} + {{ + states.update + | default([]) + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'match', input_update_inclusion_entity_searchfilter) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', supervisor_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + {% elif input_update_inclusion_mode == "searchfilter-single" %} + {{ + states.update + | default([]) + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'match', input_update_inclusion_entity_searchfilter) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', supervisor_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + | first + }} + {% endif %} + combined_list: | + {{ + ( + firmware_update_entities | default([]) | list + if firmware_update_entities is sequence + else [firmware_update_entities | string] if firmware_update_entities is not none else [] + ) + + ( + general_update_entities | default([]) | list + if general_update_entities is sequence + else [general_update_entities | string] if general_update_entities is not none else [] + ) + + ( + core_update_entity | default([]) | list + if core_update_entity is sequence + else [core_update_entity | string] if core_update_entity is not none else [] + ) + + ( + os_update_entity | default([]) | list + if os_update_entity is sequence + else [os_update_entity | string] if os_update_entity is not none else [] + ) + + ( + supervisor_update_entity | default([]) | list + if supervisor_update_entity is sequence + else [supervisor_update_entity | string] if supervisor_update_entity is not none else [] + ) + }} + pending_update_list: | + {{ + states.update | default([]) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', supervisor_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) + | selectattr("state", "eq", "on") + | selectattr('entity_id', 'in', combined_list) + | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list }} + pending_update_count: '{{ pending_update_list | list | count | int(0) }}' - input_update_exclusions: > - {% set exclusions = temp_input_update_exclusions | default([]) %} - - {% if input_core_os_update_mode == 'ignore' %} - {% set exclusions = exclusions + [core_update_entity | default(""), os_update_entity | default("")] %} - {% else %} - {% set core_installed_version = states.update | default([]) | selectattr('entity_id', 'in', core_update_entity | default("")) | map(attribute='attributes.installed_version') | list | first | default('') %} - {% set core_latest_version = states.update | default([]) | selectattr('entity_id', 'in', core_update_entity | default("")) | map(attribute='attributes.latest_version') | list | first | default('') %} - {% set core_version_change = version(core_latest_version) - version(core_installed_version) %} - {% set os_installed_version = states.update | default([]) | selectattr('entity_id', 'in', os_update_entity | default("")) | map(attribute='attributes.installed_version') | list | first | default('') %} - {% set os_latest_version = states.update | default([]) | selectattr('entity_id', 'in', os_update_entity | default("")) | map(attribute='attributes.latest_version') | list | first | default('') %} - {% set os_version_change = version(os_latest_version) - version(os_installed_version) %} - - {% if input_core_os_update_mode == 'patches' %} - {% if core_version_change.major or core_version_change.minor or (not core_version_change.patch) %} - {% set exclusions = exclusions + [core_update_entity | default("")] %} - {% endif %} - {% if os_version_change.major or os_version_change.minor or (not os_version_change.patch) %} - {% set exclusions = exclusions + [os_update_entity | default("")] %} - {% endif %} - {% elif input_core_os_update_mode == 'major_and_minor' %} - {% if (not core_version_change.major) and (not core_version_change.minor) %} - {% set exclusions = exclusions + [core_update_entity | default("")] %} - {% endif %} - {% if (not os_version_change.major) and (not os_version_change.minor) %} - {% set exclusions = exclusions + [os_update_entity | default("")] %} - {% endif %} - {% endif %} - {% endif %} - - {% if input_firmware_update_mode == 'ignore' %} - {% set exclusions = exclusions + [ firmware_update_entities ] %} - {% else %} - {% set ns = namespace(entities = []) %} - {% for entity in expand(firmware_update_entities) | rejectattr('entity_id', 'in', exclusions) | selectattr('state', 'eq', 'on') %} - {% set entity_version_change = version(entity.attributes.latest_version | default('')) - version(entity.attributes.installed_version | default('')) %} - {% if input_firmware_update_mode == 'patches' %} - {% if entity_version_change.major or entity_version_change.minor or (not entity_version_change.patch) %} - {% set ns.entities = ns.entities + [ entity.entity_id ] %} - {% endif %} - {% elif input_firmware_update_mode == 'major_and_minor' %} - {% if (not entity_version_change.major) and (not entity_version_change.minor) %} - {% set ns.entities = ns.entities + [ entity.entity_id ] %} - {% endif %} - {% endif %} - {% endfor %} - {% set exclusions = exclusions + [ ns.entities ] %} - {% endif %} - - {% if input_general_update_mode == 'ignore' %} - {% set exclusions = exclusions + [ general_update_entities ] %} - {% else %} - {% set ns = namespace(entities = []) %} - {% for entity in expand(general_update_entities) | rejectattr('entity_id', 'in', exclusions) | selectattr('state', 'eq', 'on') %} - {% set entity_version_change = version(entity.attributes.latest_version | default('')) - version(entity.attributes.installed_version | default('')) %} - {% if input_general_update_mode == 'patches' %} - {% if entity_version_change.major or entity_version_change.minor or (not entity_version_change.patch) %} - {% set ns.entities = ns.entities + [ entity.entity_id ] %} - {% endif %} - {% elif input_general_update_mode == 'major_and_minor' %} - {% if (not entity_version_change.major) and (not entity_version_change.minor) %} - {% set ns.entities = ns.entities + [ entity.entity_id ] %} - {% endif %} - {% endif %} - {% endfor %} - {% set exclusions = exclusions + [ ns.entities ] %} - {% endif %} - - {{ exclusions }} - - pending_updates_list: > - {{ states.update - | default([]) - | selectattr('state','eq','on') - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list - }} - - - alias: "Refresh update list - update entities" - action: homeassistant.update_entity - data: {} - target: - entity_id: "{{ states.update | map(attribute='entity_id') | list }}" - continue_on_error: true - variables: - updates_list: '{{ pending_updates_list }}' + update_list: '{{ pending_update_list }}' + updates_list: '{{ pending_update_list }}' + is_there_anything_to_update: '{{ pending_update_count > 0 }}' - ########## Check for skipping core/OS ########## - - alias: "Re-check conditions" - continue_on_error: true - if: > - {{ - ( - states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', input_update_exclusions) - | list - | count - | int(0) - ) < 1 - }} - then: - - stop: Nothing to update - ########## Starting ########## - - &logbook-variables - if: "{{ input_verbose_logging_bool }}" - then: - - alias: Logbook - variables + - alias: Preparation # Inform logbook and telegram which update automation is running + sequence: + - variables: + log_message: '{{ friendly_name }} is {{ "re" if is_continuing_from_a_restart else ""}}starting' + - &logbook_update + if: "{{ input_verbose_logging_bool }}" + then: + - alias: Logbook - Update + action: logbook.log + data: + name: Auto-update + entity_id: '{{ this.entity_id }}' + message: '{{ log_message }}' + continue_on_error: true continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: >- - Variables: - input_backup_bool: {{ input_backup_bool }}, - input_restart_type: {{ input_restart_type }}, - input_verbose_logging_bool: {{ input_verbose_logging_bool }}, - input_max_backup_age_seconds: {{ input_max_backup_age_seconds }}, - input_schedule_monthday: {{ input_schedule_monthday }}, - input_update_exclusions: {{ input_update_exclusions }}, - input_core_os_update_mode: {{ input_core_os_update_mode }}, - input_firmware_update_mode: {{ input_firmware_update_mode }}, - input_general_update_mode: {{ input_general_update_mode }}, - core_update_entity: {{ core_update_entity }}, - os_update_entity: {{ os_update_entity }}, - supervisor_update_entity: {{ supervisor_update_entity }}, - firmware_update_entities: {{ firmware_update_entities }}, - general_update_entities: {{ general_update_entities }}, - updates_list: {{ updates_list }} - continue_on_error: true + - if: '{{ "starting" in input_notification_telegram_select_notifications }}' + then: + - &send_telegram_message + alias: Send telegram message + if: + - condition: template + value_template: '{{ input_notification_telegram_enable }}' + alias: Check if telegram notifications are enabled + then: + - alias: Telegram bot - Send message + action: telegram_bot.send_message + data: + title: '{{ telegram_title }}' + target: "{{ input_notification_telegram_target_id }}" + disable_notification: '{{ input_notification_telegram_disable_notification }}' + message: '{{ log_message }}' - - if: "{{ input_verbose_logging_bool }}" + - alias: Report list of updates + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' then: - - alias: "Logbook - A new update is available" - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - domain: update - message: A new update is available for Home Assistant. - - alias: "Logbook - List of updates" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - domain: update - message: > + - variables: + log_message: > List of updates: - - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='name') | list | join(' + - `{{ states.update + | selectattr('state', 'eq', 'on') + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='name') | list | join('` - - ') }} - - alias: "Logbook: Running pre-update actions" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Running pre-update actions... - continue_on_error: true + - `') }}` + - *logbook_update + - if: '{{ "list_of_updates" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + else: + - variables: + log_message: "There's nothing to update" + - *logbook_update + - if: '{{ "list_of_updates" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message - - alias: "Run pre-update actions" - continue_on_error: true - choose: - - conditions: - - '{{ true }}' - sequence: !input actions_pre_update - default: [] + - alias: Set ON helper flag + if: + - '{{ input_update_process_started_entity | default([]) | count > 0 }}' + - '{{ input_update_process_started_entity[0] | string | length > 0 }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' + then: + - action: input_boolean.turn_on + target: + entity_id: !input update_process_started_entity - ########## Backup ########## - - alias: Check existing backups uploaded - continue_on_error: true + - alias: Pre-update actions if: - - "{{ input_max_backup_age_seconds > 0 }}" + - '{{ is_there_anything_to_update }}' + - '{{ not is_continuing_from_a_restart }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' then: - variables: - last_backup_timestamp_list: > - {{ - states.sensor - | selectattr("attributes.last_backup", "defined") - | map(attribute="attributes.last_backup") - | list - }} - last_backup_timestamp: '{{ last_backup_timestamp_list | max if last_backup_timestamp_list | count > 0 else None }}' - - alias: Check if backup state is defined - if: '{{ last_backup_timestamp != None }}' + input_actions_pre_update: !input actions_pre_update + - condition: '{{ input_actions_pre_update is sequence }}' + - *recalc_update_list + - variables: + log_message: "Running pre-update actions..." + - *logbook_update + - if: '{{ "pre_update_actions" in input_notification_telegram_select_notifications }}' then: - - stop: "Backup State sensor not found" - - alias: Check age of last uploaded backup - if: >- - {{ as_timestamp(now()) - as_timestamp(last_backup_timestamp) > input_max_backup_age_seconds }} + - *send_telegram_message + + - alias: "Run pre-update actions" + continue_on_error: true + sequence: !input actions_pre_update + + - variables: + log_message: "Pre-update actions completed" + - *logbook_update + - if: '{{ "pre_update_actions" in input_notification_telegram_select_notifications }}' then: - - stop: "Last uploaded backup is too old" - - alias: "Backup" - continue_on_error: true + - *send_telegram_message + + - alias: Backup if: - - "{{ input_backup_bool }}" + - '{{ is_there_anything_to_update }}' + - '{{ not is_continuing_from_a_restart }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' then: - - if: "{{ input_verbose_logging_bool }}" + - *recalc_update_list + - variables: + input_backup_bool: !input backup_bool + input_backup_timeout_minutes: !input backup_timeout + input_backup_timeout: '{{ input_backup_timeout_minutes | int(60) }}' + temp_max_backup_age: !input max_backup_age + input_max_backup_age_seconds: > + {{ + timedelta( + days=temp_max_backup_age.days, + hours=temp_max_backup_age.hours, + minutes=temp_max_backup_age.minutes, + seconds=temp_max_backup_age.seconds + ).total_seconds() + }} + - alias: Check existing backups uploaded + continue_on_error: true + if: + - '{{ not is_state("sensor.backup_state", "unknown") }}' + - '{{ not is_state("sensor.backup_state", "unavailable") }}' + - '{{ input_max_backup_age_seconds > 0 }}' then: - - alias: "Logbook - Backing up" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Backing up Home Assistant. - - alias: "Call backup service" + - variables: + last_backup_timestamp_list: > + {{ + states.sensor + | selectattr("attributes.last_backup", "defined") + | map(attribute="attributes.last_backup") + | list + }} + last_backup_timestamp: '{{ last_backup_timestamp_list | max if last_backup_timestamp_list | count > 0 else None }}' + - alias: Check if backup state is defined + if: '{{ last_backup_timestamp != None }}' + then: + - alias: Check age of last uploaded backup + if: >- + {{ as_timestamp(now()) - as_timestamp(last_backup_timestamp) > input_max_backup_age_seconds }} + then: + - variables: + log_message: "Last uploaded backup is too old" + - *logbook_update + - if: '{{ "backup" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + - stop: "Last uploaded backup is too old" + else: + - variables: + log_message: "Backup State sensor not found" + - *logbook_update + - if: '{{ "backup" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + - stop: "Backup State sensor not found" + - alias: "Backup" continue_on_error: true - action: hassio.backup_full - data: - compressed: true - - if: "{{ input_verbose_logging_bool }}" + if: + - '{{ input_backup_bool }}' + - condition: state + entity_id: !input update_process_started_entity + state: 'off' then: - - alias: "Backup triggered" + - action: input_boolean.turn_on + target: + entity_id: "{{ input_update_process_started_entity }}" continue_on_error: true - action: logbook.log + - variables: + log_message: "Backing up Home Assistant" + - *logbook_update + - if: '{{ "backup" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Call backup service" + action: hassio.backup_full data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Backup triggered. + compressed: true + continue_on_error: true + - variables: - backup_wait_time: !input backup_wait_time - backup_wait_time_seconds: '{{ (backup_wait_time | int(60)) * 60 }}' - - if: '{{ backup_wait_time_seconds > 0 }}' + log_message: > + Backup triggered + + Waiting {{ input_backup_timeout | int(60) }} minutes + - *logbook_update + - if: '{{ "backup" in input_notification_telegram_select_notifications }}' then: - - alias: "Give selected wait time for the backup" # There's no sensor for when the backup finishes - delay: '{{ backup_wait_time_seconds }}' - else: [] - - ########## Update add-ons (Standard) ########## - - *recalc_update_list - - *logbook-variables - - alias: "Update - Standard" - continue_on_error: true - repeat: - while: - - "{{ input_general_update_mode in ['patches', 'major_and_minor', 'all'] }}" - - condition: state - entity_id: !input schedule_entity - state: "on" - - condition: template - value_template: > - {{ ( states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list | count | int(0) ) > 0 - }} - sequence: - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Standard - Logbook - Starting" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Starting sequence of standard updates..." - - variables: - pending_update_list: > - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list + - *send_telegram_message + + - alias: "Wait for the backup" # There's no sensor for when the backup finishes + delay: + minutes: "{{ input_backup_timeout | int(60) }}" + + - variables: + log_message: > + Backup Wait time finished. + + Continuing... + - *logbook_update + - if: '{{ "backup" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: Update generic + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' + then: + - *recalc_update_list + - variables: + log_message: "Update generic items" + - *logbook_update + - alias: "Update - Generic" + continue_on_error: true + repeat: + while: + - "{{ input_general_update_mode in ['patches', 'major_and_minor', 'all'] }}" + - condition: state + entity_id: !input schedule_entity + state: "on" + - condition: template + value_template: > + {{ ( states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list | count | int(0) ) > 0 }} - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Standard - Logbook - Updating" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Updating {{pending_update_list[0]}} of {{pending_update_list}} ..." - - alias: "Update - Standard - Install" - continue_on_error: true - action: update.install - data: {} - target: - entity_id: '{{ pending_update_list[0] }}' - - alias: "Update - Standard - Wait" - continue_on_error: true - wait_template: "{{ is_state(pending_update_list[0], 'off') }}" - continue_on_timeout: true - timeout: '3600' - - ########## Update core items ########## - - *recalc_update_list - - *logbook-variables - - alias: "Update - Core" - continue_on_error: true - repeat: - while: - - "{{ input_core_os_update_mode in ['patches', 'major_and_minor', 'all'] }}" - - condition: state - entity_id: !input schedule_entity - state: "on" - - condition: template - value_template: > - {{ ( states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | selectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list | count | int(0) ) > 0 - }} - sequence: - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Core - Logbook - Starting" + sequence: + - variables: + log_message: "Starting sequence of standard updates..." + - *logbook_update + - variables: + pending_update_list: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + + - &log_updating + alias: Log updating + sequence: + - variables: + log_message: "Updating `{{pending_update_list[0]}}`..." + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - &update_install + alias: "Update - Install" continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Starting sequence of core items updates..." - - variables: - pending_update_list: > - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | selectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list + action: update.install + data: {} + target: + entity_id: '{{ pending_update_list[0] }}' + + - &update_wait + alias: "Update - Wait" + sequence: + - alias: Wait + continue_on_error: true + wait_template: "{{ is_state(pending_update_list[0], 'off') }}" + continue_on_timeout: true + timeout: !input update_timeout + - if: '{{ wait.completed }}' + then: + - variables: + log_message: '`{{ pending_update_list[0] }}` updated successfuly' + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + else: + - variables: + log_message: 'ERROR: `{{ pending_update_list[0] }}` update timed out' + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: Devices firmware + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' + then: + - *recalc_update_list + - variables: + log_message: "Device's firmware" + - *logbook_update + + - alias: "Update - Devices firmware" + continue_on_error: true + repeat: + while: + - "{{ input_firmware_update_mode in ['patches', 'major_and_minor', 'all'] }}" + - condition: state + entity_id: !input schedule_entity + state: "on" + - condition: template + value_template: > + {{ ( states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | selectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list | count | int(0) ) > 0 }} - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Core - Logbook - Updating" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Updating {{pending_update_list[0]}} of {{pending_update_list}} ..." - - alias: "Update - Core - Install" - continue_on_error: true - action: update.install - data: {} - target: - entity_id: '{{ pending_update_list[0] }}' - - alias: "Update - Core - Wait" - continue_on_error: true - wait_template: "{{ is_state(pending_update_list[0], 'off') }}" - continue_on_timeout: true - timeout: '3600' - - ########## Update OS ########## - - *recalc_update_list - - *logbook-variables - - alias: "Update - OS" - continue_on_error: true - repeat: - while: - - "{{ input_core_os_update_mode in ['patches', 'major_and_minor', 'all'] }}" - - condition: state - entity_id: !input schedule_entity - state: "on" - - condition: template - value_template: > - {{ ( states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | selectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list | count | int(0) ) > 0 - }} - sequence: - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - OS - Logbook - Starting" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Starting sequence of OS update..." - - variables: - pending_update_list: > - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | selectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list + sequence: + - variables: + log_message: "Starting sequence of Devices firmware update" + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - variables: + pending_update_list: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | selectattr('entity_id', 'in', firmware_update_entities) + | map(attribute='entity_id') + | list + }} + + - *log_updating + - *update_install + - *update_wait + + - alias: Update core + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' + then: + - *recalc_update_list + - variables: + log_message: "Update Core" + - *logbook_update + - alias: "Update - Core" + continue_on_error: true + repeat: + while: + - "{{ input_core_os_update_mode in ['patches', 'major_and_minor', 'all'] }}" + - condition: state + entity_id: !input schedule_entity + state: "on" + - condition: template + value_template: > + {{ ( states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list | count | int(0) ) > 0 }} - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - OS - Logbook - Updating" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Updating {{pending_update_list[0]}} of {{pending_update_list}} ..." - - alias: "Update - OS - Install" - continue_on_error: true - action: update.install - data: {} - target: - entity_id: '{{ pending_update_list[0] }}' - - alias: "Update - OS - Wait" - continue_on_error: true - wait_template: "{{ is_state(pending_update_list[0], 'off') }}" - continue_on_timeout: true - timeout: '3600' - - ########## Devices firmware ########## - - *recalc_update_list - - alias: "Update - Devices firmware" - continue_on_error: true - repeat: - while: - - "{{ input_firmware_update_mode in ['patches', 'major_and_minor', 'all'] }}" - - condition: state - entity_id: !input schedule_entity - state: "on" - - condition: template - value_template: > - {{ ( states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | selectattr('entity_id', 'in', firmware_update_entities) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list | count | int(0) ) > 0 - }} - sequence: - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Devices firmware - Logbook - Starting" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Starting sequence of Devices firmware update..." - - variables: - pending_update_list: > - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | selectattr('entity_id', 'in', firmware_update_entities) - | map(attribute='entity_id') - | list + sequence: + - variables: + log_message: "Starting sequence of core items updates..." + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - variables: + pending_update_list: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', core_update_entity) + | rejectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + + - *log_updating + - *update_install + - *update_wait + + - alias: Update OS + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' + then: + - *recalc_update_list + - variables: + log_message: "Update OS" + - *logbook_update + - alias: "Update - OS" + continue_on_error: true + repeat: + while: + - "{{ input_core_os_update_mode in ['patches', 'major_and_minor', 'all'] }}" + - condition: state + entity_id: !input schedule_entity + state: "on" + - condition: template + value_template: > + {{ ( states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | selectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list | count | int(0) ) > 0 }} - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Update - Devices firmware - Logbook - Updating" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: "Updating {{pending_update_list[0]}} of {{pending_update_list}} ..." - - alias: "Update - Devices firmware - Install" - continue_on_error: true - action: update.install - data: {} - target: - entity_id: '{{ pending_update_list[0] }}' - - alias: "Update - Devices firmware - Wait" - continue_on_error: true - wait_template: "{{ is_state(pending_update_list[0], 'off') }}" - continue_on_timeout: true - timeout: '3600' - - ########## Finishing ########## - - *recalc_update_list - - *logbook-variables - - if: "{{ input_verbose_logging_bool }}" + sequence: + - variables: + log_message: "Starting sequence of OS update" + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - variables: + pending_update_list: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', core_update_entity) + | selectattr('entity_id', 'in', os_update_entity) + | rejectattr('entity_id', 'in', firmware_update_entities) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + + - *log_updating + - *update_install + - *update_wait + + - alias: Update - Remaining # Update all remaining items => this chaches up if some update item was left behind + continue_on_error: true + if: + - '{{ is_there_anything_to_update }}' + - condition: state + entity_id: !input schedule_entity + state: 'on' then: - - alias: "Logbook - Updating" + - *recalc_update_list + - variables: + log_message: "Updating all remaining items (if any)" + - *logbook_update + - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Update - Remaining - Install" continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Finishing update process. - - alias: "Logbook - Remaining updates" + service: update.install + data: {} + target: + entity_id: >- + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + - alias: "Update - Remaining - Wait" continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: > - Remaining updates: - - {{ states.update - | selectattr('state','eq','on') - | selectattr('entity_id', 'in', pending_updates_list) + wait_template: >- + {{ + ( + states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='name') | list | join(' + | list + | count + | int(0) + ) < 1 + }} + continue_on_timeout: true + timeout: !input update_timeout + + - alias: Finishing + sequence: + - *recalc_update_list + - variables: + log_message: "Finishing update process" + - *logbook_update + - if: '{{ "remaining_updates" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message - - ') }} + - variables: + remaining_updates: | + {{ + states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', pending_update_list) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='name') + | list + }} + remaining_updates_count: '{{ remaining_updates | count | int(0) }}' + - if: '{{ remaining_updates_count > 0 }}' + then: + - variables: + log_message: | + Remaining updates: + - `{{ remaining_updates | join("` + + - `") + }}` + - *logbook_update + - if: '{{ "remaining_updates" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + else: + - variables: + log_message: "No remaining items to be updated" + - *logbook_update + - if: '{{ "remaining_updates" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message - ########## Restart Home Assistant ########## - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Restart - Logbook" + - alias: Restart # Restart Home Assistant + sequence: + - variables: + input_restart_type: !input restart_type + - condition: '{{ input_restart_type != "no-restart" }}' + - alias: Wait pending operations # Wait for any pending operations to complete continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: > - {{ states.update - | selectattr('attributes.release_summary', 'defined') - | selectattr('attributes.release_summary', 'search', "") + wait_template: | + {{ + ( + states.update + | rejectattr('state', 'eq', 'off') + | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') - | list - | count - | int(0) }} item(s) pending a restart: - - {{ states.update + | list | count | int(0) + ) < 1 + }} + continue_on_timeout: true + timeout: + seconds: 30 + - &update_pending_restart_items + variables: + pending_restart_items: | + {{ states.update | selectattr('attributes.release_summary', 'defined') | selectattr('attributes.release_summary', 'search', "") | map(attribute='entity_id') | list - | join(' - - ') }} - - alias: "Restart" - continue_on_error: true - if: - - >- - {{ input_restart_type != "no-restart" and ( - ( - states.update - | selectattr('attributes.release_summary', 'defined') - | selectattr('attributes.release_summary', 'search', "") - | map(attribute='entity_id') - | list | count | int(0) - ) > 0) - }} - then: - - alias: "Restart - Check config" - continue_on_error: true - action: homeassistant.check_config - - if: - - "{{ states.persistent_notification.invalid_config.state | default('unavailable') == 'notifying' or states.persistent_notification.homeassistant_check_config.state | default('unavailable') == 'notifying' }}" + }} + pending_restart_items_count: '{{ pending_restart_items | count | int(0) }}' + - if: '{{ pending_restart_items_count < 1 }}' then: - - if: "{{ input_verbose_logging_bool }}" + - variables: + log_message: 'Nothing requiring a restart' + - *logbook_update + - if: '{{ "restart" in input_notification_telegram_select_notifications }}' then: - - alias: "Logbook: Running pre-restart actions" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Running pre-restart actions... - - alias: "Run pre-restart actions" + - *send_telegram_message + - condition: '{{ pending_restart_items_count > 0 }}' + - variables: + log_message: > + {{ pending_restart_items_count }} item(s) pending a restart: + - `{{ pending_restart_items | join('` + - `') + }}` + - *logbook_update + - if: '{{ "restart" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Restart" + continue_on_error: true + sequence: + - condition: '{{ pending_restart_items_count > 0 }}' + - alias: "Restart - Check config" continue_on_error: true - choose: - - conditions: - - '{{ true }}' - sequence: !input actions_pre_restart - default: [] - - if: "{{ input_verbose_logging_bool }}" + action: homeassistant.check_config + - variables: + has_invalid_config: > + {{ + states.persistent_notification.invalid_config.state + | default('unavailable') == 'notifying' + }} + has_check_config_notification: > + {{ + states.persistent_notification.homeassistant_check_config.state + | default('unavailable') == 'notifying' + }} + - if: '{{ has_invalid_config or has_check_config_notification }}' then: - - alias: "Restart - Logbook - Skipping restart" + - variables: + log_message: "Running pre-restart actions..." + - *logbook_update + - if: '{{ "restart" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Run pre-restart actions" continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: > + sequence: !input actions_pre_restart + - variables: + log_message: > Skipping restart process due to notifications of invalid configurations: - states.persistent_notification.invalid_config.state = {{ states.persistent_notification.invalid_config.state | default('unavailable') }}, - states.persistent_notification.homeassistant_check_config.state = {{ states.persistent_notification.homeassistant_check_config.state | default('unavailable') }} - else: - - alias: "Restart - Logbook - Restarting" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: 'Restarting Home Assistant {{ input_restart_type }}...' - - alias: "Restart - Call reboot service" - continue_on_error: true - action: '{{ "hassio.host_reboot" if input_restart_type == "host" else "homeassistant.restart" }}' - ########## Post-update actions ########## - - if: "{{ input_verbose_logging_bool }}" - then: - - alias: "Logbook - Post-actions" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Running post-update actions... + - has_invalid_config = {{ has_invalid_config }} + + - has_check_config_notification = {{ has_check_config_notification }} + - *logbook_update + - if: '{{ "restart" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + else: + - variables: + log_message: > + Restart was asked by admin. + + Restarting Home Assistant ({{ input_restart_type }}) + - *logbook_update + - if: '{{ "restart" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Wait 15 seconds to deliver the telegram message." + delay: + seconds: 15 + + - alias: "Restart - Call reboot service" + continue_on_error: true + action: '{{ "hassio.host_reboot" if input_restart_type == "host" else "homeassistant.restart" }}' - - alias: "Run post-update actions" - continue_on_error: true - choose: - - conditions: - - '{{ true }}' + - alias: Post-update actions + sequence: + - variables: + input_actions_pos_update: !input actions_pos_update + - condition: '{{ input_actions_pos_update is sequence }}' + - variables: + log_message: "Running post-update actions" + - *logbook_update + - if: '{{ "post_update_actions" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message + + - alias: "Run post-update actions" + continue_on_error: true sequence: !input actions_pos_update - default: [] - ########## Done! ########## - - if: "{{ input_verbose_logging_bool }}" + - alias: Set OFF helper flag + if: + - '{{ input_update_process_started_entity | default([]) | count > 0 }}' + - '{{ input_update_process_started_entity[0] | string | length > 0 }}' then: - - alias: "Logbook - Done" - continue_on_error: true - action: logbook.log - data: - name: Auto-update - entity_id: '{{ this.entity_id }}' - message: Done! + - action: input_boolean.turn_off + target: + entity_id: !input update_process_started_entity + + - alias: Done # All done! + sequence: + - variables: + log_message: "Done!" + - *logbook_update + - if: '{{ "done" in input_notification_telegram_select_notifications }}' + then: + - *send_telegram_message +... From 676c1e075513a21467c1f0932d69414932f92740 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:46:05 +0100 Subject: [PATCH 2/6] New filter for each group --- .../EdwardTFN/auto_update_scheduled.yaml | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index 88fc520..d04f023 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -291,7 +291,7 @@ blueprint: number: mode: box min: 1 - max: 600 + max: 180 unit_of_measurement: minutes max_backup_age: @@ -397,6 +397,8 @@ blueprint: default: "" selector: text: + multiline: true + type: number notification_telegram_disable_notification: name: Disable the Telegram notification from the Telegram client. @@ -521,14 +523,14 @@ blueprint: Usually updates don't take that very long, so feel free to ajust this time to something that fits better your environment. - The default is 600 seconds (10 minutes). + The default is 1200 seconds (20 minutes). You can select any value between 10 seconds and 7200 seconds (2 hours). If an update times out: - The automation will log an error message - The automation will continue with the next update - The timed-out update may still complete in the background - default: 600 + default: 1200 selector: number: min: 10 @@ -850,7 +852,7 @@ action: action: telegram_bot.send_message data: title: '{{ telegram_title }}' - target: "{{ input_notification_telegram_target_id }}" + target: '{{ input_notification_telegram_target_id }}' disable_notification: '{{ input_notification_telegram_disable_notification }}' message: '{{ log_message }}' @@ -867,9 +869,7 @@ action: - `{{ states.update | selectattr('state', 'eq', 'on') | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='name') | list | join('` - - - `') }}` + | map(attribute='name') | list | join("`\n- `") }}` - *logbook_update - if: '{{ "list_of_updates" in input_notification_telegram_select_notifications }}' then: @@ -1148,9 +1148,6 @@ action: value_template: > {{ ( states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) | selectattr('entity_id', 'in', firmware_update_entities) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') @@ -1168,9 +1165,6 @@ action: pending_update_list: > {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) - | rejectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) | selectattr('entity_id', 'in', firmware_update_entities) | map(attribute='entity_id') | list @@ -1203,10 +1197,7 @@ action: value_template: > {{ ( states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) | selectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list | count | int(0) ) > 0 @@ -1223,10 +1214,7 @@ action: pending_update_list: > {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) | selectattr('entity_id', 'in', core_update_entity) - | rejectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list @@ -1259,10 +1247,7 @@ action: value_template: > {{ ( states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) - | rejectattr('entity_id', 'in', core_update_entity) | selectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list | count | int(0) ) > 0 @@ -1279,10 +1264,7 @@ action: pending_update_list: > {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) - | rejectattr('entity_id', 'in', core_update_entity) | selectattr('entity_id', 'in', os_update_entity) - | rejectattr('entity_id', 'in', firmware_update_entities) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list @@ -1364,10 +1346,7 @@ action: - variables: log_message: | Remaining updates: - - `{{ remaining_updates | join("` - - - `") - }}` + - `{{ remaining_updates | join("`\n- `") }}` - *logbook_update - if: '{{ "remaining_updates" in input_notification_telegram_select_notifications }}' then: @@ -1421,10 +1400,9 @@ action: - condition: '{{ pending_restart_items_count > 0 }}' - variables: log_message: > - {{ pending_restart_items_count }} item(s) pending a restart: - - `{{ pending_restart_items | join('` - - `') - }}` + {{ pending_restart_items_count }} item {{ "s" if pending_restart_items_count > 1 }} pending a restart: + + - `{{ pending_restart_items | join("`\n- `") }}` - *logbook_update - if: '{{ "restart" in input_notification_telegram_select_notifications }}' then: From 1acda2fec8470f13335b35d1c6ef1749b5b15bfc Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:57:28 +0100 Subject: [PATCH 3/6] Rebuild `pending_update_list` --- .../EdwardTFN/auto_update_scheduled.yaml | 83 +++++++++---------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index d04f023..a3371ad 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -779,44 +779,32 @@ action: | first }} {% endif %} - combined_list: | - {{ - ( - firmware_update_entities | default([]) | list - if firmware_update_entities is sequence - else [firmware_update_entities | string] if firmware_update_entities is not none else [] - ) + - ( - general_update_entities | default([]) | list - if general_update_entities is sequence - else [general_update_entities | string] if general_update_entities is not none else [] - ) + - ( - core_update_entity | default([]) | list - if core_update_entity is sequence - else [core_update_entity | string] if core_update_entity is not none else [] - ) + - ( - os_update_entity | default([]) | list - if os_update_entity is sequence - else [os_update_entity | string] if os_update_entity is not none else [] - ) + - ( - supervisor_update_entity | default([]) | list - if supervisor_update_entity is sequence - else [supervisor_update_entity | string] if supervisor_update_entity is not none else [] - ) - }} - pending_update_list: | - {{ - states.update - | default([]) - | selectattr("state", "eq", "on") - | selectattr('entity_id', 'in', combined_list) - | rejectattr('entity_id', 'in', input_update_exclusions) - | map(attribute='entity_id') - | list - }} + combined_list: >- + {% set entities = namespace(list=[]) %} + {%- for entity in [firmware_update_entities, general_update_entities, + core_update_entity, os_update_entity, supervisor_update_entity] %} + {%- if entity is sequence and entity is not string %} + {%- for item in entity %} + {%- if item is string %} + {%- set entities.list = entities.list + [item] %} + {%- endif %} + {%- endfor %} + {%- elif entity is string %} + {%- set entities.list = entities.list + [entity] %} + {%- endif %} + {%- endfor %} + {{ entities.list }} + + pending_update_list: >- + {% set updates = namespace(list=[]) %} + {%- for entity in states.update %} + {%- if (entity.state == "on" and + entity.entity_id in combined_list and + entity.entity_id not in input_update_exclusions) %} + {%- set updates.list = updates.list + [entity.entity_id] %} + {%- endif %} + {%- endfor %} + {{ updates.list }} pending_update_count: '{{ pending_update_list | list | count | int(0) }}' - variables: @@ -865,8 +853,8 @@ action: then: - variables: log_message: > - List of updates: - - `{{ states.update + List of updates:\n- `{{ + states.update | selectattr('state', 'eq', 'on') | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='name') | list | join("`\n- `") }}` @@ -1080,13 +1068,16 @@ action: | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list - }} + }} + update_entity_id: '{{ pending_update_list[0] }}' + update_entity_friendly_name: | + {{ select_attr(update_entity_id, "friendly_name") | default(update_entity_id) }} - &log_updating alias: Log updating sequence: - variables: - log_message: "Updating `{{pending_update_list[0]}}`..." + log_message: "Updating `{{ update_entity_friendly_name }}`..." - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: @@ -1098,27 +1089,27 @@ action: action: update.install data: {} target: - entity_id: '{{ pending_update_list[0] }}' + entity_id: '{{ update_entity_id }}' - &update_wait alias: "Update - Wait" sequence: - alias: Wait continue_on_error: true - wait_template: "{{ is_state(pending_update_list[0], 'off') }}" + wait_template: "{{ is_state(update_entity_id, 'off') }}" continue_on_timeout: true timeout: !input update_timeout - if: '{{ wait.completed }}' then: - variables: - log_message: '`{{ pending_update_list[0] }}` updated successfuly' + log_message: '`{{ update_entity_friendly_name }}` updated successfuly' - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: - *send_telegram_message else: - variables: - log_message: 'ERROR: `{{ pending_update_list[0] }}` update timed out' + log_message: 'ERROR: `{{ update_entity_friendly_name }}` update timed out' - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: From ad8ee9c0cb04a1caae713f482c5f14cfa44c6448 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:23:59 +0100 Subject: [PATCH 4/6] Lint --- .../EdwardTFN/auto_update_scheduled.yaml | 101 ++++++++++-------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index a3371ad..a81ac4a 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -329,49 +329,64 @@ blueprint: input: actions_pre_update: name: Pre-update actions (optional) - description: 'Actions to execute before the backup or any update starts. + description: | + Actions to execute before the backup or any update starts. You can use this to send notifications, turn on/off devices or activate scenes before starting the updates. - Note => Please be aware that all actions will run right before the update process, which can happens over-night. Take this in account when selecting your actions. + Note => Please be aware that all actions will run right before the update process, + which can happens over-night. Take this in account when selecting your actions. - Note => The variable "\{\{ updates_list \}\}" is available for your actions and contains the list of pending updates.' + Note => Some usefull variables available for your actions: + - "`\{\{ updates_list \}\}`" - contains the list of updates when the automation started. + - "`\{\{ updates_pending \}\}`" - contains the list of updates remaining to be updated. default: [] selector: action: {} actions_pre_restart: name: Pre-restart actions (optional) - description: 'Actions to execute before the automation requests a restart. + description: | + Actions to execute before the automation requests a restart. - You can use this to send notifications, turn on/off devices or stopping automations before restarting Home Assistant. + You can use this to send notifications, + turn on/off devices or stopping automations before restarting Home Assistant. - Note => Please be aware that all actions will run right before the restart process, which can happens over-night. Take this in account when selecting your actions. + Note => Please be aware that all actions will run right before the restart process, + which can happens over-night. Take this in account when selecting your actions. - Note => Note all the restarts will be controlled by this automation, like when a Core update is installed. These actions might not be executed in those cases. + Note => Note all the restarts will be controlled by this automation, + like when a Core update is installed. These actions might not be executed in those cases. - Note => The variable "\{\{ updates_list \}\}" is available for your actions and contains the list of pending updates.' + Note => Some usefull variables available for your actions: + - "`\{\{ updates_list \}\}`" - contains the list of updates when the automation started. + - "`\{\{ updates_pending \}\}`" - contains the list of updates remaining to be updated. default: [] selector: action: {} actions_pos_update: name: Post-update actions (optional) - description: > + description: | Actions to execute AFTER the update process finishes. You can use this to send notifications, turn on/off devices or activate scenes after applying the updates. - Note => Please be aware that all actions will run right after the update process, which can happens over-night. + Note => Please be aware that all actions will run right after the update process, + which can happens over-night. Take this in account when selecting your actions. - Note => The variable "\{\{ updates_list \}\}" is available for your actions and contains the list of pending updates. + Note => Some usefull variables available for your actions: + - "`\{\{ updates_list \}\}`" - contains the list of updates when the automation started. + - "`\{\{ updates_pending \}\}`" - contains the list of updates remaining to be updated. - **IMPORTANT** => Some updates will automatically restart Home Assistant, causing the automation to interrupt before finishing, preventing the pos-updates actions to be executed. - If you have critical actions to run after an update, consider including also in another automation based on Home Assistant start. + **IMPORTANT** => Some updates will automatically restart Home Assistant, + causing the automation to interrupt before finishing, preventing the pos-updates actions to be executed. + If you have critical actions to run after an update, + consider including also in another automation based on Home Assistant start. default: [] selector: action: {} @@ -655,7 +670,7 @@ variables: if (device_attr(u, 'identifiers') | first)[1] == 'supervisor' %} {{ u }} {% endfor %} - is_continuing_from_a_restart: > + is_resume_after_restart: > {{ input_update_process_started_entity | default([]) | count == 1 and is_state(input_update_process_started_entity | first, "on") @@ -781,7 +796,7 @@ action: {% endif %} combined_list: >- {% set entities = namespace(list=[]) %} - {%- for entity in [firmware_update_entities, general_update_entities, + {%- for entity in [firmware_update_entities, general_update_entities, core_update_entity, os_update_entity, supervisor_update_entity] %} {%- if entity is sequence and entity is not string %} {%- for item in entity %} @@ -794,28 +809,28 @@ action: {%- endif %} {%- endfor %} {{ entities.list }} - - pending_update_list: >- + updates_pending: >- {% set updates = namespace(list=[]) %} {%- for entity in states.update %} - {%- if (entity.state == "on" and - entity.entity_id in combined_list and + {%- if (entity.state == "on" and + entity.entity_id in combined_list and entity.entity_id not in input_update_exclusions) %} {%- set updates.list = updates.list + [entity.entity_id] %} {%- endif %} {%- endfor %} {{ updates.list }} - pending_update_count: '{{ pending_update_list | list | count | int(0) }}' + pending_update_list: '{{ updates_pending }}' + updates_pending_count: '{{ updates_pending | list | count | int(0) }}' - variables: - update_list: '{{ pending_update_list }}' - updates_list: '{{ pending_update_list }}' - is_there_anything_to_update: '{{ pending_update_count > 0 }}' + update_list: '{{ updates_pending }}' + updates_list: '{{ updates_pending }}' + is_there_anything_to_update: '{{ updates_pending_count > 0 }}' - alias: Preparation # Inform logbook and telegram which update automation is running sequence: - variables: - log_message: '{{ friendly_name }} is {{ "re" if is_continuing_from_a_restart else ""}}starting' + log_message: '{{ friendly_name }} is {{ "re" if is_resume_after_restart else ""}}starting' - &logbook_update if: "{{ input_verbose_logging_bool }}" then: @@ -885,7 +900,7 @@ action: - alias: Pre-update actions if: - '{{ is_there_anything_to_update }}' - - '{{ not is_continuing_from_a_restart }}' + - '{{ not is_resume_after_restart }}' - condition: state entity_id: !input schedule_entity state: 'on' @@ -915,7 +930,7 @@ action: - alias: Backup if: - '{{ is_there_anything_to_update }}' - - '{{ not is_continuing_from_a_restart }}' + - '{{ not is_resume_after_restart }}' - condition: state entity_id: !input schedule_entity state: 'on' @@ -1045,7 +1060,7 @@ action: value_template: > {{ ( states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', updates_pending) | rejectattr('entity_id', 'in', core_update_entity) | rejectattr('entity_id', 'in', os_update_entity) | rejectattr('entity_id', 'in', firmware_update_entities) @@ -1058,10 +1073,10 @@ action: log_message: "Starting sequence of standard updates..." - *logbook_update - variables: - pending_update_list: > + updates_pending: > {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', updates_pending) | rejectattr('entity_id', 'in', core_update_entity) | rejectattr('entity_id', 'in', os_update_entity) | rejectattr('entity_id', 'in', firmware_update_entities) @@ -1069,15 +1084,15 @@ action: | map(attribute='entity_id') | list }} - update_entity_id: '{{ pending_update_list[0] }}' - update_entity_friendly_name: | - {{ select_attr(update_entity_id, "friendly_name") | default(update_entity_id) }} + current_update_entity: '{{ updates_pending[0] }}' + current_update_entity_friendly_name: | + {{ select_attr(current_update_entity, "friendly_name") | default(current_update_entity) }} - &log_updating alias: Log updating sequence: - variables: - log_message: "Updating `{{ update_entity_friendly_name }}`..." + log_message: "Updating `{{ current_update_entity_friendly_name }}`..." - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: @@ -1089,27 +1104,27 @@ action: action: update.install data: {} target: - entity_id: '{{ update_entity_id }}' + entity_id: '{{ current_update_entity }}' - &update_wait alias: "Update - Wait" sequence: - alias: Wait continue_on_error: true - wait_template: "{{ is_state(update_entity_id, 'off') }}" + wait_template: "{{ is_state(current_update_entity, 'off') }}" continue_on_timeout: true timeout: !input update_timeout - if: '{{ wait.completed }}' then: - variables: - log_message: '`{{ update_entity_friendly_name }}` updated successfuly' + log_message: '`{{ current_update_entity_friendly_name }}` updated successfuly' - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: - *send_telegram_message else: - variables: - log_message: 'ERROR: `{{ update_entity_friendly_name }}` update timed out' + log_message: 'ERROR: `{{ current_update_entity_friendly_name }}` update timed out' - *logbook_update - if: '{{ "update_progress" in input_notification_telegram_select_notifications }}' then: @@ -1153,7 +1168,7 @@ action: - *send_telegram_message - variables: - pending_update_list: > + updates_pending: > {{ states.update | selectattr('state', 'eq', 'on') | selectattr('entity_id', 'in', firmware_update_entities) @@ -1202,7 +1217,7 @@ action: - *send_telegram_message - variables: - pending_update_list: > + updates_pending: > {{ states.update | selectattr('state', 'eq', 'on') | selectattr('entity_id', 'in', core_update_entity) @@ -1252,7 +1267,7 @@ action: - *send_telegram_message - variables: - pending_update_list: > + updates_pending: > {{ states.update | selectattr('state', 'eq', 'on') | selectattr('entity_id', 'in', os_update_entity) @@ -1289,7 +1304,7 @@ action: entity_id: >- {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', updates_pending) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='entity_id') | list @@ -1301,7 +1316,7 @@ action: ( states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', updates_pending) | rejectattr('entity_id', 'in', input_update_exclusions) | list | count @@ -1326,7 +1341,7 @@ action: {{ states.update | selectattr('state', 'eq', 'on') - | selectattr('entity_id', 'in', pending_update_list) + | selectattr('entity_id', 'in', updates_pending) | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='name') | list From e6374dff19bc76b85caf9fd1e2138d3ab5c2b993 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:13:56 +0100 Subject: [PATCH 5/6] Fix typo on `state_attr` --- blueprints/automation/EdwardTFN/auto_update_scheduled.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index a81ac4a..51f2332 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -868,8 +868,8 @@ action: then: - variables: log_message: > - List of updates:\n- `{{ - states.update + List of updates: + - `{{ states.update | selectattr('state', 'eq', 'on') | rejectattr('entity_id', 'in', input_update_exclusions) | map(attribute='name') | list | join("`\n- `") }}` @@ -1086,7 +1086,7 @@ action: }} current_update_entity: '{{ updates_pending[0] }}' current_update_entity_friendly_name: | - {{ select_attr(current_update_entity, "friendly_name") | default(current_update_entity) }} + {{ state_attr(current_update_entity, "friendly_name") | default(current_update_entity) }} - &log_updating alias: Log updating From d7d9fbd7025d076bde740761f47cbbc3ef20c1b0 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:12:41 +0100 Subject: [PATCH 6/6] Improved restart message --- blueprints/automation/EdwardTFN/auto_update_scheduled.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index 51f2332..7b25ca1 100644 --- a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml +++ b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml @@ -869,6 +869,7 @@ action: - variables: log_message: > List of updates: + - `{{ states.update | selectattr('state', 'eq', 'on') | rejectattr('entity_id', 'in', input_update_exclusions) @@ -1406,7 +1407,7 @@ action: - condition: '{{ pending_restart_items_count > 0 }}' - variables: log_message: > - {{ pending_restart_items_count }} item {{ "s" if pending_restart_items_count > 1 }} pending a restart: + {{ pending_restart_items_count }} item{{ "s" if pending_restart_items_count > 1 }} pending a restart: - `{{ pending_restart_items | join("`\n- `") }}` - *logbook_update @@ -1458,7 +1459,7 @@ action: else: - variables: log_message: > - Restart was asked by admin. + The system is set to restart automatically when an update requires it. Restarting Home Assistant ({{ input_restart_type }}) - *logbook_update