Files
vps/runbooks/scripts/ha-maintenance.sh
T

181 lines
5.8 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# ha-maintenance.sh — Home Assistant maintenance via the `ha` supervisor CLI.
#
# Target : hass.windy.lan (HAOS / Core), SSH user `hassio`.
# Access : non-interactive `sudo -n -i ha <cmd>`; the root login environment
# carries the supervisor API token (interactive login loads
# ~hassio/.zprofile which runs `exec sudo -i`). The whole remote
# procedure runs in ONE `sudo -n -i bash -s` login so the SSH & Web
# Terminal MOTD is printed only once, then stripped locally.
# See hosts/hass.windy.lan.md and runbooks/home-assistant-maintenance.md.
#
# Usage:
# ./ha-maintenance.sh read-only health snapshot
# ./ha-maintenance.sh --check-config validate core configuration
# ./ha-maintenance.sh --logs core|supervisor|host|apps:<slug> [N]
# ./ha-maintenance.sh --update --yes refresh + update core(--backup)/supervisor/os
# ./ha-maintenance.sh --restart-core [--safe-mode] --yes
# ./ha-maintenance.sh --rebuild-core --yes rebuild core image (after options change)
# ./ha-maintenance.sh --rollback-os --yes boot the previous OS slot (A/B rollback)
# ./ha-maintenance.sh --backup [NAME] --yes full backup (optionally named)
# ./ha-maintenance.sh --restore <slug> --yes restore a backup (DESTRUCTIVE)
# ./ha-maintenance.sh --app start|stop|restart|update <slug> --yes
#
# Environment: HA_HOST (default hass.windy.lan), HA_SSH_USER (default hassio).
# Mutating actions always require --yes; this script never prompts.
set -uo pipefail
HOST="${HA_HOST:-hass.windy.lan}"
SSH_USER="${HA_SSH_USER:-hassio}"
MODE="health"
TARGET="core"
LINES=100
SAFE_MODE=0
CONFIRM=0
ARG1=""
ARG2=""
while [ $# -gt 0 ]; do
case "$1" in
--check-config) MODE="check-config" ;;
--logs)
MODE="logs"
if [ $# -gt 1 ]; then TARGET="$2"; shift; fi
if [ $# -gt 1 ] && [[ "$2" =~ ^[0-9]+$ ]]; then LINES="$2"; shift; fi
;;
--update) MODE="update" ;;
--restart-core) MODE="restart-core" ;;
--safe-mode) SAFE_MODE=1 ;;
--rebuild-core) MODE="rebuild-core" ;;
--rollback-os) MODE="rollback-os" ;;
--reboot) MODE="reboot" ;;
--backup)
MODE="backup"
if [ $# -gt 1 ] && [[ "$2" != --* ]]; then ARG1="$2"; shift; fi
;;
--restore)
MODE="restore"
if [ $# -gt 1 ] && [[ "$2" != --* ]]; then ARG1="$2"; shift
else echo "ha-maintenance: --restore needs a backup slug" >&2; exit 2; fi
;;
--app)
MODE="app"
if [ $# -gt 1 ] && [[ "$2" != --* ]]; then ARG1="$2"; shift; fi
if [ $# -gt 1 ] && [[ "$2" != --* ]]; then ARG2="$2"; shift; fi
;;
--yes) CONFIRM=1 ;;
*)
echo "ha-maintenance: unknown option '$1'" >&2
echo "usage: $0 [--check-config|--logs TARGET [N]|--update|--restart-core [--safe-mode]|--rebuild-core|--rollback-os|--reboot|--backup [NAME]|--restore SLUG|--app ACTION SLUG] [--yes]" >&2
exit 2
;;
esac
shift
done
if [ "$MODE" = "app" ]; then
case "$ARG1" in
start|stop|restart|update) ;;
*) echo "ha-maintenance: --app action must be start|stop|restart|update (got '$ARG1')" >&2; exit 2 ;;
esac
if [ -z "$ARG2" ]; then
echo "ha-maintenance: --app needs an app slug, e.g. --app restart core_mosquitto" >&2
exit 2
fi
fi
case "$MODE" in
update|restart-core|rebuild-core|rollback-os|reboot|backup|restore|app)
if [ "$CONFIRM" -ne 1 ]; then
echo "ha-maintenance: refusing '$MODE' without --yes (mutating action)" >&2
exit 1
fi
;;
esac
REMOTE_SCRIPT=$(cat <<'EOF'
set -u
ha() { command ha --no-progress "$@"; }
case "$1" in
health)
printf '\n=== CORE ===\n'
ha core info
printf '\n=== SUPERVISOR ===\n'
ha supervisor info | grep -E '^(healthy|supported|version|version_latest|update_available|auto_update|timezone|logging):'
printf '\n=== ADDON STATES ===\n'
ha supervisor info | grep -E '^ (name|state):'
printf '\n=== OS ===\n'
ha os info
printf '\n=== HOST ===\n'
ha host info
printf '\n=== NETWORK ===\n'
ha network info | grep -E '^(address|ip_address|supervisor_internet|vlan|wifi):'
printf '\n=== AVAILABLE UPDATES ===\n'
ha available-updates
printf '\n=== JOBS ===\n'
ha jobs info
printf '\n=== RESOLUTION ===\n'
ha resolution info
printf '\n=== CORE STATS ===\n'
ha core stats
;;
check-config)
ha core check
;;
logs)
case "$2" in
core) ha core logs -n "$3" ;;
supervisor) ha supervisor logs -n "$3" ;;
host) ha host logs -n "$3" ;;
apps:*) ha apps logs -n "$3" "${2#apps:}" ;;
*) echo "unknown log target: $2" >&2; exit 2 ;;
esac
;;
update)
ha refresh-updates
printf '\n=== UPDATE CORE (with backup) ===\n'; ha core update --backup
printf '\n=== UPDATE SUPERVISOR ===\n'; ha supervisor update
printf '\n=== UPDATE OS ===\n'; ha os update
printf '\n=== PENDING AFTER ===\n'; ha available-updates
;;
restart-core)
if [ "$4" = "1" ]; then ha core restart --safe-mode; else ha core restart; fi
;;
rebuild-core)
ha core rebuild
;;
rollback-os)
ha os boot-slot other
;;
reboot)
ha host reboot
;;
backup)
if [ -n "${2:-}" ]; then ha backups new --name "$2"; else ha backups new; fi
;;
restore)
echo "WARNING: restoring backup $2"
ha backups restore "$2"
;;
app)
ha apps "$2" "$3"
;;
esac
EOF
)
if [ "$MODE" = "logs" ]; then
ARG1="$TARGET"
ARG2="$LINES"
fi
# One remote login shell (MOTD printed once), then drop the MOTD locally.
printf '%s\n' "$REMOTE_SCRIPT" \
| ssh -o BatchMode=yes -o ConnectTimeout=10 "${SSH_USER}@${HOST}" \
'sudo -n -i bash -s' "$MODE" "$ARG1" "$ARG2" "$SAFE_MODE" 2>/dev/null \
| awk 'BEGIN{on=0} /^System is ready/{on=1; next} on'