diff --git a/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml b/blueprints/automation/EdwardTFN/auto_update_scheduled.yaml index a6b8f39..7b25ca1 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: 180 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 @@ -204,53 +329,159 @@ 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: {} + 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: + multiline: true + type: number + + 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 +490,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 +505,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 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: 1200 + 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 +587,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 +605,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 +645,867 @@ 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_resume_after_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 - }} - general_update_entities: > - {{ states.update + {{ + 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('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: > + {% 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: >- + {% 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 }} + updates_pending: >- + {% 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_list: '{{ updates_pending }}' + updates_pending_count: '{{ updates_pending | 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: '{{ updates_pending }}' + updates_list: '{{ updates_pending }}' + is_there_anything_to_update: '{{ updates_pending_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_resume_after_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(' - - ') }} - - 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 + - `{{ states.update + | selectattr('state', 'eq', 'on') + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='name') | list | join("`\n- `") }}` + - *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_resume_after_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_resume_after_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', updates_pending) + | 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" + sequence: + - variables: + log_message: "Starting sequence of standard updates..." + - *logbook_update + - variables: + updates_pending: > + {{ states.update + | selectattr('state', 'eq', 'on') + | 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) + | rejectattr('entity_id', 'in', input_update_exclusions) + | map(attribute='entity_id') + | list + }} + current_update_entity: '{{ updates_pending[0] }}' + current_update_entity_friendly_name: | + {{ state_attr(current_update_entity, "friendly_name") | default(current_update_entity) }} + + - &log_updating + alias: Log updating + sequence: + - variables: + log_message: "Updating `{{ current_update_entity_friendly_name }}`..." + - *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: "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" - 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: '{{ current_update_entity }}' + + - &update_wait + alias: "Update - Wait" + sequence: + - alias: Wait + continue_on_error: true + wait_template: "{{ is_state(current_update_entity, 'off') }}" + continue_on_timeout: true + timeout: !input update_timeout + - if: '{{ wait.completed }}' + then: + - variables: + 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: `{{ current_update_entity_friendly_name }}` 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', 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: + updates_pending: > + {{ states.update + | selectattr('state', 'eq', 'on') + | 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', core_update_entity) + | 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: + updates_pending: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', core_update_entity) + | 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', os_update_entity) + | 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: + updates_pending: > + {{ states.update + | selectattr('state', 'eq', 'on') + | selectattr('entity_id', 'in', os_update_entity) + | 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', updates_pending) + | 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', updates_pending) | 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', updates_pending) + | 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("`\n- `") }}` + - *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" 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: + - *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: > + The system is set to restart automatically when an update requires it. + + 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 +...