VM 201 (Windows critical) stays out of HA by design. Added: - failover-vm201.sh: interactive failover pvemini -> pveelite with ZFS replication state - recover-vm201-to-pvemini.sh: interactive reverse migration with uptime + split-brain checks - pvemini-down-alert.sh: cron watchdog on pveelite, emails full runbook after 2min DOWN Replication RPO tightened: CT 108 + VM 201 to 5min, CT 171 to 15min. CT 171 added to HA (ha-group-main) for continuous Claude Code access. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
4.7 KiB
Bash
Executable File
140 lines
4.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# Revenire VM 201 pe pvemini după failover manual
|
|
# Deployed la /opt/scripts/recover-vm201-to-pvemini.sh pe pveelite
|
|
# Rulează DUPĂ ce pvemini e stabil din nou
|
|
set -euo pipefail
|
|
|
|
VMID=201
|
|
PRIMARY=pvemini
|
|
PRIMARY_IP=10.0.20.201
|
|
SECONDARY=$(hostname)
|
|
LOG=/var/log/recover-vm201.log
|
|
MAIL_TO=mmarius28@gmail.com
|
|
MIN_UPTIME_MIN=30 # pvemini trebuie să fie UP min. 30 min
|
|
|
|
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; }
|
|
die() { log "ABORT: $*"; exit 1; }
|
|
|
|
confirm() {
|
|
local msg="$1"
|
|
if [[ "${FORCE:-}" == "--yes" ]]; then
|
|
log "$msg → auto-confirmat (--yes)"
|
|
return 0
|
|
fi
|
|
echo
|
|
read -p "$msg (tastează 'DA'): " ANS
|
|
[[ "$ANS" == "DA" ]] || die "Anulat la pasul: $msg"
|
|
}
|
|
|
|
[[ "$SECONDARY" == "pveelite" ]] || die "Rulează pe pveelite, nu pe $SECONDARY"
|
|
[[ $EUID -eq 0 ]] || die "Trebuie root"
|
|
|
|
FORCE=${1:-}
|
|
|
|
log "=== Recovery VM $VMID → $PRIMARY pornit ==="
|
|
|
|
# === CHECK-URI PRE-RECOVERY ===
|
|
|
|
# Check 1: VM rulează pe pveelite
|
|
if ! qm status "$VMID" 2>/dev/null | grep -q running; then
|
|
die "VM $VMID NU rulează pe $SECONDARY. Nu e nimic de recuperat."
|
|
fi
|
|
log "VM $VMID running pe $SECONDARY ✓"
|
|
|
|
# Check 2: pvemini reachable + ssh OK
|
|
if ! ping -c 3 -W 2 "$PRIMARY_IP" &>/dev/null; then
|
|
die "$PRIMARY nu răspunde la ping. Abort."
|
|
fi
|
|
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "root@$PRIMARY_IP" 'pvecm status' &>/dev/null; then
|
|
die "$PRIMARY nu răspunde la ssh sau nu e în cluster. Abort."
|
|
fi
|
|
log "$PRIMARY reachable ✓"
|
|
|
|
# Check 3: uptime pvemini > threshold
|
|
UPTIME_SEC=$(ssh "root@$PRIMARY_IP" "cat /proc/uptime | awk '{print int(\$1)}'")
|
|
UPTIME_MIN=$((UPTIME_SEC / 60))
|
|
log "$PRIMARY uptime: ${UPTIME_MIN} min"
|
|
if [[ "$UPTIME_MIN" -lt "$MIN_UPTIME_MIN" ]]; then
|
|
die "$PRIMARY uptime doar ${UPTIME_MIN} min (< ${MIN_UPTIME_MIN} min). Așteaptă stabilizare."
|
|
fi
|
|
|
|
# Check 4: errors recente pe pvemini
|
|
ERR_COUNT=$(ssh "root@$PRIMARY_IP" "journalctl -p err -b --since '30 min ago' 2>/dev/null | grep -v '^-- ' | wc -l" || echo 0)
|
|
log "$PRIMARY erori în ultimele 30 min: $ERR_COUNT"
|
|
if [[ "$ERR_COUNT" -gt 50 ]]; then
|
|
log "ATENȚIE: $ERR_COUNT erori pe $PRIMARY"
|
|
confirm "Continui oricum?"
|
|
fi
|
|
|
|
# Check 5: VM 201 NU există ca running pe pvemini
|
|
if ssh "root@$PRIMARY_IP" "qm status $VMID 2>/dev/null | grep -q running"; then
|
|
die "VM $VMID rulează deja pe $PRIMARY (split brain!). Oprește manual întâi."
|
|
fi
|
|
|
|
# === EXECUȚIE ===
|
|
|
|
echo
|
|
echo "========================================"
|
|
echo "Plan recovery VM $VMID către $PRIMARY:"
|
|
echo " 1. Shutdown VM $VMID pe $SECONDARY"
|
|
echo " 2. Replicare inversă $SECONDARY → $PRIMARY"
|
|
echo " 3. Migrare offline $VMID către $PRIMARY"
|
|
echo " 4. Restore replicări normale"
|
|
echo " 5. Start VM $VMID pe $PRIMARY"
|
|
echo "========================================"
|
|
confirm "Pornesc recovery?"
|
|
|
|
# Pas 1: Shutdown VM 201 pe pveelite
|
|
log "Pas 1/5: Shutdown VM $VMID pe $SECONDARY..."
|
|
qm shutdown "$VMID" --timeout 120 || die "Shutdown VM eșuat"
|
|
sleep 5
|
|
qm status "$VMID" | grep -q stopped || die "VM $VMID nu s-a oprit"
|
|
log "VM $VMID stopped ✓"
|
|
|
|
# Pas 2: Job replicare inversă pveelite → pvemini
|
|
log "Pas 2/5: Creez replicare inversă $SECONDARY → $PRIMARY..."
|
|
if pvesr status | grep -q "^${VMID}-2 "; then
|
|
log "Job ${VMID}-2 deja există, îl șterg"
|
|
pvesr delete "${VMID}-2" --force 1 || true
|
|
sleep 2
|
|
fi
|
|
pvesr create-local-job "${VMID}-2" "$PRIMARY" --schedule '*/5' --source "$SECONDARY" \
|
|
--comment "recovery reverse"
|
|
log "Rulez replicare inversă acum (poate dura, depinde de delta)..."
|
|
pvesr run --id "${VMID}-2" || die "Replicare inversă eșuată"
|
|
log "Replicare inversă OK ✓"
|
|
|
|
# Pas 3: Migrare offline VM către pvemini
|
|
log "Pas 3/5: Migrare offline VM $VMID → $PRIMARY..."
|
|
qm migrate "$VMID" "$PRIMARY" || die "Migrare eșuată"
|
|
log "Migrare completă ✓"
|
|
|
|
# Pas 4: Șterge job reverse + verifică replicările normale
|
|
log "Pas 4/5: Curăț jobul de replicare inversă..."
|
|
pvesr delete "${VMID}-2" --force 1 || true
|
|
sleep 2
|
|
log "Replicări active:"
|
|
pvesr status | grep "^${VMID}-" | tee -a "$LOG"
|
|
|
|
# Pas 5: Start VM pe pvemini
|
|
log "Pas 5/5: Start VM $VMID pe $PRIMARY..."
|
|
ssh "root@$PRIMARY_IP" "qm start $VMID" || die "Start pe $PRIMARY eșuat"
|
|
sleep 10
|
|
PRIMARY_STATUS=$(ssh "root@$PRIMARY_IP" "qm status $VMID" | awk '{print $2}')
|
|
log "VM $VMID pe $PRIMARY: $PRIMARY_STATUS"
|
|
|
|
# Mail confirmare
|
|
{
|
|
echo "Recovery VM $VMID complet la $(date)"
|
|
echo
|
|
echo "$SECONDARY → $PRIMARY migrare reușită."
|
|
echo "Status VM pe $PRIMARY: $PRIMARY_STATUS"
|
|
echo
|
|
echo "Replicări active:"
|
|
pvesr status | grep "^${VMID}-"
|
|
echo
|
|
echo "Verifică aplicația Windows pe 10.0.20.201 (IIS etc)."
|
|
} | mail -r 'ups@romfast.ro' -s "[OK] Recovery VM $VMID pe $PRIMARY" "$MAIL_TO"
|
|
|
|
log "=== Recovery complet ==="
|