feat(cluster): corosync ring1 pe insula, test de repornire cu WoL, fixuri in scripturi

Ring1 aplicat si verificat pe cluster live: config_version 17, ring1_addr pe
10.10.10.20x la fiecare nod, interface{linknumber:1}. Validat cu `corosync -t`
inainte de instalare. Testat prin oprirea reala a lui ring0 pe pveelite: link0
disconnected, link1 connected, cvorum 3/3 neatins - exact scenariul care pe
27 august a lasat nodul mort 16 ore.

Test de repornire completa a clusterului, cu Wake-on-LAN (trezire in ~15s).
Insula a urcat singura pe toate trei nodurile; ipoteza enumerarii tarzii a
USB-ului nu s-a materializat. Unealta noua: wake-cluster.ps1.

Testul a scos la iveala o linie ramasa in /etc/fstab pe pve1 si pveelite, care
monta storage-ul NFS de pe IP-ul de productie inaintea lui pvestatd. Backup-ul
trecea tacut pe reteaua gresita, cu storage.cfg corect. cluster-startup verifica
acum asta automat, impreuna cu IP-urile de insula si conectivitatea reala.

Patru bug-uri gasite prin rulare pe cluster live:

- sonda Oracle nu avea timeout: un sqlplus agatat pe o instanta in pornire a
  blocat cluster-startup 14 minute, fara mesaj, cu propriul prag de 600s
  nefolosit, fiindca bucla n-a apucat o iteratie;
- `bash -c` in loc de `bash -lc`: sqlplus lipsea din PATH, deci baza nu se
  oprea si containerul s-ar fi inchis peste ea;
- PowerShell 5.1 pierde ghilimelele duble catre exe-uri native, deci comanda
  ajungea rupta pe nod - trecut pe trimitere codificata base64;
- backup-ul de crontab si fisierul de stare se rescriau la o a doua rulare,
  lasand monitorizarea oprita permanent si lista de repornit goala.

Documentatia de oprire planificata pornea de la o afirmatie devenita falsa
("nu exista datacenter.cfg") si de la o comanda care ar fi adaugat o a doua
linie `ha:`. Actualizata, impreuna cu inventarul de guest-uri (VM 304 lipsea
din toate listele de ordine si cadea in maturarea de dupa Oracle).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
This commit is contained in:
Marius
2026-08-29 21:13:34 +03:00
parent fd2105d1f9
commit d7b4007af8
10 changed files with 801 additions and 113 deletions

View File

@@ -1,17 +1,24 @@
# Reatasarea adaptoarelor USB LAN dupa un reset pe magistrala USB.
#
# De ce exista: adaptoarele Realtek RTL8156 (driver r8152) folosite ca placa de
# retea principala pe pve1 si pveelite se reseteaza singure pe xHCI. Kernelul
# sterge interfata si o recreeaza, dar nimeni NU o readauga in vmbr0 si nu o
# ridica - interfata e declarata "inet manual", fara "auto" si fara
# "allow-hotplug", deci se ridica doar ca efect secundar la boot.
# De ce exista: adaptoarele Realtek RTL8156 (driver r8152) de pe pve1 si pveelite
# se reseteaza singure pe xHCI. Kernelul sterge interfata si o recreeaza, dar
# nimeni NU o reconfigureaza - se ridica doar ca efect secundar la boot.
#
# Rezultatul: nodul merge perfect si e invizibil in retea, pe termen nelimitat.
# Pe pveelite asta a insemnat 16 ore de tacere in noaptea de 27 spre 28 august
# 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md
# Cand a fost scrisa regula, dongle-ul era portul puntii vmbr0, adica placa de
# retea principala: un reset lasa nodul perfect functional si invizibil in retea,
# pe termen nelimitat. Pe pveelite asta a insemnat 16 ore de tacere in noaptea de
# 27 spre 28 august 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md
#
# Din 2026-08-29 vmbr0 e pe interfata onboard, iar dongle-ul tine doar IP-ul
# retelei dedicate de cluster (10.10.10.200 / 10.10.10.202, strofa "auto ...
# inet static"). Miza s-a schimbat: un reset nu mai ia nodul offline, ci opreste
# tacut replicarea ZFS si NFS-ul backup-pvemini.
# Vezi ../../docs/switch-2.5g-dedicat-noduri.md
#
# Regula prinde momentul in care interfata reapare si reaplica configuratia.
# Se declanseaza si la boot; e inofensiv, ifreload -a e idempotent.
# Se declanseaza si la boot - inofensiv (ifreload -a e idempotent) si chiar util:
# acopera cazul in care USB-ul se enumereaza dupa networking.service, singurul
# risc ramas pentru reteaua de insula.
ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="r8152", RUN+="/usr/bin/systemctl --no-block start usb-lan-hotplug@$env{INTERFACE}.service"

View File

@@ -1,5 +1,5 @@
[Unit]
Description=Reataseaza adaptorul USB LAN %I in punte dupa un reset USB
Description=Reaplica configuratia de retea pentru adaptorul USB LAN %I dupa un reset USB
Documentation=file:///etc/udev/rules.d/70-usb-lan-hotplug.rules
After=network.target
@@ -11,9 +11,10 @@ Type=oneshot
# inainte ca astea sa se aseze prinde interfata intr-o stare intermediara.
ExecStartPre=/bin/sleep 3
# ifreload -a, nu ifup: interfata e "inet manual" si e port de punte, iar doar
# ifreload reface legatura cu vmbr0. E si comanda verificata in teren - exact ea
# a readus pveelite in retea pe 28.08 la 09:49, de la consola fizica.
# ifreload -a, nu ifup: e comanda verificata in teren - exact ea a readus pveelite
# in retea pe 28.08 la 09:49, de la consola fizica. Atunci interfata era port de
# punte si doar ifreload refacea legatura cu vmbr0; din 2026-08-29 are strofa
# proprie cu IP static pe reteaua de cluster, pe care ifreload o reaplica la fel.
# Aplica numai diferentele fata de /etc/network/interfaces, deci nu atinge
# interfetele tap/veth ale guest-urilor.
ExecStart=/usr/sbin/ifreload -a

View File

