diff --git a/proxmox/cluster/failover/failover-vm201.sh b/proxmox/cluster/failover/arhiva/failover-vm201.sh similarity index 100% rename from proxmox/cluster/failover/failover-vm201.sh rename to proxmox/cluster/failover/arhiva/failover-vm201.sh diff --git a/proxmox/cluster/failover/recover-vm201-to-pvemini.sh b/proxmox/cluster/failover/arhiva/recover-vm201-to-pvemini.sh similarity index 100% rename from proxmox/cluster/failover/recover-vm201-to-pvemini.sh rename to proxmox/cluster/failover/arhiva/recover-vm201-to-pvemini.sh diff --git a/proxmox/cluster/scripts/cluster-shutdown.sh b/proxmox/cluster/scripts/cluster-shutdown.sh index 7f0b6d4..e5c1810 100755 --- a/proxmox/cluster/scripts/cluster-shutdown.sh +++ b/proxmox/cluster/scripts/cluster-shutdown.sh @@ -9,12 +9,16 @@ # # RULEAZĂ DE PE STAȚIA DE ADMIN, nu de pe un nod Proxmox. Motivul: nodul care se # oprește ultimul nu poate raporta rezultatul propriei opriri. +# Excepție: cu --allow-on-node poate rula chiar pe pvemini (lansat din +# dashboard/Discord, detașat prin systemd-run) — nssh către pvemini devine +# execuție locală, iar pvemini se oprește ultimul, singur, la pasul 8. # # Utilizare: # ./cluster-shutdown.sh # interactiv, cere confirmare # ./cluster-shutdown.sh --yes # fără confirmare # ./cluster-shutdown.sh --dry-run # arată ce ar face, nu execută nimic # ./cluster-shutdown.sh --no-nodes # oprește doar guest-urile, lasă nodurile pornite +# ./cluster-shutdown.sh --allow-on-node # permite rularea chiar pe pvemini # set -euo pipefail @@ -45,12 +49,17 @@ GUEST_SHUTDOWN_ORDER=( 103 # dokploy 102 # docker.romfast.ro 100 # portainer - 171 # claude-agent + 171 # claude-agent — cu --allow-on-node rămâne pornit, vezi KEEP_RUNNING 109 # oracle-dr-windows 301 # docker-portainer-template 108 # central-oracle — ULTIMUL ) +# Cu --allow-on-node, acest guest NU e oprit explicit: ha-manager set --state +# stopped ar marca resursa ca oprită și HA n-ar mai reporni-o la boot. Moare +# oricum odată cu pvemini, iar shutdown_policy=freeze îl repornește după. +KEEP_RUNNING=171 + ORACLE_CT=108 ORACLE_DOCKER=oracle-xe @@ -60,14 +69,19 @@ CRON_BACKUP=/root/crontab.backup-mentenanta.txt # Cron-uri de dezactivat cât timp clusterul e jos (altfel mail storm, iar # vm109-watchdog.sh chiar pornește VM 109). -CRON_PATTERN='oom-alert|pvemini-down-alert|pveelite-down-alert|vm109-watchdog' +CRON_PATTERN='oom-alert|pvemini-down-alert|pveelite-down-alert|vm109-watchdog|weekly-dr-test-proxmox|vm109-patch-window' + +# Destinatarul instrucțiunilor de pornire, trimise prin email după ce starea +# clusterului e salvată. +ALERT_RECIPIENT="${ALERT_RECIPIENT:-mmarius28@gmail.com}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -STATE_FILE="${CLUSTER_STATE_FILE:-$SCRIPT_DIR/.cluster-state.txt}" +LOCAL_NODE=$(hostname) ASSUME_YES=0 DRY_RUN=0 SKIP_NODES=0 +ALLOW_ON_NODE=0 # ------------------------------------------------------------------- utilitare @@ -90,6 +104,10 @@ ping_host() { nssh() { local node=$1; shift + if (( ALLOW_ON_NODE )) && [[ "$node" == "$LOCAL_NODE" ]]; then + bash -c "$*" + return + fi ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new \ "root@${NODE_IP[$node]}" "$@" } @@ -115,26 +133,40 @@ confirm() { while (( $# )); do case "$1" in - --yes|-y) ASSUME_YES=1 ;; - --dry-run) DRY_RUN=1 ;; - --no-nodes) SKIP_NODES=1 ;; - -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; - *) die "Argument necunoscut: $1" ;; + --yes|-y) ASSUME_YES=1 ;; + --dry-run) DRY_RUN=1 ;; + --no-nodes) SKIP_NODES=1 ;; + --allow-on-node) ALLOW_ON_NODE=1 ;; + -h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) die "Argument necunoscut: $1" ;; esac shift done +STATE_FILE="${CLUSTER_STATE_FILE:-}" +if [[ -z "$STATE_FILE" ]]; then + if (( ALLOW_ON_NODE )); then + STATE_FILE=/opt/scripts/.cluster-state.txt + else + STATE_FILE="$SCRIPT_DIR/.cluster-state.txt" + fi +fi + # --------------------------------------------------------- garda de rulare # Scriptul NU trebuie rulat de pe un nod Proxmox (s-ar opri singur la mijloc) și # nici din CT 171 claude-agent (e în lista de oprit — s-ar sinucide). -if command -v pvecm >/dev/null 2>&1; then - die "Rulezi pe un nod Proxmox. Scriptul trebuie rulat de pe stația de admin." +# --allow-on-node ridică prima interdicție: rulat din dashboard/Discord, chiar +# pe pvemini, prin systemd-run. +if command -v pvecm >/dev/null 2>&1 && (( ! ALLOW_ON_NODE )); then + die "Rulezi pe un nod Proxmox. Scriptul trebuie rulat de pe stația de admin (sau cu --allow-on-node)." +fi +# Hostname-ul se verifica independent: /proc/1/environ nu e lizibil fara root, +# iar garda era sarita in tacere pentru userul claude. +if [[ "$(hostname)" == *claude-agent* ]]; then + die "Rulezi din CT 171 (claude-agent), care e în lista de oprit. Mută-te pe stația de admin." fi if [[ -r /proc/1/environ ]] && grep -qa 'container=lxc' /proc/1/environ 2>/dev/null; then - if [[ "$(hostname)" == *claude-agent* ]]; then - die "Rulezi din CT 171 (claude-agent), care e în lista de oprit. Mută-te pe stația de admin." - fi warn "Rulezi dintr-un container LXC. Asigură-te că nu e unul găzduit pe acest cluster." fi @@ -218,6 +250,28 @@ if (( ! DRY_RUN )); then fi fi +# Instrucțiuni de pornire, trimise acum cât timp mai există cine să le citească — +# clusterul urmează să se oprească. +if (( DRY_RUN )); then + printf '%s [dry-run] email: instrucțiuni de pornire către %s%s\n' "$c_dim" "$ALERT_RECIPIENT" "$c_off" +else + MAIL_BODY=$(cat </dev/null || true + sleep 15 + systemctl poweroff --no-block + exit 0 + fi + run "$node" "systemctl poweroff --no-block" || true (( DRY_RUN )) && continue diff --git a/proxmox/cluster/scripts/cluster-startup.sh b/proxmox/cluster/scripts/cluster-startup.sh index 28b125a..7a4af48 100755 --- a/proxmox/cluster/scripts/cluster-startup.sh +++ b/proxmox/cluster/scripts/cluster-startup.sh @@ -11,6 +11,9 @@ # implicită de mai jos. # # RULEAZĂ DE PE STAȚIA DE ADMIN, nu de pe un nod. +# Excepție: cu --allow-on-node poate rula chiar pe pvemini (lansat din +# dashboard/Discord, detașat prin systemd-run) — nssh către pvemini devine +# execuție locală. # # NODURILE TREBUIE PORNITE FIZIC ÎNTÂI (sau prin WoL). Scriptul le așteaptă. # Pornește-le pe TOATE TREI aproximativ simultan: un singur nod pornit nu are @@ -21,6 +24,7 @@ # ./cluster-startup.sh --yes # fără confirmări # ./cluster-startup.sh --dry-run # arată ce ar face # ./cluster-startup.sh --all # ignoră fișierul de stare, pornește lista implicită +# ./cluster-startup.sh --allow-on-node # permite rularea chiar pe pvemini # set -euo pipefail @@ -87,11 +91,12 @@ GUEST_WAIT=300 CRON_BACKUP=/root/crontab.backup-mentenanta.txt SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -STATE_FILE="${CLUSTER_STATE_FILE:-$SCRIPT_DIR/.cluster-state.txt}" +LOCAL_NODE=$(hostname) ASSUME_YES=0 DRY_RUN=0 USE_ALL=0 +ALLOW_ON_NODE=0 # ------------------------------------------------------------------- utilitare @@ -113,6 +118,10 @@ ping_host() { nssh() { local node=$1; shift + if (( ALLOW_ON_NODE )) && [[ "$node" == "$LOCAL_NODE" ]]; then + bash -c "$*" + return + fi ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new \ "root@${NODE_IP[$node]}" "$@" } @@ -136,17 +145,27 @@ confirm() { while (( $# )); do case "$1" in - --yes|-y) ASSUME_YES=1 ;; - --dry-run) DRY_RUN=1 ;; - --all) USE_ALL=1 ;; - -h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; - *) die "Argument necunoscut: $1" ;; + --yes|-y) ASSUME_YES=1 ;; + --dry-run) DRY_RUN=1 ;; + --all) USE_ALL=1 ;; + --allow-on-node) ALLOW_ON_NODE=1 ;; + -h|--help) sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) die "Argument necunoscut: $1" ;; esac shift done -if command -v pvecm >/dev/null 2>&1; then - die "Rulezi pe un nod Proxmox. Scriptul trebuie rulat de pe stația de admin." +STATE_FILE="${CLUSTER_STATE_FILE:-}" +if [[ -z "$STATE_FILE" ]]; then + if (( ALLOW_ON_NODE )); then + STATE_FILE=/opt/scripts/.cluster-state.txt + else + STATE_FILE="$SCRIPT_DIR/.cluster-state.txt" + fi +fi + +if command -v pvecm >/dev/null 2>&1 && (( ! ALLOW_ON_NODE )); then + die "Rulezi pe un nod Proxmox. Scriptul trebuie rulat de pe stația de admin (sau cu --allow-on-node)." fi # ------------------------------------------------- pas 1: așteaptă nodurile