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
354 lines
11 KiB
Bash
Executable File
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
|