feat(cluster): cluster-shutdown/startup --allow-on-node pentru rulare pe pvemini
Rulare detasata din dashboard/Discord: nssh local fara ssh la sine, CT 171 ramane pornit si moare odata cu pvemini (HA freeze il reporneste), email cu instructiunile de pornire, pauza si pentru testul DR si fereastra de patch. Garda CT 171 nu mai depinde de /proc/1/environ (era sarita fara root). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KddsXCqEbKMhdFJDYbAsx8
This commit is contained in:
@@ -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 <<EOF
|
||||
Clusterul romfast intră în oprire planificată.
|
||||
|
||||
Pentru pornire:
|
||||
1. JuiceSSH (prin Tailscale) → conectare la 10.0.20.36, port 22122.
|
||||
2. Rulează: C:\wolcluster.bat
|
||||
3. Așteaptă mesajul „Cluster sus" în Discord.
|
||||
|
||||
Rezervă, dacă WoL nu pornește toate nodurile:
|
||||
ssh root@10.0.20.201 /opt/scripts/cluster-startup.sh --allow-on-node --yes
|
||||
EOF
|
||||
)
|
||||
printf '%s\n' "$MAIL_BODY" | mail -s "[INFO] Cluster romfast: oprire în curs — cum îl pornești" "$ALERT_RECIPIENT" \
|
||||
&& ok "instrucțiuni de pornire trimise către $ALERT_RECIPIENT" \
|
||||
|| warn "emailul cu instrucțiuni de pornire NU a putut fi trimis"
|
||||
fi
|
||||
|
||||
confirm "Se opresc $RUNNING_COUNT guest-uri, apoi nodurile: ${NODE_SHUTDOWN_ORDER[*]}."
|
||||
|
||||
# -------------------------------------------- pas 3: shutdown_policy=freeze
|
||||
@@ -334,6 +388,10 @@ stop_guest() {
|
||||
|
||||
# întâi în ordinea definită...
|
||||
for id in "${GUEST_SHUTDOWN_ORDER[@]}"; do
|
||||
if (( ALLOW_ON_NODE )) && [[ "$id" == "$KEEP_RUNNING" ]]; then
|
||||
warn "$id rămâne pornit; se oprește odată cu pvemini, HA freeze îl repornește"
|
||||
continue
|
||||
fi
|
||||
line=$(awk -v id="$id" '$3==id && $4=="running"' <<<"$INVENTORY") || true
|
||||
[[ -n "$line" ]] || continue
|
||||
read -r type node vmid status ha <<<"$line"
|
||||
@@ -356,13 +414,22 @@ step "Verificare înainte de oprirea nodurilor"
|
||||
if (( DRY_RUN )); then
|
||||
warn "dry-run — sar peste verificare"
|
||||
else
|
||||
BAD=$(nssh pvemini "ha-manager status | grep -E 'started|migrate|relocate|fence'" || true)
|
||||
if (( ALLOW_ON_NODE )); then
|
||||
# ct:$KEEP_RUNNING rămâne intenționat „started" — nu e o scăpare.
|
||||
BAD=$(nssh pvemini "ha-manager status | grep -E 'started|migrate|relocate|fence'" | grep -v "ct:$KEEP_RUNNING" || true)
|
||||
else
|
||||
BAD=$(nssh pvemini "ha-manager status | grep -E 'started|migrate|relocate|fence'" || true)
|
||||
fi
|
||||
[[ -z "$BAD" ]] || { printf '%s\n' "$BAD"; die "Există servicii HA încă active. NU opri nodurile."; }
|
||||
ok "toate serviciile HA sunt în state stopped"
|
||||
|
||||
STILL=0
|
||||
for node in "${!NODE_IP[@]}"; do
|
||||
n=$(nssh "$node" "{ pct list; qm list; } | grep -c running || true")
|
||||
if (( ALLOW_ON_NODE )); then
|
||||
n=$(nssh "$node" "{ pct list | awk '\$2==\"running\" && \$1!=$KEEP_RUNNING'; qm list; } | grep -c running || true")
|
||||
else
|
||||
n=$(nssh "$node" "{ pct list; qm list; } | grep -c running || true")
|
||||
fi
|
||||
(( STILL += n ))
|
||||
done
|
||||
(( STILL == 0 )) || die "$STILL guest-uri încă pornite. NU opri nodurile."
|
||||
@@ -383,6 +450,23 @@ step "Oprire noduri"
|
||||
|
||||
for node in "${NODE_SHUTDOWN_ORDER[@]}"; do
|
||||
log "opresc $node (${NODE_IP[$node]})..."
|
||||
|
||||
if (( ALLOW_ON_NODE )) && [[ "$node" == "$LOCAL_NODE" ]] && (( ! DRY_RUN )); then
|
||||
# Nodul local (pvemini) e ultimul din listă — nu se poate ping-a pe sine
|
||||
# după poweroff, deci nu intră în bucla de așteptare de mai jos. Trimite
|
||||
# raportul final și se oprește direct.
|
||||
REPORT="Cluster romfast oprit. Noduri oprite: ${NODE_SHUTDOWN_ORDER[*]}."
|
||||
if [[ -n "${INFRA_LOG:-}" && -r "$INFRA_LOG" ]]; then
|
||||
REPORT+=$'\n\nUltimele linii din jurnal:\n'"$(tail -n 30 "$INFRA_LOG")"
|
||||
fi
|
||||
printf '%s\n' "$REPORT" | mail -s "[INFO] Cluster romfast: oprit" "$ALERT_RECIPIENT" \
|
||||
|| warn "raportul final nu a putut fi trimis"
|
||||
postqueue -f 2>/dev/null || true
|
||||
sleep 15
|
||||
systemctl poweroff --no-block
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run "$node" "systemctl poweroff --no-block" || true
|
||||
(( DRY_RUN )) && continue
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user