You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1041 lines
47 KiB
1041 lines
47 KiB
######################################################################
|
|
# @CCOSTAN - Follow Me on X
|
|
# For more info visit https://www.vcloudinfo.com/click-here
|
|
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
|
|
# -------------------------------------------------------------------
|
|
# Docker Infrastructure - Host patching and container alerts
|
|
# Related Issue: 1632, 1584
|
|
# APT webhook results (docker_10/14/17/69) and container down repairs.
|
|
# -------------------------------------------------------------------
|
|
# Notes: Hosts run weekly Wed 12:00 APT job and POST JSON to webhooks.
|
|
# Notes: Reboots are handled directly on each host by apt_weekly.sh.
|
|
# Notes: Reboot staggering: docker_14 first, docker_69 second, docker_10 third.
|
|
# Notes: Container monitoring is dynamic with binary_sensor status preferred over switch state.
|
|
# Notes: Includes Portainer stack status repairs, 20-minute Joanna dispatch for persistent container outages, and scheduled image prune.
|
|
######################################################################
|
|
|
|
input_datetime:
|
|
apt_docker_10_last_check:
|
|
name: "docker_10 APT last check"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_10_last_update:
|
|
name: "docker_10 APT last update"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_17_last_check:
|
|
name: "docker_17 APT last check"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_17_last_update:
|
|
name: "docker_17 APT last update"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_14_last_check:
|
|
name: "docker_14 APT last check"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_14_last_update:
|
|
name: "docker_14 APT last update"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_69_last_check:
|
|
name: "docker_69 APT last check"
|
|
has_date: true
|
|
has_time: true
|
|
apt_docker_69_last_update:
|
|
name: "docker_69 APT last update"
|
|
has_date: true
|
|
has_time: true
|
|
docker_container_alerts_snooze_until:
|
|
name: "Docker container alerts snooze until"
|
|
has_date: true
|
|
has_time: true
|
|
|
|
input_text:
|
|
apt_docker_10_last_result:
|
|
name: "docker_10 APT last result"
|
|
max: 255
|
|
apt_docker_17_last_result:
|
|
name: "docker_17 APT last result"
|
|
max: 255
|
|
apt_docker_14_last_result:
|
|
name: "docker_14 APT last result"
|
|
max: 255
|
|
apt_docker_69_last_result:
|
|
name: "docker_69 APT last result"
|
|
max: 255
|
|
|
|
switch:
|
|
- platform: group
|
|
name: Docker Monitored Containers
|
|
unique_id: docker_monitored_containers
|
|
entities:
|
|
- switch.cloudflared_kch_container
|
|
- switch.cloudflared_wp_container
|
|
- switch.codex_appliance_container
|
|
- switch.college_budget_app_container
|
|
- switch.cruise_tracker_container
|
|
- switch.dashy_container
|
|
- switch.docker_socket_proxy_container
|
|
- switch.dozzle_container
|
|
- switch.dozzle_agent_10_container
|
|
- switch.dozzle_agent_14_container
|
|
- switch.dozzle_agent_17_container
|
|
- switch.dozzle_agent_69_container
|
|
- switch.duplicati_container
|
|
- switch.esphome_container
|
|
- switch.fed437a0f191_tugtainer_socket_proxy_container
|
|
- switch.foodie_tracker_container
|
|
- switch.frigate_container
|
|
- switch.games_hub_container
|
|
- switch.home_assistant_container
|
|
- switch.imposter_container
|
|
- switch.infra_info_container
|
|
- switch.kingcrafthomes_container
|
|
- switch.lmediaservices_container
|
|
- switch.mariadb_container
|
|
- switch.mariadb_backup_container
|
|
- switch.matter_server_container
|
|
- switch.mqtt_container
|
|
- switch.nebula_sync_container
|
|
- switch.panel_notes_container
|
|
- switch.pihole_container
|
|
- switch.pihole_secondary_container
|
|
- switch.poker_tracker_container
|
|
- switch.portainer_container
|
|
- switch.portainer_agent_container
|
|
- switch.postgres_webhooks_engine_container
|
|
- switch.rc_price_checker_container
|
|
- switch.redis_webhooks_engine_container
|
|
- switch.rvtools_ppt_web_container
|
|
- switch.tapple_container
|
|
- switch.tugtainer_container
|
|
- switch.tugtainer_agent_container
|
|
- switch.tugtainer_socket_proxy_container
|
|
- switch.unifi_container
|
|
- switch.webhooks_engine_container
|
|
- switch.wordpress_db_container
|
|
- switch.wordpress_wp_container
|
|
- switch.wyze_bridge_container
|
|
|
|
template:
|
|
- sensor:
|
|
- name: "docker_10 APT status"
|
|
unique_id: apt_docker_10_status
|
|
icon: mdi:package-up
|
|
state: "{{ states('input_text.apt_docker_10_last_result') }}"
|
|
- name: "docker_10 APT last check"
|
|
unique_id: apt_docker_10_last_check
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_10_last_check') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_10 APT last update"
|
|
unique_id: apt_docker_10_last_update
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_10_last_update') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_14 APT status"
|
|
unique_id: apt_docker_14_status
|
|
icon: mdi:package-up
|
|
state: "{{ states('input_text.apt_docker_14_last_result') }}"
|
|
- name: "docker_17 APT status"
|
|
unique_id: apt_docker_17_status
|
|
icon: mdi:package-up
|
|
state: "{{ states('input_text.apt_docker_17_last_result') }}"
|
|
- name: "docker_17 APT last check"
|
|
unique_id: apt_docker_17_last_check
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_17_last_check') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_17 APT last update"
|
|
unique_id: apt_docker_17_last_update
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_17_last_update') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_14 APT last check"
|
|
unique_id: apt_docker_14_last_check
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_14_last_check') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_14 APT last update"
|
|
unique_id: apt_docker_14_last_update
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_14_last_update') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_69 APT status"
|
|
unique_id: apt_docker_69_status
|
|
icon: mdi:package-up
|
|
state: "{{ states('input_text.apt_docker_69_last_result') }}"
|
|
- name: "docker_69 APT last check"
|
|
unique_id: apt_docker_69_last_check
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_69_last_check') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
- name: "docker_69 APT last update"
|
|
unique_id: apt_docker_69_last_update
|
|
device_class: timestamp
|
|
state: >-
|
|
{% set stamp = states('input_datetime.apt_docker_69_last_update') %}
|
|
{% if stamp not in ['unknown', 'unavailable', 'none', ''] %}
|
|
{{ as_local(as_datetime(stamp)) }}
|
|
{% endif %}
|
|
|
|
- sensor:
|
|
- name: "Docker Monitored Container Count"
|
|
unique_id: docker_monitored_container_count
|
|
icon: mdi:format-list-numbered
|
|
state: >-
|
|
{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | count }}
|
|
|
|
- name: "Docker Monitored Unavailable Count"
|
|
unique_id: docker_monitored_unavailable_count
|
|
icon: mdi:lan-disconnect
|
|
state: >-
|
|
{% set ns = namespace(keys=[], unavailable=0) %}
|
|
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) %}
|
|
{% for switch_entity in monitored %}
|
|
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
|
|
{% if key not in ns.keys %}
|
|
{% set ns.keys = ns.keys + [key] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for key in ns.keys %}
|
|
{% set status_entity = 'binary_sensor.' ~ key ~ '_status' %}
|
|
{% set status_entity_alt = status_entity ~ '_2' %}
|
|
{% set state_entity = 'sensor.' ~ key ~ '_state' %}
|
|
{% set state_entity_alt = state_entity ~ '_2' %}
|
|
{% set switch_entity = 'switch.' ~ key ~ '_container' %}
|
|
{% set switch_entity_alt = switch_entity ~ '_2' %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% set effective_state = resolver.state %}
|
|
{% if effective_state == 'unavailable' %}
|
|
{% set ns.unavailable = ns.unavailable + 1 %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ ns.unavailable }}
|
|
|
|
- name: "Docker Containers Down List"
|
|
unique_id: docker_containers_down_list
|
|
icon: mdi:docker
|
|
state: >-
|
|
{% set ns = namespace(keys=[], down=[]) %}
|
|
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
|
|
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
|
|
{% for switch_entity in monitored %}
|
|
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
|
|
{% if key not in ns.keys %}
|
|
{% set ns.keys = ns.keys + [key] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for key in ns.keys | sort %}
|
|
{% set status_entity = 'binary_sensor.' ~ key ~ '_status' %}
|
|
{% set status_entity_alt = status_entity ~ '_2' %}
|
|
{% set state_entity = 'sensor.' ~ key ~ '_state' %}
|
|
{% set state_entity_alt = state_entity ~ '_2' %}
|
|
{% set switch_entity = 'switch.' ~ key ~ '_container' %}
|
|
{% set switch_entity_alt = switch_entity ~ '_2' %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% set effective_state = resolver.state %}
|
|
{% if effective_state in ['off', 'stopped'] %}
|
|
{% set ns.down = ns.down + [key] %}
|
|
{% elif not telemetry_degraded and effective_state in ['unknown', 'unavailable'] %}
|
|
{% set ns.down = ns.down + [key] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ 'ok' if (ns.down | count == 0) else 'down' }}
|
|
attributes:
|
|
down_containers: >-
|
|
{% set ns = namespace(keys=[], down=[]) %}
|
|
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
|
|
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
|
|
{% for switch_entity in monitored %}
|
|
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
|
|
{% if key not in ns.keys %}
|
|
{% set ns.keys = ns.keys + [key] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for key in ns.keys | sort %}
|
|
{% set status_entity = 'binary_sensor.' ~ key ~ '_status' %}
|
|
{% set status_entity_alt = status_entity ~ '_2' %}
|
|
{% set state_entity = 'sensor.' ~ key ~ '_state' %}
|
|
{% set state_entity_alt = state_entity ~ '_2' %}
|
|
{% set switch_entity = 'switch.' ~ key ~ '_container' %}
|
|
{% set switch_entity_alt = switch_entity ~ '_2' %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% set effective_state = resolver.state %}
|
|
{% if effective_state in ['off', 'stopped'] %}
|
|
{% set ns.down = ns.down + [key] %}
|
|
{% elif not telemetry_degraded and effective_state in ['unknown', 'unavailable'] %}
|
|
{% set ns.down = ns.down + [key] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ ns.down | sort }}
|
|
|
|
- name: "Docker Containers Down Count"
|
|
unique_id: docker_containers_down_count
|
|
icon: mdi:counter
|
|
state: >-
|
|
{% set down_items = state_attr('sensor.docker_containers_down_list', 'down_containers') | default([], true) | list %}
|
|
{{ down_items | count }}
|
|
|
|
- name: "Docker Stacks Down List"
|
|
unique_id: docker_stacks_down_list
|
|
icon: mdi:package-down
|
|
state: >-
|
|
{% set ns = namespace(down=[]) %}
|
|
{% for item in states.binary_sensor %}
|
|
{% if item.entity_id is search('^binary_sensor\\..*_stack_status$') %}
|
|
{% set st = item.state | lower %}
|
|
{% if st in ['off', 'unknown', 'unavailable'] %}
|
|
{% set stack = item.entity_id | replace('binary_sensor.', '') | regex_replace('_stack_status$', '') %}
|
|
{% set ns.down = ns.down + [stack] %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ ns.down | sort | join(', ') if (ns.down | count > 0) else 'none' }}
|
|
|
|
- name: "Docker Stacks Down Count"
|
|
unique_id: docker_stacks_down_count
|
|
icon: mdi:counter
|
|
state: >-
|
|
{% set down_list = states('sensor.docker_stacks_down_list') %}
|
|
{% if down_list in ['unknown', 'unavailable', 'none', ''] %}
|
|
0
|
|
{% else %}
|
|
{{ down_list.split(',') | map('trim') | reject('equalto', '') | list | count }}
|
|
{% endif %}
|
|
|
|
- binary_sensor:
|
|
- name: "Docker Container Telemetry Degraded"
|
|
unique_id: docker_container_telemetry_degraded
|
|
device_class: problem
|
|
icon: mdi:lan-disconnect
|
|
state: >-
|
|
{% set total = states('sensor.docker_monitored_container_count') | int(0) %}
|
|
{% set unavailable = states('sensor.docker_monitored_unavailable_count') | int(0) %}
|
|
{% set threshold = [3, ((total * 0.6) | round(0, 'ceil') | int(0))] | max %}
|
|
{{ total > 0 and unavailable >= threshold }}
|
|
|
|
- name: "Docker Container Alerts Snoozed"
|
|
unique_id: docker_container_alerts_snoozed
|
|
device_class: problem
|
|
icon: mdi:bell-sleep
|
|
state: >-
|
|
{% set stamp = states('input_datetime.docker_container_alerts_snooze_until') %}
|
|
{% set until_ts = as_local(as_datetime(stamp)) if stamp not in ['unknown', 'unavailable', 'none', ''] else none %}
|
|
{{ until_ts is not none and now() < until_ts }}
|
|
|
|
script:
|
|
docker_container_repairs_sync:
|
|
alias: Docker Container Repairs Sync
|
|
mode: parallel
|
|
fields:
|
|
entity_id:
|
|
description: Changed Portainer entity (`switch.*_container` or `binary_sensor.*_status`)
|
|
example: "switch.rc_price_checker_container"
|
|
operation:
|
|
description: "Sync operation: create or clear"
|
|
example: "create"
|
|
delay_minutes:
|
|
description: "Optional delay before evaluation (used for create path)"
|
|
example: 5
|
|
log_result:
|
|
description: "Whether to write activity log entries for create/clear actions"
|
|
example: true
|
|
sequence:
|
|
- variables:
|
|
down_states: ['off', 'stopped', 'exited', 'dead', 'unknown', 'unavailable']
|
|
src_entity: "{{ entity_id | default('', true) }}"
|
|
op: "{{ operation | default('create', true) | lower }}"
|
|
wait_minutes: "{{ delay_minutes | default(0) | int(0) }}"
|
|
log_enabled: "{{ log_result | default(true) | bool }}"
|
|
container_key: >-
|
|
{% if src_entity.startswith('binary_sensor.') %}
|
|
{{ src_entity | replace('binary_sensor.', '') | regex_replace('_status(?:_2)?$', '') }}
|
|
{% elif src_entity.startswith('switch.') %}
|
|
{{ src_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') }}
|
|
{% elif src_entity.startswith('sensor.') %}
|
|
{{ src_entity | replace('sensor.', '') | regex_replace('_state(?:_2)?$', '') }}
|
|
{% else %}
|
|
{{ src_entity }}
|
|
{% endif %}
|
|
switch_entity: "switch.{{ container_key }}_container"
|
|
switch_entity_alt: "switch.{{ container_key }}_container_2"
|
|
status_entity: "binary_sensor.{{ container_key }}_status"
|
|
status_entity_alt: "binary_sensor.{{ container_key }}_status_2"
|
|
state_entity: "sensor.{{ container_key }}_state"
|
|
state_entity_alt: "sensor.{{ container_key }}_state_2"
|
|
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) }}"
|
|
tracked_container: "{{ switch_entity in monitored_switches or switch_entity_alt in monitored_switches }}"
|
|
effective_entity: >-
|
|
{% if expand(status_entity) | count > 0 %}
|
|
{{ status_entity }}
|
|
{% elif expand(status_entity_alt) | count > 0 %}
|
|
{{ status_entity_alt }}
|
|
{% elif expand(state_entity) | count > 0 %}
|
|
{{ state_entity }}
|
|
{% elif expand(state_entity_alt) | count > 0 %}
|
|
{{ state_entity_alt }}
|
|
{% elif expand(switch_entity) | count > 0 %}
|
|
{{ switch_entity }}
|
|
{% elif expand(switch_entity_alt) | count > 0 %}
|
|
{{ switch_entity_alt }}
|
|
{% else %}
|
|
{{ src_entity }}
|
|
{% endif %}
|
|
issue_id: "docker_container_{{ container_key }}_offline"
|
|
spook_issue_id: "user_docker_container_{{ container_key }}_offline"
|
|
- condition: template
|
|
value_template: "{{ tracked_container and op in ['create', 'clear'] }}"
|
|
- choose:
|
|
- conditions: "{{ op == 'create' }}"
|
|
sequence:
|
|
- choose:
|
|
- conditions: "{{ wait_minutes > 0 }}"
|
|
sequence:
|
|
- delay:
|
|
minutes: "{{ wait_minutes }}"
|
|
- variables:
|
|
effective_state: >-
|
|
{% set candidates = [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{{ resolver.state }}
|
|
telemetry_degraded: "{{ is_state('binary_sensor.docker_container_telemetry_degraded', 'on') }}"
|
|
container_name: "{{ state_attr(effective_entity, 'friendly_name') | default(container_key, true) }}"
|
|
- condition: template
|
|
value_template: >-
|
|
{{ effective_state in down_states and
|
|
not (telemetry_degraded and effective_state in ['unknown', 'unavailable']) }}
|
|
- condition: state
|
|
entity_id: binary_sensor.docker_container_alerts_snoozed
|
|
state: "off"
|
|
- service: repairs.create
|
|
data:
|
|
issue_id: "{{ issue_id }}"
|
|
title: "Container offline: {{ container_name }}"
|
|
description: >-
|
|
{{ container_name }} has been {{ effective_state }} for over 5 minutes.
|
|
Effective entity: {{ effective_entity }}.
|
|
severity: warning
|
|
persistent: true
|
|
- choose:
|
|
- conditions: "{{ log_enabled }}"
|
|
sequence:
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "{{ container_name }} is {{ effective_state }} for over 5 minutes."
|
|
- delay:
|
|
minutes: 15
|
|
- variables:
|
|
persistent_effective_state: >-
|
|
{% set candidates = [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{{ resolver.state }}
|
|
container_name: "{{ state_attr(effective_entity, 'friendly_name') | default(container_key, true) }}"
|
|
- condition: template
|
|
value_template: >-
|
|
{{ persistent_effective_state in down_states and
|
|
not (is_state('binary_sensor.docker_container_telemetry_degraded', 'on') and
|
|
persistent_effective_state in ['unknown', 'unavailable']) }}
|
|
- condition: state
|
|
entity_id: binary_sensor.docker_container_alerts_snoozed
|
|
state: "off"
|
|
- service: script.joanna_dispatch
|
|
data:
|
|
trigger_context: >-
|
|
HA automation docker_state_sync_repairs_dynamic
|
|
(Docker State Sync - Repairs (Dynamic))
|
|
source: "home_assistant_automation.docker_state_sync_repairs_dynamic"
|
|
summary: "{{ container_name }} container has remained {{ persistent_effective_state }} for 20 minutes"
|
|
entity_ids:
|
|
- "{{ effective_entity }}"
|
|
- "{{ switch_entity }}"
|
|
diagnostics: >-
|
|
issue_id={{ issue_id }},
|
|
spook_issue_id={{ spook_issue_id }},
|
|
container_key={{ container_key }},
|
|
effective_entity={{ effective_entity }},
|
|
switch_entity={{ switch_entity }},
|
|
effective_state_initial={{ effective_state }},
|
|
effective_state_20m={{ persistent_effective_state }}
|
|
request: >-
|
|
Troubleshoot and resolve the persistent Docker container outage if possible.
|
|
Use Duplicati and the related host/container telemetry to verify recovery.
|
|
- conditions: "{{ op == 'clear' }}"
|
|
sequence:
|
|
- variables:
|
|
effective_state: >-
|
|
{% set candidates = [status_entity, status_entity_alt, state_entity, state_entity_alt, switch_entity, switch_entity_alt] %}
|
|
{% set resolver = namespace(state='unknown', chosen=false) %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set candidate_state = states(candidate) | lower %}
|
|
{% if candidate_state not in ['unknown', 'unavailable', ''] %}
|
|
{% set resolver.state = candidate_state %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if not resolver.chosen %}
|
|
{% for candidate in candidates %}
|
|
{% if not resolver.chosen and expand(candidate) | count > 0 %}
|
|
{% set resolver.state = states(candidate) | lower %}
|
|
{% set resolver.chosen = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{{ resolver.state }}
|
|
container_name: "{{ state_attr(effective_entity, 'friendly_name') | default(container_key, true) }}"
|
|
- condition: template
|
|
value_template: "{{ effective_state not in down_states }}"
|
|
- service: repairs.remove
|
|
continue_on_error: true
|
|
data:
|
|
issue_id: "{{ issue_id }}"
|
|
- service: repairs.remove
|
|
continue_on_error: true
|
|
data:
|
|
issue_id: "{{ spook_issue_id }}"
|
|
- choose:
|
|
- conditions: "{{ log_enabled }}"
|
|
sequence:
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "{{ container_name }} recovered ({{ effective_state }})."
|
|
|
|
docker_stack_repairs_sync:
|
|
alias: Docker Stack Repairs Sync
|
|
mode: parallel
|
|
fields:
|
|
entity_id:
|
|
description: Changed Portainer stack status entity (`binary_sensor.*_stack_status`)
|
|
example: "binary_sensor.vcloudinfo_stack_status"
|
|
operation:
|
|
description: "Sync operation: create or clear"
|
|
example: "create"
|
|
delay_minutes:
|
|
description: "Optional delay before evaluation (used for create path)"
|
|
example: 2
|
|
log_result:
|
|
description: "Whether to write activity log entries for create/clear actions"
|
|
example: true
|
|
sequence:
|
|
- variables:
|
|
down_states: ['off', 'unknown', 'unavailable']
|
|
src_entity: "{{ entity_id | default('', true) }}"
|
|
op: "{{ operation | default('create', true) | lower }}"
|
|
wait_minutes: "{{ delay_minutes | default(0) | int(0) }}"
|
|
log_enabled: "{{ log_result | default(true) | bool }}"
|
|
stack_key: "{{ src_entity | replace('binary_sensor.', '') | regex_replace('_stack_status$', '') }}"
|
|
stack_count_entity: "sensor.{{ stack_key }}_stack_containers_count"
|
|
tracked_stack: "{{ expand(stack_count_entity) | count > 0 }}"
|
|
issue_id: "docker_stack_{{ stack_key }}_offline"
|
|
- condition: template
|
|
value_template: "{{ src_entity.startswith('binary_sensor.') and src_entity.endswith('_stack_status') }}"
|
|
- condition: template
|
|
value_template: "{{ tracked_stack and op in ['create', 'clear'] }}"
|
|
- choose:
|
|
- conditions: "{{ op == 'create' }}"
|
|
sequence:
|
|
- choose:
|
|
- conditions: "{{ wait_minutes > 0 }}"
|
|
sequence:
|
|
- delay:
|
|
minutes: "{{ wait_minutes }}"
|
|
- variables:
|
|
effective_state: "{{ states(src_entity) | lower }}"
|
|
stack_name: "{{ state_attr(src_entity, 'friendly_name') | default(stack_key, true) }}"
|
|
- condition: template
|
|
value_template: "{{ effective_state in down_states }}"
|
|
- service: repairs.create
|
|
data:
|
|
issue_id: "{{ issue_id }}"
|
|
title: "Stack offline: {{ stack_name }}"
|
|
description: >-
|
|
{{ stack_name }} has been {{ effective_state }} for over 2 minutes.
|
|
Effective entity: {{ src_entity }}.
|
|
severity: warning
|
|
persistent: true
|
|
- choose:
|
|
- conditions: "{{ log_enabled }}"
|
|
sequence:
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "{{ stack_name }} stack is {{ effective_state }} for over 2 minutes."
|
|
- conditions: "{{ op == 'clear' }}"
|
|
sequence:
|
|
- variables:
|
|
effective_state: "{{ states(src_entity) | lower }}"
|
|
stack_name: "{{ state_attr(src_entity, 'friendly_name') | default(stack_key, true) }}"
|
|
- condition: template
|
|
value_template: "{{ effective_state not in down_states }}"
|
|
- service: repairs.remove
|
|
continue_on_error: true
|
|
data:
|
|
issue_id: "{{ issue_id }}"
|
|
- choose:
|
|
- conditions: "{{ log_enabled }}"
|
|
sequence:
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "{{ stack_name }} stack recovered ({{ effective_state }})."
|
|
|
|
automation:
|
|
- alias: "APT Update Report - Docker Hosts"
|
|
id: apt_update_report_docker_hosts
|
|
description: "Receive docker host APT results and update helpers/logbook."
|
|
mode: queued
|
|
trigger:
|
|
- platform: webhook
|
|
webhook_id: !secret apt_webhook_docker_10
|
|
id: docker_10
|
|
allowed_methods:
|
|
- POST
|
|
local_only: true
|
|
- platform: webhook
|
|
webhook_id: !secret apt_webhook_docker_14
|
|
id: docker_14
|
|
allowed_methods:
|
|
- POST
|
|
local_only: true
|
|
- platform: webhook
|
|
webhook_id: !secret apt_webhook_docker_17
|
|
id: docker_17
|
|
allowed_methods:
|
|
- POST
|
|
local_only: true
|
|
- platform: webhook
|
|
webhook_id: !secret apt_webhook_docker_69
|
|
id: docker_69
|
|
allowed_methods:
|
|
- POST
|
|
local_only: true
|
|
variables:
|
|
host_id: "{{ trigger.id }}"
|
|
payload: "{{ trigger.json | default({}) }}"
|
|
success: "{{ payload.get('success', true) | bool }}"
|
|
updated: "{{ payload.get('updated', false) | bool }}"
|
|
reboot_required: "{{ payload.get('reboot_required', false) | bool }}"
|
|
packages: "{{ payload.get('packages', 0) | int(0) }}"
|
|
message: "{{ payload.get('message', '') | string }}"
|
|
helpers:
|
|
docker_10:
|
|
last_check: input_datetime.apt_docker_10_last_check
|
|
last_update: input_datetime.apt_docker_10_last_update
|
|
last_result: input_text.apt_docker_10_last_result
|
|
docker_14:
|
|
last_check: input_datetime.apt_docker_14_last_check
|
|
last_update: input_datetime.apt_docker_14_last_update
|
|
last_result: input_text.apt_docker_14_last_result
|
|
docker_17:
|
|
last_check: input_datetime.apt_docker_17_last_check
|
|
last_update: input_datetime.apt_docker_17_last_update
|
|
last_result: input_text.apt_docker_17_last_result
|
|
docker_69:
|
|
last_check: input_datetime.apt_docker_69_last_check
|
|
last_update: input_datetime.apt_docker_69_last_update
|
|
last_result: input_text.apt_docker_69_last_result
|
|
host_helpers: "{{ helpers[host_id] if host_id in helpers else none }}"
|
|
result: >-
|
|
{% if not success %}
|
|
ERROR{% if (message | trim) != '' %}: {{ message | trim }}{% endif %}
|
|
{% elif updated %}
|
|
UPDATED {{ packages }} PKGS{% if reboot_required %} (REBOOT REQ){% endif %}
|
|
{% elif reboot_required %}
|
|
NO UPDATES (REBOOT REQ)
|
|
{% else %}
|
|
NO UPDATES
|
|
{% endif %}
|
|
log_message: >-
|
|
{{ host_id }} updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %}; reboot required{% endif %}.
|
|
condition:
|
|
- condition: template
|
|
value_template: "{{ host_helpers is not none }}"
|
|
action:
|
|
- service: input_datetime.set_datetime
|
|
target:
|
|
entity_id: "{{ host_helpers.last_check }}"
|
|
data:
|
|
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ host_helpers.last_result }}"
|
|
data:
|
|
value: "{{ result }}"
|
|
- choose:
|
|
- conditions: "{{ success and updated }}"
|
|
sequence:
|
|
- service: input_datetime.set_datetime
|
|
target:
|
|
entity_id: "{{ host_helpers.last_update }}"
|
|
data:
|
|
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "APT"
|
|
message: "{{ log_message }}"
|
|
|
|
- alias: "Docker State Sync - Repairs (Dynamic)"
|
|
id: docker_state_sync_repairs_dynamic
|
|
description: "Detect Docker container/stack state transitions and delegate Repairs sync."
|
|
mode: queued
|
|
max: 50
|
|
max_exceeded: silent
|
|
trigger:
|
|
- platform: event
|
|
event_type: state_changed
|
|
variables:
|
|
entity_id: "{{ trigger.event.data.entity_id | default('') }}"
|
|
old_state: "{{ (trigger.event.data.old_state.state if trigger.event.data.old_state is not none else '') | lower }}"
|
|
new_state: "{{ (trigger.event.data.new_state.state if trigger.event.data.new_state is not none else '') | lower }}"
|
|
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list }}"
|
|
is_monitored_container_event: >-
|
|
{% set ent = entity_id %}
|
|
{% if ent.startswith('switch.') and (ent.endswith('_container') or ent.endswith('_container_2')) %}
|
|
{{ ent in monitored_switches }}
|
|
{% elif ent.startswith('binary_sensor.') and (ent.endswith('_status') or ent.endswith('_status_2')) %}
|
|
{% set key = ent | replace('binary_sensor.', '') | regex_replace('_status(?:_2)?$', '') %}
|
|
{{ ('switch.' ~ key ~ '_container') in monitored_switches or ('switch.' ~ key ~ '_container_2') in monitored_switches }}
|
|
{% elif ent.startswith('sensor.') and (ent.endswith('_state') or ent.endswith('_state_2')) %}
|
|
{% set key = ent | replace('sensor.', '') | regex_replace('_state(?:_2)?$', '') %}
|
|
{{ ('switch.' ~ key ~ '_container') in monitored_switches or ('switch.' ~ key ~ '_container_2') in monitored_switches }}
|
|
{% else %}
|
|
false
|
|
{% endif %}
|
|
is_monitored_stack_event: >-
|
|
{% set ent = entity_id %}
|
|
{% if ent.startswith('binary_sensor.') and ent.endswith('_stack_status') %}
|
|
{% set stack_key = ent | replace('binary_sensor.', '') | regex_replace('_stack_status$', '') %}
|
|
{{ expand('sensor.' ~ stack_key ~ '_stack_containers_count') | count > 0 }}
|
|
{% else %}
|
|
false
|
|
{% endif %}
|
|
condition:
|
|
- condition: template
|
|
value_template: "{{ trigger.event.data.old_state is not none and trigger.event.data.new_state is not none }}"
|
|
- condition: template
|
|
value_template: "{{ old_state != new_state }}"
|
|
- condition: template
|
|
value_template: "{{ is_monitored_container_event or is_monitored_stack_event }}"
|
|
action:
|
|
- delay:
|
|
seconds: 2
|
|
- condition: template
|
|
value_template: "{{ states(entity_id) | lower == new_state }}"
|
|
- choose:
|
|
- conditions:
|
|
- condition: template
|
|
value_template: "{{ is_monitored_container_event }}"
|
|
sequence:
|
|
- variables:
|
|
down_states: ['off', 'stopped', 'exited', 'dead', 'unknown', 'unavailable']
|
|
- choose:
|
|
- conditions: >-
|
|
{{ new_state in down_states and old_state not in down_states and
|
|
not (is_state('binary_sensor.docker_container_telemetry_degraded', 'on') and
|
|
new_state in ['unknown', 'unavailable']) }}
|
|
sequence:
|
|
- service: script.docker_container_repairs_sync
|
|
data:
|
|
entity_id: "{{ entity_id }}"
|
|
operation: "create"
|
|
delay_minutes: 5
|
|
- conditions: "{{ old_state in down_states and new_state not in down_states }}"
|
|
sequence:
|
|
- service: script.docker_container_repairs_sync
|
|
data:
|
|
entity_id: "{{ entity_id }}"
|
|
operation: "clear"
|
|
- conditions:
|
|
- condition: template
|
|
value_template: "{{ is_monitored_stack_event }}"
|
|
sequence:
|
|
- variables:
|
|
down_states: ['off', 'unknown', 'unavailable']
|
|
- choose:
|
|
- conditions: "{{ new_state in down_states and old_state not in down_states }}"
|
|
sequence:
|
|
- service: script.docker_stack_repairs_sync
|
|
data:
|
|
entity_id: "{{ entity_id }}"
|
|
operation: "create"
|
|
delay_minutes: 2
|
|
- conditions: "{{ old_state in down_states and new_state not in down_states }}"
|
|
sequence:
|
|
- service: script.docker_stack_repairs_sync
|
|
data:
|
|
entity_id: "{{ entity_id }}"
|
|
operation: "clear"
|
|
|
|
- alias: "Docker Repairs Reconcile"
|
|
id: docker_repairs_reconcile
|
|
description: "Reconcile stale container and stack Repairs issues on startup and every 55 minutes."
|
|
mode: queued
|
|
trigger:
|
|
- platform: homeassistant
|
|
event: start
|
|
- platform: time_pattern
|
|
minutes: "/55"
|
|
action:
|
|
- variables:
|
|
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list }}"
|
|
- repeat:
|
|
for_each: "{{ monitored_switches }}"
|
|
sequence:
|
|
- service: script.docker_container_repairs_sync
|
|
data:
|
|
entity_id: "{{ repeat.item }}"
|
|
operation: "clear"
|
|
log_result: false
|
|
- variables:
|
|
stack_status_entities: >-
|
|
{% set ns = namespace(items=[]) %}
|
|
{% for item in states.binary_sensor %}
|
|
{% if item.entity_id is search('^binary_sensor\\..*_stack_status$') %}
|
|
{% set stack_key = item.entity_id | replace('binary_sensor.', '') | regex_replace('_stack_status$', '') %}
|
|
{% if expand('sensor.' ~ stack_key ~ '_stack_containers_count') | count > 0 %}
|
|
{% set ns.items = ns.items + [item.entity_id] %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ ns.items | list }}
|
|
- repeat:
|
|
for_each: "{{ stack_status_entities }}"
|
|
sequence:
|
|
- service: script.docker_stack_repairs_sync
|
|
data:
|
|
entity_id: "{{ repeat.item }}"
|
|
operation: "clear"
|
|
log_result: false
|
|
|
|
- alias: "Docker Containers Maintenance Prompt"
|
|
id: docker_containers_maintenance_prompt
|
|
description: "Prompt Carlo to snooze container alerts for maintenance when more than 3 containers are down."
|
|
mode: single
|
|
trigger:
|
|
- platform: numeric_state
|
|
entity_id: sensor.docker_containers_down_count
|
|
above: 3
|
|
condition:
|
|
- condition: state
|
|
entity_id: binary_sensor.docker_container_alerts_snoozed
|
|
state: "off"
|
|
- condition: template
|
|
value_template: >-
|
|
{% set down_items = state_attr('sensor.docker_containers_down_list', 'down_containers') | default([], true) | list %}
|
|
{{ down_items | count > 3 }}
|
|
action:
|
|
- variables:
|
|
down_items: "{{ state_attr('sensor.docker_containers_down_list', 'down_containers') | default([], true) | list }}"
|
|
down_count: "{{ down_items | count }}"
|
|
- service: script.notify_engine_two_button
|
|
data:
|
|
title: "Docker Maintenance Check"
|
|
value1: "{{ down_count }} containers are currently down."
|
|
value2: "Down: {{ down_items | join(', ') if (down_count | int(0) > 0) else 'none' }}"
|
|
who: "carlo"
|
|
group: "maintenance"
|
|
title1: "Yes, snooze 1h"
|
|
action1: "DOCKER_MAINTENANCE_SNOOZE_1H"
|
|
icon1: "sfsymbols:clock"
|
|
title2: "No, investigate"
|
|
action2: "DOCKER_MAINTENANCE_NOT_MAINTENANCE"
|
|
icon2: "sfsymbols:wrench.and.screwdriver"
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "Maintenance prompt sent to Carlo ({{ down_count }} down: {{ down_items | join(', ') if (down_count | int(0) > 0) else 'none' }})."
|
|
|
|
- alias: "Docker Maintenance Snooze 1H"
|
|
id: docker_maintenance_snooze_1h
|
|
description: "Snooze dynamic container alerts for one hour from a notification action."
|
|
mode: single
|
|
trigger:
|
|
- platform: event
|
|
event_type: mobile_app_notification_action
|
|
event_data:
|
|
action: DOCKER_MAINTENANCE_SNOOZE_1H
|
|
variables:
|
|
snooze_until: "{{ (now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
|
|
action:
|
|
- service: input_datetime.set_datetime
|
|
target:
|
|
entity_id: input_datetime.docker_container_alerts_snooze_until
|
|
data:
|
|
datetime: "{{ snooze_until }}"
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "Container alerts snoozed for 1 hour (until {{ snooze_until }})."
|
|
|
|
- alias: "Docker Maintenance Declined"
|
|
id: docker_maintenance_declined
|
|
description: "Log when maintenance snooze is declined from the dynamic container prompt."
|
|
mode: single
|
|
trigger:
|
|
- platform: event
|
|
event_type: mobile_app_notification_action
|
|
event_data:
|
|
action: DOCKER_MAINTENANCE_NOT_MAINTENANCE
|
|
action:
|
|
- variables:
|
|
down_items: "{{ state_attr('sensor.docker_containers_down_list', 'down_containers') | default([], true) | list }}"
|
|
down_count: "{{ down_items | count }}"
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "Maintenance snooze declined with {{ down_count }} containers down ({{ down_items | join(', ') if (down_count | int(0) > 0) else 'none' }})."
|
|
|
|
- alias: "Docker Telemetry Template Refresh"
|
|
id: docker_telemetry_template_refresh
|
|
description: "Refresh dynamic docker telemetry templates that derive entity IDs at runtime."
|
|
mode: single
|
|
trace:
|
|
stored_traces: 0
|
|
trigger:
|
|
- platform: time_pattern
|
|
minutes: "/1"
|
|
action:
|
|
- service: homeassistant.update_entity
|
|
target:
|
|
entity_id:
|
|
- sensor.docker_monitored_unavailable_count
|
|
- sensor.docker_containers_down_list
|
|
- sensor.docker_containers_down_count
|
|
- binary_sensor.docker_container_telemetry_degraded
|
|
|
|
- alias: "Docker Weekly Prune Unused Images"
|
|
id: docker_weekly_prune_unused_images
|
|
description: "Run weekly unguarded prune actions across Docker hosts."
|
|
mode: single
|
|
trigger:
|
|
- platform: time
|
|
at: "03:15:00"
|
|
condition:
|
|
- condition: time
|
|
weekday:
|
|
- sun
|
|
action:
|
|
- service: button.press
|
|
target:
|
|
entity_id:
|
|
- button.carlo_hass_prune_unused_images
|
|
- button.docker17_prune_unused_images
|
|
- button.docker69_prune_unused_images
|
|
- button.docker2_prune_unused_images
|
|
- service: script.send_to_logbook
|
|
data:
|
|
topic: "DOCKER"
|
|
message: "Weekly scheduled prune triggered on docker_10/17/69/14."
|