@@ -6,7 +6,15 @@ relocheze resursele** și **fără ca watchdog-ul să reseteze hard vreun nod**.
Se aplică oricărei opriri planificate a întregului cluster: lucrări electrice,
mutare rack, mentenanță UPS, intervenții la switch.
**Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-27
**Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-29
> **Atenție, s-a schimbat ceva important pe 2026-08-29.** Clusterul are acum o rețea
> dedicată, `10.10.10.0/24` („insula"), pe un switch separat — vezi
> [switch 2.5G dedicat nodurilor](switch-2.5g-dedicat-noduri.md). De ea atârnă
> **toată replicarea ZFS** (`migration: network=10.10.10.0/24` în `datacenter.cfg`)
> și storage-ul NFS `backup-pvemini-nfs` (server `10.10.10.201`). Pe pve1 și
> pveelite IP-ul de insulă stă pe **dongle-ul USB**, deci la revenire trebuie
> verificat că a urcat — vezi [Repornirea, pasul 3](#repornirea).
---
@@ -24,11 +32,18 @@ cd E:\proiecte\ROMFASTSQL\proxmox\cluster\scripts
.\cluster-shutdown.ps1 -DryRun # arată exact ce ar face, nu atinge nimic
.\cluster-shutdown.ps1 # oprire completă, cu confirmări
# ... după revenirea curentului, cu nodurile pornite fizic ...
# ... după revenirea curentului ...
.\wake-cluster.ps1 # trezește nodurile prin Wake-on-LAN
.\cluster-startup.ps1 # repornire completă
```
**Wake-on-LAN merge pe toate trei nodurile** — verificat pe 2026-08-29, trezire în ~15 secunde.
E posibil abia de la mutarea lui `vmbr0` pe interfețele onboard: dongle-urile USB nu suportă WoL
și pierd alimentarea la oprire. Dacă lucrarea e la rețeaua electrică și nodurile sunt oprite
de tine, `wake-cluster.ps1` te scutește de drumul până la ele. Stația de admin trebuie să fie
pe același segment L2 (`10.0.20.0/24`) — magic packet-ul e broadcast, nu trece prin router.
Cere doar clientul OpenSSH din Windows (`C:\Windows\System32\OpenSSH\ssh.exe`),
deja prezent, și cheia SSH pentru root pe noduri. Testate pe PowerShell 5.1.
@@ -57,13 +72,15 @@ necesar și cum se face manual din GUI, dacă preferi.
## TL;DR (procedura manuală)
1. **Datacenter → Options → HA Settings → Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele
1. **Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele. Din 2026-08-29 e deja setat permanent — doar verifică (Pas 0), nu adăuga o linie nouă în `datacenter.cfg`
2. Dezactivează cron-urile de alertă (`pvemini-down-alert`, `vm109-watchdog`, `pveelite-down-alert`, `oom-alert`)
3. Oprește guest-urile manual — **Oracle CT 108 primul**, cu `shutdown immediate` în DB
4. `ha-manager status` → toate serviciile trebuie să fie `stopped`
5. Oprește nodurile: **pveelite → pve1 → pvemini**
La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum).
La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum),
apoi verifici că insula `10.10.10.0/24` a urcat pe pve1 și pveelite **înainte** de a
porni guest-urile.
---
@@ -71,8 +88,8 @@ La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai qu
### Capcana 1 — politica implicită de shutdown este `conditional`
Pe clusterul `romfast` **nu există** `/etc/pve/datacenter.cfg`, deci PVE folosește
default-ul `shutdown_policy=conditional`:
Fără o linie `ha:` în `/etc/pve/datacenter.cfg`, PVE folosește default-ul
`shutdown_policy=conditional`:
| Acțiune pe nod | Comportament HA cu `conditional` |
|----------------|----------------------------------|
@@ -84,6 +101,11 @@ VM 201 și restul resurselor HA pe pve1/pveelite — pe noduri care oricum urmea
să fie și ele oprite, cu 16 GB RAM pe pveelite și riscul din
[incidentul 2026-04-20](../incidents/2026-04-20-cluster-outage.md).
> **Din 2026-08-29 capcana asta e deja dezamorsată permanent.** Fișierul
> `/etc/pve/datacenter.cfg` **există** și conține `ha: shutdown_policy=freeze`.
> Pasul 0 de mai jos e, în mod normal, doar o verificare. Nu presupune că fișierul
> lipsește — mai conține și linia `migration:`, care nu trebuie pierdută.
### Capcana 2 — watchdog-ul se auto-fence la pierderea quorumului
Cu 3 noduri, quorum = 2. Dacă oprești două noduri și al treilea **încă are
@@ -106,14 +128,37 @@ inofensivă.
## Pas 0 — Shutdown Policy = Freeze
**GUI:** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze`
**CLI (echivalent):**
De obicei **nu ai nimic de făcut aici** — politica e `freeze` din 2026-08-29.
Verifică întâi:
```bash
ssh root@10.0.20.201 "echo 'ha: shutdown_policy=freeze' >> /etc/pve/datacenter.cfg"
ssh root@10.0.20.201 "cat /etc/pve/datacenter.cfg"
```
Așteptat (ambele linii):
```
ha: shutdown_policy=freeze
migration: network=10.10.10.0/24,type=insecure
```
Dacă `shutdown_policy` **nu** e `freeze`, **nu adăuga o linie nouă cu `>>`** —
fișierul are deja o linie `ha:`, iar a doua e o duplicare. Comanda corectă
înlocuiește linia existentă și lasă `migration:` neatinsă:
```bash
ssh root@10.0.20.201 "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta && \
sed -i '/^ha:/d' /etc/pve/datacenter.cfg && \
printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg && \
cat /etc/pve/datacenter.cfg"
```
**GUI (echivalent):** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze`
> **Nu șterge linia `migration:`.** Fără ea replicarea ZFS se întoarce tăcut pe
> rețeaua de producție — merge, dar pe altă cale decât cea proiectată, iar
> traficul de replicare ajunge peste producție.
`Freeze` = la oprirea nodului serviciile HA sunt înghețate, **nu relocate**;
repornesc pe același nod când nodul revine.
@@ -200,17 +245,18 @@ VM 201 (IIS) și CT 104 (flowise) folosesc baza din CT 108.
| # | Nod | Guest | În HA | Cum |
|---|-----|-------|-------|-----|
| 1 | pvemini | VM 303 Win11-Adina | Nu | Shutdown |
| 2 | pvemini | VM 302 oracle-test | Nu | Shutdown |
| 3 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) |
| 4 | pve1 | CT 101 minecraft | Da | Shutdown |
| 5 | pve1 | CT 110 moltbot | Da | Shutdown |
| 6 | pvemini | CT 104 flowise | Da | Shutdown |
| 7 | pvemini | CT 106 gitea | Da | Shutdown |
| 8 | pvemini | CT 103 dokploy | Da | Shutdown |
| 9 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown |
| 10 | pvemini | CT 100 portainer | Da | Shutdown |
| 11 | pvemini | CT 171 claude-agent | Da | Shutdown |
| 12 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT |
| 2 | pvemini | VM 304 Win11-Marius | Nu | Shutdown |
| 3 | pvemini | VM 302 oracle-test | Nu | Shutdown |
| 4 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) |
| 5 | pve1 | CT 101 minecraft | Da | Shutdown |
| 6 | pve1 | CT 110 moltbot | Da | Shutdown |
| 7 | pvemini | CT 104 flowise | Da | Shutdown |
| 8 | pvemini | CT 106 gitea | Da | Shutdown |
| 9 | pvemini | CT 103 dokploy | Da | Shutdown |
| 10 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown |
| 11 | pvemini | CT 100 portainer | Da | Shutdown |
| 12 | pvemini | CT 171 claude-agent | Da | Shutdown |
| 13 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT |
pveelite nu are nimic pornit în mod normal (CT 301 și VM 109 sunt `stopped`).
@@ -275,9 +321,10 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
## Repornirea
1. **Pornește toate trei nodurile aproximativ simultan.** Un singur nod pornit
nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest și
nu poți modifica nimic în GUI.
1. **Pornește toate trei nodurile aproximativ simultan** — fizic, sau cu
`.\wake-cluster.ps1` (Wake-on-LAN, verificat pe 2026-08-29). Un singur nod
pornit nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest
și nu poți modifica nimic în GUI.
2. Așteaptă quorum complet:
@@ -286,19 +333,56 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
ssh root@10.0.20.201 "ha-manager status"
```
3. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201,
3. **Verifică rețeaua de cluster `10.10.10.0/24` — pasul cel mai ușor de uitat.**
Pe pve1 și pveelite IP-ul de insulă stă pe dongle-ul USB, adus la boot de un
`auto enx...` din `/etc/network/interfaces`. Dacă nucleul enumerează adaptorul
USB **după** ce `networking.service` a terminat, interfața rămâne fără IP.
Nodul pornește perfect normal pe producție — dar **replicarea ZFS și storage-ul
NFS `backup-pvemini` tac**, fără nicio alertă.
```bash
ssh root@10.0.20.200 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.200 pe dongle
ssh root@10.0.20.202 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.202 pe dongle
ssh root@10.0.20.201 "ip -br a | grep -E 'vmbr0|enp87s0'" # 10.10.10.201 pe enp87s0
ssh root@10.0.20.201 "ping -c1 -W2 10.10.10.200; ping -c1 -W2 10.10.10.202"
ssh root@10.0.20.200 "findmnt -no SOURCE /mnt/pve/backup-pvemini" # 10.10.10.201:/mnt/backup
```
Dacă un IP lipsește, reparația e `ifreload -a` pe nodul respectiv:
```bash
ssh root@10.0.20.202 "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a"
```
pvemini are interfața de insulă onboard (Intel `enp87s0`) — el nu are problema asta.
**Testat pe 2026-08-29, la o oprire completă reală: insula a urcat singură pe
toate trei nodurile.** Ipoteza enumerării târzii nu s-a materializat. Pe pve1
s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot, care oricum ar
fi reparat-o. Verificarea rămâne totuși în procedură — un singur test reușit nu
exclude o cursă de sincronizare care depinde de temporizări.
**Ce a găsit totuși testul:** o linie rămasă în `/etc/fstab` pe pve1 și pveelite
monta storage-ul NFS de pe `10.0.20.201` (producție) înainte ca `pvestatd` să
apuce, deci backup-ul mergea tăcut pe rețeaua greșită, cu `storage.cfg` arătând
corect. Liniile au fost comentate (backup: `/root/fstab.bak-2026-08-29`).
Dacă vezi vreodată NFS-ul montat de pe alt server decât `10.10.10.201`, acolo
să te uiți întâi. `cluster-startup` verifică asta automat.
4. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201,
apoi restul. Pornirea unui guest HA îi repune state-ul pe `started`.
4. Verifică Oracle:
5. Verifică Oracle:
```bash
ssh root@10.0.20.201 "pct exec 108 -- docker exec oracle-xe bash -lc \
\"printf 'select status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\""
```
5. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup.
6. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup.
6. Verifică replicarea:
7. Verifică replicarea:
```bash
ssh root@10.0.20.201 "pvesr status"
@@ -306,7 +390,8 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
Job-urile `*/15` (108, 171, 201) au ratat rulări; recuperează la primul tick.
7. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0.
8. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0.
În mod normal rămâne `freeze`, deci nu ai ce face.
---
@@ -329,7 +414,7 @@ orchestrat care poate intra peste procedura de aici.
---
## Stare de referință a clusterului (2026-08-27)
## Stare de referință a clusterului (2026-08-29)
Resurse HA configurate — 10 în total:
@@ -346,16 +431,28 @@ Resurse HA configurate — 10 în total:
| vm:109 | pveelite | ha-prefer-pveelite | oracle-dr-windows (`stopped`) |
| vm:201 | pvemini | ha-prefer-pvemini | roacentral |
Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, VM 310.
Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, **VM 304**, VM 310.
`nofailback` este `1` doar pe `ha-prefer-pveelite`; celelalte două grupuri au
`nofailback 0`, deci resursele se întorc automat pe nodul preferat când acesta revine.
Rețea și storage de care depinde procedura, verificate pe 2026-08-29:
| Ce | Valoare |
|---|---|
| `datacenter.cfg` | `ha: shutdown_policy=freeze` + `migration: network=10.10.10.0/24,type=insecure` |
| insulă pve1 / pvemini / pveelite | `10.10.10.200` (USB) / `10.10.10.201` (`enp87s0`) / `10.10.10.202` (USB) |
| NFS `backup-pvemini-nfs` | server `10.10.10.201`, montat pe pve1 și pveelite |
| corosync | **două linkuri**: `LINK ID 0` pe `10.0.20.20x`, `LINK ID 1` pe `10.10.10.20x`, `config_version: 17` |
| Wake-on-LAN | merge pe toate 3 (`ethtool <if>` → `Wake-on: g`); unealta: `../scripts/wake-cluster.ps1` |
---
## Referințe
- [Switch 2.5G dedicat nodurilor — rețeaua `10.10.10.0/24`](switch-2.5g-dedicat-noduri.md)
- [Strategia de failover și HA](../failover/README.md)
- [Incident 2026-04-20 — cluster outage](../incidents/2026-04-20-cluster-outage.md)
- [Incident 2026-08-27 — pveelite fără rețea 16h (USB LAN reset)](../incidents/2026-08-27-pveelite-down.md)
- [Cluster README — SSH, storage, noduri](../README.md)
- [UPS](../ups/README.md)

View File

@@ -4,8 +4,12 @@
**Autor:** Claude Code (mmarius28@gmail.com)
**Status: TERMINAT pe 2026-08-29.** Rețeaua dedicată de cluster `10.10.10.0/24` e activă pe toate
3 nodurile, `vmbr0` a fost mutat pe interfețele onboard, uplink-ul dintre switch-uri a fost scos,
iar replicarea și backup-ul NFS trec verificat pe insulă. Singurele lucruri rămase sunt
opționale — vezi „Ce a rămas de făcut".
iar replicarea și backup-ul NFS trec verificat pe insulă.
**Completat în seara aceleiași zile:** al doilea inel corosync (ring1) pe insulă, verificat prin
oprirea reală a lui ring0; testul de repornire completă a clusterului, cu Wake-on-LAN. Testul a
scos la iveală o linie rămasă în `/etc/fstab` care fixa tăcut backup-ul NFS pe rețeaua de
producție. Toate trei sunt descrise mai jos. Singurul lucru rămas e opțional (MTU 9000).
---
@@ -278,13 +282,42 @@ fiecare pas: link 2500 Mbps, ping pe toată insula, **262 / 272 MB/s**, job real
Opționale, în ordinea valorii:
1. **corosync ring1** pe `10.10.10.0/24` — vezi mai jos, cere fereastră dedicată.
1. ~~**corosync ring1** pe `10.10.10.0/24`~~ — **APLICAT pe 2026-08-29 seara**, vezi mai jos.
2. **MTU 9000** pe segmentul dedicat — de testat, nu se știe dacă switch-ul suportă.
3. **Test la reboot.** Configurația nu a fost validată printr-o repornire. Dongle-urile USB au
acum `auto` în loc să fie porturi de punte; dacă la boot USB-ul se enumeră târziu, interfața
de insulă poate rămâne neconfigurată. Chiar și atunci nodul pornește normal pe producție și
doar replicarea are de suferit — invers față de cum era înainte, când o problemă pe dongle
lua nodul offline complet. De verificat la următoarea repornire planificată.
3. ~~**Test la reboot.**~~ — **FĂCUT pe 2026-08-29 seara**, vezi mai jos.
### Test la repornire — făcut pe 2026-08-29, ipoteza infirmată
Clusterul a fost oprit complet (toate 3 nodurile) și pornit înapoi. **Insula a urcat singură pe
toate trei nodurile**, fără intervenție: enumerarea târzie a USB-ului nu s-a produs. Pe pve1
s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot (20:27:33, terminată cu succes),
care oricum ar fi reparat situația; pe pveelite a fost suficientă strofa `auto`.
**Wake-on-LAN merge pe toate trei nodurile** — nodurile oprite au fost trezite cu magic packet
de pe stația de admin, timp de răspuns ~15 secunde. E câștigul mutării lui `vmbr0` pe interfețele
onboard: dongle-urile USB nu suportă WoL. Unealta: `../scripts/wake-cluster.ps1` (MAC-urile de
producție sunt în ea). `Wake-on: g` era deja armat pe toate trei, iar BIOS-urile nu taie
alimentarea plăcii în S5.
### Ce a găsit testul: o linie rămasă în `/etc/fstab`
Repornirea a scos la iveală o deriva tăcută pe **pve1 și pveelite**:
```
10.0.20.201:/mnt/backup /mnt/pve/backup-pvemini nfs4 defaults,_netdev 0 0
```
Montarea NFS a storage-ului PVE e treaba lui `pvestatd`, din `/etc/pve/storage.cfg` (unde
serverul e corect, `10.10.10.201`). Linia din fstab o lua însă înainte la boot, ocupa punctul
de montare, iar PVE o lăsa așa. Rezultatul: **tot traficul de backup trecea pe rețeaua de
producție**, cu `storage.cfg` arătând perfect corect.
Remontarea manuală făcută pe 29 august a corectat simptomul, dar nu și cauza — starea bună a
supraviețuit exact până la prima repornire. Liniile au fost comentate pe ambele noduri
(backup: `/root/fstab.bak-2026-08-29`), montările refăcute și verificate:
`clientaddr=10.10.10.200` / `10.10.10.202`, scriibile.
`cluster-startup` verifică acum asta automat la fiecare pornire.
**De reținut:** `vmbr0` și-a schimbat MAC-ul pe toate 3 nodurile (a preluat MAC-ul noii
interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza.
@@ -293,7 +326,7 @@ interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza.
## Îmbunătățiri posibile, în ordinea impactului
### 1. Al doilea inel corosync (link1) pe switch-ul nou — cel mai valoros pas următor
### 1. Al doilea inel corosync (link1) pe switch-ul nou — APLICAT pe 2026-08-29
Cluster-ul are azi un singur inel corosync (`linknumber: 0`, peste `vmbr0`/IP-ul principal —
vezi `/etc/pve/corosync.conf`). Când adaptorul USB al lui pveelite s-a resetat (aprilie și
@@ -305,14 +338,49 @@ inclusiv pe pve1 dacă i s-ar întâmpla la fel.
verificate. Ring0 stă acum pe interfețele onboard (mai robuste), iar ring1 pe insulă ar acoperi
și cazul în care dongle-ul USB sau portul din switch cedează.
#### Cum s-a aplicat
Fereastra: guest-urile oprite cu `cluster-shutdown.ps1 -NoNodes`, care lasă exact starea
necesară — toate resursele HA în `stopped`, watchdog-ul dezarmat, dar clusterul **pornit și
cvorat**, fiindcă `corosync.conf` stă pe pmxcfs și fără cvorum devine read-only.
```bash
pvecm status # verifică starea curentă înainte de orice modificare
# adăugare link1 se face editând /etc/pve/corosync.conf, incrementând config_version,
# adăugând ring1_addr = 10.10.10.20x la fiecare nod + interface { linknumber: 1 } în totem
# backup pe FIECARE nod, si copia din /etc/pve, si cea locala pe care o citeste corosync
cp /etc/pve/corosync.conf /root/corosync.conf.bak-2026-08-29
cp /etc/corosync/corosync.conf /root/corosync.local.bak-2026-08-29
# validare INAINTE de instalare - corosync stie sa-si testeze propriul fisier
COROSYNC_MAIN_CONFIG_FILE=/root/corosync.new.conf corosync -t # exit 0 = sintaxa buna
cp /root/corosync.new.conf /etc/pve/corosync.conf.new
mv /etc/pve/corosync.conf.new /etc/pve/corosync.conf # pmxcfs propaga singur
```
**Neaplicat** — schimbarea corosync.conf e sensibilă (poate rupe cvorumul dacă sintaxa greșește)
și merită o fereastră dedicată.
Modificările: `ring1_addr: 10.10.10.20x` la fiecare nod, un al doilea bloc
`interface { linknumber: 1 }` în `totem`, `config_version` 16 → 17. `link_mode` era deja
`passive`, potrivit pentru două linkuri. Propagarea pe toate 3 nodurile a durat sub 12 secunde.
#### Verificat prin oprirea reală a lui ring0
Nu doar configurat — **testat**. Cu `eno1` scos administrativ pe pveelite (plasă de siguranță:
`ifreload -a` programat la 120s), corosync a arătat de pe pvemini:
```
LINK ID 0 nodeid 3: disconnected
LINK ID 1 nodeid 3: connected
Quorate: Yes, Total votes: 3
```
Exact situația din 27 august, când pveelite a fost declarat mort 16 ore. Acum clusterul nici
nu clipește. Restaurarea s-a făcut prin **calea out-of-band de pe insulă**
(`ssh` de pe pvemini către `10.10.10.202`), care a funcționat cu nodul invizibil pe producție.
#### Ce NU rezolvă ring1 — de reținut
În timpul testului, **pveelite a apărut gri/indisponibil în GUI-ul Proxmox**, deși cvorumul era
intact. E corect: GUI-ul nu se uită la corosync, ci vorbește prin API pe `8006`, peste IP-ul de
management din `10.0.20.x`. Ring1 protejează **cvorumul, pmxcfs și HA** — nu accesul de
management. Pentru management out-of-band există SSH pe insulă, de pe un nod pe altul.
### 2, 3 și 4 — aplicate pe 2026-08-29
@@ -327,9 +395,9 @@ pe 1G nu costă nimic pe pve1/pveelite.
## Ce NU s-a schimbat
- `/etc/pve/corosync.conf` — **neatins.** Ring0 folosește în continuare IP-urile `10.0.20.20x`,
care acum stau pe interfețele onboard — deci corosync a câștigat robustețe fără nicio
modificare de configurație.
- `/etc/pve/corosync.conf` — neatins la mutarea rețelei; **modificat seara aceleiași zile**,
pentru ring1 (`config_version: 17`) — vezi mai sus. Ring0 folosește în continuare IP-urile
`10.0.20.20x`, care acum stau pe interfețele onboard.
- Cablurile nodurilor — **niciunul mutat de scripturi.** Toate mutările fizice au fost făcute
manual de utilizator (routerul pe switch 2, dongle-ul lui pveelite pe p4, al lui pve1 pe p3).
- Cage-urile SFP+ ale plăcii X710 de pe pvemini — rămân goale, placa nefolosită. La verificări,

View File

@@ -53,6 +53,7 @@ $NodeShutdownOrder = @('pveelite', 'pve1', 'pvemini')
# Orice guest pornit care nu apare aici e oprit la final, intr-o maturare.
$GuestShutdownOrder = @(
303, # Win11-Adina - desktop, fara dependente
304, # Win11-Marius - desktop, fara dependente
302, # oracle-test-302 - VM de test
310, # Win11-Template - template
201, # roacentral - IIS reverse proxy, consumator Oracle
@@ -110,6 +111,21 @@ function Invoke-Node {
return $out
}
# Comenzi cu ghilimele imbricate NU se trimit prin Invoke-Node.
#
# PowerShell 5.1 pierde ghilimelele duble cand paseaza un argument catre un
# executabil nativ: `bash -lc "printf '...' | sqlplus"` ajunge pe nod ca
# `bash -lc printf '...' | sqlplus`, adica printf fara format, iar sqlplus rulat
# pe NOD, unde nu exista. Esecul e usor de citit gresit ca "lipseste sqlplus".
# In loc sa ne luptam cu nivelurile de citare, trimitem scriptul codificat.
# Verificat pe 2026-08-29, dupa ce varianta directa a esuat pe cluster live.
function Invoke-NodeScript {
param([string]$Node, [string]$Script)
$lf = $Script -replace "`r`n", "`n"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($lf))
return Invoke-Node -Node $Node -Command "echo $b64 | base64 -d | bash"
}
# Ca Invoke-Node, dar respecta -DryRun (pentru comenzi care modifica starea).
function Invoke-NodeChange {
param([string]$Node, [string]$Command)
@@ -226,10 +242,22 @@ if ($running.Count -eq 0) {
# reporneasca VM-uri oprite intentionat (302, 310, 301...).
# Format identic cu varianta bash, ca cele doua sa fie interschimbabile.
if (-not $DryRun) {
# Nu rescrie peste o stare existenta cu un inventar gol. Cazul real: scriptul
# e rulat a doua oara (dupa -NoNodes, sau dupa o eroare), cand guest-urile sunt
# deja oprite - inventarul e gol si am pierde exact lista de repornit.
$existing = @()
if (Test-Path $StateFile) {
$existing = @(Get-Content $StateFile | Where-Object { $_ -notmatch '^\s*#' -and $_.Trim() })
}
if ($running.Count -eq 0 -and $existing.Count -gt 0) {
Write-Warn "Niciun guest pornit, dar $StateFile contine deja $($existing.Count) intrari - il PASTREZ."
Write-Warn "Altfel cluster-startup n-ar mai sti ce sa porneasca."
} else {
$lines = @("# stare cluster salvata la $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')")
foreach ($g in $running) { $lines += "$($g.Type) $($g.Node) $($g.Id) $($g.Status) $($g.Ha)" }
Set-Content -Path $StateFile -Value $lines -Encoding utf8
Write-Ok "stare salvata in $StateFile"
}
}
Confirm-Step "Se opresc $($running.Count) guest-uri, apoi nodurile: $($NodeShutdownOrder -join ', ')."
@@ -246,6 +274,9 @@ if ($currentPolicy -eq 'freeze') {
Write-Ok "deja pe freeze"
} else {
if ($null -ne $currentPolicy -or ($dcCfg | Measure-Object).Count -gt 0) {
# sed sterge DOAR linia 'ha:', nu rescrie fisierul: din 2026-08-29 acolo
# sta si 'migration: network=10.10.10.0/24,type=insecure', fara de care
# replicarea se intoarce tacut pe reteaua de productie.
Invoke-NodeChange -Node 'pvemini' -Command 'cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta' | Out-Null
Invoke-NodeChange -Node 'pvemini' -Command "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" | Out-Null
} else {
@@ -262,7 +293,10 @@ Write-Step "Dezactivare cron-uri de alerta"
$sedCron = "crontab -l | sed -E '/^[^#]/ s%^(.*($CronPattern)\.sh.*)`$%#MENTENANTA \1%' | crontab -"
foreach ($node in $NodeIp.Keys) {
Invoke-NodeChange -Node $node -Command "crontab -l > $CronBackup 2>/dev/null || true" | Out-Null
# Backup DOAR daca crontab-ul curent nu e deja comentat. Altfel o a doua rulare
# a scriptului (dupa o eroare, de exemplu) ar salva peste backup versiunea deja
# comentata, iar cluster-startup ar "restaura" monitorizarea oprita - tacut.
Invoke-NodeChange -Node $node -Command "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup pastrat'; else crontab -l > $CronBackup 2>/dev/null || true; echo 'backup nou'; fi" | Out-Null
Invoke-NodeChange -Node $node -Command $sedCron | Out-Null
if (-not $DryRun) {
$n = Invoke-Node -Node $node -Command "crontab -l | grep -c '^#MENTENANTA' || true"
@@ -285,13 +319,27 @@ if (-not $oracleGuest) {
if ($DryRun) {
Write-Host " [dry-run] shutdown immediate in $OracleDocker" -ForegroundColor DarkGray
} else {
$sqlCmd = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''shutdown immediate\nexit\n'' | sqlplus -s / as sysdba"'
$out = Invoke-Node -Node $oracleGuest.Node -Command $sqlCmd
if ($LASTEXITCODE -ne 0) {
Write-Warn "Shutdown-ul bazei a raportat eroare. Verifica manual inainte de a continua!"
$out | Select-Object -Last 5 | Write-Host
# 'bash -lc', NU 'bash -c': fara shell de login, PATH-ul Oracle nu e incarcat
# si comanda esueaza cu "sqlplus: command not found" - adica baza ramane
# DESCHISA, iar containerul ar fi oprit peste ea.
# '2>&1' se face pe nod, nu in PowerShell: altfel stderr-ul lui ssh devine
# ErrorRecord in PS 5.1 si arunca exceptie in loc sa fie tratat aici.
# 'timeout 180': un 'shutdown immediate' poate dura, dar nu la infinit. Fara
# el, un sqlplus blocat ar tine scriptul agatat fara niciun mesaj (patit pe
# 2026-08-29, la sonda echivalenta din cluster-startup).
$sqlScript = @"
timeout 180 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba" 2>&1
"@
$out = Invoke-NodeScript -Node $oracleGuest.Node -Script $sqlScript
# Codul de iesire nu e o dovada: sqlplus intoarce 0 si cand n-a inchis nimic.
# Singura confirmare reala e mesajul instantei.
if (($out -join "`n") -match 'ORACLE instance shut down') {
Write-Ok "instanta Oracle oprita curat"
} else {
Write-Ok "instanta Oracle oprita"
Write-Warn "Baza NU a confirmat 'ORACLE instance shut down'. Ultimele linii:"
$out | Select-Object -Last 8 | Write-Host
Confirm-Step "Oprirea CT $OracleCt peste o baza posibil deschisa. Continui?"
}
}
}

View File

@@ -34,6 +34,7 @@ NODE_SHUTDOWN_ORDER=(pveelite pve1 pvemini)
# Orice guest pornit care nu apare aici e oprit la final, într-o măturare.
GUEST_SHUTDOWN_ORDER=(
303 # Win11-Adina — desktop, fără dependențe
304 # Win11-Marius — desktop, fără dependențe
302 # oracle-test-302 — VM de test
310 # Win11-Template — template
201 # roacentral — IIS reverse proxy, consumator Oracle
@@ -202,9 +203,19 @@ fi
# Salvează starea LOCAL — cluster-startup.sh pornește exact ce era pornit,
# ca să nu repornească VM-uri oprite intenționat (302, 310, 301...).
if (( ! DRY_RUN )); then
# Nu rescrie peste o stare existentă cu un inventar gol. Cazul real: scriptul
# e rulat a doua oară (după --no-nodes, sau după o eroare), când guest-urile
# sunt deja oprite — inventarul e gol și am pierde exact lista de repornit.
EXISTING=0
[[ -f "$STATE_FILE" ]] && EXISTING=$(grep -cv -e '^[[:space:]]*#' -e '^[[:space:]]*$' "$STATE_FILE" || true)
if [[ -z "$RUNNING" ]] && (( EXISTING > 0 )); then
warn "Niciun guest pornit, dar $STATE_FILE conține deja $EXISTING intrări — îl PĂSTREZ."
warn "Altfel cluster-startup.sh n-ar mai ști ce să pornească."
else
{ echo "# stare cluster salvată la $(date '+%Y-%m-%d %H:%M:%S')"
printf '%s\n' "${RUNNING:-}"; } > "$STATE_FILE"
ok "stare salvată în $STATE_FILE"
fi
fi
confirm "Se opresc $RUNNING_COUNT guest-uri, apoi nodurile: ${NODE_SHUTDOWN_ORDER[*]}."
@@ -218,6 +229,9 @@ if [[ "$CURRENT_POLICY" == "freeze" ]]; then
ok "deja pe freeze"
else
if nssh pvemini "test -f /etc/pve/datacenter.cfg"; then
# sed șterge DOAR linia 'ha:', nu rescrie fișierul: din 2026-08-29 acolo
# stă și 'migration: network=10.10.10.0/24,type=insecure', fără de care
# replicarea se întoarce tăcut pe rețeaua de producție.
run pvemini "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta"
run pvemini "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg"
else
@@ -231,7 +245,10 @@ fi
step "Dezactivare cron-uri de alertă"
for node in "${!NODE_IP[@]}"; do
run "$node" "crontab -l > $CRON_BACKUP 2>/dev/null || true"
# Backup DOAR dacă crontab-ul curent nu e deja comentat. Altfel o a doua rulare
# a scriptului (după o eroare, de exemplu) ar salva peste backup versiunea deja
# comentată, iar cluster-startup.sh ar „restaura" monitorizarea oprită — tăcut.
run "$node" "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup păstrat'; else crontab -l > $CRON_BACKUP 2>/dev/null || true; fi"
run "$node" "crontab -l | sed -E '/^[^#]/ s%^(.*($CRON_PATTERN)\.sh.*)\$%#MENTENANTA \1%' | crontab -"
if (( ! DRY_RUN )); then
n=$(nssh "$node" "crontab -l | grep -c '^#MENTENANTA' || true")
@@ -253,9 +270,23 @@ else
if (( DRY_RUN )); then
printf '%s [dry-run] shutdown immediate în %s%s\n' "$c_dim" "$ORACLE_DOCKER" "$c_off"
else
nssh "$ORACLE_NODE" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\"" \
|| warn "Shutdown-ul bazei a raportat eroare. Verifică manual înainte de a continua!"
ok "instanța Oracle oprită"
# `bash -lc`, NU `bash -c`: fără shell de login, PATH-ul Oracle nu e încărcat
# și comanda eșuează cu „sqlplus: command not found" — adică baza rămâne
# DESCHISĂ, iar containerul ar fi oprit peste ea.
# `timeout 180`: un „shutdown immediate" poate dura, dar nu la infinit. Fără
# el, un sqlplus blocat ar ține scriptul agățat fără niciun mesaj (pățit pe
# 2026-08-29, la sonda echivalentă din cluster-startup.sh).
SQL_OUT=$(nssh "$ORACLE_NODE" "timeout 180 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\" 2>&1" || true)
# Codul de ieșire nu e o dovadă: sqlplus întoarce 0 și când n-a închis nimic.
# Singura confirmare reală e mesajul instanței.
if grep -q 'ORACLE instance shut down' <<<"$SQL_OUT"; then
ok "instanța Oracle oprită curat"
else
warn "Baza NU a confirmat „ORACLE instance shut down\". Ultimele linii:"
tail -8 <<<"$SQL_OUT"
confirm "Oprirea CT $ORACLE_CT peste o bază posibil deschisă. Continui?"
fi
fi
fi

View File

@@ -61,6 +61,7 @@ $GuestStartOrder = @(
101, # minecraft
110, # moltbot
303, # Win11-Adina
304, # Win11-Marius
302, # oracle-test-302
109, # oracle-dr-windows
301, # docker-portainer-template
@@ -74,9 +75,26 @@ $GuestStartOrder = @(
# 301, 310 - template-uri, nu servicii.
$NeverAutostart = @(109, 301, 310)
# Reteaua dedicata de cluster ("insula"), existenta din 2026-08-29. De ea atarna
# replicarea ZFS (migration: network=10.10.10.0/24 in datacenter.cfg) si storage-ul
# NFS backup-pvemini. Pe pve1 si pveelite IP-ul sta pe dongle-ul USB, adus la boot
# de un "auto enx..." din /etc/network/interfaces: daca nucleul enumereaza USB-ul
# DUPA ce networking.service a terminat, interfata ramane fara IP, iar nodul pare
# perfect sanatos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md
$IslandIp = [ordered]@{
pve1 = '10.10.10.200'
pvemini = '10.10.10.201'
pveelite = '10.10.10.202'
}
$IslandNfsMount = '/mnt/pve/backup-pvemini'
$IslandNfsServer = '10.10.10.201'
$IslandNfsNodes = @('pve1', 'pveelite')
$OracleCt = 108
$OracleDocker = 'oracle-xe'
$OracleWait = 600 # secunde asteptate pana baza raspunde la interogari
# Masurat pe 2026-08-29, la pornire de la rece dupa oprirea completa a clusterului:
# instanta a avut nevoie de ~10 minute ca sa ajunga OPEN. 600s erau prea strimti.
$OracleWait = 1200 # secunde asteptate pana baza raspunde la interogari
$NodeWait = 1800 # secunde asteptate pana apar toate nodurile
$QuorumWait = 300
@@ -119,6 +137,21 @@ function Invoke-NodeChange {
return Invoke-Node -Node $Node -Command $Command
}
# Comenzi cu ghilimele imbricate NU se trimit prin Invoke-Node.
#
# PowerShell 5.1 pierde ghilimelele duble cand paseaza un argument catre un
# executabil nativ: `bash -lc "printf '...' | sqlplus"` ajunge pe nod ca
# `bash -lc printf '...' | sqlplus`, adica printf fara format, iar sqlplus rulat
# pe NOD, unde nu exista. Esecul e usor de citit gresit ca "lipseste sqlplus".
# In loc sa ne luptam cu nivelurile de citare, trimitem scriptul codificat.
# Verificat pe 2026-08-29, dupa ce varianta directa a esuat pe cluster live.
function Invoke-NodeScript {
param([string]$Node, [string]$Script)
$lf = $Script -replace "`r`n", "`n"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($lf))
return Invoke-Node -Node $Node -Command "echo $b64 | base64 -d | bash"
}
function Confirm-Step {
param([string]$Message)
if ($Yes -or $DryRun) { return }
@@ -209,7 +242,92 @@ Write-Ok "quorum OK, $votes/3 voturi"
Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Select-Object -First 4 | Write-Host
# ------------------------------------------------- pas 3: ce trebuie pornit
# ------------------------------------ pas 3: reteaua dedicata de cluster (insula)
# Se verifica INAINTE de pornirea guest-urilor: daca insula e jos, replicarea si
# backup-ul NFS tac fara nicio alerta, iar simptomul apare abia peste ore.
# Nu blocheaza pornirea - la revenirea curentului, Oracle sus conteaza mai mult
# decat replicarea - dar nu te lasa sa treci pe langa problema fara sa o vezi.
Write-Step "Verific reteaua de cluster (10.10.10.0/24)"
if ($DryRun) {
Write-Warn "dry-run - sar peste verificarea insulei"
} else {
$islandBad = @()
foreach ($node in $NodeIp.Keys) {
$want = $IslandIp[$node]
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
if ($have.Trim() -eq '0') {
# Cazul asteptat de esec: dongle-ul USB enumerat tarziu la boot.
# ifreload -a aplica doar diferentele fata de /etc/network/interfaces,
# deci nu atinge interfetele tap/veth ale guest-urilor.
Write-Warn "$node - lipseste $want, incerc 'ifreload -a'"
Invoke-Node -Node $node -Command 'PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a' | Out-Null
Start-Sleep -Seconds 8
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
}
if ($have.Trim() -eq '0') {
$islandBad += $node
Write-Warn "$node - TOT fara $want dupa ifreload"
} else {
Write-Ok "$node - $want prezent"
}
}
# Un IP configurat local nu garanteaza si cablul/switch-ul. pvemini e sursa
# replicarii si serverul NFS, deci se verifica din el spre celelalte doua.
foreach ($node in @('pve1', 'pveelite')) {
$ip = $IslandIp[$node]
$p = (Invoke-Node -Node 'pvemini' -Command "ping -c1 -W2 $ip >/dev/null 2>&1 && echo OK || echo FAIL") -join ''
if ($p.Trim() -eq 'OK') {
Write-Ok "pvemini -> $node ($ip) - ping OK"
} else {
if ($islandBad -notcontains $node) { $islandBad += $node }
Write-Warn "pvemini -> $node ($ip) - NU raspunde (cablu? switch?)"
}
}
$nfsBad = @()
foreach ($node in $IslandNfsNodes) {
$src = (Invoke-Node -Node $node -Command "findmnt -no SOURCE $IslandNfsMount 2>/dev/null || true") -join ''
if ($src -match [regex]::Escape($IslandNfsServer)) {
Write-Ok "$node - NFS $IslandNfsMount montat de pe $IslandNfsServer"
} elseif ($src.Trim()) {
# Prins pe 2026-08-29 la primul boot la rece: o linie ramasa in /etc/fstab
# monta storage-ul de pe IP-ul de productie INAINTE ca pvestatd sa apuce,
# deci backup-ul mergea tacut pe reteaua gresita, cu storage.cfg corect.
$nfsBad += $node
Write-Warn "$node - NFS montat de pe ALT server: $src (asteptat $IslandNfsServer)"
Write-Warn " verifica: ssh root@$($NodeIp[$node]) 'grep nfs /etc/fstab'"
} else {
# pvestatd remonteaza singur in cateva zeci de secunde daca reteaua e sus.
Write-Warn "$node - NFS $IslandNfsMount NEMONTAT (pvestatd il reia daca insula urca)"
}
}
if ($nfsBad.Count -gt 0) {
Write-Warn "Backup-ul NFS trece pe alta retea decat cea proiectata pe: $($nfsBad -join ', ')"
}
if ($islandBad.Count -gt 0) {
Write-Warn ""
Write-Warn "INSULA E JOS pe: $($islandBad -join ', ')"
Write-Warn "Consecinta: replicarea ZFS si storage-ul NFS backup-pvemini NU vor functiona."
Write-Warn "Guest-urile pornesc si merg normal - problema e tacuta, se vede abia in pvesr status."
Write-Warn "Verifica: ssh root@<nod> `"ip -br a; dmesg | tail -20`" (dongle USB enumerat tarziu?)"
Confirm-Step "Continui cu pornirea guest-urilor, cu replicarea jos?"
} elseif ($nfsBad.Count -eq 0) {
Write-Ok "insula completa - replicarea si NFS-ul au calea libera"
} else {
Write-Warn "insula e sus, dar NFS-ul nu o foloseste - vezi mai sus"
}
}
# ------------------------------------------------- pas 4: ce trebuie pornit
Write-Step "Ce se porneste"
@@ -262,7 +380,7 @@ foreach ($g in $toStart) {
Confirm-Step "Se pornesc guest-urile de mai sus, Oracle primul."
# ------------------------------------------------- pas 4: pornire guest-uri
# ------------------------------------------------- pas 5: pornire guest-uri
Write-Step "Pornire guest-uri"
@@ -306,11 +424,24 @@ function Wait-Oracle {
Write-Log "astept ca instanta Oracle sa accepte interogari..."
if ($DryRun) { return }
$sql = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n'' | sqlplus -s / as sysdba"'
# 'bash -lc', NU 'bash -c': fara shell de login nu exista sqlplus in PATH, iar
# asteptarea ar expira dupa $OracleWait secunde chiar cu baza perfect deschisa.
#
# 'timeout 30' e OBLIGATORIU, nu o precautie. Pe 2026-08-29, la pornirea de la
# rece, sqlplus lansat catre o instanta inca in startup s-a blocat definitiv:
# docker exec a mostenit blocajul, ssh a asteptat, iar bucla de mai jos n-a
# apucat sa faca nici macar o iteratie. Scriptul a stat agatat 14 minute fara
# niciun mesaj, cu propriul prag de asteptare nefolosit. Sonda trebuie sa
# raspunda mereu, fie si cu esec.
$sql = @"
timeout 30 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'set pagesize 0 feedback off\nselect status from v\`$instance;\nexit\n' | sqlplus -s / as sysdba" 2>&1
"@
$waited = 0
while ($waited -lt $OracleWait) {
$out = Invoke-Node -Node $Node -Command $sql
if ($LASTEXITCODE -eq 0 -and (($out -join '') -match '[A-Z]')) {
$out = Invoke-NodeScript -Node $Node -Script $sql
# 'OPEN' e raspunsul instantei deschise; orice altceva (ORA-, container
# inca in pornire) inseamna ca mai asteptam.
if (($out -join '') -match 'OPEN') {
Write-Host "`r `r" -NoNewline
Write-Ok "Oracle deschis si interogabil (${waited}s)"
return
@@ -342,7 +473,7 @@ foreach ($g in $toStart) {
if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" }
}
# ------------------------------------------------ pas 5: restaurare cron-uri
# ------------------------------------------------ pas 6: restaurare cron-uri
Write-Step "Restaurare cron-uri de monitorizare"
@@ -363,7 +494,7 @@ foreach ($node in $NodeIp.Keys) {
}
}
# ------------------------------------------------------- pas 6: verificare
# ------------------------------------------------------- pas 7: verificare
Write-Step "Verificare finala"
@@ -396,6 +527,9 @@ if ($failed.Count -gt 0) {
Write-Host ""
Write-Host " De verificat manual:"
Write-Host " - shutdown_policy e inca 'freeze' (recomandat sa ramana asa)"
Write-Host " - datacenter.cfg are inca AMBELE linii: 'ha: shutdown_policy=freeze'"
Write-Host " si 'migration: network=10.10.10.0/24,type=insecure'"
Write-Host " - daca lipsesc date recente, compara cu ultima replicare din pvesr status"
Write-Host " - daca a fost prima repornire dupa 2026-08-29: noteaza in"
Write-Host " docs/switch-2.5g-dedicat-noduri.md daca insula a urcat singura la boot"
Write-Host ""

View File

@@ -46,6 +46,7 @@ GUEST_START_ORDER=(
101 # minecraft
110 # moltbot
303 # Win11-Adina
304 # Win11-Marius
302 # oracle-test-302
109 # oracle-dr-windows — DR, în mod normal rămâne oprit
301 # docker-portainer-template
@@ -59,9 +60,26 @@ GUEST_START_ORDER=(
# 301, 310 — template-uri, nu servicii.
NEVER_AUTOSTART=(109 301 310)
# Rețeaua dedicată de cluster („insula"), existentă din 2026-08-29. De ea atârnă
# replicarea ZFS (migration: network=10.10.10.0/24 în datacenter.cfg) și storage-ul
# NFS backup-pvemini. Pe pve1 și pveelite IP-ul stă pe dongle-ul USB, adus la boot
# de un „auto enx..." din /etc/network/interfaces: dacă nucleul enumerează USB-ul
# DUPĂ ce networking.service a terminat, interfața rămâne fără IP, iar nodul pare
# perfect sănătos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md
declare -A ISLAND_IP=(
[pve1]=10.10.10.200
[pvemini]=10.10.10.201
[pveelite]=10.10.10.202
)
ISLAND_NFS_MOUNT=/mnt/pve/backup-pvemini
ISLAND_NFS_SERVER=10.10.10.201
ISLAND_NFS_NODES=(pve1 pveelite)
ORACLE_CT=108
ORACLE_DOCKER=oracle-xe
ORACLE_WAIT=600 # secunde așteptate până baza răspunde la interogări
# Măsurat pe 2026-08-29, la pornire de la rece după oprirea completă a clusterului:
# instanța a avut nevoie de ~10 minute ca să ajungă OPEN. 600s erau prea strâmți.
ORACLE_WAIT=1200 # secunde așteptate până baza răspunde la interogări
NODE_WAIT=1800 # secunde așteptate până apar toate nodurile
QUORUM_WAIT=300
@@ -177,7 +195,91 @@ ok "quorum OK, $VOTES/3 voturi"
nssh pvemini "ha-manager status | head -4"
# ------------------------------------------------- pas 3: ce trebuie pornit
# ----------------------------------- pas 3: rețeaua dedicată de cluster (insula)
# Se verifică ÎNAINTE de pornirea guest-urilor: dacă insula e jos, replicarea și
# backup-ul NFS tac fără nicio alertă, iar simptomul apare abia peste ore.
# Nu blochează pornirea — la revenirea curentului, Oracle sus contează mai mult
# decât replicarea — dar nu te lasă să treci pe lângă problemă fără să o vezi.
step "Verific rețeaua de cluster (10.10.10.0/24)"
if (( DRY_RUN )); then
warn "dry-run — sar peste verificarea insulei"
else
ISLAND_BAD=()
for node in "${!NODE_IP[@]}"; do
want="${ISLAND_IP[$node]}"
have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true")
if [[ "$have" == "0" ]]; then
# Cazul așteptat de eșec: dongle-ul USB enumerat târziu la boot.
# ifreload -a aplică doar diferențele față de /etc/network/interfaces,
# deci nu atinge interfețele tap/veth ale guest-urilor.
warn "$node — lipsește $want, încerc 'ifreload -a'"
nssh "$node" "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a" || true
sleep 8
have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true")
fi
if [[ "$have" == "0" ]]; then
ISLAND_BAD+=("$node")
warn "$node — TOT fără $want după ifreload"
else
ok "$node — $want prezent"
fi
done
# Un IP configurat local nu garantează și cablul/switch-ul. pvemini e sursa
# replicării și serverul NFS, deci se verifică din el spre celelalte două.
for node in pve1 pveelite; do
ip="${ISLAND_IP[$node]}"
if nssh pvemini "ping -c1 -W2 $ip >/dev/null 2>&1"; then
ok "pvemini → $node ($ip) — ping OK"
else
printf '%s\n' "${ISLAND_BAD[@]:-}" | grep -qx "$node" || ISLAND_BAD+=("$node")
warn "pvemini → $node ($ip) — NU răspunde (cablu? switch?)"
fi
done
NFS_BAD=()
for node in "${ISLAND_NFS_NODES[@]}"; do
src=$(nssh "$node" "findmnt -no SOURCE $ISLAND_NFS_MOUNT 2>/dev/null || true")
if [[ "$src" == *"$ISLAND_NFS_SERVER"* ]]; then
ok "$node — NFS $ISLAND_NFS_MOUNT montat de pe $ISLAND_NFS_SERVER"
elif [[ -n "${src// /}" ]]; then
# Prins pe 2026-08-29 la primul boot la rece: o linie rămasă în /etc/fstab
# monta storage-ul de pe IP-ul de producție ÎNAINTE ca pvestatd să apuce,
# deci backup-ul mergea tăcut pe rețeaua greșită, cu storage.cfg corect.
NFS_BAD+=("$node")
warn "$node — NFS montat de pe ALT server: $src (așteptat $ISLAND_NFS_SERVER)"
warn " verifică: ssh root@${NODE_IP[$node]} 'grep nfs /etc/fstab'"
else
# pvestatd remontează singur în câteva zeci de secunde dacă rețeaua e sus.
warn "$node — NFS $ISLAND_NFS_MOUNT NEMONTAT (pvestatd îl reia dacă insula urcă)"
fi
done
if (( ${#NFS_BAD[@]} )); then
warn "Backup-ul NFS trece pe altă rețea decât cea proiectată pe: ${NFS_BAD[*]}"
fi
if (( ${#ISLAND_BAD[@]} )); then
warn ""
warn "INSULA E JOS pe: ${ISLAND_BAD[*]}"
warn "Consecință: replicarea ZFS și storage-ul NFS backup-pvemini NU vor funcționa."
warn "Guest-urile pornesc și merg normal — problema e tăcută, se vede abia în pvesr status."
warn "Verifică: ssh root@<nod> \"ip -br a; dmesg | tail -20\" (dongle USB enumerat târziu?)"
confirm "Continui cu pornirea guest-urilor, cu replicarea jos?"
elif (( ${#NFS_BAD[@]} == 0 )); then
ok "insulă completă — replicarea și NFS-ul au calea liberă"
else
warn "insula e sus, dar NFS-ul nu o folosește — vezi mai sus"
fi
fi
# ------------------------------------------------- pas 4: ce trebuie pornit
step "Ce se pornește"
@@ -223,7 +325,7 @@ done <<<"$TO_START"
confirm "Se pornesc guest-urile de mai sus, în ordinea: ${GUEST_START_ORDER[*]}"
# ------------------------------------------------- pas 4: pornire guest-uri
# ------------------------------------------------- pas 5: pornire guest-uri
step "Pornire guest-uri"
@@ -269,7 +371,16 @@ wait_oracle() {
(( DRY_RUN )) && return 0
local waited=0
while (( waited < ORACLE_WAIT )); do
if nssh "$node" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q '[A-Z]'"; then
# `bash -lc`, NU `bash -c`: fără shell de login nu există sqlplus în PATH, iar
# așteptarea ar expira după $ORACLE_WAIT secunde chiar cu baza perfect deschisă.
#
# `timeout 30` e OBLIGATORIU, nu o precauție. Pe 2026-08-29, la pornirea de la
# rece, sqlplus lansat către o instanță încă în startup s-a blocat definitiv:
# docker exec a moștenit blocajul, ssh a așteptat, iar bucla n-a apucat să
# facă nici măcar o iterație. Scriptul a stat agățat 14 minute fără niciun
# mesaj, cu propriul prag de așteptare nefolosit. Sonda trebuie să răspundă
# mereu, fie și cu eșec.
if nssh "$node" "timeout 30 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'set pagesize 0 feedback off\nselect status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q OPEN"; then
ok "Oracle deschis și interogabil (${waited}s)"
return 0
fi
@@ -305,7 +416,7 @@ while read -r type node id status ha; do
start_guest "$type" "$node" "$id" "$ha" || FAILED+=("$type:$id")
done <<<"$TO_START"
# ------------------------------------------------ pas 5: restaurare cron-uri
# ------------------------------------------------ pas 6: restaurare cron-uri
step "Restaurare cron-uri de monitorizare"
@@ -325,7 +436,7 @@ for node in "${!NODE_IP[@]}"; do
fi
done
# ------------------------------------------------------- pas 6: verificare
# ------------------------------------------------------- pas 7: verificare
step "Verificare finală"
@@ -354,7 +465,10 @@ fi
cat <<EOF
De verificat manual:
- shutdown_policy e încă 'freeze' (recomandat să rămână așa)
- datacenter.cfg are încă AMBELE linii: 'ha: shutdown_policy=freeze'
și 'migration: network=10.10.10.0/24,type=insecure'
- dacă lipsesc date recente, compară cu ultima replicare din pvesr status
- dacă a fost prima repornire după 2026-08-29: notează în
docs/switch-2.5g-dedicat-noduri.md dacă insula a urcat singură la boot
EOF

View File

@@ -3,22 +3,31 @@
Instaleaza reatasarea automata a adaptoarelor USB LAN dupa un reset USB.
.DESCRIPTION
Adaptoarele Realtek RTL8156 (driver r8152) folosite ca placa de retea
principala pe pve1 si pveelite se reseteaza singure pe magistrala USB.
Kernelul sterge interfata si o recreeaza, dar nimeni nu o readauga in vmbr0:
in /etc/network/interfaces ea e declarata "inet manual", fara "auto" si fara
"allow-hotplug", deci se ridica doar ca efect secundar, la boot.
Consecinta e un nod care merge perfect si e invizibil in retea, pe termen
nelimitat. Pe pveelite asta a insemnat 16 ore de tacere in noaptea de 27 spre
28 august 2026, plus inca un reset a doua zi la 10:31.
Adaptoarele Realtek RTL8156 (driver r8152) de pe pve1 si pveelite se
reseteaza singure pe magistrala USB. Kernelul sterge interfata si o
recreeaza, dar nimeni nu o reconfigureaza: se ridica doar ca efect secundar,
la boot.
Scriptul instaleaza o regula udev care prinde reaparitia interfetei si o
unitate systemd care reaplica configuratia de retea. Detecteaza singur ce
noduri au adaptor r8152 si le sare pe celelalte - pvemini are placa Intel
igc pe PCIe si nu are nevoie.
unitate systemd care reaplica configuratia de retea (ifreload -a).
Detecteaza singur ce noduri au adaptor r8152 si le sare pe celelalte -
pvemini are placa Intel pe PCIe si nu are nevoie.
CE S-A SCHIMBAT PE 2026-08-29. Cand a fost scris scriptul, dongle-ul USB era
portul puntii vmbr0, adica placa de retea PRINCIPALA - de-aia un reset USB
lasa nodul complet invizibil (16 ore de tacere pe pveelite in noaptea de 27
spre 28 august 2026). Intre timp vmbr0 a fost mutat pe interfata onboard, iar
dongle-ul a ramas doar cu IP-ul retelei dedicate de cluster
(10.10.10.200 / 10.10.10.202), declarat acum "auto ... inet static".
Regula ramane la fel de utila, dar miza s-a schimbat: un reset USB nu mai ia
nodul offline, ci opreste tacut replicarea ZFS si storage-ul NFS
backup-pvemini. In plus, fiind pe ACTION=="add", regula acopera si cazul in
care USB-ul se enumereaza tarziu la boot, dupa networking.service - singurul
risc ramas pentru insula. Neconfirmat inca la o repornire reala.
Context: ../incidents/2026-08-27-pveelite-down.md
../docs/switch-2.5g-dedicat-noduri.md
RULEAZA DE PE STATIA DE ADMIN. SSH intre nodurile Proxmox trece prin
Tailscale si cere autentificare interactiva.
@@ -138,13 +147,28 @@ done
}
Write-Info "adaptoare r8152: $($ifs -join ', ')"
# Interfata conteaza doar daca e chiar portul puntii; altfel regula e inutila.
$port = Invoke-NodeScript -Ip $ip -Script @'
sed -n 's/^[[:space:]]*bridge-ports[[:space:]]*//p' /etc/network/interfaces
# Regula e utila doar daca interfata chiar are ce sa i se reaplice: fie e
# portul puntii (configuratia dinainte de 2026-08-29), fie are propria strofa
# in /etc/network/interfaces (azi: IP-ul retelei dedicate de cluster).
# Fara niciuna, ifreload -a n-ar avea ce configura si regula n-ar folosi la nimic.
$rol = Invoke-NodeScript -Ip $ip -Script @'
port=$(sed -n 's/^[[:space:]]*bridge-ports[[:space:]]*//p' /etc/network/interfaces)
for n in /sys/class/net/*; do
i=${n##*/}
d=$(readlink -f "$n/device/driver" 2>/dev/null)
case "$d" in *r8152) ;; *) continue ;; esac
case " $port " in *" $i "*) echo "$i: port al puntii vmbr0"; continue ;; esac
if grep -qE "^[[:space:]]*(auto|allow-hotplug)[[:space:]]+$i([[:space:]]|$)" /etc/network/interfaces; then
echo "$i: strofa proprie ($(sed -n "s/^[[:space:]]*iface[[:space:]]\+$i[[:space:]]\+//p" /etc/network/interfaces | head -1)), IP: $(ip -4 -o addr show "$i" | awk '{print $4}' | tr '\n' ' ')"
else
echo "$i: NECONFIGURAT in /etc/network/interfaces"
fi
done
'@
Write-Info "bridge-ports vmbr0: $port"
if ($ifs -notcontains $port) {
Write-Warn "adaptorul USB nu e portul puntii - instalez oricum, dar verifica manual"
$rol | ForEach-Object { Write-Info $_ }
if ($rol -match 'NECONFIGURAT') {
Write-Warn "un adaptor r8152 nu e nici port de punte, nici cu strofa proprie -"
Write-Warn "regula se instaleaza, dar pentru el ifreload -a n-are ce aplica"
}
if ($DryRun) {

View File

@@ -0,0 +1,164 @@
<#
.SYNOPSIS
Porneste nodurile Proxmox prin Wake-on-LAN.
.DESCRIPTION
Trimite magic packet catre placile de retea de PRODUCTIE ale nodurilor si
asteapta sa apara pe retea.
De ce merge abia din 2026-08-29: pana atunci pve1 si pveelite aveau IP-ul
principal pe dongle-uri USB Realtek, care nu suporta WoL si oricum pierd
alimentarea cand nodul e oprit. Mutarea lui vmbr0 pe interfetele onboard a
facut WoL posibil - vezi ../docs/switch-2.5g-dedicat-noduri.md.
NU trezi un singur nod si atat, daca ai oprit tot clusterul: un nod pornit
singur nu are quorum, /etc/pve ramane read-only si nu poti porni niciun guest.
Implicit scriptul le trezeste pe toate trei.
RULEAZA DE PE STATIA DE ADMIN, care trebuie sa fie pe acelasi segment L2
(10.0.20.0/24) - magic packet-ul e broadcast si nu trece prin router.
.PARAMETER Node
Trezeste doar nodul dat. Implicit: toate trei.
.PARAMETER Wait
Secunde de asteptat sa apara nodurile (implicit 300).
.PARAMETER NoWait
Trimite pachetele si iese, fara sa astepte.
.EXAMPLE
.\wake-cluster.ps1
.EXAMPLE
.\wake-cluster.ps1 -Node pveelite
#>
[CmdletBinding()]
param(
[ValidateSet('pve1','pvemini','pveelite')]
[string]$Node,
[int]$Wait = 300,
[switch]$NoWait
)
$ErrorActionPreference = 'Stop'
# MAC-ul interfetei de PRODUCTIE (portul puntii vmbr0), nu al dongle-ului de insula.
# Citite de pe noduri pe 2026-08-29 cu: ethtool <if>; cat /sys/class/net/<if>/address
$Nodes = [ordered]@{
pve1 = @{ Ip = '10.0.20.200'; Mac = 'fc:3f:db:0a:0d:d8'; If = 'eno1' }
pvemini = @{ Ip = '10.0.20.201'; Mac = '58:47:ca:7d:51:3c'; If = 'enp90s0' }
pveelite = @{ Ip = '10.0.20.202'; Mac = '84:69:93:57:b2:ea'; If = 'eno1' }
}
function Write-Step { param([string]$Text) Write-Host ""; Write-Host "== $Text" -ForegroundColor Green }
function Write-Ok { param([string]$Text) Write-Host " [ok] $Text" -ForegroundColor Green }
function Write-Warn { param([string]$Text) Write-Host " [!] $Text" -ForegroundColor Yellow }
function Write-Info { param([string]$Text) Write-Host " $Text" -ForegroundColor DarkGray }
function Test-Alive {
param([string]$Address, [int]$TimeoutMs = 1500)
try {
$p = New-Object System.Net.NetworkInformation.Ping
return ($p.Send($Address, $TimeoutMs).Status -eq 'Success')
} catch { return $false }
}
function Send-MagicPacket {
param([string]$Mac)
$bytes = ($Mac -split '[:-]') | ForEach-Object { [byte]([Convert]::ToInt32($_, 16)) }
if ($bytes.Count -ne 6) { throw "MAC invalid: $Mac" }
# Magic packet: 6 octeti 0xFF, apoi MAC-ul repetat de 16 ori.
$packet = New-Object byte[] 102
for ($i = 0; $i -lt 6; $i++) { $packet[$i] = 0xFF }
for ($r = 0; $r -lt 16; $r++) { [Array]::Copy($bytes, 0, $packet, 6 + $r * 6, 6) }
# Trimis si pe broadcast global, si pe cel de subretea: unele stive le trateaza
# diferit, iar pachetul e mic - nu costa nimic sa mearga pe ambele.
# Porturile 9 si 7 sunt cele uzuale; placile asculta oricum dupa continut.
$udp = New-Object System.Net.Sockets.UdpClient
try {
$udp.EnableBroadcast = $true
foreach ($dest in @('255.255.255.255', '10.0.20.255')) {
foreach ($port in @(9, 7)) {
[void]$udp.Send($packet, $packet.Length, $dest, $port)
}
}
} finally {
$udp.Close()
}
}
$targets = if ($Node) { @($Node) } else { $Nodes.Keys }
Write-Step "Trimit magic packet"
$deja = @()
foreach ($n in $targets) {
if (Test-Alive $Nodes[$n].Ip) {
Write-Ok "$n ($($Nodes[$n].Ip)) - deja pornit, nu trimit"
$deja += $n
continue
}
Send-MagicPacket -Mac $Nodes[$n].Mac
Write-Ok "$n -> $($Nodes[$n].Mac) ($($Nodes[$n].If))"
}
$asteptate = @($targets | Where-Object { $deja -notcontains $_ })
if ($asteptate.Count -eq 0) {
Write-Warn "Toate nodurile cerute erau deja pornite."
exit 0
}
if ($NoWait) {
Write-Info "-NoWait: nu astept. Verifica singur cu ping."
exit 0
}
Write-Step "Astept nodurile (max ${Wait}s)"
# Retrimitem periodic: daca placa nu era inca alimentata la primul pachet
# (alimentare tocmai revenita, de exemplu), al doilea o prinde.
$waited = 0
$up = @()
while ($waited -lt $Wait) {
foreach ($n in $asteptate) {
if ($up -contains $n) { continue }
if (Test-Alive $Nodes[$n].Ip) {
$up += $n
Write-Ok "$n a pornit (${waited}s)"
}
}
if ($up.Count -eq $asteptate.Count) { break }
Start-Sleep -Seconds 5
$waited += 5
if ($waited % 60 -eq 0) {
foreach ($n in $asteptate) {
if ($up -notcontains $n) { Send-MagicPacket -Mac $Nodes[$n].Mac }
}
Write-Info "am retrimis pachetele (${waited}s)"
}
}
$lipsa = @($asteptate | Where-Object { $up -notcontains $_ })
Write-Step "Rezultat"
if ($lipsa.Count -eq 0) {
Write-Ok "toate nodurile cerute au pornit"
Write-Host ""
Write-Host " Urmatorul pas: .\cluster-startup.ps1"
Write-Host ""
} else {
Write-Warn "NU au pornit: $($lipsa -join ', ')"
Write-Info "Cauza cea mai probabila e in BIOS, nu in retea:"
Write-Info " - 'Wake on LAN' / 'Power On by PCI-E' dezactivat"
Write-Info " - ErP Ready / Deep Sleep ACTIVAT (taie alimentarea placii in S5)"
Write-Info "Verifica si ca statia de admin e pe acelasi segment L2 (10.0.20.0/24)."
Write-Info "Pe nodurile pornite, starea WoL se vede cu: ethtool <if> | grep Wake-on"
exit 1
}