feat(ups): monitorizare UPS pe serverul 36, in locul ViewPower
ViewPower nu putea dialoga cu acest UPS: 5777 de "QPI return(NAK" si zero citiri reusite in fereastra 6-10 august, pentru ca UPS-ul vorbeste HID Power Device standard, nu dialectul text Voltronic. Serviciile au fost dezactivate pe 10.08.2026 la 22:34, lasand serverul fara nicio alertare 17 zile. Nici inainte nu exista: emailReceivers = 0, iar excuteProgram era gol, deci Oracle nu era oprit curat oricum. UPS-ul comunica insa perfect cu Windows pe canalul HID - dovedit de perechile de evenimente Kernel-Power 105 la panele reale din 2026. Monitorul nou citeste GetSystemPowerStatus, acelasi semnal pe care il foloseste Windows pentru propria actiune la baterie critica. Alerteaza pe email la trecerea pe baterie, la revenirea curentului si cand UPS-ul nu mai comunica - cazul care a trecut neobservat. Opreste curat listenerul, apoi instanta cu shutdown immediate, apoi Windows-ul, inaintea pragului brutal de 20% al Windows-ului. Ruleaza ca task programat sub SYSTEM, cu AllowStartIfOnBatteries si DontStopIfGoingOnBatteries - fara ele Windows ar fi oprit taskul exact cand serverul trece pe baterie. Validat pe 27.08.2026: citire, email, secventa de oprire in dry-run si pierderea comunicatiei (alerta la exact 8 cicluri, apoi revenire). Pragurile de 10 min / 35% sunt conservatoare, nu masurate - testul de autonomie reala ramane de facut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RRyaDj39hPQ89SZS6URRpS
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -25,3 +25,6 @@ docs/handoff_*.md
|
|||||||
|
|
||||||
# Stare runtime salvata de cluster-shutdown.sh (nu e documentatie)
|
# Stare runtime salvata de cluster-shutdown.sh (nu e documentatie)
|
||||||
proxmox/cluster/scripts/.cluster-state.txt
|
proxmox/cluster/scripts/.cluster-state.txt
|
||||||
|
|
||||||
|
# Configurarea reala a monitorului UPS - contine parola SMTP, traieste doar pe server
|
||||||
|
scripts/ups-monitor/config.json
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ system_instructions/ # SQL migration guidelines and knowledge base
|
|||||||
├── system_prompt.md # SQL migration rules - READ BEFORE WRITING SQL
|
├── system_prompt.md # SQL migration rules - READ BEFORE WRITING SQL
|
||||||
└── knowledge_base/ # Example scripts and PACK_MIGRARE.pck
|
└── knowledge_base/ # Example scripts and PACK_MIGRARE.pck
|
||||||
|
|
||||||
scripts/ # Python utilities
|
scripts/ # Utilities
|
||||||
├── generare_proces_verbal.py # Generate assembly minutes (DOCX)
|
├── generare_proces_verbal.py # Generate assembly minutes (DOCX)
|
||||||
└── verificare_voturi_AG.py # Verify assembly votes
|
├── verificare_voturi_AG.py # Verify assembly votes
|
||||||
|
└── ups-monitor/ # UPS monitoring for production Oracle server 10.0.20.36
|
||||||
|
|
||||||
input/ # Oracle DMP files for import
|
input/ # Oracle DMP files for import
|
||||||
```
|
```
|
||||||
@@ -52,6 +53,7 @@ input/ # Oracle DMP files for import
|
|||||||
- **Kit de instalare pentru client (DMP-uri șablon + scripturi, gata de dus la client)**: `proxmox/lxc108-oracle/scripts/build-client-kit.ps1`
|
- **Kit de instalare pentru client (DMP-uri șablon + scripturi, gata de dus la client)**: `proxmox/lxc108-oracle/scripts/build-client-kit.ps1`
|
||||||
- **VM 302 test environment for ROA setup**: `proxmox/vm302-oracle-test/README.md`
|
- **VM 302 test environment for ROA setup**: `proxmox/vm302-oracle-test/README.md`
|
||||||
- **Diagnostic spațiu Oracle la clienți (job zilnic + emailuri)**: `docs/diagnostic-spatiu-clienti.md`
|
- **Diagnostic spațiu Oracle la clienți (job zilnic + emailuri)**: `docs/diagnostic-spatiu-clienti.md`
|
||||||
|
- **Monitorizare UPS pe serverul Oracle de producție 10.0.20.36 (înlocuiește ViewPower)**: `docs/ups-server36-monitorizare.md`
|
||||||
- **Acces SSH la clienți cu chei publice (angajați noi)**: `docs/acces-ssh-chei-angajati.md`
|
- **Acces SSH la clienți cu chei publice (angajați noi)**: `docs/acces-ssh-chei-angajati.md`
|
||||||
- **Acces administrativ la server client prin Tailscale + SSH (fără forward pe router)**: `docs/acces-client-tailscale-ssh.md`
|
- **Acces administrativ la server client prin Tailscale + SSH (fără forward pe router)**: `docs/acces-client-tailscale-ssh.md`
|
||||||
- **Cazuri clienți (depanare DB)**: `proxmox/lxc108-oracle/clienti/README.md`
|
- **Cazuri clienți (depanare DB)**: `proxmox/lxc108-oracle/clienti/README.md`
|
||||||
|
|||||||
266
docs/ups-server36-monitorizare.md
Normal file
266
docs/ups-server36-monitorizare.md
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
# Monitorizare UPS pe serverul Oracle de producție (10.0.20.36)
|
||||||
|
|
||||||
|
**Server:** `ROA-CARAPETRU2` / 10.0.20.36 — Windows 11 Pro, Oracle 19c SE2, instanța `ROA`.
|
||||||
|
Acces: `ssh -p 22122 Administrator@10.0.20.36`.
|
||||||
|
|
||||||
|
**Pe scurt:** ViewPower nu a putut niciodată dialoga cu acest UPS și a fost dezactivat pe
|
||||||
|
10 august 2026, lăsând serverul fără nicio alertare timp de 17 zile. UPS-ul, în schimb,
|
||||||
|
comunică perfect cu Windows pe canalul HID nativ. Monitorizarea nouă folosește exact acel
|
||||||
|
canal. ViewPower e închis și nu mai pornește.
|
||||||
|
|
||||||
|
Se citește împreună cu [`incident-2026-08-10-server36-standby.md`](incident-2026-08-10-server36-standby.md)
|
||||||
|
— cele două se leagă în seara de 10 august.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Diagnosticul: ce era stricat, de fapt
|
||||||
|
|
||||||
|
### UPS-ul comunică — și a comunicat tot timpul
|
||||||
|
|
||||||
|
| Verificare | Rezultat (27.08.2026) |
|
||||||
|
|---|---|
|
||||||
|
| `USB\VID_0665&PID_5161\HID_UPS` | Status **OK**, enumerat corect |
|
||||||
|
| `HID\...&MI_01`, driver `HidBatt` | „HID UPS Battery", Status **OK** |
|
||||||
|
| `Win32_Battery` | `HID UPS`, Status `OK`, BatteryStatus `2` (pe rețea), încărcare **100%** |
|
||||||
|
| Kernel-Power ID **105** (schimbare sursă) | perechi AC↔baterie reale: 10 iun, 13 mai, 5 mai, 25 feb 2026 |
|
||||||
|
|
||||||
|
Perechile de evenimente 105 sunt dovada care închide discuția: la fiecare pană reală Windows
|
||||||
|
a *văzut* trecerea pe baterie și revenirea. Ultima pană: **10 iunie 2026**. Din 10 august
|
||||||
|
(dezactivarea ViewPower) până azi nu a mai fost niciuna, deci nu s-a pierdut niciun eveniment
|
||||||
|
— noroc, nu proiectare.
|
||||||
|
|
||||||
|
### ViewPower nu vorbește limba acestui UPS
|
||||||
|
|
||||||
|
`C:\ViewPower\log\log4j.log` acoperă **6 aug 15:40:37 → 10 aug 22:34:11** (patru treceri peste
|
||||||
|
miezul nopții, numărate în fișier). Din 5787 de linii, **5777 sunt `QPI return(NAK`**. Zero
|
||||||
|
citiri reușite: nicio tensiune, nicio stare de baterie, niciun eveniment electric.
|
||||||
|
|
||||||
|
`QPI` este comanda de identificare a protocolului Voltronic. UPS-ul răspunde `(NAK` —
|
||||||
|
„comandă nesuportată" — deci ViewPower rămâne blocat în bucla de identificare și nu ajunge
|
||||||
|
niciodată să interogheze starea.
|
||||||
|
|
||||||
|
Nu e o problemă de port: `GlobalConfig.portName=USB1F0775B7` din `config\ups.properties`
|
||||||
|
corespunde exact interfeței `HID\VID_0665&PID_5161&MI_00\8&1F0775B7&0&0000`. ViewPower e
|
||||||
|
legat de dispozitivul corect; **setul de comenzi e greșit**. UPS-ul vorbește HID Power Device
|
||||||
|
standard — de aceea îl citește Windows fără niciun driver suplimentar — nu dialectul text pe
|
||||||
|
care îl trimite ViewPower. Nu există setare care să repare asta.
|
||||||
|
|
||||||
|
### Cronologia serii de 10 august
|
||||||
|
|
||||||
|
```
|
||||||
|
22:24:12 upsTomcat pornit (o ultimă încercare de repornire)
|
||||||
|
22:24:31 ups.properties rescris
|
||||||
|
22:34:11 ultimul QPI return(NAK
|
||||||
|
22:34:44 System 7040: upsMonitor auto start → disabled
|
||||||
|
22:34:46 System 7040: upsTomcat demand start → disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
Cineva a încercat să repare, nu a reușit și a dezactivat serviciile. Din acel moment: **zero
|
||||||
|
monitorizare**. Tray-ul care se deschidea și dădea `start ups monitor service error!` încerca,
|
||||||
|
de fapt, să pornească un serviciu pus pe `Disabled` (registry `Start=4`).
|
||||||
|
|
||||||
|
### Ce nu exista oricum, chiar și cu ViewPower funcțional
|
||||||
|
|
||||||
|
- **Alertare: niciuna.** Logul arată `---------emailReceivers.length()-----0`, iar
|
||||||
|
`SmsInfo.mobileNums` e gol. Nimeni nu ar fi aflat de o pană nici dacă softul mergea.
|
||||||
|
- **Oprire curată a Oracle: niciuna.** `Shutdownconfigure.excuteProgram` e gol — nu rula
|
||||||
|
niciun script înainte de shutdown.
|
||||||
|
- Regula proprie „30 min pe baterie → oprire" (`batModeShutdown=true`,
|
||||||
|
`batModeShutdownTime=30`) era inactivă odată cu serviciile.
|
||||||
|
- Vârsta acumulatorilor neurmărită (`GlobalConfig.batteryLifetime=0`).
|
||||||
|
|
||||||
|
### Plasa care exista, totuși
|
||||||
|
|
||||||
|
Planul de alimentare Windows, pe canalul HID care funcționează:
|
||||||
|
|
||||||
|
| Setare | Valoare |
|
||||||
|
|---|---|
|
||||||
|
| Critical battery level | **20 %** |
|
||||||
|
| Critical battery action | **Shut down** (și pe AC, și pe DC) |
|
||||||
|
| Low battery level / action | 40 % / *Do nothing* |
|
||||||
|
| Reserve battery level | 25 % |
|
||||||
|
|
||||||
|
Deci serverul s-ar fi oprit singur la 20 %, dar brutal: fără avertisment, fără `shutdown
|
||||||
|
immediate` pe Oracle, fără ca cineva să afle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Ce s-a pus în loc
|
||||||
|
|
||||||
|
Un singur script PowerShell care citește **`GetSystemPowerStatus`** — exact API-ul pe care îl
|
||||||
|
folosește Windows pentru propria acțiune de „critical battery". Consecința practică: dacă
|
||||||
|
oprirea automată a Windows-ului ar funcționa, funcționează și scriptul; nu există un al doilea
|
||||||
|
canal care să se strice separat.
|
||||||
|
|
||||||
|
| Fișier în repo | Rol |
|
||||||
|
|---|---|
|
||||||
|
| `scripts/ups-monitor/ups-monitor.ps1` | monitorul propriu-zis (buclă + moduri de test) |
|
||||||
|
| `scripts/ups-monitor/install-ups-monitor.ps1` | instalare: copiere, `config.json`, ACL, task programat |
|
||||||
|
| `scripts/ups-monitor/test-comms-loss.ps1` | test: dezactivează temporar bateria HID și verifică alerta de comunicație |
|
||||||
|
| `scripts/ups-monitor/config.example.json` | șablon; **fișierul real nu se comite** (conține parola SMTP) |
|
||||||
|
|
||||||
|
Pe server:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\ROMFAST\ups-monitor\ups-monitor.ps1
|
||||||
|
C:\ROMFAST\ups-monitor\config.json <- parola SMTP; ACL: doar SYSTEM + Administratori
|
||||||
|
C:\ROMFAST\ups-monitor\state.json <- starea între cicluri
|
||||||
|
C:\ROMFAST\ups-monitor\log\ups-monitor-YYYY-MM.log
|
||||||
|
C:\ROMFAST\_deploy\ <- punctul de livrare (de aici rulează instalarea)
|
||||||
|
```
|
||||||
|
|
||||||
|
Task programat **`ROMFAST UPS Monitor`**, rulează ca `NT AUTHORITY\SYSTEM` (cont deja membru
|
||||||
|
`ORA_DBA` pe acest server, deci `sqlplus / as sysdba` merge fără parolă).
|
||||||
|
|
||||||
|
### Ce face
|
||||||
|
|
||||||
|
| Eveniment | Acțiune |
|
||||||
|
|---|---|
|
||||||
|
| Trecere pe baterie | email imediat: **„PANĂ DE CURENT"** |
|
||||||
|
| Revenirea curentului | email cu durata cât s-a stat pe baterie |
|
||||||
|
| **UPS-ul nu mai e văzut de Windows** | email de alertă, repetat la 24 h cât ține defectul |
|
||||||
|
| Comunicația revine | email de confirmare |
|
||||||
|
| Prag de oprire atins | email, apoi oprire curată |
|
||||||
|
|
||||||
|
### Pragurile de oprire
|
||||||
|
|
||||||
|
```
|
||||||
|
10 minute pe baterie SAU încărcare ≤ 35 %
|
||||||
|
(Windows rămâne plasa de siguranță, la 20 %)
|
||||||
|
```
|
||||||
|
|
||||||
|
Se declanșează doar după **3 citiri consecutive** care confirmă starea pe baterie — o citire
|
||||||
|
aberantă nu poate opri serverul. Iar dacă UPS-ul **nu comunică**, scriptul nu ia nicio decizie
|
||||||
|
de oprire; alertează și atât.
|
||||||
|
|
||||||
|
### Secvența de oprire
|
||||||
|
|
||||||
|
1. email (pleacă **primul** — în pană de curent poate cădea și switch-ul în orice moment)
|
||||||
|
2. `Stop-Service OracleOraDB19Home1TNSListener` — nu mai intră conexiuni noi
|
||||||
|
3. `sqlplus / as sysdba` → `shutdown immediate`, cu timeout 180 s
|
||||||
|
4. `Stop-Service OracleServiceROA` — plasă de siguranță; serviciul e el însuși configurat
|
||||||
|
`ORA_ROA_SHUTDOWNTYPE=immediate`, `ORA_ROA_SHUTDOWN_TIMEOUT=90`, deci și oprirea lui e curată
|
||||||
|
5. `shutdown /s /f /t 30`
|
||||||
|
|
||||||
|
> La revenirea curentului serverul **nu pornește singur** decât dacă e activat
|
||||||
|
> *Restore on AC power loss* în BIOS. De verificat separat, la prima intervenție fizică.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Operare
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# starea curentă: UPS, Oracle, task, ultimele linii de log
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\ups-monitor\ups-monitor.ps1 -Mode Status
|
||||||
|
|
||||||
|
# verifică alertarea pe email
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\ups-monitor\ups-monitor.ps1 -Mode TestMail
|
||||||
|
|
||||||
|
# parcurge secvența de oprire în gol — nu oprește nimic
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\ups-monitor\ups-monitor.ps1 -Mode TestShutdown
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ce s-a validat la punerea în funcțiune (27.08.2026)
|
||||||
|
|
||||||
|
| Test | Rezultat |
|
||||||
|
|---|---|
|
||||||
|
| Citirea stării | `pe rețea`, 100 %, `Win32_Battery: HID UPS, Status=OK` |
|
||||||
|
| `-Mode TestMail` | email primit — `mail.romfast.ro:587`, STARTTLS, `ups@romfast.ro` |
|
||||||
|
| `-Mode TestShutdown` (dry-run) | secvența parcursă integral; Oracle a rămas `Running` |
|
||||||
|
| Pierderea comunicației (`test-comms-loss.ps1`) | dispozitiv oprit 15:24:44 → alertă **15:26:44** (exact 8 cicluri × 15 s) + email; reactivare → „comunicația a revenit" + email; `Win32_Battery` înapoi la 1 instanță |
|
||||||
|
| Taskul rulează ca SYSTEM | proces `ups-monitor.ps1 -Mode Run`, `state.json` actualizat la fiecare ciclu |
|
||||||
|
|
||||||
|
**Netestat, pentru că cere pană reală:** trecerea pe baterie, pragurile de oprire și oprirea
|
||||||
|
efectivă a Oracle. Ramura de decizie e comună cu cea testată în dry-run, dar declanșatorul nu
|
||||||
|
a fost exercitat pe hardware. Vezi punctul 1 din secțiunea 6.
|
||||||
|
|
||||||
|
**Dezarmare de urgență** (monitorul rămâne pornit, dar nu mai ia nicio decizie):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
New-Item -ItemType File 'C:\ROMFAST\ups-monitor\DEZACTIVAT'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ștergerea fișierului îl rearmează, fără repornire. Există și `"enabled": false` în
|
||||||
|
`config.json`, dar acela cere repornirea taskului.
|
||||||
|
|
||||||
|
**Reinstalare / schimbare praguri** (de pe stația de administrare):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scp -P 22122 ups-monitor.ps1 install-ups-monitor.ps1 Administrator@10.0.20.36:'C:/ROMFAST/_deploy/'
|
||||||
|
ssh -p 22122 Administrator@10.0.20.36 "powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\install-ups-monitor.ps1 -SmtpPassword ... -ShutdownAfterMinutesOnBattery 10 -ShutdownAtBatteryPercent 35"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Emailuri:** de la `ups@romfast.ro` către `marius.mutu@romfast.ro`, prin
|
||||||
|
`mail.romfast.ro:587` (STARTTLS). Același cont SMTP pe care îl folosesc și cele trei noduri
|
||||||
|
Proxmox — credențialele sunt în `/etc/postfix/sasl_passwd` pe noduri.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Ce s-a întâmplat cu ViewPower
|
||||||
|
|
||||||
|
- procesele `ViewPower.exe`, `upsTray.exe`, `javaw.exe` — **oprite**;
|
||||||
|
- serviciile `upsMonitor` și `upsTomcat` — rămân **Disabled** (erau deja, din 10 august);
|
||||||
|
- **nu există autostart** — nici în `Run` (HKLM/HKCU), nici în folderele Startup; procesele
|
||||||
|
văzute pe 27 august fuseseră pornite manual;
|
||||||
|
- instalarea din `C:\ViewPower` a fost **lăsată pe disc**, ca referință pentru loguri și
|
||||||
|
configurație.
|
||||||
|
|
||||||
|
Efect secundar util: nu mai ascultă nimic pe **15178** și **8005** — un Tomcat 8.5.51 cu
|
||||||
|
OpenJDK 11.0.7 (2020–2021) nu mai stă pornit degeaba pe un server de producție.
|
||||||
|
|
||||||
|
Dezinstalarea completă (`C:\ViewPower\Uninstaller.exe`) e opțională. Înainte de ea merită
|
||||||
|
salvat `C:\ViewPower\datas` — baza Derby de ~71 MB cu istoricul softului.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Capcane plătite (ca să nu se redescopere)
|
||||||
|
|
||||||
|
**Task-urile programate sunt oprite de Windows exact când serverul trece pe baterie.** E
|
||||||
|
comportamentul implicit. Fără `-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries` la
|
||||||
|
`New-ScheduledTaskSettingsSet`, monitorul ar fi fost ucis fix în secunda în care avea treabă.
|
||||||
|
Asta e greșeala care ar fi trecut neobservată până la prima pană reală.
|
||||||
|
|
||||||
|
**`$PSScriptRoot` este gol în valoarea implicită a unui parametru.** La legarea parametrilor
|
||||||
|
încă nu e populat, deci `[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.json')` crapă
|
||||||
|
cu *„Cannot bind argument to parameter 'Path' because it is an empty string"*. Se rezolvă în
|
||||||
|
corpul scriptului.
|
||||||
|
|
||||||
|
**`[TimeSpan]::MaxValue` pentru `-RepetitionDuration` e respins**, deși e documentat ca „la
|
||||||
|
nesfârșit": *„The task XML contains a value which is incorrectly formatted or out of range.
|
||||||
|
Duration: P99999999DT23H59M59S"*. Se omite parametrul — asta chiar înseamnă la nesfârșit.
|
||||||
|
|
||||||
|
**PowerShell 5.1 nu acceptă `if`/`try` ca expresie** într-un literal de hashtable
|
||||||
|
(`@{ x = if (...) {...} }`) sau la atribuire din `try`. Scriptul e scris în consecință; se
|
||||||
|
verifică înainte de livrare cu:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$e=$null; [System.Management.Automation.PSParser]::Tokenize((Get-Content x.ps1 -Raw), [ref]$e); $e
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ghilimelele simple supraviețuiesc prin `ssh` → `powershell -File`.** `-SmtpPassword
|
||||||
|
'#Ups2020#'` a ajuns în `config.json` ca `'#Ups2020#'`, cu tot cu apostrofuri, iar serverul de
|
||||||
|
mail a răspuns *„SMTP AUTH is required for message submission on port 587"* — adică nu s-a
|
||||||
|
autentificat deloc. Pentru orice valoare cu caractere speciale: se trimite un `.ps1` cu `scp`
|
||||||
|
și se rulează acolo, nu se construiește comanda din ghilimele.
|
||||||
|
|
||||||
|
**Fișierul `.ps1` e intenționat fără diacritice.** PowerShell 5.1 citește un `.ps1` fără BOM
|
||||||
|
ca ANSI; diacriticele din literalii de șir ar ajunge corupte în emailuri. Documentația (`.md`)
|
||||||
|
nu are constrângerea asta.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Rămâne de făcut
|
||||||
|
|
||||||
|
1. **Testul de autonomie reală.** Pragurile de 10 min / 35 % sunt alese conservator, nu
|
||||||
|
măsurate. Nu se știe cât ține UPS-ul sub sarcina actuală, deci nu se știe dacă cele 10
|
||||||
|
minute lasă destul timp pentru `shutdown immediate` + oprirea Windows-ului. Se măsoară
|
||||||
|
scoțând UPS-ul din priză, cu monitorul **dezarmat** (`DEZACTIVAT`) și cu cineva care
|
||||||
|
urmărește `-Mode Status`. Până atunci, pragurile sunt o presupunere rezonabilă, nu o
|
||||||
|
garanție.
|
||||||
|
2. **Vârsta acumulatorilor.** Necunoscută. Pe cluster există deja criteriul de vârstă în
|
||||||
|
testul lunar (`proxmox/cluster/ups/`); aici nu există nimic echivalent.
|
||||||
|
3. **BIOS: *Restore on AC power loss*.** De verificat la prima intervenție fizică — altfel
|
||||||
|
după o pană lungă serverul rămâne oprit până vine cineva să apese butonul.
|
||||||
|
4. **Legătura cu incidentul din 10 august:** stările de somn (S3) sunt încă disponibile pe
|
||||||
|
acest server. O oprire declanșată de UPS ajunge la `shutdown /s`, nu la sleep, deci nu e
|
||||||
|
afectată — dar problema de fond rămâne deschisă în documentul incidentului.
|
||||||
30
scripts/ups-monitor/README.md
Normal file
30
scripts/ups-monitor/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# ups-monitor — monitorizare UPS pentru serverul Oracle de producție (10.0.20.36)
|
||||||
|
|
||||||
|
Înlocuiește ViewPower, care nu putea dialoga cu acest UPS. Citește starea alimentării prin
|
||||||
|
`GetSystemPowerStatus` (canalul HID nativ Windows), alertează pe email și oprește curat Oracle
|
||||||
|
înainte de epuizarea bateriei.
|
||||||
|
|
||||||
|
**Documentația completă — diagnostic, praguri, operare, capcane:**
|
||||||
|
[`docs/ups-server36-monitorizare.md`](../../docs/ups-server36-monitorizare.md)
|
||||||
|
|
||||||
|
| Fișier | Rol |
|
||||||
|
|---|---|
|
||||||
|
| `ups-monitor.ps1` | monitorul: buclă + modurile `Status`, `TestMail`, `TestShutdown`, `Once` |
|
||||||
|
| `install-ups-monitor.ps1` | instalare pe server: copiere, `config.json`, ACL, task programat |
|
||||||
|
| `config.example.json` | șablon de configurare |
|
||||||
|
|
||||||
|
`config.json` real trăiește **doar pe server** (`C:\ROMFAST\ups-monitor\`), cu ACL restrâns —
|
||||||
|
conține parola SMTP și nu se comite.
|
||||||
|
|
||||||
|
## Livrare
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scp -P 22122 ups-monitor.ps1 install-ups-monitor.ps1 Administrator@10.0.20.36:'C:/ROMFAST/_deploy/'
|
||||||
|
ssh -p 22122 Administrator@10.0.20.36 "powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\install-ups-monitor.ps1 -SmtpPassword ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Înainte de livrare, verificarea de sintaxă pentru PowerShell 5.1 (ținta de pe server):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$e=$null; [System.Management.Automation.PSParser]::Tokenize((Get-Content ups-monitor.ps1 -Raw), [ref]$e); $e
|
||||||
|
```
|
||||||
35
scripts/ups-monitor/config.example.json
Normal file
35
scripts/ups-monitor/config.example.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"_comentariu": "Sablon. Fisierul real se genereaza de install-ups-monitor.ps1 direct pe server, in C:\\ROMFAST\\ups-monitor\\config.json, si NU se comiteaza - contine parola SMTP.",
|
||||||
|
|
||||||
|
"enabled": true,
|
||||||
|
"pollSeconds": 15,
|
||||||
|
|
||||||
|
"shutdownAfterMinutesOnBattery": 10,
|
||||||
|
"shutdownAtBatteryPercent": 35,
|
||||||
|
"shutdownAtRuntimeSeconds": 0,
|
||||||
|
|
||||||
|
"confirmPolls": 3,
|
||||||
|
"commsLostPolls": 8,
|
||||||
|
"commsAlertRepeatHours": 24,
|
||||||
|
|
||||||
|
"logPath": "C:\\ROMFAST\\ups-monitor\\log",
|
||||||
|
"stateFile": "C:\\ROMFAST\\ups-monitor\\state.json",
|
||||||
|
|
||||||
|
"mailTo": [ "marius.mutu@romfast.ro" ],
|
||||||
|
|
||||||
|
"smtp": {
|
||||||
|
"server": "mail.romfast.ro",
|
||||||
|
"port": 587,
|
||||||
|
"user": "ups@romfast.ro",
|
||||||
|
"password": "PAROLA_AICI",
|
||||||
|
"from": "ups@romfast.ro"
|
||||||
|
},
|
||||||
|
|
||||||
|
"oracle": {
|
||||||
|
"home": "C:\\Users\\Administrator\\Downloads\\WINDOWS.X64_193000_db_home",
|
||||||
|
"sid": "ROA",
|
||||||
|
"service": "OracleServiceROA",
|
||||||
|
"listenerService": "OracleOraDB19Home1TNSListener",
|
||||||
|
"shutdownTimeoutSeconds": 180
|
||||||
|
}
|
||||||
|
}
|
||||||
197
scripts/ups-monitor/install-ups-monitor.ps1
Normal file
197
scripts/ups-monitor/install-ups-monitor.ps1
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Instaleaza monitorizarea UPS pe serverul Oracle de productie: copiaza scriptul,
|
||||||
|
scrie config.json, restrictioneaza drepturile si inregistreaza taskul programat.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Se ruleaza PE SERVER, dintr-un PowerShell ridicat (Administrator).
|
||||||
|
|
||||||
|
Taskul ruleaza ca NT AUTHORITY\SYSTEM - cont care e deja membru ORA_DBA pe acest
|
||||||
|
server, deci "sqlplus / as sysdba" merge fara parola.
|
||||||
|
|
||||||
|
Doua detalii care fac diferenta si sunt usor de ratat:
|
||||||
|
- AllowStartIfOnBatteries + DontStopIfGoingOnBatteries: fara ele, Windows
|
||||||
|
opreste taskul exact cand serverul trece pe baterie, adica exact cand ai
|
||||||
|
nevoie de el;
|
||||||
|
- al doilea declansator, care repeta la 10 minute cu MultipleInstances=IgnoreNew:
|
||||||
|
daca procesul moare, revine singur; daca traieste, pornirea e ignorata.
|
||||||
|
|
||||||
|
.PARAMETER SmtpPassword
|
||||||
|
Parola contului SMTP. Ajunge in config.json, care primeste ACL doar pentru
|
||||||
|
SYSTEM si Administratori. NU se comiteaza in git.
|
||||||
|
|
||||||
|
.PARAMETER Uninstall
|
||||||
|
Sterge taskul programat. Nu sterge directorul, logurile sau configuratia.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\install-ups-monitor.ps1 -SmtpPassword '...'
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$InstallDir = 'C:\ROMFAST\ups-monitor',
|
||||||
|
|
||||||
|
[string]$SmtpServer = 'mail.romfast.ro',
|
||||||
|
[int]$SmtpPort = 587,
|
||||||
|
[string]$SmtpUser = 'ups@romfast.ro',
|
||||||
|
[string]$SmtpFrom = 'ups@romfast.ro',
|
||||||
|
[string]$SmtpPassword,
|
||||||
|
|
||||||
|
[string[]]$MailTo = @('marius.mutu@romfast.ro'),
|
||||||
|
|
||||||
|
[int]$PollSeconds = 15,
|
||||||
|
[int]$ShutdownAfterMinutesOnBattery = 10,
|
||||||
|
[int]$ShutdownAtBatteryPercent = 35,
|
||||||
|
|
||||||
|
[string]$OracleHome = 'C:\Users\Administrator\Downloads\WINDOWS.X64_193000_db_home',
|
||||||
|
[string]$OracleSid = 'ROA',
|
||||||
|
[string]$OracleService = 'OracleServiceROA',
|
||||||
|
[string]$OracleListenerService = 'OracleOraDB19Home1TNSListener',
|
||||||
|
[int]$OracleShutdownTimeoutSeconds = 180,
|
||||||
|
|
||||||
|
[switch]$Uninstall
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$TaskName = 'ROMFAST UPS Monitor'
|
||||||
|
|
||||||
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||||
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
throw 'Scriptul trebuie rulat dintr-un PowerShell ridicat (Administrator).'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Uninstall) {
|
||||||
|
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
|
||||||
|
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||||
|
Write-Host "Task '$TaskName' sters. Directorul $InstallDir a ramas pe loc."
|
||||||
|
} else {
|
||||||
|
Write-Host "Taskul '$TaskName' nu era inregistrat."
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SmtpPassword) {
|
||||||
|
throw 'Lipseste -SmtpPassword. Fara el nu pleaca nicio alerta.'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- fisiere ---------------------------------------------------------------
|
||||||
|
|
||||||
|
$logDir = Join-Path $InstallDir 'log'
|
||||||
|
foreach ($d in @($InstallDir, $logDir)) {
|
||||||
|
if (-not (Test-Path -LiteralPath $d)) {
|
||||||
|
New-Item -ItemType Directory -Path $d -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceScript = Join-Path $PSScriptRoot 'ups-monitor.ps1'
|
||||||
|
if (-not (Test-Path -LiteralPath $sourceScript)) {
|
||||||
|
throw "Nu gasesc ups-monitor.ps1 langa install-ups-monitor.ps1 ($sourceScript)"
|
||||||
|
}
|
||||||
|
Copy-Item -LiteralPath $sourceScript -Destination $InstallDir -Force
|
||||||
|
Write-Host "Copiat ups-monitor.ps1 in $InstallDir"
|
||||||
|
|
||||||
|
$config = [ordered]@{
|
||||||
|
enabled = $true
|
||||||
|
pollSeconds = $PollSeconds
|
||||||
|
shutdownAfterMinutesOnBattery = $ShutdownAfterMinutesOnBattery
|
||||||
|
shutdownAtBatteryPercent = $ShutdownAtBatteryPercent
|
||||||
|
shutdownAtRuntimeSeconds = 0
|
||||||
|
confirmPolls = 3
|
||||||
|
commsLostPolls = 8
|
||||||
|
commsAlertRepeatHours = 24
|
||||||
|
logPath = $logDir
|
||||||
|
stateFile = (Join-Path $InstallDir 'state.json')
|
||||||
|
mailTo = $MailTo
|
||||||
|
smtp = [ordered]@{
|
||||||
|
server = $SmtpServer
|
||||||
|
port = $SmtpPort
|
||||||
|
user = $SmtpUser
|
||||||
|
password = $SmtpPassword
|
||||||
|
from = $SmtpFrom
|
||||||
|
}
|
||||||
|
oracle = [ordered]@{
|
||||||
|
home = $OracleHome
|
||||||
|
sid = $OracleSid
|
||||||
|
service = $OracleService
|
||||||
|
listenerService = $OracleListenerService
|
||||||
|
shutdownTimeoutSeconds = $OracleShutdownTimeoutSeconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$configPath = Join-Path $InstallDir 'config.json'
|
||||||
|
$config | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $configPath -Encoding UTF8
|
||||||
|
Write-Host "Scris $configPath"
|
||||||
|
|
||||||
|
# --- drepturi: config.json contine parola SMTP ------------------------------
|
||||||
|
|
||||||
|
$acl = Get-Acl -LiteralPath $InstallDir
|
||||||
|
$acl.SetAccessRuleProtection($true, $false) # taie mostenirea, fara sa copieze regulile
|
||||||
|
foreach ($r in @($acl.Access)) { $acl.RemoveAccessRule($r) | Out-Null }
|
||||||
|
foreach ($who in @('NT AUTHORITY\SYSTEM', 'BUILTIN\Administrators')) {
|
||||||
|
$acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule(
|
||||||
|
$who, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
|
||||||
|
}
|
||||||
|
Set-Acl -LiteralPath $InstallDir -AclObject $acl
|
||||||
|
Write-Host "Drepturi restranse pe $InstallDir : doar SYSTEM si Administratori"
|
||||||
|
|
||||||
|
# --- verificari inainte de inregistrare -------------------------------------
|
||||||
|
|
||||||
|
foreach ($svc in @($OracleService, $OracleListenerService)) {
|
||||||
|
if (-not (Get-Service -Name $svc -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Warning "Serviciul '$svc' nu exista pe acest server - verifica parametrii Oracle."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath (Join-Path $OracleHome 'bin\sqlplus.exe'))) {
|
||||||
|
Write-Warning "sqlplus.exe nu e la $OracleHome\bin - oprirea va cadea pe Stop-Service."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- taskul programat -------------------------------------------------------
|
||||||
|
|
||||||
|
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument (
|
||||||
|
'-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}\ups-monitor.ps1" -Mode Run' -f $InstallDir)
|
||||||
|
|
||||||
|
$triggerBoot = New-ScheduledTaskTrigger -AtStartup
|
||||||
|
|
||||||
|
# Al doilea declansator: reporneste procesul daca a murit, fara sa-l dubleze.
|
||||||
|
# -RepetitionDuration se lasa nespecificat intentionat: inseamna "la nesfarsit".
|
||||||
|
# [TimeSpan]::MaxValue, desi documentat pentru asta, e respins de Task Scheduler
|
||||||
|
# ("P99999999DT23H59M59S ... out of range").
|
||||||
|
$triggerRepeat = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
|
||||||
|
-RepetitionInterval (New-TimeSpan -Minutes 10)
|
||||||
|
|
||||||
|
$settings = New-ScheduledTaskSettingsSet `
|
||||||
|
-AllowStartIfOnBatteries `
|
||||||
|
-DontStopIfGoingOnBatteries `
|
||||||
|
-StartWhenAvailable `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||||
|
-RestartCount 999 `
|
||||||
|
-RestartInterval (New-TimeSpan -Minutes 1)
|
||||||
|
|
||||||
|
$taskPrincipal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||||
|
|
||||||
|
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
|
||||||
|
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||||
|
}
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $TaskName `
|
||||||
|
-Description 'Monitorizeaza UPS-ul prin canalul HID nativ, alerteaza pe email si opreste curat Oracle inainte de epuizarea bateriei. Inlocuieste ViewPower.' `
|
||||||
|
-Action $action -Trigger @($triggerBoot, $triggerRepeat) `
|
||||||
|
-Settings $settings -Principal $taskPrincipal | Out-Null
|
||||||
|
|
||||||
|
Start-ScheduledTask -TaskName $TaskName
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '--- rezultat ---'
|
||||||
|
Get-ScheduledTask -TaskName $TaskName | Format-Table -AutoSize TaskName, State
|
||||||
|
Get-ScheduledTaskInfo -TaskName $TaskName | Format-List LastRunTime, LastTaskResult, NumberOfMissedRuns
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Verificari utile:'
|
||||||
|
Write-Host (" powershell -ExecutionPolicy Bypass -File {0}\ups-monitor.ps1 -Mode Status" -f $InstallDir)
|
||||||
|
Write-Host (" powershell -ExecutionPolicy Bypass -File {0}\ups-monitor.ps1 -Mode TestMail" -f $InstallDir)
|
||||||
|
Write-Host (" powershell -ExecutionPolicy Bypass -File {0}\ups-monitor.ps1 -Mode TestShutdown" -f $InstallDir)
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host ("Oprire de urgenta a automatismului: New-Item -ItemType File '{0}\DEZACTIVAT'" -f $InstallDir)
|
||||||
65
scripts/ups-monitor/test-comms-loss.ps1
Normal file
65
scripts/ups-monitor/test-comms-loss.ps1
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Verifica alerta "UPS-ul nu mai comunica", dezactivand temporar dispozitivul de
|
||||||
|
baterie HID - echivalentul software al cablului USB scos din UPS.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Se ruleaza PE SERVER, ridicat (Administrator), cu monitorul deja pornit.
|
||||||
|
|
||||||
|
Reactivarea dispozitivului e in blocul finally, ca sa nu ramana oprit daca pica
|
||||||
|
sesiunea SSH sau se intrerupe scriptul. Testul e sigur cat timp serverul e pe
|
||||||
|
retea: fara baterie vizibila, monitorul intra pe ramura de "comunicatie pierduta"
|
||||||
|
si NU ia nicio decizie de oprire.
|
||||||
|
|
||||||
|
Asteptarea implicita e calculata din config.json: commsLostPolls * pollSeconds,
|
||||||
|
plus o marja.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\test-comms-loss.ps1
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$InstallDir = 'C:\ROMFAST\ups-monitor',
|
||||||
|
[int]$WaitSeconds = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$dev = Get-PnpDevice -Class Battery -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.InstanceId -like 'HID\*' } | Select-Object -First 1
|
||||||
|
if (-not $dev) {
|
||||||
|
throw 'Nu am gasit niciun dispozitiv de baterie HID - UPS-ul nu e vazut de Windows nici acum.'
|
||||||
|
}
|
||||||
|
Write-Host "Dispozitiv tinta: $($dev.FriendlyName) [$($dev.InstanceId)]"
|
||||||
|
|
||||||
|
if ($WaitSeconds -le 0) {
|
||||||
|
$cfg = Get-Content -LiteralPath (Join-Path $InstallDir 'config.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
$WaitSeconds = ($cfg.commsLostPolls * $cfg.pollSeconds) + 45
|
||||||
|
}
|
||||||
|
Write-Host "Astept $WaitSeconds secunde cat dispozitivul e oprit."
|
||||||
|
|
||||||
|
try {
|
||||||
|
Disable-PnpDevice -InstanceId $dev.InstanceId -Confirm:$false -ErrorAction Stop
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
$n = @(Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue).Count
|
||||||
|
Write-Host "dezactivat : $((Get-PnpDevice -InstanceId $dev.InstanceId).Status) | instante Win32_Battery = $n"
|
||||||
|
Start-Sleep -Seconds $WaitSeconds
|
||||||
|
} finally {
|
||||||
|
Enable-PnpDevice -InstanceId $dev.InstanceId -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
$n = @(Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue).Count
|
||||||
|
Write-Host "reactivat : $((Get-PnpDevice -InstanceId $dev.InstanceId).Status) | instante Win32_Battery = $n"
|
||||||
|
if ($n -lt 1) {
|
||||||
|
Write-Warning 'UPS-ul NU a revenit in Win32_Battery. Verifica Device Manager -> Batteries inainte de a pleca.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 45
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '--- ultimele linii de log ---'
|
||||||
|
Get-Content (Join-Path $InstallDir ('log\ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM'))) -Tail 12
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Asteptat: o linie ALERT "UPS-ul nu mai este vazut de Windows", un email trimis,'
|
||||||
|
Write-Host 'apoi "Comunicatia cu UPS-ul a revenit" si al doilea email.'
|
||||||
605
scripts/ups-monitor/ups-monitor.ps1
Normal file
605
scripts/ups-monitor/ups-monitor.ps1
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Monitorizeaza UPS-ul serverului Oracle de productie (10.0.20.36 / ROA-CARAPETRU2)
|
||||||
|
prin canalul HID nativ Windows si opreste curat baza inainte de epuizarea bateriei.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Inlocuieste ViewPower, care nu reusea handshake-ul de protocol cu acest UPS
|
||||||
|
(5777 x "QPI return(NAK", zero citiri reusite) si a fost dezactivat pe 10.08.2026.
|
||||||
|
|
||||||
|
Sursa de date este API-ul Windows GetSystemPowerStatus - exact acelasi semnal pe
|
||||||
|
care il foloseste si Windows pentru actiunea proprie de "critical battery", deci
|
||||||
|
daca merge oprirea automata a Windows-ului merge si scriptul asta.
|
||||||
|
|
||||||
|
Ce face:
|
||||||
|
- alerteaza pe email la trecerea pe baterie si la revenirea curentului;
|
||||||
|
- alerteaza cand UPS-ul nu mai comunica deloc cu calculatorul (cazul care a
|
||||||
|
trecut neobservat 17 zile in august 2026);
|
||||||
|
- opreste curat Oracle (listener, apoi "shutdown immediate") si abia apoi
|
||||||
|
Windows-ul, inainte ca bateria sa ajunga la pragul brutal al Windows-ului.
|
||||||
|
|
||||||
|
Documentatie: docs/ups-server36-monitorizare.md
|
||||||
|
|
||||||
|
.PARAMETER ConfigPath
|
||||||
|
Calea catre config.json. Implicit, langa script.
|
||||||
|
|
||||||
|
.PARAMETER Mode
|
||||||
|
Run - bucla continua (modul in care ruleaza taskul programat).
|
||||||
|
Once - o singura evaluare, apoi iese. Pentru depanare.
|
||||||
|
Status - afiseaza starea curenta (UPS, Oracle, task, ultimele evenimente).
|
||||||
|
TestMail - trimite un email de test si iese.
|
||||||
|
TestShutdown - parcurge secventa de oprire in gol (dry-run), fara sa opreasca nimic.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Fisierul e intentionat fara diacritice: PowerShell 5.1 citeste .ps1 fara BOM ca
|
||||||
|
ANSI, iar diacriticele ar ajunge corupte in emailuri.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
# Implicit, config.json de langa script. Nu se poate folosi $PSScriptRoot direct
|
||||||
|
# in valoarea implicita: la legarea parametrilor e inca gol.
|
||||||
|
[string]$ConfigPath,
|
||||||
|
|
||||||
|
[ValidateSet('Run', 'Once', 'Status', 'TestMail', 'TestShutdown')]
|
||||||
|
[string]$Mode = 'Run'
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
if (-not $ConfigPath) {
|
||||||
|
$scriptDir = $PSScriptRoot
|
||||||
|
if (-not $scriptDir) { $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
||||||
|
$ConfigPath = Join-Path $scriptDir 'config.json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# API-ul de alimentare al Windows-ului
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if (-not ('RomfastPowerStatus' -as [type])) {
|
||||||
|
Add-Type -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct SYSTEM_POWER_STATUS {
|
||||||
|
public byte ACLineStatus;
|
||||||
|
public byte BatteryFlag;
|
||||||
|
public byte BatteryLifePercent;
|
||||||
|
public byte SystemStatusFlag;
|
||||||
|
public int BatteryLifeTime;
|
||||||
|
public int BatteryFullLifeTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RomfastPowerStatus {
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS status);
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
|
||||||
|
# ACLineStatus: 0 = pe baterie, 1 = pe retea, 255 = necunoscut
|
||||||
|
# BatteryFlag: 1 = high, 2 = low, 4 = critical, 8 = charging,
|
||||||
|
# 128 = NU EXISTA BATERIE (UPS-ul nu mai comunica), 255 = necunoscut
|
||||||
|
# BatteryLifePercent: 0..100, 255 = necunoscut
|
||||||
|
# BatteryLifeTime: secunde ramase, -1 = necunoscut
|
||||||
|
|
||||||
|
function Get-PowerReading {
|
||||||
|
$st = New-Object SYSTEM_POWER_STATUS
|
||||||
|
$ok = [RomfastPowerStatus]::GetSystemPowerStatus([ref]$st)
|
||||||
|
if (-not $ok) {
|
||||||
|
throw "GetSystemPowerStatus a esuat (Win32 error $([Runtime.InteropServices.Marshal]::GetLastWin32Error()))"
|
||||||
|
}
|
||||||
|
|
||||||
|
$noBattery = ($st.BatteryFlag -eq 128) -or ($st.BatteryFlag -eq 255)
|
||||||
|
|
||||||
|
$percent = $null
|
||||||
|
if ($st.BatteryLifePercent -le 100) { $percent = [int]$st.BatteryLifePercent }
|
||||||
|
|
||||||
|
$runtime = $null
|
||||||
|
if ($st.BatteryLifeTime -ge 0) { $runtime = [int]$st.BatteryLifeTime }
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
UpsPresent = (-not $noBattery)
|
||||||
|
OnBattery = ($st.ACLineStatus -eq 0)
|
||||||
|
AcKnown = ($st.ACLineStatus -eq 0 -or $st.ACLineStatus -eq 1)
|
||||||
|
Percent = $percent
|
||||||
|
RuntimeSec = $runtime
|
||||||
|
ACLineStatus = [int]$st.ACLineStatus
|
||||||
|
BatteryFlag = [int]$st.BatteryFlag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-UpsDeviceInfo {
|
||||||
|
# Confirmare a doua, din WMI - folosita doar in alerte si in modul Status,
|
||||||
|
# ca sa nu interogam WMI la fiecare ciclu.
|
||||||
|
try {
|
||||||
|
$b = Get-CimInstance Win32_Battery -ErrorAction Stop | Select-Object -First 1
|
||||||
|
if ($null -eq $b) { return 'Win32_Battery: nicio instanta (UPS-ul nu e vazut de Windows)' }
|
||||||
|
return "Win32_Battery: $($b.Name), Status=$($b.Status), BatteryStatus=$($b.BatteryStatus), Incarcare=$($b.EstimatedChargeRemaining)%"
|
||||||
|
} catch {
|
||||||
|
return "Win32_Battery: interogare esuata ($($_.Exception.Message))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Configuratie, log, stare
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Get-Config {
|
||||||
|
param([string]$Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) {
|
||||||
|
throw "Lipseste fisierul de configurare: $Path"
|
||||||
|
}
|
||||||
|
$cfg = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
|
||||||
|
# Valori implicite pentru cheile care pot lipsi.
|
||||||
|
$defaults = @{
|
||||||
|
enabled = $true
|
||||||
|
pollSeconds = 15
|
||||||
|
shutdownAfterMinutesOnBattery = 10
|
||||||
|
shutdownAtBatteryPercent = 35
|
||||||
|
shutdownAtRuntimeSeconds = 0 # 0 = criteriu dezactivat
|
||||||
|
confirmPolls = 3
|
||||||
|
commsLostPolls = 8
|
||||||
|
commsAlertRepeatHours = 24
|
||||||
|
}
|
||||||
|
foreach ($k in $defaults.Keys) {
|
||||||
|
if (-not ($cfg.PSObject.Properties.Name -contains $k)) {
|
||||||
|
$cfg | Add-Member -NotePropertyName $k -NotePropertyValue $defaults[$k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:LogDir = $null
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param(
|
||||||
|
[string]$Message,
|
||||||
|
[ValidateSet('INFO', 'WARN', 'ALERT', 'ERROR')]
|
||||||
|
[string]$Level = 'INFO'
|
||||||
|
)
|
||||||
|
$line = '{0} {1,-5} {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
|
||||||
|
Write-Host $line
|
||||||
|
if ($script:LogDir) {
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path -LiteralPath $script:LogDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $script:LogDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$file = Join-Path $script:LogDir ('ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM'))
|
||||||
|
Add-Content -LiteralPath $file -Value $line -Encoding UTF8
|
||||||
|
} catch {
|
||||||
|
# Logul nu are voie sa opreasca monitorizarea.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-State {
|
||||||
|
param([string]$Path)
|
||||||
|
if (Test-Path -LiteralPath $Path) {
|
||||||
|
try {
|
||||||
|
$loaded = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
return (Initialize-State -State $loaded)
|
||||||
|
} catch {
|
||||||
|
Write-Log "Fisierul de stare e corupt, se reia de la zero: $($_.Exception.Message)" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return New-State
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-State {
|
||||||
|
[pscustomobject]@{
|
||||||
|
powerState = 'unknown' # online | battery | unknown
|
||||||
|
onBatterySince = $null
|
||||||
|
commsState = 'unknown' # ok | lost | unknown
|
||||||
|
lastCommsAlert = $null
|
||||||
|
shutdownTriggered = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Initialize-State {
|
||||||
|
# Un state.json scris de o versiune mai veche poate sa nu aiba toate cheile,
|
||||||
|
# iar Set-StrictMode ar pica pe prima proprietate lipsa.
|
||||||
|
param([object]$State)
|
||||||
|
$template = New-State
|
||||||
|
foreach ($p in $template.PSObject.Properties) {
|
||||||
|
if (-not ($State.PSObject.Properties.Name -contains $p.Name)) {
|
||||||
|
$State | Add-Member -NotePropertyName $p.Name -NotePropertyValue $p.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $State
|
||||||
|
}
|
||||||
|
|
||||||
|
function Save-State {
|
||||||
|
param([object]$State, [string]$Path)
|
||||||
|
try {
|
||||||
|
$dir = Split-Path -Parent $Path
|
||||||
|
if ($dir -and -not (Test-Path -LiteralPath $dir)) {
|
||||||
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$State | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $Path -Encoding UTF8
|
||||||
|
} catch {
|
||||||
|
Write-Log "Nu am putut salva starea in $Path : $($_.Exception.Message)" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Email
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Send-UpsMail {
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$Subject,
|
||||||
|
[string]$Body
|
||||||
|
)
|
||||||
|
# Un email esuat nu are voie sa opreasca bucla si nici secventa de oprire:
|
||||||
|
# in pana de curent e foarte posibil sa fie cazut si switch-ul.
|
||||||
|
try {
|
||||||
|
$smtp = New-Object System.Net.Mail.SmtpClient($Config.smtp.server, [int]$Config.smtp.port)
|
||||||
|
$smtp.EnableSsl = $true
|
||||||
|
$smtp.Timeout = 20000
|
||||||
|
# UseDefaultCredentials trebuie pus inainte de Credentials: setarea lui
|
||||||
|
# sterge acreditarile deja atribuite.
|
||||||
|
$smtp.UseDefaultCredentials = $false
|
||||||
|
$smtp.Credentials = New-Object System.Net.NetworkCredential($Config.smtp.user, $Config.smtp.password)
|
||||||
|
|
||||||
|
$msg = New-Object System.Net.Mail.MailMessage
|
||||||
|
$msg.From = New-Object System.Net.Mail.MailAddress($Config.smtp.from, "UPS $env:COMPUTERNAME")
|
||||||
|
foreach ($to in $Config.mailTo) { $msg.To.Add($to) }
|
||||||
|
$msg.Subject = $Subject
|
||||||
|
$msg.Body = $Body
|
||||||
|
$msg.SubjectEncoding = [Text.Encoding]::UTF8
|
||||||
|
$msg.BodyEncoding = [Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
$smtp.Send($msg)
|
||||||
|
$msg.Dispose()
|
||||||
|
$smtp.Dispose()
|
||||||
|
Write-Log "Email trimis: $Subject"
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
Write-Log "Email NETRIMIS ($Subject): $($_.Exception.Message)" -Level ERROR
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-Reading {
|
||||||
|
param([object]$Reading, [object]$Config)
|
||||||
|
|
||||||
|
$pct = 'necunoscut'
|
||||||
|
if ($null -ne $Reading.Percent) { $pct = "$($Reading.Percent)%" }
|
||||||
|
|
||||||
|
$rt = 'neraportat de UPS'
|
||||||
|
if ($null -ne $Reading.RuntimeSec) { $rt = '{0} min' -f [math]::Round($Reading.RuntimeSec / 60, 1) }
|
||||||
|
|
||||||
|
$alim = 'necunoscuta'
|
||||||
|
if ($Reading.OnBattery) { $alim = 'PE BATERIE' } elseif ($Reading.AcKnown) { $alim = 'pe retea' }
|
||||||
|
|
||||||
|
$vazut = 'NU'
|
||||||
|
if ($Reading.UpsPresent) { $vazut = 'da' }
|
||||||
|
|
||||||
|
$oracle = 'nedeterminat'
|
||||||
|
try {
|
||||||
|
$svc = Get-Service -Name $Config.oracle.service -ErrorAction Stop
|
||||||
|
$lsn = Get-Service -Name $Config.oracle.listenerService -ErrorAction Stop
|
||||||
|
$oracle = "$($svc.Name)=$($svc.Status), $($lsn.Name)=$($lsn.Status)"
|
||||||
|
} catch {
|
||||||
|
$oracle = "nedeterminat ($($_.Exception.Message))"
|
||||||
|
}
|
||||||
|
|
||||||
|
@"
|
||||||
|
Server : $env:COMPUTERNAME (10.0.20.36)
|
||||||
|
Moment : $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')
|
||||||
|
Alimentare : $alim
|
||||||
|
Incarcare : $pct
|
||||||
|
Autonomie estim. : $rt
|
||||||
|
UPS vazut de OS : $vazut
|
||||||
|
$(Get-UpsDeviceInfo)
|
||||||
|
Oracle : $oracle
|
||||||
|
|
||||||
|
Praguri configurate:
|
||||||
|
oprire dupa $($Config.shutdownAfterMinutesOnBattery) min pe baterie
|
||||||
|
oprire sub $($Config.shutdownAtBatteryPercent)% incarcare
|
||||||
|
Windows opreste singur, ca ultima plasa, la 20%
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Oprirea curata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Stop-OracleCleanly {
|
||||||
|
param([object]$Config, [switch]$DryRun)
|
||||||
|
|
||||||
|
$oraHome = $Config.oracle.home
|
||||||
|
$sid = $Config.oracle.sid
|
||||||
|
$timeoutMs = [int]$Config.oracle.shutdownTimeoutSeconds * 1000
|
||||||
|
|
||||||
|
Write-Log "Opresc listenerul $($Config.oracle.listenerService)"
|
||||||
|
if (-not $DryRun) {
|
||||||
|
try {
|
||||||
|
Stop-Service -Name $Config.oracle.listenerService -Force -ErrorAction Stop
|
||||||
|
Write-Log 'Listener oprit'
|
||||||
|
} catch {
|
||||||
|
Write-Log "Listenerul nu s-a oprit: $($_.Exception.Message)" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlplus = Join-Path $oraHome 'bin\sqlplus.exe'
|
||||||
|
if (-not (Test-Path -LiteralPath $sqlplus)) {
|
||||||
|
Write-Log "sqlplus.exe lipseste la $sqlplus - trec direct pe oprirea serviciului" -Level WARN
|
||||||
|
} else {
|
||||||
|
Write-Log "Oracle: shutdown immediate pe instanta $sid (timeout $($Config.oracle.shutdownTimeoutSeconds)s)"
|
||||||
|
if (-not $DryRun) {
|
||||||
|
$tmpSql = Join-Path $env:TEMP ('ups-shutdown-{0}.sql' -f $PID)
|
||||||
|
$tmpOut = Join-Path $env:TEMP ('ups-shutdown-{0}.out' -f $PID)
|
||||||
|
try {
|
||||||
|
Set-Content -LiteralPath $tmpSql -Encoding ASCII -Value @(
|
||||||
|
'whenever sqlerror exit failure'
|
||||||
|
'shutdown immediate'
|
||||||
|
'exit'
|
||||||
|
)
|
||||||
|
$env:ORACLE_HOME = $oraHome
|
||||||
|
$env:ORACLE_SID = $sid
|
||||||
|
|
||||||
|
$p = Start-Process -FilePath $sqlplus `
|
||||||
|
-ArgumentList @('-S', '-L', '/ as sysdba', "@$tmpSql") `
|
||||||
|
-NoNewWindow -PassThru `
|
||||||
|
-RedirectStandardOutput $tmpOut -RedirectStandardError "$tmpOut.err"
|
||||||
|
|
||||||
|
if ($p.WaitForExit($timeoutMs)) {
|
||||||
|
$out = if (Test-Path $tmpOut) { (Get-Content -LiteralPath $tmpOut -Raw).Trim() } else { '' }
|
||||||
|
Write-Log "sqlplus a iesit cu codul $($p.ExitCode). Iesire: $out"
|
||||||
|
} else {
|
||||||
|
Write-Log 'sqlplus nu a terminat in timpul alocat - il opresc si merg pe serviciu' -Level WARN
|
||||||
|
try { $p.Kill() } catch { }
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log "shutdown immediate a esuat: $($_.Exception.Message)" -Level WARN
|
||||||
|
} finally {
|
||||||
|
Remove-Item -LiteralPath $tmpSql, $tmpOut, "$tmpOut.err" -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plasa de siguranta: serviciul e configurat el insusi cu ORA_ROA_SHUTDOWNTYPE=immediate
|
||||||
|
# si ORA_ROA_SHUTDOWN_TIMEOUT=90, deci oprirea lui e tot curata.
|
||||||
|
Write-Log "Opresc serviciul $($Config.oracle.service)"
|
||||||
|
if (-not $DryRun) {
|
||||||
|
try {
|
||||||
|
Stop-Service -Name $Config.oracle.service -Force -ErrorAction Stop
|
||||||
|
Write-Log 'Serviciul Oracle oprit'
|
||||||
|
} catch {
|
||||||
|
Write-Log "Serviciul Oracle nu s-a oprit: $($_.Exception.Message)" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-UpsShutdown {
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[object]$Reading,
|
||||||
|
[string]$Reason,
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
$tag = if ($DryRun) { '[DRY-RUN] ' } else { '' }
|
||||||
|
Write-Log "${tag}DECLANSEZ OPRIREA: $Reason" -Level ALERT
|
||||||
|
|
||||||
|
# Emailul pleaca inainte de orice - in pana de curent reteaua poate cadea oricand.
|
||||||
|
Send-UpsMail -Config $Config `
|
||||||
|
-Subject "$tag[UPS] OPRESC serverul $env:COMPUTERNAME - $Reason" `
|
||||||
|
-Body ("Motiv: $Reason`n`n" + (Format-Reading -Reading $Reading -Config $Config) + @"
|
||||||
|
|
||||||
|
Secventa care urmeaza:
|
||||||
|
1. oprire listener Oracle
|
||||||
|
2. shutdown immediate pe instanta $($Config.oracle.sid)
|
||||||
|
3. oprire serviciu Oracle
|
||||||
|
4. shutdown Windows
|
||||||
|
|
||||||
|
La revenirea curentului serverul NU porneste singur decat daca e activat
|
||||||
|
"Restore on AC power loss" in BIOS.
|
||||||
|
"@) | Out-Null
|
||||||
|
|
||||||
|
Stop-OracleCleanly -Config $Config -DryRun:$DryRun
|
||||||
|
|
||||||
|
Write-Log "${tag}Opresc Windows-ul"
|
||||||
|
if (-not $DryRun) {
|
||||||
|
# Out-Null: altfel iesirea lui shutdown.exe s-ar amesteca in valoarea
|
||||||
|
# returnata de Invoke-Cycle, care e obiectul de stare.
|
||||||
|
& shutdown.exe /s /f /t 30 /c "UPS: $Reason" | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Un ciclu de evaluare
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Invoke-Cycle {
|
||||||
|
param([object]$Config, [object]$State, [switch]$DryRun)
|
||||||
|
|
||||||
|
$now = Get-Date
|
||||||
|
$r = Get-PowerReading
|
||||||
|
|
||||||
|
# --- comunicatia cu UPS-ul ---------------------------------------------
|
||||||
|
if (-not $r.UpsPresent) {
|
||||||
|
$script:CommsMissCount++
|
||||||
|
if ($script:CommsMissCount -ge $Config.commsLostPolls) {
|
||||||
|
$repeat = $false
|
||||||
|
if ($State.lastCommsAlert) {
|
||||||
|
$repeat = ((New-TimeSpan -Start ([datetime]$State.lastCommsAlert) -End $now).TotalHours -ge $Config.commsAlertRepeatHours)
|
||||||
|
}
|
||||||
|
if ($State.commsState -ne 'lost' -or $repeat) {
|
||||||
|
Write-Log 'UPS-ul nu mai este vazut de Windows' -Level ALERT
|
||||||
|
Send-UpsMail -Config $Config `
|
||||||
|
-Subject "[UPS] $env:COMPUTERNAME nu mai comunica cu UPS-ul" `
|
||||||
|
-Body ((Format-Reading -Reading $r -Config $Config) + @"
|
||||||
|
|
||||||
|
Windows nu mai vede bateria UPS-ului (BatteryFlag=$($r.BatteryFlag)).
|
||||||
|
Cat timp e asa, NU exista nicio protectie: nici oprirea automata a scriptului,
|
||||||
|
nici plasa de 20% a Windows-ului nu se pot declansa.
|
||||||
|
|
||||||
|
De verificat, in ordine: cablul USB, portul, alimentarea UPS-ului, apoi
|
||||||
|
Device Manager -> Batteries -> "HID UPS Battery".
|
||||||
|
"@) | Out-Null
|
||||||
|
$State.commsState = 'lost'
|
||||||
|
$State.lastCommsAlert = $now.ToString('o')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $State # fara comunicatie nu luam nicio decizie de oprire
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:CommsMissCount -gt 0 -or $State.commsState -eq 'lost') {
|
||||||
|
if ($State.commsState -eq 'lost') {
|
||||||
|
Write-Log 'Comunicatia cu UPS-ul a revenit'
|
||||||
|
Send-UpsMail -Config $Config `
|
||||||
|
-Subject "[UPS] $env:COMPUTERNAME - comunicatia cu UPS-ul a revenit" `
|
||||||
|
-Body (Format-Reading -Reading $r -Config $Config) | Out-Null
|
||||||
|
}
|
||||||
|
$script:CommsMissCount = 0
|
||||||
|
$State.commsState = 'ok'
|
||||||
|
$State.lastCommsAlert = $null
|
||||||
|
}
|
||||||
|
if ($State.commsState -eq 'unknown') { $State.commsState = 'ok' }
|
||||||
|
|
||||||
|
# --- trecerea pe baterie / revenirea curentului -------------------------
|
||||||
|
if ($r.OnBattery) {
|
||||||
|
$script:OnBatteryCount++
|
||||||
|
|
||||||
|
if ($State.powerState -ne 'battery') {
|
||||||
|
$State.powerState = 'battery'
|
||||||
|
$State.onBatterySince = $now.ToString('o')
|
||||||
|
$State.shutdownTriggered = $false
|
||||||
|
Write-Log "PANA DE CURENT - trecut pe baterie ($($r.Percent)%)" -Level ALERT
|
||||||
|
Send-UpsMail -Config $Config `
|
||||||
|
-Subject "[UPS] PANA DE CURENT - $env:COMPUTERNAME a trecut pe baterie" `
|
||||||
|
-Body ((Format-Reading -Reading $r -Config $Config) + @"
|
||||||
|
|
||||||
|
Daca curentul nu revine, serverul va fi oprit curat la primul prag atins.
|
||||||
|
Urmeaza un email si atunci.
|
||||||
|
"@) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$since = [datetime]$State.onBatterySince
|
||||||
|
$minutes = (New-TimeSpan -Start $since -End $now).TotalMinutes
|
||||||
|
|
||||||
|
if (-not $State.shutdownTriggered -and $script:OnBatteryCount -ge $Config.confirmPolls) {
|
||||||
|
$reason = $null
|
||||||
|
if ($minutes -ge $Config.shutdownAfterMinutesOnBattery) {
|
||||||
|
$reason = "$([math]::Round($minutes)) minute pe baterie (prag: $($Config.shutdownAfterMinutesOnBattery))"
|
||||||
|
} elseif ($null -ne $r.Percent -and $r.Percent -le $Config.shutdownAtBatteryPercent) {
|
||||||
|
$reason = "incarcare $($r.Percent)% (prag: $($Config.shutdownAtBatteryPercent)%)"
|
||||||
|
} elseif ($Config.shutdownAtRuntimeSeconds -gt 0 -and $null -ne $r.RuntimeSec -and $r.RuntimeSec -le $Config.shutdownAtRuntimeSeconds) {
|
||||||
|
$reason = "autonomie estimata $([math]::Round($r.RuntimeSec / 60, 1)) min (prag: $([math]::Round($Config.shutdownAtRuntimeSeconds / 60, 1)) min)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reason) {
|
||||||
|
$State.shutdownTriggered = $true
|
||||||
|
Save-State -State $State -Path $Config.stateFile
|
||||||
|
Invoke-UpsShutdown -Config $Config -Reading $r -Reason $reason -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$script:OnBatteryCount = 0
|
||||||
|
|
||||||
|
if ($State.powerState -eq 'battery') {
|
||||||
|
$durata = if ($State.onBatterySince) {
|
||||||
|
'{0:N1} minute' -f (New-TimeSpan -Start ([datetime]$State.onBatterySince) -End $now).TotalMinutes
|
||||||
|
} else { 'necunoscuta' }
|
||||||
|
Write-Log "Curentul a revenit dupa $durata"
|
||||||
|
Send-UpsMail -Config $Config `
|
||||||
|
-Subject "[UPS] Curentul a revenit - $env:COMPUTERNAME" `
|
||||||
|
-Body ("Serverul a stat pe baterie $durata.`n`n" + (Format-Reading -Reading $r -Config $Config)) | Out-Null
|
||||||
|
}
|
||||||
|
$State.powerState = 'online'
|
||||||
|
$State.onBatterySince = $null
|
||||||
|
$State.shutdownTriggered = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
return $State
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Punctul de intrare
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
$cfg = Get-Config -Path $ConfigPath
|
||||||
|
$script:LogDir = $cfg.logPath
|
||||||
|
$script:OnBatteryCount = 0
|
||||||
|
$script:CommsMissCount = 0
|
||||||
|
|
||||||
|
switch ($Mode) {
|
||||||
|
|
||||||
|
'Status' {
|
||||||
|
$r = Get-PowerReading
|
||||||
|
Write-Host (Format-Reading -Reading $r -Config $cfg)
|
||||||
|
Write-Host ''
|
||||||
|
$st = Get-State -Path $cfg.stateFile
|
||||||
|
Write-Host "Stare salvata : power=$($st.powerState) comms=$($st.commsState) onBatterySince=$($st.onBatterySince)"
|
||||||
|
try {
|
||||||
|
$task = Get-ScheduledTask -TaskName 'ROMFAST UPS Monitor' -ErrorAction Stop
|
||||||
|
$info = Get-ScheduledTaskInfo -TaskName 'ROMFAST UPS Monitor'
|
||||||
|
Write-Host "Task programat : $($task.State), ultima rulare $($info.LastRunTime), rezultat $($info.LastTaskResult)"
|
||||||
|
} catch {
|
||||||
|
Write-Host 'Task programat : NEINREGISTRAT'
|
||||||
|
}
|
||||||
|
$log = Join-Path $cfg.logPath ('ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM'))
|
||||||
|
if (Test-Path -LiteralPath $log) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "Ultimele linii din $log :"
|
||||||
|
Get-Content -LiteralPath $log -Tail 15 | ForEach-Object { " $_" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'TestMail' {
|
||||||
|
$r = Get-PowerReading
|
||||||
|
$ok = Send-UpsMail -Config $cfg `
|
||||||
|
-Subject "[UPS] Test - monitorizare $env:COMPUTERNAME" `
|
||||||
|
-Body ("Email de test din ups-monitor.ps1. Daca il primesti, alertarea functioneaza.`n`n" + (Format-Reading -Reading $r -Config $cfg))
|
||||||
|
if (-not $ok) { exit 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
'TestShutdown' {
|
||||||
|
$r = Get-PowerReading
|
||||||
|
Invoke-UpsShutdown -Config $cfg -Reading $r -Reason 'TEST (dry-run, nu se opreste nimic)' -DryRun
|
||||||
|
}
|
||||||
|
|
||||||
|
'Once' {
|
||||||
|
$st = Get-State -Path $cfg.stateFile
|
||||||
|
$st = Invoke-Cycle -Config $cfg -State $st
|
||||||
|
Save-State -State $st -Path $cfg.stateFile
|
||||||
|
Write-Host (Format-Reading -Reading (Get-PowerReading) -Config $cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
'Run' {
|
||||||
|
Write-Log "Pornit (interval $($cfg.pollSeconds)s, praguri: $($cfg.shutdownAfterMinutesOnBattery) min / $($cfg.shutdownAtBatteryPercent)%)"
|
||||||
|
$st = Get-State -Path $cfg.stateFile
|
||||||
|
$disableMarker = Join-Path (Split-Path -Parent $ConfigPath) 'DEZACTIVAT'
|
||||||
|
$wasDisabled = $false
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
try {
|
||||||
|
if ((Test-Path -LiteralPath $disableMarker) -or -not $cfg.enabled) {
|
||||||
|
if (-not $wasDisabled) {
|
||||||
|
Write-Log 'Monitorizare DEZACTIVATA (marcaj DEZACTIVAT sau enabled=false) - doar astept' -Level WARN
|
||||||
|
$wasDisabled = $true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($wasDisabled) {
|
||||||
|
Write-Log 'Monitorizare reactivata'
|
||||||
|
$wasDisabled = $false
|
||||||
|
}
|
||||||
|
$st = Invoke-Cycle -Config $cfg -State $st
|
||||||
|
Save-State -State $st -Path $cfg.stateFile
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log "Eroare in ciclu: $($_.Exception.Message)" -Level ERROR
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds $cfg.pollSeconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user