#!/usr/bin/env bash # AWOOOI post-reboot machine-readable readiness summary. # Read-only by design: this script never restarts, reloads, repairs, imports, # deletes, patches, or writes remote runtime state. set -uo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" NO_COLOR_FLAG=0 SHOW_LOGS=0 RUN_188_HYGIENE=1 RUN_WAZUH_GATES=1 ARTIFACT_DIR="${ARTIFACT_DIR:-/tmp/awoooi-post-reboot-readiness-$(date '+%Y%m%d-%H%M%S')}" POST_START_QUICK_CHECK_TIMEOUT_SECONDS="${POST_START_QUICK_CHECK_TIMEOUT_SECONDS:-90}" usage() { cat <<'USAGE' Usage: post-reboot-readiness-summary.sh [options] Runs the existing read-only post-reboot checks and prints a stable key/value summary for operators and AI agents. Options: --no-color Disable color in delegated checks. --show-logs Print delegated check logs after the summary. --skip-188-hygiene Skip the 188 host hygiene boundary checklist. --skip-wazuh-gates Skip repo-side Wazuh no-false-green gates. -h, --help Show this help. Exit codes: 0 = core services are green. Boundary blockers may still be present. 2 = core service blocker observed. This script stores read-only evidence logs under ARTIFACT_DIR. USAGE } while [[ $# -gt 0 ]]; do case "$1" in --no-color) NO_COLOR_FLAG=1 ;; --show-logs) SHOW_LOGS=1 ;; --skip-188-hygiene) RUN_188_HYGIENE=0 ;; --skip-wazuh-gates) RUN_WAZUH_GATES=0 ;; -h|--help) usage exit 0 ;; *) printf 'Unknown argument: %s\n' "$1" >&2 usage >&2 exit 2 ;; esac shift done mkdir -p "$ARTIFACT_DIR" run_with_timeout() { local timeout_seconds="$1" shift if command -v timeout >/dev/null 2>&1; then timeout --foreground --kill-after=3s "${timeout_seconds}s" "$@" elif command -v gtimeout >/dev/null 2>&1; then gtimeout --foreground --kill-after=3s "${timeout_seconds}s" "$@" else "$@" fi } extract_key() { local key="$1" local file="$2" awk -F= -v key="$key" '$1 == key {value=$2; found=1} END {if (found) print value; else print ""}' "$file" } extract_summary_number() { local label="$1" local line="$2" sed -nE "s/.*${label}=([0-9]+).*/\\1/p" <<<"$line" | tail -n 1 } extract_named_token() { local name="$1" local line="$2" tr ' ' '\n' <<<"$line" | awk -F= -v name="$name" '$1 == name {print $2; found=1} END {if (!found) print ""}' } post_start_log="$ARTIFACT_DIR/post-start-quick-check.log" post_start_args=(--no-color) if [[ "$NO_COLOR_FLAG" -eq 1 ]]; then post_start_args=(--no-color) fi if [[ "${POST_REBOOT_SKIP_RUNNER_GUARD:-0}" == "1" ]]; then post_start_args+=(--skip-runner-guard) fi post_start_rc=0 post_start_timed_out=0 run_with_timeout "$POST_START_QUICK_CHECK_TIMEOUT_SECONDS" \ bash "$ROOT_DIR/scripts/reboot-recovery/post-start-quick-check.sh" "${post_start_args[@]}" \ >"$post_start_log" 2>&1 || post_start_rc=$? if [[ "$post_start_rc" == "124" || "$post_start_rc" == "137" ]]; then post_start_timed_out=1 printf 'POST_START_QUICK_CHECK_TIMEOUT=1 timeout_seconds=%s\n' \ "$POST_START_QUICK_CHECK_TIMEOUT_SECONDS" >>"$post_start_log" fi post_summary="$(grep -E '^POST_START_QUICK_CHECK PASS=[0-9]+ WARN=[0-9]+ BLOCKED=[0-9]+' "$post_start_log" | tail -n 1 || true)" post_warning_summary="$(grep -E '^POST_START_QUICK_CHECK_WARNINGS SERVICE=[0-9]+ BOUNDARY=[0-9]+ EVIDENCE=[0-9]+' "$post_start_log" | tail -n 1 || true)" post_result="$(grep -E '^RESULT=' "$post_start_log" | tail -n 1 | cut -d= -f2- || true)" post_pass="$(extract_summary_number PASS "$post_summary")" post_warn="$(extract_summary_number WARN "$post_summary")" post_blocked="$(extract_summary_number BLOCKED "$post_summary")" service_warn="$(extract_named_token SERVICE "$post_warning_summary")" boundary_warn="$(extract_named_token BOUNDARY "$post_warning_summary")" evidence_warn="$(extract_named_token EVIDENCE "$post_warning_summary")" service_green=0 if [[ "${post_blocked:-999}" == "0" && "${service_warn:-999}" == "0" ]]; then service_green=1 fi backup_core_green=0 grep -q 'OK backup core blockers are 0' "$post_start_log" && backup_core_green=1 product_data_green=0 if grep -q '^STOCK_FRESHNESS_STATUS ok$' "$post_start_log" \ && grep -q '^DB_DAILY_FRESHNESS ' "$post_start_log"; then product_data_green=1 fi stock_freshness_status="$(awk '$1 == "STOCK_FRESHNESS_STATUS" {value=$2} END {print value}' "$post_start_log")" stock_latest_trading_date="$(awk '$1 == "STOCK_LATEST_TRADING_DATE" {value=$2} END {print value}' "$post_start_log")" stock_blockers="$(grep -E '^STOCK_BLOCKERS ' "$post_start_log" | tail -n 1 | cut -d' ' -f2- || true)" if [[ "${stock_freshness_status:-}" == "ok" && -z "${stock_blockers:-}" ]]; then stock_blockers="none" fi stock_eod_window_pending="$(awk '$1 == "STOCK_EOD_WINDOW_PENDING" {value=$2} END {print value}' "$post_start_log")" stock_eod_classification="$(awk '$1 == "STOCK_EOD_CLASSIFICATION" {value=$2} END {print value}' "$post_start_log")" stock_eod_next_action="$(awk '$1 == "STOCK_EOD_NEXT_ACTION" {value=$2} END {print value}' "$post_start_log")" stock_eod_first_full_window_end="$(awk '$1 == "STOCK_EOD_FIRST_FULL_WINDOW_END_LOCAL" {value=$2} END {print value}' "$post_start_log")" stock_eod_final_retry_window_end="$(awk '$1 == "STOCK_EOD_FINAL_RETRY_WINDOW_END_LOCAL" {value=$2} END {print value}' "$post_start_log")" escrow_missing_count="$(grep -Eo 'escrow_missing=[0-9]+' "$post_start_log" | tail -n 1 | cut -d= -f2 || true)" dr_escrow_blocked=0 escrow_evidence_unknown=0 if [[ -n "$escrow_missing_count" && "$escrow_missing_count" != "0" ]]; then dr_escrow_blocked=1 elif [[ "$post_result" == "FULL_STACK_GREEN_DR_ESCROW_BLOCKED" ]]; then dr_escrow_blocked=1 elif [[ -z "$escrow_missing_count" ]]; then dr_escrow_blocked=1 escrow_evidence_unknown=1 fi host_188_hygiene_blocked="unknown" host_188_service_green="unknown" host_188_runtime_action="0" host_188_result="skipped" host_188_rc=0 if [[ "$RUN_188_HYGIENE" -eq 1 ]]; then host_188_log="$ARTIFACT_DIR/188-host-hygiene.log" bash "$ROOT_DIR/scripts/reboot-recovery/188-host-hygiene-maintenance-checklist.sh" --no-color >"$host_188_log" 2>&1 || host_188_rc=$? host_188_hygiene_blocked="$(extract_key HOST_HYGIENE_BLOCKED "$host_188_log")" host_188_service_green="$(extract_key SERVICE_GREEN "$host_188_log")" host_188_runtime_action="$(extract_key RUNTIME_ACTION_AUTHORIZED "$host_188_log")" host_188_result="$(grep -E '^Result:' "$host_188_log" | tail -n 1 | sed 's/^Result: //')" fi wazuh_registry_accepted="unknown" wazuh_coverage_scope="unknown" wazuh_direct_active="unknown" wazuh_no_transport="unknown" wazuh_ssh_blocked="unknown" wazuh_route_code="unknown" wazuh_transport_count="unknown" wazuh_dashboard_degraded="unknown" wazuh_dashboard_api_connection="unknown" wazuh_dashboard_index_ok="unknown" wazuh_runtime_gate="0" if [[ "$RUN_WAZUH_GATES" -eq 1 ]]; then wazuh_coverage_log="$ARTIFACT_DIR/wazuh-managed-host-coverage.log" wazuh_runtime_log="$ARTIFACT_DIR/wazuh-runtime-gate.log" python3 "$ROOT_DIR/scripts/security/wazuh-managed-host-coverage-gate.py" --root "$ROOT_DIR" >"$wazuh_coverage_log" 2>&1 || true python3 "$ROOT_DIR/scripts/security/wazuh-agent-visibility-runtime-gate.py" --root "$ROOT_DIR" >"$wazuh_runtime_log" 2>&1 || true coverage_line="$(tail -n 1 "$wazuh_coverage_log" || true)" runtime_line="$(tail -n 1 "$wazuh_runtime_log" || true)" wazuh_registry_accepted="$(extract_named_token registry "$coverage_line")" wazuh_coverage_scope="$(extract_named_token scope "$coverage_line")" wazuh_direct_active="$(extract_named_token direct_active "$coverage_line")" wazuh_no_transport="$(extract_named_token no_transport "$coverage_line")" wazuh_ssh_blocked="$(extract_named_token ssh_blocked "$coverage_line")" wazuh_route_code="$(extract_named_token route "$runtime_line")" wazuh_transport_count="$(extract_named_token transport "$runtime_line")" wazuh_dashboard_degraded="$(extract_named_token dashboard_degraded "$runtime_line")" wazuh_dashboard_api_connection="$(extract_named_token api_connection "$runtime_line")" wazuh_dashboard_index_ok="$(extract_named_token index_ok "$runtime_line")" wazuh_runtime_gate="$(extract_named_token runtime_gate "$runtime_line")" fi runtime_action_authorized=0 if [[ "$host_188_runtime_action" =~ ^[1-9][0-9]*$ || "$wazuh_runtime_gate" =~ ^[1-9][0-9]*$ ]]; then runtime_action_authorized=1 fi overall_declaration="GREEN" if [[ "$service_green" != "1" ]]; then overall_declaration="SERVICE_BLOCKED" elif [[ "$product_data_green" != "1" && "$stock_eod_window_pending" == "1" ]]; then overall_declaration="PRODUCT_DATA_PENDING_EOD_WINDOW" elif [[ "$product_data_green" != "1" ]]; then overall_declaration="PRODUCT_DATA_BLOCKED" elif [[ "$dr_escrow_blocked" == "1" ]]; then overall_declaration="FULL_STACK_GREEN_DR_ESCROW_BLOCKED" elif [[ "$host_188_hygiene_blocked" == "1" ]]; then overall_declaration="FULL_STACK_GREEN_HOST_HYGIENE_BLOCKED" elif [[ "$wazuh_registry_accepted" == "0" ]]; then overall_declaration="FULL_STACK_GREEN_SECURITY_REGISTRY_BLOCKED" elif [[ "$evidence_warn" != "0" && -n "$evidence_warn" ]]; then overall_declaration="GREEN_WITH_EVIDENCE_WARNINGS" fi next_required_gates=() if [[ "$product_data_green" != "1" && "$stock_eod_window_pending" == "1" ]]; then next_required_gates+=("stockplatform_eod_window_completion") elif [[ "$product_data_green" != "1" ]]; then next_required_gates+=("product_data_freshness_recovery") fi [[ "$backup_core_green" != "1" ]] && next_required_gates+=("backup_core_readback_recovery") [[ "$post_start_timed_out" == "1" ]] && next_required_gates+=("post_start_quick_check_timeout_recovery") [[ "$dr_escrow_blocked" == "1" ]] && next_required_gates+=("credential_escrow_evidence") [[ "$host_188_hygiene_blocked" == "1" ]] && next_required_gates+=("host_188_hygiene_maintenance_window") [[ "$wazuh_registry_accepted" == "0" ]] && next_required_gates+=("wazuh_manager_registry_export") if [[ "${#next_required_gates[@]}" -eq 0 ]]; then next_required_gates_csv="none" else next_required_gates_csv="$(IFS=,; echo "${next_required_gates[*]}")" fi summary_file="$ARTIFACT_DIR/summary.txt" { cat <