diff --git a/config/packages/README.md b/config/packages/README.md index 8aa2c126..3c081403 100755 --- a/config/packages/README.md +++ b/config/packages/README.md @@ -45,7 +45,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this | [lightning.yaml](lightning.yaml) | Blitzortung lightning counter monitoring with snoozeable push actions. | `sensor.blitzortung_lightning_counter`, `input_boolean.snooze_lightning`, notify engine actions | | [logbook_activity_feed.yaml](logbook_activity_feed.yaml) | Dummy `sensor.activity_feed` + helper to write clean Activity entries (Issue #1550). | `sensor.activity_feed`, `script.send_to_logbook` | | [mariadb_monitoring.yaml](mariadb_monitoring.yaml) | MariaDB health sensors and Lovelace dashboard snippet for recorder stats. | `sensor.mariadb_status`, `sensor.database_size` | -| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching + container-down Repairs alerts. | `sensor.docker_*_apt_status`, `repairs.create` | +| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching + staggered auto-reboot flow + container-down Repairs alerts. | `sensor.docker_*_apt_status`, `repairs.create`, `repairs.remove` | | [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` | | [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts. | `persistent_notification.create`, `input_datetime.tugtainer_last_update` | | [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` | diff --git a/config/packages/docker_infrastructure.yaml b/config/packages/docker_infrastructure.yaml index 55b38571..090c4ca5 100644 --- a/config/packages/docker_infrastructure.yaml +++ b/config/packages/docker_infrastructure.yaml @@ -7,6 +7,8 @@ # APT webhook results and container down repairs. # ------------------------------------------------------------------- # Notes: Hosts run weekly Wed 12:00 APT job and POST JSON to webhooks. +# Notes: If reboot is required, hosts auto-schedule reboot after posting status. +# Notes: Reboot staggering: docker_14 reboots first, docker_10 reboots later. # Notes: Container watchlist is explicit; extend entity list as needed. ###################################################################### @@ -179,17 +181,21 @@ automation: updated: "{{ payload.get('updated', false) | bool }}" packages: "{{ payload.get('packages', 0) | int(0) }}" reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + auto_reboot_scheduled: "{{ payload.get('auto_reboot_scheduled', false) | bool }}" + reboot_delay_minutes: "{{ payload.get('reboot_delay_minutes', 0) | int(0) }}" message: "{{ payload.get('message', '') | string }}" result: >- {% if not success %} ERROR{% if (message | trim) != '' %}: {{ message | trim }}{% endif %} + {% elif reboot_required and not updated %} + REBOOT REQUIRED{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% elif updated %} - UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %} + UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %}{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% else %} NO UPDATES {% endif %} log_message: >- - docker_10 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}. + docker_10 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}{% if auto_reboot_scheduled %}; auto reboot in {{ reboot_delay_minutes }} minute{% if reboot_delay_minutes != 1 %}s{% endif %}{% endif %}. action: - service: input_datetime.set_datetime target: @@ -240,17 +246,21 @@ automation: updated: "{{ payload.get('updated', false) | bool }}" packages: "{{ payload.get('packages', 0) | int(0) }}" reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + auto_reboot_scheduled: "{{ payload.get('auto_reboot_scheduled', false) | bool }}" + reboot_delay_minutes: "{{ payload.get('reboot_delay_minutes', 0) | int(0) }}" message: "{{ payload.get('message', '') | string }}" result: >- {% if not success %} ERROR{% if (message | trim) != '' %}: {{ message | trim }}{% endif %} + {% elif reboot_required and not updated %} + REBOOT REQUIRED{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% elif updated %} - UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %} + UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %}{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% else %} NO UPDATES {% endif %} log_message: >- - docker_14 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}. + docker_14 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}{% if auto_reboot_scheduled %}; auto reboot in {{ reboot_delay_minutes }} minute{% if reboot_delay_minutes != 1 %}s{% endif %}{% endif %}. action: - service: input_datetime.set_datetime target: @@ -301,17 +311,21 @@ automation: updated: "{{ payload.get('updated', false) | bool }}" packages: "{{ payload.get('packages', 0) | int(0) }}" reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + auto_reboot_scheduled: "{{ payload.get('auto_reboot_scheduled', false) | bool }}" + reboot_delay_minutes: "{{ payload.get('reboot_delay_minutes', 0) | int(0) }}" message: "{{ payload.get('message', '') | string }}" result: >- {% if not success %} ERROR{% if (message | trim) != '' %}: {{ message | trim }}{% endif %} + {% elif reboot_required and not updated %} + REBOOT REQUIRED{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% elif updated %} - UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %} + UPDATED {{ packages }} PKGS{% if reboot_required %}; REBOOT REQUIRED{% endif %}{% if auto_reboot_scheduled %}; AUTO REBOOT {{ reboot_delay_minutes }}M{% endif %} {% else %} NO UPDATES {% endif %} log_message: >- - docker_69 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}. + docker_69 updated {{ packages }} package{% if packages != 1 %}s{% endif %}{% if reboot_required %} (reboot required){% endif %}{% if auto_reboot_scheduled %}; auto reboot in {{ reboot_delay_minutes }} minute{% if reboot_delay_minutes != 1 %}s{% endif %}{% endif %}. action: - service: input_datetime.set_datetime target: @@ -346,6 +360,105 @@ automation: topic: "APT" message: "{{ log_message }}" + - alias: "APT Boot Report - docker_10" + id: apt_boot_report_docker_10 + description: "Clear or keep reboot-required flag after docker_10 boots." + mode: queued + trigger: + - platform: webhook + webhook_id: !secret apt_boot_webhook_docker_10 + allowed_methods: + - POST + local_only: true + variables: + payload: "{{ trigger.json | default({}) }}" + reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + action: + - choose: + - conditions: "{{ reboot_required }}" + sequence: + - service: input_boolean.turn_on + target: + entity_id: input_boolean.apt_docker_10_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_10 boot report: reboot is still required." + default: + - service: input_boolean.turn_off + target: + entity_id: input_boolean.apt_docker_10_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_10 reboot completed and reboot flag cleared." + + - alias: "APT Boot Report - docker_14" + id: apt_boot_report_docker_14 + description: "Clear or keep reboot-required flag after docker_14 boots." + mode: queued + trigger: + - platform: webhook + webhook_id: !secret apt_boot_webhook_docker_14 + allowed_methods: + - POST + local_only: true + variables: + payload: "{{ trigger.json | default({}) }}" + reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + action: + - choose: + - conditions: "{{ reboot_required }}" + sequence: + - service: input_boolean.turn_on + target: + entity_id: input_boolean.apt_docker_14_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_14 boot report: reboot is still required." + default: + - service: input_boolean.turn_off + target: + entity_id: input_boolean.apt_docker_14_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_14 reboot completed and reboot flag cleared." + + - alias: "APT Boot Report - docker_69" + id: apt_boot_report_docker_69 + description: "Clear or keep reboot-required flag after docker_69 boots." + mode: queued + trigger: + - platform: webhook + webhook_id: !secret apt_boot_webhook_docker_69 + allowed_methods: + - POST + local_only: true + variables: + payload: "{{ trigger.json | default({}) }}" + reboot_required: "{{ payload.get('reboot_required', false) | bool }}" + action: + - choose: + - conditions: "{{ reboot_required }}" + sequence: + - service: input_boolean.turn_on + target: + entity_id: input_boolean.apt_docker_69_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_69 boot report: reboot is still required." + default: + - service: input_boolean.turn_off + target: + entity_id: input_boolean.apt_docker_69_reboot_required + - service: script.send_to_logbook + data: + topic: "APT" + message: "docker_69 reboot completed and reboot flag cleared." + - alias: "APT Reboot Repairs" id: apt_reboot_repairs description: "Create or clear Repairs issues when Docker hosts need a reboot." diff --git a/config/shell_scripts/README.md b/config/shell_scripts/README.md index 1ea04354..96994ee7 100755 --- a/config/shell_scripts/README.md +++ b/config/shell_scripts/README.md @@ -27,6 +27,8 @@ Longer-running shell helpers referenced by automations, packages, or cron. Anyth | File | Why it matters | | --- | --- | | [HAUpdate.sh](HAUpdate.sh) | One-command Home Assistant update helper. | +| [apt_weekly.sh](apt_weekly.sh) | Weekly APT updater that posts webhook status and can schedule reboot when needed. | +| [apt_reboot_report.sh](apt_reboot_report.sh) | Boot-time webhook status reporter that clears/keeps reboot-required state in HA. | | [gitupdate.sh](gitupdate.sh) | Pull the latest config changes on demand. | | [basketball.yaml](basketball.yaml) | ESPN stat scraping helper used by sensors. | | [Jinja Code.py](Jinja Code.py) | Reference Jinja snippets for templating. | diff --git a/config/shell_scripts/apt_reboot_report.service.sample b/config/shell_scripts/apt_reboot_report.service.sample new file mode 100644 index 00000000..c503888c --- /dev/null +++ b/config/shell_scripts/apt_reboot_report.service.sample @@ -0,0 +1,13 @@ +[Unit] +Description=Report boot completion for APT reboot tracking +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/apt_reboot_report.sh https://HOMEASSISTANT_HOST:8123/api/webhook/REPLACE_WITH_BOOT_WEBHOOK_ID docker_10 +User=root +Group=root + +[Install] +WantedBy=multi-user.target diff --git a/config/shell_scripts/apt_reboot_report.sh b/config/shell_scripts/apt_reboot_report.sh new file mode 100644 index 00000000..fb48487f --- /dev/null +++ b/config/shell_scripts/apt_reboot_report.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Boot report for APT-managed hosts. +# Sends current reboot-required state after each system boot. + +WEBHOOK_URL="$1" +HOST_NAME="${2:-$(hostname -s)}" + +if [[ -z "$WEBHOOK_URL" ]]; then + echo "Usage: $0 [host_name]" >&2 + exit 1 +fi + +REBOOT_REQUIRED=false +if [[ -f /var/run/reboot-required ]]; then + REBOOT_REQUIRED=true +fi + +payload=$(cat <" >&2 + echo "Usage: $0 [host_name] [reboot_delay_minutes]" >&2 exit 1 fi +AUTO_REBOOT=false +if [[ -n "$REBOOT_DELAY_MINUTES" ]]; then + if [[ "$REBOOT_DELAY_MINUTES" =~ ^[0-9]+$ ]]; then + AUTO_REBOOT=true + else + echo "reboot_delay_minutes must be a non-negative integer" >&2 + exit 1 + fi +fi + log() { echo "[$(date --iso-8601=seconds)] $*"; } UPDATED=false @@ -45,10 +57,13 @@ fi payload=$(cat </dev/null 2>&1 || true + if [[ "$REBOOT_DELAY_MINUTES" -eq 0 ]]; then + log "Reboot required; rebooting immediately." + shutdown -r now "APT weekly maintenance reboot" + else + log "Reboot required; scheduling reboot in ${REBOOT_DELAY_MINUTES} minute(s)." + shutdown -r +"$REBOOT_DELAY_MINUTES" "APT weekly maintenance reboot" + fi +fi