#!/bin/bash # # deploy.sh — copiază scripturile DR din repo pe destinații, cu verificări. # # Până acum deploy-ul se făcea manual (`cp ... /opt/scripts/`), de unde și # fișierele .bak-* rămase pe pveelite. Scriptul ăsta face aceiași pași, dar # refuză situațiile care au costat deja timp: # # * suprascrierea unui script care RULEAZĂ — bash citește scriptul # incremental de pe disc, iar suprascrierea în timpul rulării corupe # execuția (blocant real: testul DR durează ~18 min); # * deploy pe un singur nod — cron-ul e pe pveelite ȘI pvemini, iar garda # cluster-aware face ca doar nodul care găzduiește VM 109 să execute; # dacă lipsește de pe celălalt, după failover HA testul dispare tăcut; # * fișiere cu CRLF — bash pe Linux răspunde "$'\r': command not found"; # * flag-ul de debug al watchdog-ului lăsat în urmă după deploy pe guest. # # Utilizare: # ./deploy.sh # doar nodurile Proxmox (implicit) # ./deploy.sh --guest # noduri + scripturile PowerShell pe VM 109 # ./deploy.sh --guest-only # doar VM 109 # ./deploy.sh --check # nu copiază nimic, doar raportează diferențele # # VM 109 e oprit între teste, deci --guest îl pornește, copiază și îl oprește # la loc. Dacă VM 109 era deja pornit (test în curs), refuză. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PVEMINI="10.0.20.201" PVEELITE="10.0.20.202" NODES=("$PVEMINI" "$PVEELITE") # Distribuția NU e simetrică, iar asimetria e intenționată — nu o "repara". # # BOTH scripturi cluster-aware, care urmăresc VM 109 oriunde ar fi; # garda `[ -f /etc/pve/qemu-server/109.conf ]` face ca doar nodul # gazdă să execute. Lipsa lor de pe un nod = testul dispare tăcut # după un failover HA. # PVEELITE scripturi legate de storage: acolo trăiește datasetul ZFS # oracle-backups (sursa replicării și a mirror-ului). # PVEMINI scripturi care reacționează la CĂDEREA pveelite. Puse pe pveelite # ar fi inutile (nodul e jos) și periculoase: failover-ul rulat de pe # nodul greșit e exact scenariul de split-brain pe care scripturile # încearcă să-l evite. SCRIPTS_BOTH=( weekly-dr-test-proxmox.sh vm109-patch-window.sh vm109-watchdog.sh ) SCRIPTS_PVEELITE=( oracle-backup-monitor-proxmox.sh zfs-replicate-oracle-backups.sh nightly-backup-mirror.sh ) SCRIPTS_PVEMINI=( pveelite-down-alert.sh failover-dr-to-pvemini.sh failback-dr-to-pveelite.sh ) # Pe ambele noduri: sursă pentru deploy-ul către guest, iar VM 109 poate fi # găzduit de oricare dintre ele. STAGED_PS_SCRIPTS=(check_servicing.ps1 install_updates.ps1) # Lista de fișiere pentru un nod anume. scripts_for_node() { local node="$1" printf '%s\n' "${SCRIPTS_BOTH[@]}" "${STAGED_PS_SCRIPTS[@]}" if [ "$node" = "$PVEELITE" ]; then printf '%s\n' "${SCRIPTS_PVEELITE[@]}" elif [ "$node" = "$PVEMINI" ]; then printf '%s\n' "${SCRIPTS_PVEMINI[@]}" fi } GUEST_SCRIPTS=(check_servicing.ps1 install_updates.ps1 rman_restore_from_zero.ps1 cleanup_database.ps1) DR_VM_ID=109 DR_VM_HOST="10.0.20.202" # nodul de pe care se face saltul către guest DR_VM_IP="10.0.20.37" DR_VM_PORT=22122 DR_VM_USER="romfast" GUEST_DIR="D:/oracle/scripts" DEBUG_FLAG="/var/run/vm109-debug.flag" DO_NODES=true DO_GUEST=false CHECK_ONLY=false case "${1:-}" in --guest) DO_GUEST=true ;; --guest-only) DO_GUEST=true; DO_NODES=false ;; --check) CHECK_ONLY=true ;; "") ;; *) echo "Utilizare: $0 [--guest|--guest-only|--check]" >&2; exit 2 ;; esac red() { printf '\033[0;31m%s\033[0m\n' "$*"; } grn() { printf '\033[0;32m%s\033[0m\n' "$*"; } ylw() { printf '\033[1;33m%s\033[0m\n' "$*"; } fail() { red "EROARE: $*"; exit 1; } # ---------------------------------------------------------------- verificări # Un CR în fișier ajunge pe Linux ca "$'\r': command not found". .gitattributes # ar trebui să prevină asta, dar un clone vechi sau o editare pe Windows o # reintroduce — verificăm înainte să trimitem, nu după. check_line_endings() { local f="$1" if grep -qU $'\r' "$f" 2>/dev/null; then fail "$f are CRLF. Rulează: dos2unix '$f' (sau verifică .gitattributes)" fi } check_syntax() { local f="$1" case "$f" in *.sh) bash -n "$f" || fail "$f nu trece bash -n" ;; esac } # ------------------------------------------------------------- noduri Proxmox deploy_nodes() { echo "=== Noduri Proxmox: ${NODES[*]} ===" local f for f in "${SCRIPTS_BOTH[@]}" "${SCRIPTS_PVEELITE[@]}" "${SCRIPTS_PVEMINI[@]}" "${STAGED_PS_SCRIPTS[@]}"; do [ -f "$SCRIPT_DIR/$f" ] || { ylw " (lipsește din repo, sar peste: $f)"; continue; } check_line_endings "$SCRIPT_DIR/$f" check_syntax "$SCRIPT_DIR/$f" done grn " verificări locale OK (fără CRLF, bash -n trece)" local node for node in "${NODES[@]}"; do echo "--- $node ---" for f in $(scripts_for_node "$node"); do [ -f "$SCRIPT_DIR/$f" ] || continue # Nu atingem un script aflat în execuție. # # Parantezele din "[/]opt" nu sunt cosmetice: comanda trimisă prin # ssh apare ea însăși în lista de procese, deci un `pgrep -f # /opt/scripts/x.sh` s-ar găsi pe sine și ar raporta orice script ca # fiind "în execuție". Regexul [/]opt se potrivește cu textul # /opt/scripts/x.sh, dar NU cu propria linie de comandă, care # conține literal "[/]opt/scripts/x.sh". if ssh -o ConnectTimeout=10 "root@$node" "pgrep -f '[/]opt/scripts/$f' >/dev/null 2>&1"; then red " RULEAZĂ ACUM, nu suprascriu: $f" red " (reia deploy-ul după ce se termină; verifică: ssh root@$node \"pgrep -af $f\")" continue fi # Diferă de ce e pe nod? local local_sum remote_sum local_sum=$(md5sum "$SCRIPT_DIR/$f" | awk '{print $1}') remote_sum=$(ssh -o ConnectTimeout=10 "root@$node" "md5sum /opt/scripts/$f 2>/dev/null | awk '{print \$1}'" || echo "") if [ "$local_sum" = "$remote_sum" ]; then echo " = $f (identic)" continue fi if [ "$CHECK_ONLY" = true ]; then if [ -z "$remote_sum" ]; then ylw " ~ $f (LIPSEȘTE pe nod)" else ylw " ~ $f (DIFERĂ)" fi continue fi scp -q "$SCRIPT_DIR/$f" "root@$node:/opt/scripts/$f" case "$f" in *.sh) ssh -o ConnectTimeout=10 "root@$node" "chmod +x /opt/scripts/$f && bash -n /opt/scripts/$f" \ || fail "$f nu trece bash -n PE $node (transfer trunchiat?)" ;; esac grn " + $f" done done } # ------------------------------------------------------------------ guest 109 deploy_guest() { echo "=== VM $DR_VM_ID (guest Windows) ===" local vm_was_running=false local flag_set_by_us=false if ssh -o ConnectTimeout=10 "root@$DR_VM_HOST" "qm status $DR_VM_ID | grep -q running"; then vm_was_running=true ylw " VM $DR_VM_ID rulează deja." ylw " Dacă e testul DR în curs, oprește deploy-ul (Ctrl-C) — altfel continuă." sleep 5 fi if [ "$CHECK_ONLY" = true ]; then ylw " --check: nu pornesc VM $DR_VM_ID" return 0 fi # Curățenia trebuie să ruleze și dacă pică ceva la mijloc. cleanup_guest() { if [ "$flag_set_by_us" = true ]; then ssh -o ConnectTimeout=10 "root@$DR_VM_HOST" "rm -f $DEBUG_FLAG" 2>/dev/null || true echo " flag debug watchdog șters" fi if [ "$vm_was_running" = false ]; then ssh -o ConnectTimeout=10 "root@$DR_VM_HOST" "qm stop $DR_VM_ID" 2>/dev/null || true echo " VM $DR_VM_ID oprit la loc" fi } trap cleanup_guest EXIT if [ "$vm_was_running" = false ]; then # Watchdog-ul oprește VM 109 dacă rulează în afara ferestrei de test. ssh -o ConnectTimeout=10 "root@$DR_VM_HOST" "touch $DEBUG_FLAG" flag_set_by_us=true echo " pornesc VM $DR_VM_ID..." ssh -o ConnectTimeout=10 "root@$DR_VM_HOST" "qm start $DR_VM_ID" fi echo " aștept SSH pe $DR_VM_IP:$DR_VM_PORT..." local waited=0 while [ $waited -lt 300 ]; do if ssh -o ConnectTimeout=15 "root@$DR_VM_HOST" \ "ssh -p $DR_VM_PORT -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes $DR_VM_USER@$DR_VM_IP 'echo ok'" >/dev/null 2>&1; then grn " guest disponibil după ${waited}s" break fi sleep 10 waited=$((waited + 10)) done [ $waited -lt 300 ] || fail "VM $DR_VM_ID nu a răspuns în 300s" # Stația nu are rută directă la guest — pveelite e jump host, deci copiem în doi pași. local f present=() for f in "${GUEST_SCRIPTS[@]}"; do if [ -f "$SCRIPT_DIR/$f" ]; then present+=("$SCRIPT_DIR/$f") else ylw " (lipsește din repo, sar peste: $f)" fi done [ ${#present[@]} -gt 0 ] || fail "niciun script de copiat pe guest" scp -q "${present[@]}" "root@$DR_VM_HOST:/tmp/" local names="" for f in "${present[@]}"; do names="$names /tmp/$(basename "$f")"; done ssh -o ConnectTimeout=20 "root@$DR_VM_HOST" \ "scp -P $DR_VM_PORT -o StrictHostKeyChecking=no $names $DR_VM_USER@$DR_VM_IP:'$GUEST_DIR/'" \ || fail "copierea pe guest a eșuat" ssh -o ConnectTimeout=20 "root@$DR_VM_HOST" "rm -f $names" 2>/dev/null || true for f in "${present[@]}"; do grn " + $(basename "$f")"; done # Verificare funcțională: guard-ul trebuie să răspundă, altfel testul DR # îl va sări tăcut (fail-open) și rămânem fără plasa de siguranță. local state state=$(ssh -o ConnectTimeout=20 "root@$DR_VM_HOST" \ "ssh -p $DR_VM_PORT -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' || echo "") if echo "$state" | grep -q "^STATE="; then grn " check_servicing.ps1 răspunde: $state" else red " ATENȚIE: check_servicing.ps1 nu răspunde corect (guard-ul va fi sărit)" fi } [ "$DO_NODES" = true ] && deploy_nodes [ "$DO_GUEST" = true ] && deploy_guest echo if [ "$CHECK_ONLY" = true ]; then grn "Verificare terminată (nu s-a copiat nimic)." else grn "Deploy terminat." echo "Cron-ul NU e modificat de scriptul ăsta. Verifică pe fiecare nod:" echo " ssh root@10.0.20.202 'crontab -l | grep -E \"dr-test|patch-window|watchdog\"'" fi