Files
awoooi/scripts/reboot-recovery/188-host-hygiene-maintenance-checklist.sh
Your Name 709825a89f
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
fix(recovery): bound post reboot summary readbacks
2026-06-30 20:22:51 +08:00

354 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# 188 host hygiene 維護前只讀檢查。
# 本腳本不會 restart、reload、renew、reset-failed、restore、pg_resetwal 或修改遠端狀態。
set -uo pipefail
REMOTE_188="${REMOTE_188:-ollama@192.168.0.188}"
REMOTE_110="${REMOTE_110:-wooo@192.168.0.110}"
SSH_BATCH_MODE="${SSH_BATCH_MODE:-yes}"
SSH_STRICT_HOST_KEY_CHECKING="${SSH_STRICT_HOST_KEY_CHECKING:-accept-new}"
SSH_CONNECT_TIMEOUT="${SSH_CONNECT_TIMEOUT:-8}"
SSH_COMMAND_TIMEOUT_SECONDS="${SSH_COMMAND_TIMEOUT_SECONDS:-25}"
NO_COLOR=0
usage() {
cat <<'USAGE'
Usage: bash scripts/reboot-recovery/188-host-hygiene-maintenance-checklist.sh [--no-color]
Read-only pre-maintenance checklist for host 188 hygiene issues.
It prints evidence for host PostgreSQL, certbot, product containers, backup core,
and route health. It never runs pg_resetwal, certbot renew, reset-failed, restore,
Nginx reload, Docker/systemd restart, or any host write.
Environment:
REMOTE_188=ollama@192.168.0.188
REMOTE_110=wooo@192.168.0.110
SSH_BATCH_MODE=yes
SSH_STRICT_HOST_KEY_CHECKING=accept-new
SSH_CONNECT_TIMEOUT=8
SSH_COMMAND_TIMEOUT_SECONDS=25
USAGE
}
while [ "$#" -gt 0 ]; do
case "$1" in
--no-color)
NO_COLOR=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 64
;;
esac
done
if [ "$NO_COLOR" = "1" ]; then
green=""
yellow=""
red=""
blue=""
reset=""
else
green="$(printf '\033[32m')"
yellow="$(printf '\033[33m')"
red="$(printf '\033[31m')"
blue="$(printf '\033[34m')"
reset="$(printf '\033[0m')"
fi
PASS=0
WARN=0
BLOCKED=0
SERVICE_GREEN=1
HOST_HYGIENE_BLOCKED=0
ssh_opts=(
-o BatchMode="$SSH_BATCH_MODE"
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
-o ConnectionAttempts=1
-o ServerAliveInterval=5
-o ServerAliveCountMax=1
-o PreferredAuthentications=publickey
-o NumberOfPasswordPrompts=0
-o StrictHostKeyChecking="$SSH_STRICT_HOST_KEY_CHECKING"
)
local_ip_list() {
{
hostname -I 2>/dev/null | tr ' ' '\n' || true
ip -o -4 addr show 2>/dev/null | awk '{split($4,a,"/"); print a[1]}' || true
ifconfig 2>/dev/null | awk '$1 == "inet" {print $2}' || true
} | awk 'NF'
}
is_local_target() {
local target="$1"
local host="${target##*@}"
local ips
[[ "$host" == "127.0.0.1" || "$host" == "localhost" ]] && return 0
ips="$(local_ip_list)"
grep -Fxq "$host" <<<"$ips"
}
section() {
printf "\n%s=== %s ===%s\n" "$blue" "$1" "$reset"
}
ok() {
PASS=$((PASS + 1))
printf "%sOK%s %s\n" "$green" "$reset" "$*"
}
warn() {
WARN=$((WARN + 1))
printf "%sWARN%s %s\n" "$yellow" "$reset" "$*"
}
blocked() {
BLOCKED=$((BLOCKED + 1))
HOST_HYGIENE_BLOCKED=1
printf "%sBLOCKED%s %s\n" "$red" "$reset" "$*"
}
ssh_cmd() {
local target="$1"
local command="$2"
local quoted_command=""
if is_local_target "$target"; then
bash -lc "$command"
return $?
fi
printf -v quoted_command '%q' "$command"
ssh "${ssh_opts[@]}" "$target" \
"if command -v timeout >/dev/null 2>&1; then timeout ${SSH_COMMAND_TIMEOUT_SECONDS}s bash -lc ${quoted_command}; else bash -lc ${quoted_command}; fi"
}
echo "AWOOOI 188 host hygiene maintenance checklist"
date '+%Y-%m-%d %H:%M:%S %Z'
echo "Scope: 188 host PostgreSQL 14/main, startup unit, certbot / ACME hygiene."
echo "RUNTIME_ACTION_AUTHORIZED=0"
section "188 host state"
if out=$(ssh_cmd "$REMOTE_188" '
hostname
date --iso-8601=seconds
uptime
echo "SYSTEMD_STATE $(systemctl is-system-running || true)"
systemctl --failed --no-pager --plain || true
df -h /
free -h
' 2>&1); then
echo "$out"
ok "188 SSH readable"
if grep -q 'SYSTEMD_STATE running' <<<"$out"; then
ok "188 systemd running"
else
blocked "188 systemd is not fully running; failed units require owner disposition"
fi
grep -q 'postgresql@14-main.service' <<<"$out" && blocked "host PostgreSQL failed unit visible" || ok "host PostgreSQL failed unit not visible"
grep -q 'certbot.service' <<<"$out" && blocked "apt certbot failed unit visible" || ok "apt certbot failed unit not visible"
grep -q 'snap.certbot.renew.service' <<<"$out" && blocked "snap certbot renew failed unit visible" || ok "snap certbot renew failed unit not visible"
else
SERVICE_GREEN=0
blocked "188 SSH host state unavailable"
echo "$out"
fi
section "188 product container state"
if out=$(ssh_cmd "$REMOTE_188" '
docker ps --format "{{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || true
' 2>&1); then
echo "$out"
grep -q '^momo-pro-system[[:space:]].*healthy' <<<"$out" && ok "MOMO Pro container healthy" || { SERVICE_GREEN=0; blocked "MOMO Pro container not confirmed healthy"; }
grep -q '^momo-db[[:space:]].*Up' <<<"$out" && ok "MOMO DB container up" || { SERVICE_GREEN=0; blocked "MOMO DB container not confirmed up"; }
grep -q '^vibework-production-web-1[[:space:]].*healthy' <<<"$out" && ok "VibeWork production web container healthy" || warn "VibeWork production web container health not confirmed"
else
SERVICE_GREEN=0
blocked "188 docker ps unavailable"
echo "$out"
fi
section "188 host PostgreSQL 14/main"
if out=$(ssh_cmd "$REMOTE_188" '
pg_lsclusters 2>/dev/null || true
systemctl status postgresql@14-main.service --no-pager || true
echo "PG_ISREADY_LOCAL $(pg_isready -h localhost -p 5432 2>/dev/null || true)"
echo "RECOVERY_CONTAINER $(docker inspect -f "{{.State.Running}} {{.HostConfig.NetworkMode}} {{.HostConfig.RestartPolicy.Name}}" k3s-postgres-recovery 2>/dev/null || echo missing)"
' 2>&1); then
echo "$out"
recovery_container_ready=0
if grep -q '^RECOVERY_CONTAINER true host ' <<<"$out" && grep -q 'PG_ISREADY_LOCAL .*accepting connections' <<<"$out"; then
recovery_container_ready=1
fi
if grep -Eq '^14[[:space:]]+main[[:space:]]+5432[[:space:]]+down' <<<"$out"; then
if [[ "$recovery_container_ready" -eq 1 ]]; then
warn "host PostgreSQL cluster 14/main is down, but controlled k3s-postgres-recovery runtime is accepting connections"
else
blocked "host PostgreSQL cluster 14/main is down and no controlled recovery runtime was accepted"
fi
else
ok "host PostgreSQL cluster 14/main not reported down"
fi
if grep -Eiq 'invalid primary checkpoint record|could not locate a valid checkpoint record|PANIC:' <<<"$out"; then
if [[ "$recovery_container_ready" -eq 1 ]]; then
warn "PostgreSQL checkpoint/WAL error remains historical host-cluster evidence; pg_resetwal is still break-glass only"
else
blocked "PostgreSQL checkpoint/WAL error detected; pg_resetwal is break-glass only"
fi
fi
if [[ "$recovery_container_ready" -eq 1 ]]; then
ok "PostgreSQL runtime is provided by k3s-postgres-recovery on host network"
elif grep -q 'PG_ISREADY_LOCAL .*accepting connections' <<<"$out"; then
warn "pg_isready accepts on localhost; do not use this alone as host 14/main health"
fi
else
blocked "PostgreSQL evidence unavailable"
echo "$out"
fi
section "188 certbot / ACME"
if out=$(ssh_cmd "$REMOTE_188" '
systemctl show certbot.service snap.certbot.renew.service certbot.timer snap.certbot.renew.timer -p Id -p ActiveState -p SubState -p Result -p UnitFileState --no-pager || true
systemctl list-timers --all --no-pager | grep -i certbot || true
' 2>&1); then
echo "$out"
if grep -q 'Id=certbot.service' <<<"$out" && grep -A3 'Id=certbot.service' <<<"$out" | grep -q 'Result=failed'; then
blocked "apt certbot service currently failed"
else
ok "apt certbot service is not currently failed"
fi
if grep -q 'Id=snap.certbot.renew.service' <<<"$out" && grep -A3 'Id=snap.certbot.renew.service' <<<"$out" | grep -q 'Result=failed'; then
blocked "snap certbot renew service currently failed"
else
ok "snap certbot renew service is not currently failed"
fi
if grep -A4 'Id=certbot.timer' <<<"$out" | grep -q 'UnitFileState=disabled'; then
ok "legacy apt certbot timer disabled to avoid duplicate renewals"
else
warn "legacy apt certbot timer is not disabled"
fi
if grep -A4 'Id=snap.certbot.renew.timer' <<<"$out" | grep -q 'ActiveState=active' && grep -A4 'Id=snap.certbot.renew.timer' <<<"$out" | grep -q 'UnitFileState=enabled'; then
ok "snap certbot renew timer enabled"
else
blocked "snap certbot renew timer is not enabled and active"
fi
else
blocked "certbot status unavailable"
echo "$out"
fi
cert_out="$(openssl s_client -servername sentry.wooo.work -connect sentry.wooo.work:443 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates 2>/dev/null || true)"
printf '%s\n' "$cert_out"
if grep -q 'notAfter=' <<<"$cert_out"; then
ok "sentry.wooo.work public certificate readable"
else
warn "sentry.wooo.work public certificate not readable"
fi
section "110 backup / escrow boundary"
if out=$(ssh_cmd "$REMOTE_110" '
test -x /backup/scripts/backup-status.sh && /backup/scripts/backup-status.sh --no-notify --no-refresh
' 2>&1); then
echo "$out"
grep -q 'core_blockers=0' <<<"$out" && ok "backup core blockers remain zero" || blocked "backup core blockers not zero"
grep -q 'escrow_missing=0' <<<"$out" && ok "credential escrow complete" || warn "credential escrow still incomplete; DR_COMPLETE remains blocked"
else
warn "110 backup status unavailable"
echo "$out"
fi
section "Public route health"
route_fail=0
for url in \
https://mo.wooo.work/health \
https://signoz.wooo.work/ \
https://vibework.wooo.work/ \
https://awoooi.wooo.work/api/v1/health
do
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 12 "$url" 2>/dev/null || echo 000)"
printf 'PUBLIC_ROUTE %s %s\n' "$code" "$url"
case "$code" in
2??|3??) ;;
*) route_fail=1 ;;
esac
done
if [ "$route_fail" -eq 0 ]; then
ok "critical public routes respond"
else
SERVICE_GREEN=0
blocked "one or more critical public routes failed"
fi
section "Maintenance decision tree"
if [ "$SERVICE_GREEN" -eq 1 ] && [ "$HOST_HYGIENE_BLOCKED" -eq 0 ]; then
cat <<'STEPS'
Current expected outcome:
SERVICE_GREEN=1
HOST_HYGIENE_BLOCKED=0
RESULT=HOST_188_HYGIENE_GREEN
Allowed next step:
1. Keep this host in the normal post-reboot summary.
2. Wait for snap certbot timer / ACME-window readback before declaring formal certificate renewal success.
3. Keep DR credential escrow and Wazuh registry evidence as separate blockers.
Forbidden without separate approval:
- pg_resetwal
- DB restore
- Docker/systemd restart
- firewall change
- Wazuh active response or agent re-enroll
STEPS
else
cat <<'STEPS'
Current expected outcome when 188 service is green but host hygiene is not:
SERVICE_GREEN=1
HOST_HYGIENE_BLOCKED=1
RESULT=SERVICE_GREEN_HOST_HYGIENE_BLOCKED
Allowed next step:
1. Collect owner disposition for host PostgreSQL 14/main:
- retired cluster, restore from backup, or break-glass repair.
2. Collect DNS / ACME / certificate owner evidence before certbot renew.
3. Open an explicit maintenance window with rollback owner and post-check.
Forbidden without maintenance approval:
- pg_resetwal
- systemctl reset-failed
- certbot renew
- nginx reload
- DB restore
- Docker/systemd restart
- host file write
STEPS
fi
echo
echo "SERVICE_GREEN=$SERVICE_GREEN"
echo "HOST_HYGIENE_BLOCKED=$HOST_HYGIENE_BLOCKED"
echo "RUNTIME_ACTION_AUTHORIZED=0"
echo "PASS=$PASS WARN=$WARN BLOCKED=$BLOCKED"
if [ "$SERVICE_GREEN" -eq 1 ] && [ "$HOST_HYGIENE_BLOCKED" -eq 1 ]; then
echo "Result: SERVICE_GREEN_HOST_HYGIENE_BLOCKED. Open maintenance window; do not auto-repair."
exit 1
fi
if [ "$BLOCKED" -gt 0 ]; then
echo "Result: BLOCKED. Service or host evidence needs triage."
exit 2
fi
echo "Result: HOST_188_HYGIENE_GREEN."
exit 0