#!/bin/bash # # vm109-patch-window.sh — fereastră lunară de patching pentru VM 109 (DR Oracle). # # De ce există: incident 2026-08-08. # VM 109 este pornit doar în timpul testului DR săptămânal (Sâmbătă 06:00). # Prin urmare acela era singurul moment în care Windows Update putea rula — # iar 06:00 este în afara Active Hours (08:00-17:00), deci Windows îl trata ca # fereastră de mentenanță validă. KB5101001 a fost descărcat în timpul testului # din 2026-08-01, a rămas staged după `qm stop`, și s-a finalizat la boot-ul # testului din 2026-08-08: RestartManager a omorât powershell.exe (restore-ul) # și sshd, VM-ul a rebootat la 72s după boot, iar testul a raportat # "Restore failed" fără niciun log RMAN. # # Soluția: VM 109 are acum NoAutoUpdate=1 (Windows Update nu mai pornește # singur), iar patching-ul se face aici — într-o fereastră dedicată, unde # reboot-urile sunt așteptate și inofensive. # # Instalare (pe nodul care găzduiește VM 109 — și pe pvemini, pentru failover): # cp vm109-patch-window.sh /opt/scripts/ # chmod +x /opt/scripts/vm109-patch-window.sh # crontab -e # # Prima duminică din lună, 03:00 — cron face OR între DOM și DOW, # # deci restricția pe duminică se face în script (GUARD_FIRST_SUNDAY). # 0 3 1-7 * * /opt/scripts/vm109-patch-window.sh >/dev/null 2>&1 # # Rulare manuală (ignoră garda de calendar): # /opt/scripts/vm109-patch-window.sh --now set -euo pipefail export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" DR_VM_ID="109" DR_VM_IP="10.0.20.37" DR_VM_PORT="22122" DR_VM_USER="romfast" DEBUG_FLAG="/var/run/vm109-debug.flag" LOG="/var/log/oracle-dr/patch-window.log" MAX_BOOT_WAIT=300 # secunde de așteptare pentru SSH după boot/reboot MAX_REBOOTS=3 # cicluri de reboot permise într-o fereastră MAIL_TO="root" mkdir -p "$(dirname "$LOG")" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; } # Rulăm doar pe nodul care găzduiește efectiv VM 109 (cluster-aware, ca watchdog-ul). [ -f "/etc/pve/qemu-server/${DR_VM_ID}.conf" ] || exit 0 # Garda de calendar: prima duminică din lună. Cron nu poate exprima asta singur. if [ "${1:-}" != "--now" ]; then if [ "$(date +%u)" != "7" ]; then exit 0; fi if [ "$(date +%-d)" -gt 7 ]; then exit 0; fi fi # Nu ne suprapunem peste testul DR (Sâmbătă 05:55-07:30) și nici peste altă rulare. exec 9>/var/run/vm109-patch-window.lock if ! flock -n 9; then log "Another patch window is already running, exiting" exit 0 fi VM_STARTED_BY_US=false FLAG_SET_BY_US=false RESULT="UNKNOWN" cleanup() { local rc=$? if [ "$VM_STARTED_BY_US" = "true" ] && qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then log "Cleanup: stopping VM $DR_VM_ID" ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 15 /f" 2>/dev/null || true sleep 45 qm stop "$DR_VM_ID" 2>/dev/null || true fi if [ "$FLAG_SET_BY_US" = "true" ]; then rm -f "$DEBUG_FLAG" log "Cleanup: watchdog debug flag cleared" fi log "Patch window finished with result=$RESULT (rc=$rc)" exit $rc } trap cleanup EXIT wait_for_ssh() { local waited=0 while [ $waited -lt $MAX_BOOT_WAIT ]; do if ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes \ "$DR_VM_USER@$DR_VM_IP" "powershell -Command 'Write-Output ready'" >/dev/null 2>&1; then log "VM responsive after ${waited}s" return 0 fi sleep 10 waited=$((waited + 10)) done log "ERROR: VM did not become responsive within ${MAX_BOOT_WAIT}s" return 1 } # NoAutoUpdate: 1 = Windows Update dezactivat (starea normală a VM 109), # 0 = permis, doar pe durata acestei ferestre. set_auto_update() { local value="$1" ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \ "powershell -Command \"Set-ItemProperty -Path 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU' -Name NoAutoUpdate -Value $value -Type DWord\"" 2>/dev/null } log "==========================================" log "VM $DR_VM_ID monthly patch window - Starting" log "==========================================" # Watchdog-ul oprește forțat VM 109 dacă rulează > 60 min în afara ferestrei de # test. Patching-ul depășește ușor 60 min, deci cerem exceptarea documentată. if [ ! -f "$DEBUG_FLAG" ]; then touch "$DEBUG_FLAG" FLAG_SET_BY_US=true log "Watchdog debug flag set (patching exceeds the 60 min watchdog limit)" fi if qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then log "ERROR: VM $DR_VM_ID is already running - refusing to interfere" RESULT="SKIPPED_VM_BUSY" exit 0 fi log "Starting VM $DR_VM_ID" qm start "$DR_VM_ID" VM_STARTED_BY_US=true wait_for_ssh || { RESULT="BOOT_FAILED"; exit 1; } log "Enabling Windows Update for the duration of this window" set_auto_update 0 reboots=0 while :; do log "Running update scan/download/install..." set +e out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=20 "$DR_VM_USER@$DR_VM_IP" \ "powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\install_updates.ps1" 2>&1 | tr -d '\r') set -e echo "$out" >> "$LOG" found=$(echo "$out" | grep -oP '^FOUND=\K\d+' | tail -1 || echo 0) installed=$(echo "$out"| grep -oP '^INSTALLED=\K\d+' | tail -1 || echo 0) failed=$(echo "$out" | grep -oP '^FAILED=\K\d+' | tail -1 || echo 0) reboot_req=$(echo "$out" | grep -oP '^REBOOT_REQUIRED=\K\S+' | tail -1 || echo false) ps_result=$(echo "$out"| grep -oP '^RESULT=\K\S+' | tail -1 || echo ERROR) log "Scan result: found=$found installed=$installed failed=$failed reboot=$reboot_req status=$ps_result" if [ "$ps_result" = "NO_UPDATES" ]; then log "System fully patched" RESULT="OK_NO_UPDATES" break fi if [ "$ps_result" = "ERROR" ]; then log "ERROR: update installation reported failures" RESULT="UPDATE_ERRORS" break fi if [ "$reboot_req" != "true" ]; then log "Updates installed, no reboot required" RESULT="OK_PATCHED" break fi reboots=$((reboots + 1)) if [ $reboots -gt $MAX_REBOOTS ]; then log "ERROR: exceeded $MAX_REBOOTS reboot cycles, giving up" RESULT="TOO_MANY_REBOOTS" break fi log "Reboot required - rebooting VM (cycle $reboots/$MAX_REBOOTS)" ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /r /t 5 /f" 2>/dev/null || true sleep 60 wait_for_ssh || { RESULT="REBOOT_FAILED"; break; } done # Re-armăm blocarea, indiferent de rezultat: VM 109 nu are voie să intre în # testul DR de sâmbătă cu Windows Update activ. log "Re-arming NoAutoUpdate=1" set_auto_update 1 # Verificăm că sistemul rămâne într-o stare curată pentru testul următor. set +e servicing=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \ "powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r') set -e log "Post-patch servicing state: ${servicing:-unavailable}" if ! echo "${servicing:-}" | grep -q "STATE=IDLE"; then log "WARNING: servicing not idle after patch window - next DR test may be affected" RESULT="${RESULT}_DIRTY" fi # Notificare doar când e ceva de semnalat — succesul tăcut e suficient. if [ "$RESULT" != "OK_NO_UPDATES" ] && [ "$RESULT" != "OK_PATCHED" ]; then printf 'VM %s monthly patch window ended with result: %s\n\nLog: %s\n' \ "$DR_VM_ID" "$RESULT" "$LOG" \ | mail -s "[DR] VM $DR_VM_ID patch window: $RESULT" "$MAIL_TO" 2>/dev/null || true fi