diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..543c12f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Scripturile shell rulează pe Proxmox/Linux și sunt deployate direct din +# working tree (scp către /opt/scripts). Cu core.autocrlf=true pe Windows ele +# ajungeau în working tree cu CRLF, iar bash le refuză pe Linux +# ("$'\r': command not found"). Forțăm LF indiferent de platformă. +*.sh text eol=lf + +# Scripturile Windows rămân CRLF. +*.ps1 text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/proxmox/vm109-windows-dr/README.md b/proxmox/vm109-windows-dr/README.md index ef9c3a0..0b00bfd 100644 --- a/proxmox/vm109-windows-dr/README.md +++ b/proxmox/vm109-windows-dr/README.md @@ -31,6 +31,81 @@ ssh root@10.0.20.202 "qm status 109" # trebuie stopped între teste Script-urile refuză să ruleze dacă cealaltă parte e accesibilă (anti-split-brain). +## 🩹 Windows Update pe VM 109 — incident 2026-08-08 + +**Simptom:** testul DR din 2026-08-08 a raportat `FAILED`, `Database Restore: Restore failed` după 11 secunde, `Tables restored: 0`, iar raportul nu conținea niciun log RMAN („No restore logs or RMAN scripts found"). Testul din 2026-08-01 trecuse în 18 minute pe exact același cod (scripturile nu s-au modificat din 2026-04-25). + +**Cauza reală: RMAN nici nu a apucat să pornească.** Windows Update a rebootat VM-ul în mijlocul testului. + +Cronologia din Event Log-ul guest-ului: + +| Ora | Eveniment | +|-----|-----------| +| 06:00:26 | boot VM 109 | +| 06:00:53 | scriptul DR confirmă SSH + PowerShell | +| 06:00:55 | STEP 4 pornește `rman_restore_from_zero.ps1` | +| 06:00:58 | `RestartManager 10010` — nu poate reporni `powershell.exe` (pid 5964) = **procesul de restore** | +| 06:00:59 | `Winlogon 6004` — TrustedInstaller a eșuat o notificare critică | +| 06:01:02 | `SCM 7023` — Update Orchestrator Service terminat | +| 06:01:04 | `SCM 7034` — **OpenSSH SSH Server terminat neașteptat** | +| 06:01:06 | pveelite vede `client_loop: send disconnect: Broken pipe` → restore marcat FAILED | +| 06:01:38 | **VM-ul se reboteaza singur** (al doilea eveniment `Kernel-General 12`) | +| 06:02:20 | scriptul renunță la SSH și forțează `qm stop 109` | + +Update-ul vinovat: **KB5101001**, `InstalledOn 8/8/2026`. + +**De ce s-a întâmplat exact atunci — problemă structurală, nu ghinion:** + +- VM 109 e pornit **doar** în timpul testului DR. Deci fereastra testului era singura fereastră în care Windows Update putea rula vreodată. +- Update-ul fusese descărcat **în timpul testului precedent** — evenimentele `WindowsUpdateClient id=44` sunt la 08-01 06:13–06:17, adică în interiorul rulării din 1 august. A rămas staged după `qm stop` și s-a finalizat la boot-ul următor = testul următor. +- Active Hours erau 08:00–17:00, deci 06:00 era pentru Windows o fereastră de mentenanță **validă**. +- `NoAutoRebootWithLoggedOnUsers=1` nu ajută: o sesiune SSH nu e logon interactiv. + +**Factor agravant:** `sc qfailure sshd` nu avea nicio acțiune de recovery (`RESET_PERIOD: 0`). După ce sshd a murit a rămas mort, deci și STEP 6 (colectare log) și STEP 7 (shutdown grațios) au eșuat — de aici raportul fără loguri. + +**Ce NU a fost:** host-ul e curat — fără OOM, fără erori de storage, cluster quorate, replicarea ZFS a rulat normal la 06:00. Backup-urile nu au fost implicate în niciun fel. + +### Măsuri aplicate (2026-08-08) + +| # | Măsură | Unde | +|---|--------|------| +| 1 | `NoAutoUpdate=1` + `AUOptions=2` — Windows Update nu mai pornește singur | registry VM 109, `HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` | +| 2 | Acțiuni de recovery pentru sshd: restart la 5s / 10s / 30s, `reset=86400` | serviciu `sshd` pe VM 109 | +| 3 | Guard „STEP 3b: Windows servicing" înainte de restore | `weekly-dr-test-proxmox.sh` + `check_servicing.ps1` | +| 4 | Fereastră lunară dedicată de patching | `vm109-patch-window.sh` + `install_updates.ps1` | + +**3 — guard-ul de servicing.** Între verificarea NFS și restore, scriptul rulează `check_servicing.ps1` pe guest, care întoarce `STATE=IDLE` sau `STATE=BUSY REBOOT_PENDING=... REASONS=...` (CBS RebootPending, WU RebootRequired, PendingFileRename, TiWorker, TrustedInstaller, UsoSvc). Comportament: + +- așteaptă până la 300s ca servicing-ul să devină idle; +- dacă e reboot în așteptare, îl consumă controlat (un singur reboot) și reia verificarea; +- dacă tot nu e curat, **abandonează înainte de restore** cu `ABORTED - Windows servicing` în loc de un „Restore failed" înșelător, care arată ca o problemă de backup; +- dacă `check_servicing.ps1` lipsește sau nu răspunde, guard-ul se auto-dezactivează cu un warning — nu are voie să pice testul din cauza propriei absențe. + +**4 — fereastra de patching.** Prima duminică din lună, 03:00, pe nodul care găzduiește VM 109: + +```bash +0 3 1-7 * * /opt/scripts/vm109-patch-window.sh > /dev/null 2>&1 # garda „duminică" e în script +``` + +Scriptul pornește VM 109 (cu `vm109-debug.flag` setat, fiindcă patching-ul depășește limita de 60 min a watchdog-ului), permite temporar Windows Update (`NoAutoUpdate=0`), aplică update-urile prin API-ul COM `Microsoft.Update.Session`, tolerează până la 3 cicluri de reboot, **re-armează `NoAutoUpdate=1` indiferent de rezultat**, verifică starea finală și oprește VM-ul. Trimite mail doar când rezultatul nu e curat. + +Rulare manuală (ignoră garda de calendar): + +```bash +ssh root@10.0.20.202 "/opt/scripts/vm109-patch-window.sh --now" +tail -f /var/log/oracle-dr/patch-window.log +``` + +**Verificare stare Windows Update pe VM 109:** + +```bash +ssh root@10.0.20.202 "ssh -p 22122 romfast@10.0.20.37 \ + 'powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1'" +# Așteptat între ferestre: STATE=IDLE +``` + +**Zgomot preexistent, fără legătură cu incidentul:** la fiecare boot apar `VSS 8213` + `SCM 7023` pentru `OracleVssWriterROA` („General access denied"). Serviciul VSS writer al Oracle nu are drepturi suficiente. Nu afectează restore-ul RMAN (care nu folosește VSS) — de rezolvat separat. + --- # 🛡️ Oracle DR System - Complete Architecture @@ -156,6 +231,8 @@ Scheduled Tasks: /opt/scripts/ ├── oracle-backup-monitor-proxmox.sh # Daily backup monitoring ├── weekly-dr-test-proxmox.sh # Weekly DR test +├── vm109-patch-window.sh # Monthly Windows patching (incident 08-08) +├── vm109-watchdog.sh # Force-stop VM 109 outside test window └── PROXMOX_NOTIFICATIONS_README.md # Documentation /mnt/pve/oracle-backups/ROA/autobackup/ @@ -166,6 +243,8 @@ Scheduled Tasks: Cron Jobs: 0 9 * * * /opt/scripts/oracle-backup-monitor-proxmox.sh 0 6 * * 6 /opt/scripts/weekly-dr-test-proxmox.sh +0 3 1-7 * * /opt/scripts/vm109-patch-window.sh # prima duminică (gardă în script) +* * * * * /opt/scripts/vm109-watchdog.sh ``` ### 📁 DR VM 109 (10.0.20.37) - When Running @@ -173,6 +252,8 @@ Cron Jobs: D:\oracle\scripts\ ├── rman_restore_from_zero.cmd # Main restore script ⭐ ├── cleanup_database.cmd # Cleanup after test +├── check_servicing.ps1 # Servicing guard (incident 08-08) +├── install_updates.ps1 # Used only by vm109-patch-window.sh └── mount-nfs.bat # Mount F:\ at startup F:\ (NFS mount from Proxmox) @@ -248,6 +329,14 @@ ssh root@10.0.20.202 "qm stop 109" ## 🐛 Troubleshooting +### ⚡ Simptome frecvente — unde te uiți întâi + +| Simptom în raportul DR | Cauză probabilă | +|---|---| +| `Restore failed` în < 60s, **fără log RMAN**, `Broken pipe` în log-ul bash | Nu e problemă de backup — sesiunea SSH a murit. Vezi [Windows Update pe VM 109](#-windows-update-pe-vm-109--incident-2026-08-08). Verifică `SCM 7034 sshd` în Event Log-ul guest-ului. | +| `ABORTED - Windows servicing` | Guard-ul STEP 3b a oprit testul intenționat: Windows Update era activ. Backup-urile nu sunt implicate. Rulează fereastra de patching manual. | +| `Restore failed` după 10+ minute, cu log RMAN | Problemă reală de restore — continuă cu secțiunile de mai jos. | + ### 🔍 Debugging Restore Tests #### Check Backup Files on Proxmox (10.0.20.202) diff --git a/proxmox/vm109-windows-dr/scripts/check_servicing.ps1 b/proxmox/vm109-windows-dr/scripts/check_servicing.ps1 new file mode 100644 index 0000000..40cee21 --- /dev/null +++ b/proxmox/vm109-windows-dr/scripts/check_servicing.ps1 @@ -0,0 +1,72 @@ +# check_servicing.ps1 — raportează dacă stack-ul de servicing Windows este activ. +# +# De ce există: incident 2026-08-08. KB5101001 a fost descărcat în timpul testului +# DR din 2026-08-01 (singurul moment în care VM 109 este pornit), a rămas staged +# după `qm stop`, și s-a finalizat la următorul boot — adică exact la testul +# următor. RestartManager a omorât procesul powershell.exe care rula restore-ul +# (event 10010), apoi sshd (SCM 7034), iar VM-ul a rebootat la 72s după boot. +# Testul a raportat "Restore failed" fără niciun log RMAN, deși RMAN nici măcar +# nu apucase să pornească. +# +# Ieșire (o singură linie, parsabilă din bash): +# STATE=IDLE +# STATE=BUSY REBOOT_PENDING= REASONS= +# +# Cod de ieșire: 0 = IDLE, 1 = BUSY. Nu aruncă niciodată excepții — un check +# care crapă nu are voie să pice testul DR. + +$ErrorActionPreference = "SilentlyContinue" + +$reasons = @() +$rebootPending = $false + +# 1. Component Based Servicing: update aplicat, așteaptă reboot. +if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { + $reasons += "CBS_RebootPending" + $rebootPending = $true +} + +# 2. Windows Update: reboot cerut explicit. +if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\NT\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { + $reasons += "WU_RebootRequired" + $rebootPending = $true +} + +# 3. Fișiere programate pentru redenumire la boot (semnătură clasică de servicing). +$pfro = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations).PendingFileRenameOperations +if ($pfro) { + $reasons += "PendingFileRename" + $rebootPending = $true +} + +# 4. Redenumire de computer în așteptare — tot reboot cere. +if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting") { + $reasons += "PostRebootReporting" +} + +# 5. TiWorker.exe = worker-ul de servicing. Prezent => CBS lucrează chiar acum. +if (Get-Process -Name "TiWorker" -ErrorAction SilentlyContinue) { + $reasons += "TiWorker_running" +} + +# 6. TrustedInstaller pornit => Windows Modules Installer aplică ceva. +if ((Get-Service -Name "TrustedInstaller" -ErrorAction SilentlyContinue).Status -eq "Running") { + $reasons += "TrustedInstaller_running" +} + +# 7. Update Orchestrator activ => descărcare/scheduling în curs. +if ((Get-Service -Name "UsoSvc" -ErrorAction SilentlyContinue).Status -eq "Running") { + $reasons += "UsoSvc_running" +} + +# Notă: wuauserv nu este verificat intenționat — pornește și se oprește de la +# sine în mod normal (inclusiv pentru definițiile Defender) și ar produce +# fals-pozitive la fiecare rulare. + +if ($reasons.Count -eq 0) { + Write-Output "STATE=IDLE" + exit 0 +} else { + Write-Output ("STATE=BUSY REBOOT_PENDING=" + $rebootPending.ToString().ToLower() + " REASONS=" + ($reasons -join ",")) + exit 1 +} diff --git a/proxmox/vm109-windows-dr/scripts/install_updates.ps1 b/proxmox/vm109-windows-dr/scripts/install_updates.ps1 new file mode 100644 index 0000000..3f4242e --- /dev/null +++ b/proxmox/vm109-windows-dr/scripts/install_updates.ps1 @@ -0,0 +1,87 @@ +# install_updates.ps1 — aplică update-urile Windows pe VM 109, controlat. +# +# Rulat exclusiv din vm109-patch-window.sh (fereastra lunară de patching). +# În restul timpului VM 109 are NoAutoUpdate=1, ca Windows Update să nu mai +# poată porni singur în timpul testului DR săptămânal (incident 2026-08-08). +# +# Folosește API-ul COM Microsoft.Update.Session — funcționează într-o sesiune +# SSH non-interactivă, spre deosebire de UsoClient care raportează asincron și +# nu întoarce niciun status utilizabil. +# +# Ieșire (linii parsabile din bash): +# FOUND= +# INSTALLED= +# FAILED= +# REBOOT_REQUIRED= +# RESULT= + +$ErrorActionPreference = "Stop" + +try { + $session = New-Object -ComObject Microsoft.Update.Session + $searcher = $session.CreateUpdateSearcher() + + Write-Output "Searching for updates..." + $result = $searcher.Search("IsInstalled=0 AND IsHidden=0") + + Write-Output ("FOUND=" + $result.Updates.Count) + + if ($result.Updates.Count -eq 0) { + Write-Output "INSTALLED=0" + Write-Output "FAILED=0" + Write-Output "REBOOT_REQUIRED=false" + Write-Output "RESULT=NO_UPDATES" + exit 0 + } + + $toInstall = New-Object -ComObject Microsoft.Update.UpdateColl + foreach ($u in $result.Updates) { + if ($u.EulaAccepted -eq $false) { $u.AcceptEula() } + Write-Output (" + " + $u.Title) + $toInstall.Add($u) | Out-Null + } + + Write-Output "Downloading..." + $downloader = $session.CreateUpdateDownloader() + $downloader.Updates = $toInstall + $downloader.Download() | Out-Null + + $ready = New-Object -ComObject Microsoft.Update.UpdateColl + foreach ($u in $toInstall) { + if ($u.IsDownloaded) { $ready.Add($u) | Out-Null } + } + + if ($ready.Count -eq 0) { + Write-Output "INSTALLED=0" + Write-Output "FAILED=0" + Write-Output "REBOOT_REQUIRED=false" + Write-Output "RESULT=ERROR" + exit 1 + } + + Write-Output "Installing..." + $installer = $session.CreateUpdateInstaller() + $installer.Updates = $ready + $installResult = $installer.Install() + + # ResultCode: 2 = succeeded, 3 = succeeded with errors, restul = eșec. + $ok = 0 + $ko = 0 + for ($i = 0; $i -lt $ready.Count; $i++) { + $code = $installResult.GetUpdateResult($i).ResultCode + if ($code -eq 2 -or $code -eq 3) { $ok++ } else { $ko++ } + } + + Write-Output ("INSTALLED=" + $ok) + Write-Output ("FAILED=" + $ko) + Write-Output ("REBOOT_REQUIRED=" + $installResult.RebootRequired.ToString().ToLower()) + + if ($ko -gt 0) { Write-Output "RESULT=ERROR"; exit 1 } + Write-Output "RESULT=OK" + exit 0 +} +catch { + Write-Output ("ERROR: " + $_.Exception.Message) + Write-Output "RESULT=ERROR" + exit 1 +} diff --git a/proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh b/proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh new file mode 100644 index 0000000..959f93b --- /dev/null +++ b/proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh @@ -0,0 +1,202 @@ +#!/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 diff --git a/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh b/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh index 94c21fb..572152e 100644 --- a/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh +++ b/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh @@ -357,6 +357,7 @@ run_dr_test() { local cleanup_freed=0 local backup_count=0 local restore_log="Not collected" + local servicing_abort=false log "==========================================" log "Oracle DR Weekly Test - Starting" @@ -467,12 +468,92 @@ run_dr_test() { WARNINGS+=("NFS mount may need manual intervention") fi + # Step 3b: Windows servicing guard + # + # Incident 2026-08-08: KB5101001 descărcat în timpul testului din + # 2026-08-01 s-a finalizat la boot-ul următorului test. RestartManager + # a omorât powershell.exe (restore-ul) la 06:00:58, sshd la 06:01:04, + # iar VM-ul a rebootat la 06:01:38 — testul a raportat "Restore failed" + # la 11s, fără log RMAN, deși RMAN nu pornise deloc. + # + # Măsura principală este NoAutoUpdate=1 pe VM 109 (vezi + # vm109-patch-window.sh pentru fereastra lunară de patching). + # Guard-ul de aici este plasa de siguranță: dacă totuși stack-ul de + # servicing este activ, nu pornim restore-ul într-un VM care e pe + # cale să se reboteze — raportăm cauza reală în loc de un + # "Restore failed" care arată ca o problemă de backup. + step_start=$(date +%s) + log "STEP 3b: Checking Windows servicing state" + + local servicing_out="" + local servicing_idle=false + local SERVICING_WAIT=300 + local servicing_elapsed=0 + + while [ $servicing_elapsed -lt $SERVICING_WAIT ]; do + if servicing_out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$DR_VM_USER@$DR_VM_IP" \ + "powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r'); then + servicing_idle=true + break + fi + + # Check absent (ieșire non-zero fără output) => nu blocăm testul. + if [ -z "$servicing_out" ]; then + log_warning "check_servicing.ps1 did not respond, skipping servicing guard" + WARNINGS+=("Servicing guard skipped: check_servicing.ps1 missing or unreachable on VM $DR_VM_ID") + servicing_idle=true + break + fi + + log "Windows servicing busy: $servicing_out (${servicing_elapsed}s/${SERVICING_WAIT}s)" + sleep 15 + servicing_elapsed=$((servicing_elapsed + 15)) + done + + if [ "$servicing_idle" = true ]; then + track_step "Windows Servicing Check" true "Servicing stack idle" "$step_start" + else + # Reboot în așteptare: îl consumăm controlat, o singură dată, + # ca testul să poată continua pe un sistem stabil. + if echo "$servicing_out" | grep -q "REBOOT_PENDING=true"; then + log_warning "Pending reboot detected, rebooting VM $DR_VM_ID once before restore" + ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /r /t 5 /f" 2>/dev/null || true + sleep 45 + + local reboot_elapsed=0 + while [ $reboot_elapsed -lt 300 ]; do + if servicing_out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes "$DR_VM_USER@$DR_VM_IP" \ + "powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r'); then + servicing_idle=true + break + fi + sleep 15 + reboot_elapsed=$((reboot_elapsed + 15)) + done + fi + + if [ "$servicing_idle" = true ]; then + track_step "Windows Servicing Check" true "Servicing completed after controlled reboot" "$step_start" + WARNINGS+=("VM $DR_VM_ID had a pending servicing reboot; consumed before restore. Check NoAutoUpdate policy.") + else + track_step "Windows Servicing Check" false \ + "Windows Update/servicing active, restore not attempted - backups NOT implicated ($servicing_out)" "$step_start" + test_result="ABORTED - Windows servicing" + servicing_abort=true + fi + fi + # Step 4: Run restore step_start=$(date +%s) local restore_start=$step_start log "STEP 4: Running database restore" - if ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \ + if [ "$servicing_abort" = true ]; then + log_error "Skipping restore: Windows servicing active on VM $DR_VM_ID" + # Fără track_step aici: eșecul e deja raportat de "Windows Servicing + # Check", iar un al doilea ERRORS ar duplica alarma pentru o singură cauză. + restore_log="Restore not attempted. Windows Update/servicing was active on VM $DR_VM_ID: $servicing_out" + elif ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \ "powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\rman_restore_from_zero.ps1 -TestMode" 2>&1 | tee -a "$LOG_FILE"; then local restore_end=$(date +%s)