From 1eb9ecd0f93c9c175f9383545804e9007598b95a Mon Sep 17 00:00:00 2001 From: Pasquale Mauriello <32157261+paki81@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:53:49 +0200 Subject: [PATCH 1/2] i18n: add Italian (it) UI dictionary Adds app/web/i18n/it.js with all 557 keys translated, loads it in index.html, and extends tests/test_i18n.py so the consistency checks run over every shipped dictionary instead of en/de only. manual.file points at the English manual until a translated one exists; an it-IT browser gets the Italian UI automatically via resolveLang(). --- app/web/i18n/it.js | 620 +++++++++++++++++++++++++++++++++++++++++++++ app/web/index.html | 1 + tests/test_i18n.py | 47 ++-- 3 files changed, 648 insertions(+), 20 deletions(-) create mode 100644 app/web/i18n/it.js diff --git a/app/web/i18n/it.js b/app/web/i18n/it.js new file mode 100644 index 0000000..3aeecd5 --- /dev/null +++ b/app/web/i18n/it.js @@ -0,0 +1,620 @@ +"use strict"; +// Italian UI dictionary. +// Keep en.js, de.js and it.js key sets identical — tests/test_i18n.py enforces this. +// "UPS" stays the common Italian term for the device; the product name is PVE-UPS. +// Copyright 2026 Florian Finder + +window.I18N = window.I18N || {}; +I18N.it = { + "manual.file": "manual.html", + + // topbar / navigation + "nav.dashboard": "Dashboard", + "nav.settings": "Impostazioni", + "nav.history": "Storico", + "nav.logout": "Esci", + "nav.manualTitle": "Manuale utente", + + // theme button + "theme.auto": "Tema: automatico (sistema)", + "theme.light": "Tema: chiaro", + "theme.dark": "Tema: scuro", + + // login / first run + "login.title": "Accedi", + "login.hint": "Inserisci la password dell'interfaccia.", + "login.pwPlaceholder": "Password", + "login.submit": "Accedi", + "firstrun.title": "Configurazione iniziale", + "firstrun.hint": "Imposta una password per l'interfaccia web.", + "firstrun.pwPlaceholder": "Nuova password (min. 6 caratteri)", + "firstrun.submit": "Imposta password e avvia", + + // dashboard: topology card + "topo.title": "Alimentazione (UPS → host)", + "topo.doc": "Leggi nel manuale: Come funziona e logica", + "topo.legendStar": "questo host (appliance, si spegne per ultimo)", + "topo.mains": "rete elettrica", + "topo.battery": "a batteria", + "topo.trig": "spegnimento attivato", + "topo.unreach": "irraggiungibile", + "topo.diagram": "Diagramma alimentazione", + + // dashboard: appliance / hosts / events / actions + "dash.appliance": "Appliance", + "dash.state": "Stato", + "dash.mode": "Modalità", + "dash.notif": "Notifiche", + "dash.trig": "Spegnimento attivato", + "dash.reason": "Motivo", + "dash.countdown": "Conto alla rovescia", + "dash.uptime": "Uptime", + "dash.hosts": "Host", + "dash.thHost": "Host", + "dash.thFeeds": "Alimentato da", + "dash.thStatus": "Stato", + "dash.thError": "Errore", + "dash.events": "Eventi", + "dash.clearLog": "Svuota registro", + "dash.clearLogTitle": "Elimina irreversibilmente tutte le voci del registro eventi.", + "dash.thTime": "Ora", + "dash.thSeverity": "Gravità", + "dash.thEvent": "Evento", + "dash.thDetail": "Dettaglio", + "dash.actions": "Azioni", + "dash.testShutdown": "Test spegnimento (dry-run)", + "dash.testShutdownTitle": "Registra soltanto l'ordine di spegnimento degli host. Non viene spento nulla — usalo per verificare la configurazione.", + "dash.reset": "Reimposta stato", + "dash.resetTitle": "Azzera lo stato interno di trigger/allarme dopo un test o un allarme, così la dashboard torna a mostrare “normale”.", + "dash.actionsHelp": "Test spegnimento: simula l'ordine di spegnimento senza effetti. Reimposta stato: azzera un trigger di test/allarme. Esegui auto-test ora: verifica subito token, privilegi e stato del cluster — il salvataggio delle impostazioni non lo fa di proposito. Ripristina cluster: riarma HA e rimuove i flag di manutenzione di Ceph.", + "dash.selftest": "Esegui auto-test ora", + "dash.selftestTitle": "Esegue subito i controlli su credenziali e cluster invece di attendere il prossimo slot pianificato. Il salvataggio delle impostazioni non avvia di proposito alcun controllo, quindi usalo dopo aver cambiato un token o attivato l'opzione cluster.", + "msg.selftesting": "Auto-test in corso…", + "dash.restoreCluster": "Ripristina cluster", + "dash.restoreClusterTitle": "Riarma l'HA manager e rimuove i flag di manutenzione di Ceph. Fallo solo quando il cluster è di nuovo completamente attivo.", + "cluster.nodes": "nodi", + "cluster.ok": "pronto per il normale funzionamento", + "cluster.noQuorum": "nessun quorum", + "cluster.cephFlags": "Flag Ceph ancora impostati: {flags}", + "cluster.haDisarmed": "HA è {state}", + "cluster.guests": "{running} di {total} guest in esecuzione", + "cluster.selfOnCeph": "questa appliance è su storage Ceph", + "cluster.selfUnknown": "guest proprio non selezionato — i guest non verranno arrestati", + "confirm.restoreCluster": "Riarmare l'HA manager e rimuovere i flag di manutenzione di Ceph? Fallo solo quando ogni nodo è di nuovo attivo.", + "msg.restoringCluster": "Ripristino del cluster…", + + // engine / shutdown state labels + "state.engine.ONLINE": "Su rete elettrica", + "state.engine.ON_BATTERY": "A batteria", + "state.engine.SHUTDOWN_PENDING": "Spegnimento in attesa", + "state.engine.SHUTTING_DOWN": "Spegnimento in corso", + "state.shutdown.idle": "pronto", + "state.shutdown.sent": "inviato", + "state.shutdown.failed": "fallito", + "common.yes": "sì", + "common.no": "no", + "common.beta": "Beta", + "mode.armed": "ATTIVO", + + // UPS status cards (dashboard) + "ups.incomplete": "incompleto", + "ups.incompleteTitle": "Questo UPS è salvato senza {what}, quindi non viene mai interrogato e risulta permanentemente irraggiungibile. Essendo fail-safe, questo significa anche che a ogni host che alimenta viene rifiutato lo spegnimento. Completalo in Impostazioni -> UPS.", + "ups.srcMains": "Rete", + "ups.srcBattery": "Batteria", + "ups.tipUnreach": "irraggiungibile", + "ups.tipTrig": "spegnimento attivato", + "ups.tipBattery": "a batteria", + "ups.tipMains": "su rete elettrica", + "ups.source": "Sorgente", + "ups.charge": "Carica", + "ups.runtime": "Autonomia residua", + "ups.load": "Carico in uscita", + "ups.loadTitle": "Quanta capacità nominale dell'UPS assorbe il carico collegato. Dato informativo — non attiva nulla.", + "ups.battery": "Batteria", + "ups.trigger": "Trigger", + "ups.triggered": "attivato", + "ups.countdown": "Conto alla rovescia", + "ups.commLossIn": "Spegnimento per perdita comunicazione tra", + "ups.lastPoll": "Ultimo polling", + "ups.via": "via {src}", + "ups.viaMib": "via {src}, {mib}", + "ups.none": "nessun UPS configurato", + + // banner / header status chip + "banner.trig": "Spegnimento attivato: {reason}", + "banner.outage": "Blackout — {n} UPS a batteria", + "banner.outageCountdown": " (spegnimento tra {s} s)", + "banner.outageTriggered": "{m} UPS attivati — gli host si spengono appena la loro policy UPS (AND/OR) è soddisfatta.", + "banner.unreachCountdown": "UPS irraggiungibile — il conto alla rovescia a batteria continua, spegnimento alla scadenza ({s}).", + "banner.unreachCommLoss": "UPS irraggiungibile — spegnimento per perdita prolungata tra {s}.", + "banner.unreachAlarm": "UPS irraggiungibile — allarme (nessuno spegnimento).", + "banner.restoredUnconfirmed": "{names}: un timer a batteria è stato ripristinato dopo un riavvio, ma l'UPS non ha ancora risposto. È mantenuto finché un polling non conferma il blackout — nessuno spegnimento fino ad allora.", + "banner.dryRun": "Il dry-run è attivo — l'appliance registra soltanto ciò che farebbe e non spegne nulla. Disattivalo in Impostazioni una volta testata la configurazione.", + + // shown when the service restarted into a new version while this tab stayed open + "reload.newVersion": "È stata installata la versione v{v} — ricarica la pagina affinché l'interfaccia corrisponda al servizio in esecuzione.", + "reload.btn": "Ricarica", + "chip.mains": "Su rete elettrica", + "chip.battery": "A batteria", + "chip.trig": "Spegnimento attivato", + "chip.unreach": "UPS irraggiungibile", + + // dashboard hosts table + "hosts.staleFeed": "nessun UPS", + "hosts.incomplete": "incompleto", + "hosts.incompleteTitle": "Questo host è salvato senza {what}, quindi uno spegnimento non può raggiungerlo — e questo si scopre solo durante un blackout. Completalo, o deseleziona “attivo”, in Impostazioni -> Host.", + "hosts.staleFeedTitle": "Questo host è assegnato a UPS che non esistono più ({ids}). A meno che non gli resti un'altra alimentazione, non verrà mai spento. Correggilo in Impostazioni -> Host.", + "hosts.allUps": "tutti gli UPS", + "hosts.policyAnd": "AND", + "hosts.policyOr": "OR", + "hosts.thisChip": "★ questo", + "hosts.thisChipTitle": "Questo host — l'appliance, si spegne per ultimo", + "hosts.clusterChipTitle": "Membro del cluster — il cluster viene preparato una sola volta prima che il primo nodo si spenga", + // Node-name verdict per host (app/proxmox.py: NODE_STATES). One entry per member, the + // chip is shown only for the three that mean "does not match this API". + "nodest.ok": "nome nodo ok", + "nodest.invalid": "nome nodo non valido", + "nodest.wrong": "nodo sconosciuto", + "nodest.proxied": "altro nodo", + "nodest.unverified": "nome nodo non verificato", + "nodest.chipTitle": "Il nome del nodo non corrisponde all'API dietro questa voce. Lo spegnimento raggiunge comunque la macchina dietro il suo URL API, ma questo nome identifica l'host qui e in ogni evento — correggilo sulla scheda host. Dettagli nel registro eventi.", + "hosts.none": "nessun host configurato", + "hosts.seq": "Sequenza di spegnimento:", + "hosts.seqHint": "Stesso numero = contemporaneamente. ★ “Questo host” è sempre l'ultimo, qualunque sia il suo numero.", + "hosts.seqNone": "ancora nessun host", + "events.none": "nessun evento", + + // confirms + short action messages + "confirm.testShutdown": "Eseguire un test dry-run? NULLA verrà spento.", + "confirm.clearLog": "Eliminare davvero l'intero registro eventi?", + "confirm.import": "Importare la configurazione da file? Le impostazioni esistenti saranno sostituite.", + "confirm.update": "Caricare e applicare il pacchetto di aggiornamento? Il servizio verrà riavviato dopo.", + "msg.reset": "Reimpostato.", + "msg.testing": "Test in corso…", + "msg.saving": "Salvataggio…", + "msg.saved": "✓ Salvato.", + "msg.pwChanged": "✓ Password cambiata.", + "msg.exporting": "Esportazione…", + "msg.exported": "✓ Esportato.", + "msg.importing": "Importazione…", + "msg.imported": "✓ Importato.", + "msg.uploading": "Caricamento…", + "err.prefix": "Errore: ", + + // settings: operating mode + "wiz.mode": "Modalità operativa", + "wiz.dryRun": "Dry-run (registra soltanto, non spegne nulla)", + "wiz.dryRunTitle": "Interruttore di sicurezza principale. Quando è attivo, l'engine registra soltanto ciò che farebbe e non spegne nulla. Disattivalo (“attivo”) solo dopo aver testato la configurazione.", + + // settings: UPS devices + "wiz.ups": "Dispositivi UPS", + "wiz.upsDoc": "Leggi nel manuale: Sorgenti UPS", + "wiz.upsHelp": "Monitora uno o più UPS — via SNMP (UPS con scheda di rete propria) o tramite un server NUT, che è il modo in cui si leggono gli UPS USB e seriali. Per server con alimentatori ridondanti usa un UPS per alimentazione — l'associazione si fa al passo 2. Le soglie opzionali per UPS sovrascrivono i valori globali del passo 3.", + "wiz.addUps": "+ UPS", + + // settings: hosts + "wiz.hosts": "Host Proxmox (VE / Backup Server)", + "wiz.hostsDoc": "Leggi nel manuale: Creare il token API", + "wiz.hostsHelp": "Scegli il tipo per ogni target: Proxmox VE richiede un token con Sys.PowerMgmt, Proxmox Backup Server uno con Sys.PowerManagement. L'host marcato ★ “Questo host” (l'appliance) viene spento per ultimo.", + "wiz.addHost": "+ Host", + + // settings: triggers + "wiz.triggers": "Trigger (soglie)", + "wiz.triggersDoc": "Leggi nel manuale: Trigger e soglie", + "wiz.triggersHelp": "Basta una condizione soddisfatta. Campo vuoto = condizione disattivata.", + "th.onBattery": "A batteria per più di (s)", + "th.onBatteryTitle": "Attiva uno spegnimento quando l'UPS è a batteria ininterrottamente per questo numero di secondi. Vuoto = condizione disattivata.", + "th.runtime": "Autonomia sotto (min)", + "th.runtimeTitle": "Attiva uno spegnimento quando l'autonomia residua stimata dell'UPS scende a questo valore (minuti) o sotto. Vuoto = disattivata.", + "th.charge": "Carica sotto (%)", + "th.chargeTitle": "Attiva uno spegnimento quando la carica della batteria scende a questa percentuale o sotto. Vuoto = disattivata.", + "th.batteryLow": "Immediato quando l'UPS segnala “batteria scarica/esaurita”", + "th.batteryLowTitle": "Spegne immediatamente appena l'UPS stesso segnala “batteria scarica” o “esaurita” — indipendentemente dalle soglie numeriche sopra.", + "th.pollNormal": "Intervallo polling su rete (s)", + "th.pollNormalTitle": "Intervallo di polling durante il funzionamento normale (su rete elettrica). Più grande = meno carico, più piccolo = reazione più rapida a un blackout.", + "th.pollBattery": "Intervallo polling a batteria (s)", + "th.pollBatteryTitle": "Intervallo di polling durante un blackout (a batteria). Di solito più breve che su rete, per controllare le soglie da vicino.", + "th.hostTimeout": "Timeout spegnimento host (s)", + "th.hostTimeoutTitle": "Tempo massimo di attesa perché l'API di un target accetti il comando di spegnimento. È una scadenza rigida per host: i target con lo stesso ordine di spegnimento partono insieme, quindi una macchina che non risponde non ritarda mai le altre. NON è il tempo entro cui l'host è effettivamente spento — i guest li spegne Proxmox stesso.", + "th.unreachablePolls": "Segnala irraggiungibile dopo (polling)", + "th.unreachablePollsTitle": "Quanti polling consecutivi devono fallire prima che l'UPS sia segnalato come irraggiungibile e venga inviata una notifica. Brevi interruzioni restano silenziose — alzalo se una rete occupata produce falsi allarmi.", + "th.rearm": "Riarmo dopo ritorno rete (min)", + "th.rearmTitle": "Per quanto ogni UPS deve essere tornato su rete elettrica prima che l'appliance rilasci i latch di uno spegnimento completato e sia pronta per il blackout successivo. Senza questo valore lo stato si sblocca solo con il pulsante “Reimposta stato”, e un secondo blackout troverebbe ogni host ancora marcato come già spento. Vuoto = solo manuale. Il ripristino di un cluster preparato resta comunque manuale.", + "th.unreachableHint": "{polls} polling consecutivi falliti ≈ {total} s all'attuale intervallo su rete di {every} s. Un'interruzione più breve viene scritta nel registro eventi ma non notificata.", + "th.clusterGroup": "Preparazione cluster", + "th.clusterTimeout": "Timeout disarmo HA / flag Ceph (s)", + "th.clusterTimeoutTitle": "Budget per la parte di control-plane della preparazione di un cluster — il disarmo di HA e i flag Ceph — e il tempo effettivamente concesso al disarmo per completarsi. È anche un limite rigido: la preparazione avviene mentre la batteria si scarica. Sotto circa mezzo minuto il disarmo di solito non può essere confermato: lo stack HA risponde a giri di dieci secondi e ogni nodo deve rilasciare il suo watchdog. Lo spegnimento dei guest ha un budget proprio qui accanto.", + "th.clusterGuestTimeout": "Timeout spegnimento guest (s)", + "th.clusterGuestTimeoutTitle": "Quanto può durare lo spegnimento dei guest a livello di cluster. Volutamente un numero a sé: il budget a sinistra è misurato in giri HA di dieci secondi e varia poco, mentre questo scala con quanti guest hai e quanto impiegano a fermarsi. Un unico valore lascerebbe ai guest il budget del disarmo.", + "th.clusterGuestForce": "Arresto forzato guest dopo (s)", + "th.clusterGuestForceTitle": "Dopo quanti secondi un guest che ignora la richiesta di spegnimento viene fermato forzatamente. Il valore è passato a Proxmox anche come timeout della chiamata di spegnimento, quindi l'arresto avviene comunque se questa appliance perde la rete subito dopo la richiesta. Vuoto = mai forzare; un guest bloccato fa allora fallire la preparazione e viene nominato.", + "th.clusterGuestForcePh": "mai", + "th.clusterTotalHint": "Nel caso peggiore l'intera sequenza trattiene la batteria per {total} s prima ancora che all'ultimo nodo venga detto di spegnersi. Il trigger deve scattare abbastanza presto per questo.", + "appl.box": "Questa appliance", + "appl.pick": "È in esecuzione come guest", + "appl.pickTitle": "La VM o il container in cui gira questa stessa appliance — l'unico guest che uno spegnimento cluster-wide non tocca mai. Si sceglie dalla lista invece di digitarlo, perché un id sbagliato significherebbe che l'appliance arresta sé stessa nel mezzo di un blackout.", + "appl.pickNone": "— non selezionato —", + "appl.pickExternal": "non è un guest di questo cluster (Docker, bare metal, altrove)", + "appl.pickMissing": "{vmid} — selezionato, ma non trovato in questo cluster", + "appl.reload": "Carica lista", + "appl.help": "Il nodo su cui gira questo guest viene spento per ultimo, quindi il marchio “questo host” su quella scheda host segue automaticamente questa selezione. Finché nulla è selezionato i guest non vengono arrestati prima dello spegnimento — arrestarli tutti arresterebbe anche questa appliance.", + "appl.cephWarn": "Questo container non deve risiedere su storage Ceph. Una volta che gli OSD dei nodi che si spengono portano il pool sotto min_size, il suo stesso disco smette di rispondere e non può più spegnere nulla — a metà del blackout. Usa uno storage locale (local-lvm, local-zfs).", + "appl.detected": "Rilevato: {guest}", + "appl.needHost": "Seleziona prima “Arresta prima tutti i guest a livello cluster” (o l'opzione Ceph) su una scheda host del cluster, poi carica la lista.", + "appl.loading": "Caricamento lista guest…", + "appl.failed": "Impossibile leggere la lista dei guest.", + "th.clusterAbort": "Annulla lo spegnimento se la preparazione del cluster fallisce", + "th.clusterAbortTitle": "Disattivato di default. Una preparazione fallita lascia l'HA manager armato, e un HA manager armato arresta comunque i guest da solo — degradato, ma le macchine si spengono.", + "th.clusterAbortWarn": "Disattivato di default, ed è l'impostazione più sicura: se la preparazione fallisce, l'HA manager resta armato e arresta i guest da solo — più rumoroso, ma le macchine si spengono comunque. Attivarlo significa che una preparazione fallita lascia l'intero cluster acceso finché la batteria si esaurisce e la corrente semplicemente viene a mancare.", + "th.keepShutdown": "Non annullare lo spegnimento in caso di perdita di connessione mentre è a batteria", + "th.keepShutdownTitle": "Se il contatto con l'UPS si perde mentre è già a batteria (blackout in corso, es. uno switch intermedio perde alimentazione), il conto alla rovescia a batteria già avviato NON viene annullato ma continua sull'orologio locale. Richiede un valore in “A batteria per più di (s)”. Una pura perdita di comunicazione su rete resta fail-safe.", + "th.commLoss": "Spegnimento per pura perdita di comunicazione dopo (min)", + "th.commLossTitle": "Spegnimento opzionale per una pura perdita di comunicazione (scavalca il fail-safe).", + "th.commLossPh": "vuoto = disattivato", + "th.warn": "Vuoto di default (fail-safe): perdere il contatto con l'UPS non attiva alcuno spegnimento, perché di solito è un difetto di rete/UPS e non un blackout. Impostalo solo quando una perdita di comunicazione prolungata deve essere trattata deliberatamente come un blackout — lo stato reale dell'alimentazione è allora sconosciuto.", + + // settings: notifications + "notif.title": "Notifiche", + "notif.optional": "(opzionale)", + "notif.doc": "Leggi nel manuale: Notifiche webhook", + "notif.webhook": "Webhook", + "notif.webhookTitle": "Invia un POST HTTP a un URL a ogni evento rilevante (es. a un sistema di ticket/chat/monitoraggio).", + "notif.lastFailed": "L'ultima notifica a questo target è fallita: {err}", + "notif.deadChip": "{n} webhook in errore", + "notif.deadChipTitle": "L'ultima notifica a {names} non è arrivata. Le notifiche sono best-effort e non influenzano mai uno spegnimento, ma un allarme che nessuno riceve è un allarme su cui nessuno agisce. Controllalo in Impostazioni -> Notifiche.", + "notif.url": "URL", + "notif.urlTitle": "URL di destinazione del webhook. Riceve un POST nel formato selezionato a ogni evento.", + "notif.format": "Formato", + "notif.formatTitle": "Forma del payload inviato all'URL. Scegli quello che il tuo sistema di destinazione capisce.", + "notif.minSeverity": "Invia dal livello", + "notif.minSeverityTitle": "Quali eventi vengono inviati. Tutto ciò che è sotto il livello selezionato viene solo scritto nel registro eventi.", + "notif.test": "Invia notifica di test", + "notif.testTitle": "Invia una notifica di esempio con le impostazioni qui inserite — non serve salvare prima, e il filtro di livello non si applica.", + "notif.help": "Ogni evento rilevante viene inviato in POST a ogni webhook attivo. Aggiungi un target per ogni chat o sistema di monitoraggio — un target che fallisce non influisce mai sullo spegnimento.", + "notif.addWebhook": "+ Webhook", + "notif.removeWebhook": "Rimuovi webhook", + "notif.webhookEnabled": "Attivo", + "notif.disabled": "inattivo", + "notif.nameTitle": "Etichetta libera per questo target, mostrata nell'intestazione della scheda e nel log quando un invio fallisce.", + "notif.contentType": "Content type", + "notif.contentTypeTitle": "L'header Content-Type inviato con il body. I valori sono escapati in JSON automaticamente quando contiene “json”.", + "notif.template": "Body", + "notif.templateTitle": "Il body da inviare. I placeholder elencati sotto sono sostituiti; tutto il resto è inviato verbatim.", + "notif.templateHelp": "Placeholder: {{subject}}, {{body}}, {{severity}}, {{severity_upper}}, {{facts}}, {{facts_json}}, {{status_json}}, {{timestamp}}, {{version}}.", + "notif.authSummary": "Autenticazione ed header extra", + "notif.authOptional": "(opzionale)", + "notif.authName": "Header", + "notif.authNameTitle": "Nome di un header aggiuntivo, es. “Authorization” per un topic ntfy protetto o un header API-key.", + "notif.authValue": "Valore", + "notif.authValueTitle": "Valore di quell'header, es. “Bearer tk_…”. Salvato cifrato nel file di configurazione e mai restituito dall'API.", + "notif.extraHeaders": "Header extra", + "notif.extraHeadersTitle": "Un header per riga, scritto come “Nome: Valore”. Per target che richiedono più dell'unico header sopra — Nextcloud Talk, ad esempio, si aspetta OCS-APIRequest e Accept oltre alla sua Basic auth.", + "notif.extraHeadersHelp": "Uno per riga, come “Nome: Valore”. Nessun segreto qui — sono salvati e mostrati in chiaro; usa il campo Valore sopra per tutto ciò che è riservato. Sovrascrivono gli header propri del formato, ma mai l'header di autenticazione. Le righe non interpretabili sono ignorate.", + "whfmt.json": "JSON (stato completo)", + "whfmt.teams": "Microsoft Teams (adaptive card)", + "whfmt.text": "Testo semplice", + "whfmt.jsonHelp": "Invia in POST oggetto, corpo, gravità e stato completo come JSON — per sistemi di ticket, monitoraggio e automazione.", + "whfmt.teamsHelp": "Pubblica una card formattata in un canale Teams. Usa l'URL di un workflow “Posta in un canale quando viene ricevuta una richiesta webhook” (o di un classico incoming webhook).", + "whfmt.textHelp": "Invia in POST un breve stato leggibile come text/plain — per qualsiasi cosa che mostri semplicemente una riga di testo.", + "whfmt.slack": "Slack", + "whfmt.slackHelp": "Pubblica in un canale Slack tramite incoming webhook. Usa l'URL dell'integrazione “Incoming Webhooks” di un'app Slack (https://hooks.slack.com/services/…). La gravità appare come barra colorata.", + "whfmt.discord": "Discord", + "whfmt.discordHelp": "Pubblica in un canale Discord come embed. Usa l'URL da Canale → Modifica → Integrazioni → Webhook.", + "whfmt.ntfy": "ntfy", + "whfmt.ntfyHelp": "Invia un push in testo semplice via ntfy, con titolo, priorità e tag presi dall'evento. Usa l'URL del topic (https://ntfy.sh/il-tuo-topic). Per un topic protetto aggiungi un header “Authorization” sotto.", + "whfmt.custom": "Personalizzato (template)", + "whfmt.customHelp": "Costruisci tu il body e imposta il content type — per un target a cui nessun preset si adatta. I placeholder sono sostituiti letteralmente; non c'è un linguaggio di espressioni.", + "whlvl.info": "Tutti gli eventi (incluso info)", + "whlvl.warning": "Avvisi e critici (default)", + "whlvl.critical": "Solo critici", + + // settings: system + "sys.title": "Sistema", + "sys.doc": "Leggi nel manuale: Aggiornamenti e funzionamento", + "sys.ntp": "Server NTP", + "sys.ntpTitle": "Server orario per l'appliance (systemd-timesyncd dentro il container). L'ora corretta conta per TLS, log e token API. Vuoto = lascia invariato il default di sistema.", + "sys.ntpPh": "es. pool.ntp.org (vuoto = invariato)", + "sys.tz": "Fuso orario", + "sys.tzTitle": "Fuso orario dell'appliance (nome IANA, es. Europe/Rome). Applicato dentro il container; il servizio si riavvia dopo. Importante: l'orario di avvio dell'auto-test è interpretato in quest'ora locale. Vuoto = lascia invariato il default di sistema (di solito UTC).", + "sys.tzPh": "es. Europe/Rome (vuoto = invariato)", + "sys.selftest": "Auto-test automatico delle connessioni ai target", + "sys.selftestTitle": "Verifica automaticamente per host, secondo pianificazione, che il token API e il suo privilegio di power-management funzionino ancora — così credenziali scadute/errate si notano prima che servano in emergenza. Il successo è registrato discretamente (al massimo una volta al giorno), un fallimento è segnalato/notificato.", + "sys.selftestHour": "Orario di avvio auto-test (ora 0–23)", + "sys.selftestHourTitle": "Ancora della pianificazione dell'auto-test: l'ora piena (0–23) nel fuso orario impostato sopra (senza fuso: ora del container, di solito UTC). Insieme all'intervallo produce orari fissi del giorno, es. avvio 09:00 ogni 6 h → 09:00, 15:00, 21:00, 03:00.", + "sys.selftestInterval": "Intervallo", + "sys.selftestIntervalTitle": "Ogni quanto l'auto-test si ripete, ancorato all'orario di avvio. “Giornaliero (24 h)” è il default e corrisponde al comportamento precedente. Mentre un UPS è a batteria l'auto-test viene saltato.", + "sys.iv15": "ogni 15 min", + "sys.iv30": "ogni 30 min", + "sys.iv60": "ogni ora", + "sys.iv120": "ogni 2 h", + "sys.iv180": "ogni 3 h", + "sys.iv360": "ogni 6 h", + "sys.iv720": "ogni 12 h", + "sys.iv1440": "giornaliero (24 h)", + "sys.selftestLogOk": "Registra gli auto-test riusciti nel registro eventi", + "sys.selftestLogOkTitle": "Disattivato: il registro eventi menziona l'auto-test solo quando fallisce. Il controllo continua a girare all'intervallo sopra, i fallimenti sono comunque registrati e notificati, e il pulsante “Test ora” riporta sempre il suo esito.", + "sys.dockerNote": "Ora e fuso orario sono controllati dall'host Docker / orchestratore, non da questo container.", + + // settings: backup & transfer + "backup.title": "Backup e trasferimento", + "backup.help": "Salva la configurazione come file JSON o ripristinala — ideale per reinstallazioni o migrazioni.", + "backup.export": "Esporta (JSON)", + "backup.import": "Importa …", + "backup.warn": "Il file esportato contiene credenziali in chiaro (community SNMP, token API). Conservalo in modo sicuro!", + + // settings: update + "upd.title": "Aggiornamento", + "upd.current": "Versione attuale:", + "upd.helpRest": "Carica un pacchetto di release (.tar.gz/.tar/.zip); il sistema lo applica e riavvia il servizio. Safari scompatta i .tar.gz al download — il .tar risultante funziona ugualmente.", + "upd.upload": "Carica pacchetto di aggiornamento …", + "upd.diag": "Diagnostica (coda e log agente)", + "upd.stuckHint": "⚠ L'aggiornamento non viene elaborato (drainer della coda inattivo). Esegui una volta dentro il container: systemctl start pve-usv-agent.service — poi continua da solo.", + "upd.queueN": "Coda: {n} job — {list}", + "upd.queueEmpty": "Coda: vuota", + "upd.noLog": "(ancora nessun log dell'agente)", + "upd.inQueue": "⏳ In coda — l'agente di sistema applica l'aggiornamento (≈ ogni 20 s).", + "upd.failed": "✗ Aggiornamento fallito — vedi diagnostica sotto / eventi.", + "upd.applied": "✓ Aggiornamento applicato. Ora in esecuzione v{v}.", + "upd.restarted": "✓ Servizio riavviato. Ora in esecuzione v{v}.", + "upd.noReply": "⏳ Ancora nessuna risposta dall'agente. ", + "upd.uploaded": "✓ Caricato (versione pacchetto {v}). Applicazione…", + "upd.unknown": "sconosciuta", + "upd.sameVersion": " ⚠ Attenzione: la versione del pacchetto è uguale alla versione in esecuzione {v} — il pacchetto potrebbe non contenere la nuova versione (compilalo con make-dist dalla working copy).", + "upd.dockerNote": "Aggiornamenti in modalità Docker: scarica un nuovo tag immagine e ricrea il container (docker compose pull && docker compose up -d). Configurazione e registro eventi persistono nei volumi montati.", + + // settings: change password + "pw.title": "Cambia password", + "pw.current": "Password attuale", + "pw.currentTitle": "Inserisci la password attualmente valida dell'interfaccia web per confermare.", + "pw.new": "Nuova password", + "pw.newTitle": "Nuova password dell'interfaccia web (almeno 6 caratteri). Protegge tutti gli endpoint modificanti; /api/status e /api/health restano leggibili senza autenticazione.", + "pw.change": "Cambia", + + // save bar + "save.cardNo": "scheda {n}", + "save.needHostName": "L'host {who} non ha nome nodo — compilalo o rimuovi la scheda. Il salvataggio lo scarterebbe, token API incluso.", + "save.needHostUrl": "L'host {who} non ha URL API — compilalo, rimuovi la scheda o deseleziona “attivo”. Senza di esso lo spegnimento non ha dove andare, e questo si scopre solo durante un blackout.", + "save.needHostToken": "L'host {who} non ha token ID — compilalo, rimuovi la scheda o deseleziona “attivo”. Senza di esso lo spegnimento viene rifiutato, e questo si scopre solo durante un blackout.", + "save.needHostSecret": "Il nuovo host {who} non ha segreto del token — compilalo, rimuovi la scheda o deseleziona “attivo”. Verrebbe salvato vuoto e rifiutato con 401, e questo si scopre solo durante un blackout.", + "save.needUps": "L'UPS {who} non ha né indirizzo né nome — compilane uno o rimuovi la scheda. Il salvataggio lo scarterebbe, password memorizzate incluse.", + "save.needUpsHost": "L'UPS {who} non ha indirizzo — compilalo o rimuovi la scheda. Senza di esso il dispositivo non viene mai interrogato, è segnalato come permanentemente irraggiungibile, e a ogni host che alimenta viene rifiutato lo spegnimento per sicurezza.", + "save.needUpsName": "L'UPS {who} non ha nome UPS — inserisci il nome della sezione dal ups.conf del server NUT, o rimuovi la scheda. Senza di esso il dispositivo non viene mai interrogato, è segnalato come permanentemente irraggiungibile, e a ogni host che alimenta viene rifiutato lo spegnimento per sicurezza.", + "save.needWebhookUrl": "Il webhook {who} è attivo ma non ha URL — compilalo, rimuovi la scheda o deseleziona “attivo”. Senza di esso non viene mai inviato nulla, e questo si scopre solo quando un evento dovrebbe essere partito.", + "save.btn": "Salva configurazione", + "save.title": "Salva tutte le impostazioni nel file di configurazione e le applica immediatamente all'engine in esecuzione (nessun riavvio). Campi password vuoti significano “invariato”.", + + // UPS test button messages (any source) + "probe.ok": "✓ raggiungibile — sorgente {src}, batteria {batt}, {min} min, {pct}%", + "probe.fail": "✗ irraggiungibile: {err}", + "probe.diag": "Dettagli per oggetto", + "probe.head": "{ok} di {n} oggetti hanno risposto", + "probe.missingTriggers": "⚠ Questo UPS non riporta: {list}. Le soglie basate su di essi non scatteranno mai — usa invece il timer “A batteria per più di (s)”.", + "probe.trg.on_battery": "stato rete/batteria", + "probe.trg.battery_low": "batteria scarica", + "probe.trg.runtime": "autonomia residua", + "probe.trg.charge": "carica batteria", + "probe.st.ok": "ok", + "probe.st.noSuchObject": "non presente (noSuchObject)", + "probe.st.noSuchInstance": "non presente (noSuchInstance)", + "probe.st.endOfMibView": "fine della vista MIB", + "probe.st.noSuchName": "non presente (noSuchName — SNMPv1)", + "probe.st.missing": "non fornito da questo driver", + "probe.st.stale": "dati obsoleti (il driver NUT non sta parlando con l'UPS)", + "probe.st.error": "errore: {err}", + "probe.st.skipped": "non interrogato (interrotto dopo il primo fallimento)", + "cprobe.diag": "Dettagli per query cluster", + "cprobe.missingPrivs": "Privilegi mancanti su '/': {list}", + "cprobe.st.ok": "ok", + "cprobe.st.denied": "non consentito (privilegio mancante)", + "cprobe.st.absent": "non disponibile", + "cprobe.st.error": "errore: {err}", + "cprobe.st.unneeded": "non leggibile (non necessario)", + + // dynamic UPS config card (settings) + "cfg.name": "Nome", + "cfg.nameTitle": "Nome visualizzato di questo UPS, es. “UPS rack sinistra”.", + "cfg.upsNamePh": "UPS {id}", + "cfg.srcType": "Lettura via", + "cfg.srcTypeTitle": "Come viene letto questo UPS. SNMP = UPS con scheda di rete propria. NUT = un server Network UPS Tools (upsd), che è il modo in cui si raggiungono gli UPS USB e seriali.", + "cfg.hostip": "Host/IP", + "cfg.hostipTitle": "Hostname o IP: con SNMP la scheda di rete dell'UPS, con NUT la macchina che esegue upsd.", + "cfg.port": "Porta", + "cfg.portTitle": "SNMP: porta UDP della scheda UPS, default 161. NUT: porta TCP di upsd, default 3493.", + "cfg.timeout": "Timeout (s)", + "cfg.timeoutTitle": "Quanto può durare una singola query prima di contare come nessuna risposta. Alzalo su una rete occupata o lenta.", + "cfg.retries": "Tentativi", + "cfg.retriesTitle": "Quante volte una query è ripetuta entro un polling prima che l'UPS conti come irraggiungibile.", + "cfg.tuningHelp": "Questi valori tollerano una rete occupata: 1 tentativo significa che un singolo pacchetto perso per polling passa inosservato. Un UPS è segnalato irraggiungibile solo dopo il numero di polling falliti impostato in “Soglie”.", + "cfg.version": "Versione", + "cfg.versionTitle": "Versione del protocollo SNMP.", + "cfg.mib": "MIB", + "cfg.mibTitle": "Quale MIB viene letto. “Automatico” legge lo standard RFC 1628 e passa a un MIB del produttore quando l'UPS risponde a quello — la scelta giusta per quasi ogni dispositivo. Scegline uno esplicitamente per forzarlo — o quando “Test UPS” dice che la scheda non risponde a una richiesta di più valori insieme (alcune schede CyberPower): “CyberPower” allora viene letto un valore per richiesta.", + "cfg.community": "Community", + "cfg.communityTitle": "Community per SNMP v1/v2c — di solito “public” (sola lettura).", + "cfg.v3user": "Utente", + "cfg.v3userTitle": "Nome utente SNMPv3.", + "cfg.v3auth": "Auth", + "cfg.v3authTitle": "Algoritmo di autenticazione per SNMPv3.", + "cfg.v3authpw": "Password auth", + "cfg.v3authpwTitle": "Password di autenticazione per SNMPv3.", + "cfg.v3priv": "Priv", + "cfg.v3privTitle": "Algoritmo di cifratura per SNMPv3.", + "cfg.v3privpw": "Password priv", + "cfg.v3privpwTitle": "Password privacy per SNMPv3.", + "cfg.nutName": "Nome UPS", + "cfg.nutNameTitle": "Nome dell'UPS come configurato in upsd (il nome della sezione nel suo ups.conf). “upsc -l ” elenca i nomi disponibili.", + "cfg.nutUser": "Utente", + "cfg.nutUserTitle": "Nome utente NUT — serve solo se il server limita la lettura. Lascia vuoto per un upsd standard.", + "cfg.nutPw": "Password", + "cfg.nutPwTitle": "Password per l'utente NUT. Lascia vuoto se non è impostato alcun utente.", + "cfg.nutPwPh": "(opzionale)", + "cfg.nutHint": "PVE-UPS legge soltanto da upsd — non invia mai comandi e non esegue mai upsmon. La logica di spegnimento resta qui.", + "cfg.unchanged": "•••• (invariato)", + "cfg.overrideSummary": "Sovrascrivi le soglie per questo UPS", + "cfg.overrideGlobal": "(vuoto = globale)", + "cfg.oObs": "A batteria > (s)", + "cfg.oObsTitle": "Sovrascrive “A batteria per più di (s)” solo per questo UPS.", + "cfg.oRbm": "Autonomia < (min)", + "cfg.oRbmTitle": "Sovrascrive “Autonomia sotto (min)” solo per questo UPS.", + "cfg.oCbp": "Carica < (%)", + "cfg.oCbpTitle": "Sovrascrive “Carica sotto (%)” solo per questo UPS.", + "cfg.oObl": "batteria scarica", + "cfg.oOblTitle": "Sovrascrive “Immediato a batteria scarica/esaurita”.", + "cfg.oClm": "Perdita comunicazione dopo (min)", + "cfg.oClmTitle": "Sovrascrive “Spegnimento per pura perdita di comunicazione dopo (min)”.", + "cfg.oKsc": "Mantieni a perdita batteria", + "cfg.oKscTitle": "Sovrascrive “Non annullare lo spegnimento in caso di perdita di connessione mentre è a batteria”.", + "cfg.testUps": "Test UPS", + "cfg.removeUps": "✕ Rimuovi UPS", + "src.snmp": "SNMP (scheda di rete)", + "src.nut": "Server NUT (USB/seriale)", + "mib.auto": "Automatico", + "mib.rfc1628": "RFC 1628 (standard)", + "mib.apc": "APC PowerNet", + "mib.cyberpower": "CyberPower", + "proto.none": "nessuno", + "tristate.global": "(globale)", + "tristate.on": "sempre attivo", + "tristate.off": "sempre disattivo", + "htype.pve": "Proxmox VE", + "htype.pbs": "Proxmox Backup Server", + // Short forms for tight spots (dashboard chip, host card heading); the type dropdown + // uses the full names above. + "htype.pveShort": "Proxmox VE", + "htype.pbsShort": "Proxmox BS", + "htype.pveHelp": "Richiede un token API con il privilegio “Sys.PowerMgmt” su /nodes.", + "htype.pbsHelp": "Porta di default 8007. Richiede un token API con il privilegio “Sys.PowerManagement” su /system/status — su PBS solo il ruolo “Admin” lo possiede, e la voce serve sia all'utente sia al token.", + + // dynamic host row (settings) + "host.type": "Tipo", + "host.typeTitle": "Quale prodotto Proxmox esegue questo target. Decide lo schema del token API e il privilegio verificato — un Proxmox Backup Server inserito come “Proxmox VE” viene rifiutato come token non valido.", + "host.node": "Nodo", + "host.nodeTitle": "Nome del nodo esattamente come in Proxmox (es. “pve01”). Deve corrispondere al percorso API /nodes/.", + "host.nodeSuggest": "Usare “{node}”?", + "host.nodeFilled": "Nome nodo “{node}” letto dall'API.", + "host.dupUrl": "Questo URL API è già usato da un'altra voce host. Ogni nodo richiede il proprio URL \u2014 uno già spento non può inoltrare lo spegnimento per gli altri. Finché non è corretto, entrambe le voci sono indirizzate dal loro nome nodo configurato, quindi quel nome deve essere esattamente giusto.", + "host.name": "Nome", + "host.nameTitle": "Un'etichetta libera per questo Backup Server, mostrata nella dashboard e negli eventi. Proxmox Backup Server ignora il nome nodo nel percorso API, quindi nulla qui deve corrispondere all'hostname.", + "host.hintDoc": "Passi di configurazione →", + "host.apiurl": "URL API", + "host.apiurlTitle": "URL base dell'API inclusa la porta, es. https://10.0.0.10:8006 (Proxmox VE) o https://10.0.0.20:8007 (Backup Server)", + "host.tokenId": "Token ID", + "host.tokenIdTitle": "Identificatore del token API nella forma utente@realm!nometoken, es. ups@pve!shutdown. In un cluster PVE lo stesso token è valido per ogni nodo.", + "host.tokenSecret": "Segreto del token", + "host.tokenSecretTitle": "Valore segreto del token (UUID). Salvato in modo sicuro e mai restituito.", + "host.tokenSecretPh": "Segreto", + "host.feeds": "Alimentato da", + "host.feedsAllHint": "Niente di spuntato = alimentato da ogni UPS configurato. Spunta le caselle solo per restringere.", + "host.feedsTitle": "Quale/i UPS alimentano questo host. Con alimentatori ridondanti spuntane diversi.", + "host.policy": "Logica", + "host.policyTitle": "AND = spegne solo quando TUTTI gli UPS spuntati sono critici (alimentatori ridondanti). OR = spegne appena UNO è critico (carico separato, non ridondante).", + "host.order": "Ordine (0 = primo)", + "host.orderTitle": "Ordine di spegnimento, crescente — 0 parte per primo. Gli host con lo stesso numero si spengono contemporaneamente (uno stadio), che è il default più rapido e sicuro; alzalo solo quando un target deve davvero spegnersi prima di un altro. “Questo host” è sempre l'ultimo indipendentemente dal numero.", + "host.verify": "Verifica TLS", + "host.verifyTitle": "Verifica il certificato TLS. Lascia disattivato finché Proxmox usa un certificato self-signed.", + "host.this": "★ Questo host", + "host.thisTitle": "Marca l'host su cui gira questa appliance (LXC) — viene sempre spento per ULTIMO. Suggerimento: collega questo host a TUTTI gli UPS (AND), così scatta per ultimo.", + "host.enabled": "Attivo", + "host.enabledTitle": "Includi l'host nello spegnimento. Disattivato = saltato.", + "host.sumCluster": "Cluster", + "host.clusterBox": "Cluster Proxmox VE", + "host.cluster": "Membro del cluster", + "host.clusterTitle": "Prepara il cluster una volta prima che i nodi si spengano. I membri sono raggruppati automaticamente dal nome del cluster letto dall'API, quindi la preparazione viene eseguita una volta per cluster indipendentemente da quanti nodi portano questa spunta — mettila su ogni membro, o solo su uno.", + "host.clusterCeph": "Prepara il cluster Ceph (arresta tutti i guest, poi imposta i flag)", + "host.clusterCephTitle": "Solo per cluster che eseguono davvero Ceph — disattivato di default. Esegue la procedura ufficiale di spegnimento: prima viene arrestato ogni VM e container del cluster (tranne questa appliance), poi vengono impostati noout, nobackfill, norecover e norebalance affinché Ceph non inizi a fare healing mentre gli OSD si spengono uno a uno. Richiede Sys.Modify su “/” più VM.Audit e VM.PowerMgmt; senza Ceph l'intero passo viene saltato e nulla di tutto questo è richiesto.", + "host.clusterHa": "Disarma l'HA manager", + "host.clusterHaTitle": "Disarma HA prima dello spegnimento così i servizi non vengono ripristinati su nodi che si stanno spegnendo a loro volta. Richiede Proxmox VE 9.2 o più recente e il privilegio Sys.Console su “/” — che concede accesso shell ai nodi, quindi è un interruttore a parte.", + "host.clusterHelp": "Entrambi i passi sono verificati prima che lo spegnimento continui. Se la preparazione fallisce, lo spegnimento va comunque avanti di default — vedi “Trigger”. Dopo, il cluster resta preparato finché non premi “Ripristina cluster” sulla dashboard.", + "host.clusterCephHelp": "Arrestare prima i guest qui non è opzionale, ed è per questo che questa opzione attiva da sé l'arresto guest sopra: se ogni nodo arrestasse solo i propri guest mentre si spegne, il pool scenderebbe sotto min_size mentre i guest girano ancora sui nodi sopravvissuti — la loro IO si blocca, il loro spegnimento non finisce mai e quel nodo non si spegne mai.", + "host.clusterGuests": "Arresta prima tutti i guest a livello cluster", + "host.clusterGuestsTitle": "Disattivato di default. Prima che il primo nodo si spenga, a ogni VM e container del cluster (tranne questa appliance) viene chiesto di spegnersi — tutti insieme, in parallelo — invece che ogni nodo smaltisca i propri guest in ordine di avvio inverso mentre si spegne. Più rapido quando la batteria è poca, ma l'ordine di avvio configurato è ignorato, e i guest non fermati in tempo vengono forzati. Richiede VM.Audit e VM.PowerMgmt; dove HA gestisce i guest, HA deve essere disarmato prima.", + "host.clusterGuestsHelp": "Ignora l'ordine di avvio di proposito: a tutto viene chiesto di fermarsi insieme. Un guest ancora in esecuzione dopo “Arresto forzato guest dopo” viene fermato forzatamente, e il guest proprio dell'appliance deve essere scelto in Trigger → Questa appliance, o nessun guest viene arrestato. Dove HA gestisce i guest, vengono arrestati solo mentre HA può essere disarmato (Proxmox VE 9.2+). Richiede VM.Audit e VM.PowerMgmt su “/” o “/vms”.", + "host.clusterGuestsDoc": "Spegnimento guest nel manuale", + "host.clusterGuestsByCeph": "Incluso nell'opzione Ceph sotto — su un cluster Ceph arrestare prima ogni guest fa parte della procedura.", + "host.clusterNeeds92": "La preparazione del cluster richiede Proxmox VE 9.2 o più recente (disarm-ha). Sulle release precedenti HA non viene disarmato, e dove HA gestisce i guest non vengono nemmeno arrestati a livello cluster; i flag Ceph vengono comunque impostati.", + "host.thisHostDerived": "preso dalla selezione appliance in “Trigger”", + "host.clusterShutdownAll": "Spegni l'intero cluster come un'unità", + "host.clusterShutdownAllTitle": "Quando un nodo di questo cluster deve spegnersi, spegni ogni suo nodo — inclusi quelli il cui UPS non ha attivato il trigger. Attivo di default, perché la preparazione è comunque a livello cluster: disarma HA e, con l'arresto guest o l'opzione Ceph, arresta ogni guest del cluster.", + "host.clusterShutdownAllHelp": "Disattivalo solo per un cluster semplice dove HA dovrebbe spostare i guest di un singolo nodo guasto sugli altri — lì uno spegnimento parziale è un buon risultato.", + "host.clusterShutdownAllWarn": "Con l'arresto guest dovrebbe restare attivo. La preparazione arresta comunque ogni guest del cluster; se poi si spengono solo alcuni nodi, gli altri restano lì senza guest, senza HA e — su Ceph, una volta che i monitor li seguono — senza storage.", + "host.clusterCephGroup": "Iperconvergente (Ceph)", + "host.clusterCephDoc": "Sezione Ceph del manuale", + "host.clusterPrivHint": "Questo richiede più del privilegio di spegnimento — ma solo per le opzioni che spunti: Sys.Audit per leggere lo stato, Sys.Modify per i flag Ceph, VM.Audit e VM.PowerMgmt per l'arresto guest, e Sys.Console per armare/disarmare HA, quest'ultimo di fatto concede accesso shell ai nodi. “Test” verifica anche questi e indica ciò che manca, insieme a cosa serve.", + "host.clusterPrivDoc": "comandi pveum nel manuale", + "host.clusterBetaNote": "Nuovo in 4.0.0 e marcato Beta: la sequenza è verificata anziché presunta e gira sotto un timeout rigido, ma finora ha visto pochi cluster reali. Nulla accade senza la spunta sopra. Segnalazioni su ciò che trovi sono benvenute:", + "host.clusterBetaLink": "Issue GitHub", + "host.test": "Test", + "host.remove": "✕ Rimuovi host", + "host.newName": "Nuovo host", + "host.inactive": "· inattivo", + "host.noUps": "nessun UPS", + + // --- History tab (#37) --- + "hist.title": "Storico", + "hist.doc": "Leggi nel manuale: Storico", + "hist.rangeLabel": "Intervallo temporale", + "hist.upsLabel": "UPS", + "hist.chartLabel": "Autonomia, carico e carica nel tempo", + "hist.range.1h": "1 h", + "hist.range.6h": "6 h", + "hist.range.24h": "24 h", + "hist.range.7d": "7 giorni", + "hist.range.30d": "30 giorni", + "hist.range.90d": "90 giorni", + "hist.range.all": "Tutto", + "hist.rangeTooLong": "Più lungo di quanto lo storico conservi ({days} giorni)", + "hist.allUps": "Tutti", + "hist.m.runtime": "Autonomia", + "hist.m.load": "Carico", + "hist.m.charge": "Carica", + "hist.p.runtime": "Autonomia residua (min)", + "hist.p.load": "Carico (%)", + "hist.p.charge": "Carica (%)", + "hist.p.percent": "Carico e carica (%)", + "hist.threshold": "trigger a {min} min", + "hist.zoomReset": "Reimposta periodo", + "hist.from": "Da", + "hist.to": "A", + "hist.apply": "Mostra", + "hist.spanTitle": "Scegli inizio e fine del periodo da mostrare. I campi mostrano sempre il periodo sullo schermo.", + "hist.spanInvalid": "Inserisci un inizio e una fine, con l'inizio prima della fine.", + "hist.csv": "CSV", + "hist.info": "Eventi info", + "hist.infoTitle": "Marca anche gli eventi informativi come gli auto-test. Avvisi ed eventi critici sono sempre marcati.", + "hist.noUps": "Nessun UPS configurato ancora.", + "hist.noData": "Ancora nessun valore in questo periodo. Lo storico inizia a registrare entro un minuto dall'attivazione.", + "hist.hint": "Trascina sul grafico per ingrandire; doppio clic per ridurre. Passa sopra un marcatore per l'evento dietro di esso.", + "hist.zoomed": "Un periodo fisso — non si aggiorna automaticamente. Doppio clic sul grafico o usa “Reimposta periodo” per tornare al preset.", + "hist.stMains": "Su rete elettrica", + "hist.stBattery": "A batteria", + "hist.stTriggered": "Spegnimento attivato", + "hist.stUnreachable": "Non raggiungibile", + "hist.stBypass": "Bypass", + "hist.stOther": "Altro stato", + "hist.stNoData": "Nessun dato", + "hist.lgWarning": "Evento di avviso", + "hist.lgCritical": "Evento critico", + "hist.outages": "Blackout in questo periodo", + "hist.outagesHelp": "Una riga è un intervallo a batteria. Cliccala per zoomare il grafico su quel blackout.", + "hist.colStart": "Inizio", + "hist.colUps": "UPS", + "hist.colDuration": "Durata", + "hist.colRuntime": "Autonomia minima", + "hist.colCharge": "Carica minima", + "hist.colLoad": "Carico massimo", + "hist.colShutdown": "Spegnimento", + "hist.noOutages": "Nessun blackout in questo periodo.", + "hist.ongoing": "in corso", + "hist.yes": "attivato", + "hist.no": "no", + "hist.zoomTo": "Zooma il grafico su questo blackout", + "sys.historyGroup": "Storico", + "sys.history": "Registra lo storico degli UPS", + "sys.historyTitle": "Registra autonomia, carico, carica e sorgente di alimentazione di ogni UPS per la scheda Storico.", + "sys.historyDays": "Conserva lo storico per (giorni)", + "sys.historyDaysTitle": "Per quanto lo storico viene conservato, in giorni (1–365).", + "sys.historyClear": "Svuota storico", + "sys.historyHelp": "Circa 1.440 valori per UPS al giorno — pochi MB per UPS su 90 giorni. Non incluso nel backup della configurazione. Disattivandolo si fermano sia la registrazione sia la scheda Storico; ciò che è stato registrato resta finché non scade o viene cancellato.", + "sys.historySize": "Attualmente {size}, dal {since}.", + "confirm.clearHistory": "Eliminare davvero l'intero storico degli UPS?", +}; diff --git a/app/web/index.html b/app/web/index.html index ee3ab45..1a25c79 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -480,6 +480,7 @@

Actions

+ diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 1c427bc..88a75bc 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,6 +1,6 @@ """UI i18n consistency checks (regex-based, no JS engine needed). -Enforces: en.js and de.js define identical key sets, no duplicate keys, and every +Enforces: all dictionaries define identical key sets, no duplicate keys, and every key referenced from index.html (data-i18n* attributes) or app.js (literal t() calls) exists in the English dictionary. Dynamic keys built at runtime ("theme." + mode, "state.engine." + s, ...) are covered by the key-set checks on the dictionaries. @@ -18,23 +18,28 @@ # data-i18n, data-i18n-html, data-i18n-title, data-i18n-placeholder in index.html. ATTR_RE = re.compile(r'data-i18n(?:-[a-z]+)*="([^"]+)"') +# All shipped UI dictionaries; a new language joins the checks by joining this tuple. +DICTS = ("en.js", "de.js", "it.js") + def dict_keys(fname: str) -> list[str]: return KEY_RE.findall((WEB / "i18n" / fname).read_text(encoding="utf-8")) def test_dictionaries_have_no_duplicate_keys(): - for fname in ("en.js", "de.js"): + for fname in DICTS: keys = dict_keys(fname) dupes = {k for k in keys if keys.count(k) > 1} assert not dupes, f"{fname}: duplicate keys {sorted(dupes)}" def test_dictionary_key_sets_are_identical(): - en, de = set(dict_keys("en.js")), set(dict_keys("de.js")) - assert en == de, ( - f"missing in de.js: {sorted(en - de)}; missing in en.js: {sorted(de - en)}" - ) + en = set(dict_keys("en.js")) + for fname in DICTS[1:]: + keys = set(dict_keys(fname)) + assert en == keys, ( + f"missing in {fname}: {sorted(en - keys)}; missing in en.js: {sorted(keys - en)}" + ) def test_index_html_keys_exist_in_english_dictionary(): @@ -54,11 +59,13 @@ def placeholders(fname): text = (WEB / "i18n" / fname).read_text(encoding="utf-8") return {k: set(ph_re.findall(rest)) for k, rest in line_re.findall(text)} - en, de = placeholders("en.js"), placeholders("de.js") - for key in en.keys() & de.keys(): - assert en[key] == de[key], ( - f"{key}: placeholders differ (en={sorted(en[key])}, de={sorted(de[key])})" - ) + en = placeholders("en.js") + for fname in DICTS[1:]: + other = placeholders(fname) + for key in en.keys() & other.keys(): + assert en[key] == other[key], ( + f"{key}: placeholders differ (en={sorted(en[key])}, {fname}={sorted(other[key])})" + ) def test_app_js_keys_exist_in_english_dictionary(): @@ -70,12 +77,12 @@ def test_app_js_keys_exist_in_english_dictionary(): def _both_dictionaries_define(prefix: str, names) -> list[str]: - en, de = set(dict_keys("en.js")), set(dict_keys("de.js")) - return [f"{prefix}{n}" for n in names if f"{prefix}{n}" not in en or f"{prefix}{n}" not in de] + dicts = [set(dict_keys(f)) for f in DICTS] + return [f"{prefix}{n}" for n in names if any(f"{prefix}{n}" not in d for d in dicts)] def test_probe_status_keys_exist_in_both_dictionaries(): - """Every per-object probe status the backend can emit needs a UI label in EN and DE.""" + """Every per-object probe status the backend can emit needs a UI label in every dictionary.""" from app.ups import PROBE_STATUSES missing = _both_dictionaries_define("probe.st.", PROBE_STATUSES) @@ -83,7 +90,7 @@ def test_probe_status_keys_exist_in_both_dictionaries(): def test_cluster_probe_status_keys_exist_in_both_dictionaries(): - """Every per-endpoint outcome of the cluster check needs a UI label in EN and DE.""" + """Every per-endpoint outcome of the cluster check needs a UI label in every dictionary.""" from app.cluster import CLUSTER_PROBE_STATUSES missing = _both_dictionaries_define("cprobe.st.", CLUSTER_PROBE_STATUSES) @@ -91,7 +98,7 @@ def test_cluster_probe_status_keys_exist_in_both_dictionaries(): def test_node_state_keys_exist_in_both_dictionaries(): - """Every node-name verdict needs a UI label in EN and DE. + """Every node-name verdict needs a UI label in every dictionary. The dashboard shows a chip for the three states that mean "does not match this API"; the other two exist so the closed set stays labelled, exactly like the probe statuses. @@ -111,7 +118,7 @@ def test_probe_trigger_keys_exist_in_both_dictionaries(): def test_source_type_keys_exist_in_both_dictionaries(): - """Every UPS source type needs a label in EN and DE (wizard dropdown + dashboard).""" + """Every UPS source type needs a label in every dictionary (wizard dropdown + dashboard).""" from app.config import UPS_SOURCE_MODELS, UpsSourceType kinds = [t.value for t in UpsSourceType] @@ -148,7 +155,7 @@ def test_manual_deep_links_resolve_in_both_manuals(): def test_host_type_keys_exist_in_both_dictionaries(): - """Every shutdown target type needs a label + a help line in EN and DE.""" + """Every shutdown target type needs a label + a help line in every dictionary.""" from app.config import TARGET_MODELS, HostType kinds = [t.value for t in HostType] @@ -168,7 +175,7 @@ def test_host_type_keys_exist_in_both_dictionaries(): def test_snmp_mib_keys_exist_in_both_dictionaries(): - """Every selectable MIB needs a label in EN and DE (wizard dropdown + dashboard).""" + """Every selectable MIB needs a label in every dictionary (wizard dropdown + dashboard).""" from app.ups import PROFILES from app.config import SnmpMib @@ -213,7 +220,7 @@ def _js_option_values(const_name: str) -> list[str]: def test_webhook_format_keys_exist_in_both_dictionaries(): - """Every payload format needs a renderer, a dropdown entry and labels in EN and DE.""" + """Every payload format needs a renderer, a dropdown entry and labels in every dictionary.""" from app.config import WebhookFormat from app.notify import FORMATTERS From 2ec22e65850fdf696dd6923aed16a6a94bbe806e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 22 Oct 2026 11:58:22 +0200 Subject: [PATCH 2/2] i18n: add Italian user manual (manuale.html) Point the Italian dictionary's manual.file at a full Italian translation of the user manual, keeping every anchor id identical so UI deep links keep working. The deep-link test now resolves the manual each dictionary points to instead of hardcoding the English and German files. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- app/web/i18n/it.js | 2 +- app/web/manuale.html | 1797 ++++++++++++++++++++++++++++++++++++++++++ tests/test_i18n.py | 19 +- 3 files changed, 1814 insertions(+), 4 deletions(-) create mode 100644 app/web/manuale.html diff --git a/app/web/i18n/it.js b/app/web/i18n/it.js index 3aeecd5..1c61688 100644 --- a/app/web/i18n/it.js +++ b/app/web/i18n/it.js @@ -6,7 +6,7 @@ window.I18N = window.I18N || {}; I18N.it = { - "manual.file": "manual.html", + "manual.file": "manuale.html", // topbar / navigation "nav.dashboard": "Dashboard", diff --git a/app/web/manuale.html b/app/web/manuale.html new file mode 100644 index 0000000..a3016cb --- /dev/null +++ b/app/web/manuale.html @@ -0,0 +1,1797 @@ + + + + + + + + PVE-UPS — Manuale utente + + + +
+ + + PVE-UPS Manuale utente · v4.3.0 + + + + + GitHub + + +
+ +
+ + +
+
+

PVE-UPS — Manuale utente

+

Un'appliance snella per lo spegnimento da UPS per Proxmox VE: monitora uno o + più UPS — via SNMP o tramite un server NUT — e, in caso di blackout, spegne + in modo ordinato gli host Proxmox assegnati, senza file di configurazione scritti + a mano da nessuna parte. Questo manuale è rivolto ad amministratori con mentalità + tecnica che sono nuovi al prodotto.

+
+ +
+

1 Cos'è PVE-UPS?

+

PVE-UPS sostituisce le classiche appliance di spegnimento UPS legate al produttore (come APC + PowerChute Network Shutdown) per ambienti Proxmox. Il software gira come un singolo + LXC unprivileged (~256 MB RAM) e non richiede altro che uno script di installazione.

+

Funzionalità principali

+
    +
  • Monitoraggio di uno o più UPS — con scheda di rete SNMP + (standard RFC 1628 o un MIB del produttore: APC PowerNet, CyberPower, + SNMP v1/v2c/v3, implementazione in puro Python) o tramite un server NUT, + che è il modo in cui si leggono gli UPS USB e seriali.
  • +
  • Spegnimento ordinato degli host tramite token API Proxmox — nessun accesso + root via SSH. I target possono essere nodi Proxmox VE (privilegio + Sys.PowerMgmt) o un Proxmox Backup Server + (Sys.PowerManagement), selezionabile per host.
  • +
  • Mappatura host↔UPS con logica per host (AND/OR) per alimentatori + ridondanti o carico separato, con diagramma dell'alimentazione nella web UI.
  • +
  • Configurazione completa tramite procedura guidata web con pulsanti di test.
  • +
  • Web UI in inglese, tedesco e italiano — scelta automaticamente dalla lingua + del browser.
  • +
  • Fail-safe: perdere il contatto con l'UPS genera un allarme di default, + non spegne mai nulla.
  • +
  • Modalità dry-run per una messa in servizio senza rischi.
  • +
  • Stato REST/JSON in sola lettura per il monitoraggio (/api/status, + /api/health).
  • +
+
+ +
+

2 Requisiti

+
    +
  • Un host Proxmox VE su cui far girare l'LXC. Tipicamente uno degli host da + proteggere. Come target funzionano sia host standalone sia nodi di un cluster — ogni + host viene spento singolarmente tramite la propria API, e PVE-UPS non tocca né + l'HA manager né il quorum.
  • +
  • Uno o più UPS, raggiungibili in uno di due modi: +
      +
    • con una scheda di rete SNMP che espone lo standard + RFC 1628 UPS MIB (indipendente dal produttore) o un MIB del produttore + supportato — APC PowerNet o CyberPower, che coprono anche le schede senza RFC 1628 (vedi + sezione 5), oppure
    • +
    • collegato via cavo USB o seriale a una macchina che esegue un + server NUT (upsd) — vedi sezione 5.
    • +
    +
  • +
  • Connettività di rete tra l'appliance e la sorgente UPS (basta l'accesso in + lettura: UDP 161 per SNMP, TCP 3493 per NUT) e verso l'API dei target + protetti (HTTPS, porta 8006 per Proxmox VE, 8007 per Proxmox Backup Server).
  • +
+

Oltre ai nodi Proxmox VE, si può spegnere anche un Proxmox Backup Server — + ogni voce host porta un tipo. I due differiscono solo in come vengono + indirizzati; tutto il resto (mappatura UPS, logica AND/OR, ordine, soglie) funziona + allo stesso modo per entrambi. Vedi sezione 4.

+
+ PVE-UPS va su una rete interna (di management). Contiene token + API che possono spegnere server, quindi trattalo come qualsiasi altro strumento di + management: raggiungibile solo da reti fidate, mai esposto a internet, e dietro + lo stesso firewalling, segmentazione e controllo accessi delle interfacce web + di Proxmox stesse. La web UI è protetta da password, ma + /api/status e /api/health sono volutamente leggibili + senza login affinché il monitoraggio possa interrogarli — un motivo in più per cui + l'appliance non dovrebbe essere raggiungibile pubblicamente. +
+
+ +
+

3 Installazione e configurazione

+

Installa direttamente dalla shell del nodo Proxmox (web UI → nodo → + >_ Shell, come root). Lo script scarica l'ultimo pacchetto di release + da GitHub, lo scompatta e crea l'LXC:

+
bash -c "$(curl -fsSL https://github.com/ffind-dev/pve-ups/releases/latest/download/install.sh)"
+# ad es. con un IP statico:
+curl -fsSL https://github.com/ffind-dev/pve-ups/releases/latest/download/install.sh | bash -s -- \
+  --ctid 950 --ip 10.0.0.50/24 --gateway 10.0.0.1 --hostname pve-usv
+

Tutte le opzioni sono facoltative; --help le elenca con i default + (come one-liner: … | bash -s -- --help).

+ + + + + + + + + + + + + + + + + + + + + + + + +
OpzioneDefaultSignificato
--ctid <id>950ID del nuovo container
--hostname <name>pve-usvHostname del container
--storage <name>automaticoStorage per il disco del container (content rootdir). Senza: + local-lvm, poi local-zfs, poi il primo altro + storage non-Ceph
--template-storage <name>localStorage su cui viene scaricato il template Debian 12 (content + vztmpl). Il default ripiega su un altro storage adatto; uno + esplicito viene rifiutato se non può contenere template
--bridge <bridge>vmbr0Bridge di rete
--ip <dhcp|indirizzo/cidr>dhcpdhcp o un indirizzo statico come 10.0.0.50/24
--gateway <ip>–Gateway di default, usato solo con --ip statico
--memory <MB>256RAM del container
--disk <GB>4Dimensione del disco del container
--allow-ceph-storageoffAccetta uno --storage basato su Ceph — vedi + il guest proprio dell'appliance
+

PVE-UPS è elencato anche su community-scripts.org (cerca “PVE-UPS”), una + raccolta mantenuta dalla community di script di supporto per Proxmox. Il one-liner + sopra resta il percorso di riferimento.

+

Poi apri la web UI su http://<container-ip>:8080 e segui + la procedura guidata:

+
    +
  1. Imposta la password dell'UI.
  2. +
  3. Aggiungi gli UPS (sorgente, indirizzo, credenziali — vedi sezione 5) e + verifica ciascuno con “Test UPS”.
  4. +
  5. Aggiungi gli host: scegli il tipo per ogni voce (Proxmox VE o Proxmox + Backup Server), inserisci il suo token API (vedi sezione 4) e usa “Test”. Definisci quali + UPS alimentano ogni host e con quale logica (AND/OR).
  6. +
  7. Imposta le soglie o mantieni i default.
  8. +
  9. Opzionalmente configura il webhook di notifica (HTTP POST verso un + sistema di ticket/chat/monitoraggio): scegli formato e livello, poi verificalo con + “Invia notifica di test” — vedi Notifiche webhook.
  10. +
+
+ Mentre il dry-run è attivo nulla viene spento — ideale per i test. + Solo quando tutto torna, disattiva il dry-run (modalità “ATTIVO”). +
+
+ Se l'LXC gira su uno degli host protetti, marca quell'host come + “Questo host” nella lista host — così è garantito che si spenga per ultimo. +
+
+ +
+

4 Creare il token API

+

Ogni voce host si autentica con un token API — revocabile, e sostituto + dell'accesso root via SSH. Quale token creare dipende dal tipo selezionato + per quella voce, perché Proxmox VE e Proxmox Backup Server usano schemi di token + e nomi di privilegio diversi:

+ + + + + + + + + + + +
Proxmox VEProxmox Backup Server
Porta API80068007
PrivilegioSys.PowerMgmtSys.PowerManagement
Concesso su/nodes/system/status
Campo “Nodo”il vero nome del nodoun'etichetta libera (PBS lo ignora)
+
+ Scegliere il tipo sbagliato è la causa più comune di un test fallito: un + Backup Server inserito come “Proxmox VE” rifiuta la richiesta e riporta + “Authentication failed (token invalid?)”, anche se il token stesso è + perfettamente valido. +
+ +

Proxmox VE

+

Crea un utente dedicato con il solo privilegio di power-management e un token + API. Esegui una sola volta, nella shell di un qualsiasi nodo Proxmox:

+
pveum user add ups@pve
+pveum role add UpsShutdown -privs "Sys.PowerMgmt"
+pveum acl modify /nodes -user ups@pve -role UpsShutdown
+pveum user token add ups@pve shutdown --privsep 0
+

L'ultimo comando stampa il token ID (ups@pve!shutdown) e + il segreto. Inserisci entrambi nella procedura guidata e verifica con “Test”.

+
+ In un cluster i comandi si eseguono una sola volta. Utenti, token API e + ACL vivono a livello datacenter (/etc/pve) e valgono quindi su + ogni nodo — inserisci lo stesso token ID e segreto per ogni nodo. Solo gli + host standalone che non condividono un cluster hanno bisogno ognuno di un proprio + utente e token. +
+

Dai a ogni voce host il proprio URL API + (https://<ip-di-questo-nodo>:8006). L'API Proxmox farebbe da proxy + per una richiesta a un altro nodo, ma un nodo già spento non può fare da proxy + per quelli che devono ancora spegnersi.

+
+ Dai a ogni voce host il proprio URL API — è quello che decide dove lo + spegnimento arriva. PVE-UPS indirizza il nodo dietro l'URL che hai inserito + (/nodes/localhost/status), quindi la macchina che risponde è quella che + viene spenta. Proxmox gestisce questa forma localmente invece di cercare il nodo nel + cluster, che è esattamente ciò che vuoi mentre i nodi cadono e il quorum è + traballante. La conseguenza: un “Nodo” scritto male non può più deviare uno spegnimento — + ma due voci che condividono un URL sì, quindi PVE-UPS avvisa di questo sia mentre + modifichi sia nell'auto-test. +
+
+ “Nodo” viene comunque controllato, perché etichetta tutto il resto. È il nome + dell'host nella dashboard e in ogni evento, quindi un nome che non corrisponde a nessun + nodo rende tutto fuorviante. “Test” lo confronta con la lista dei nodi riportata + dall'API, ti propone quello giusto da accettare con un clic e riempie un campo vuoto. + Valgono le regole di Proxmox: solo lettere, cifre e - — niente punti, quindi un + FQDN come pve01.example.com viene rifiutato — e maiuscole/minuscole + sono distinte, quindi PVE01 non è pve01. Dove le voci + condividono un URL API il nome torna nel percorso dello spegnimento, e una + discrepanza è riportata come errore anziché come avviso. +
+ +

Proxmox Backup Server

+

Esegui questo nella shell del Backup Server. Nota che l'ACL viene concessa + due volte — una all'utente, una al token:

+
proxmox-backup-manager user create ups@pbs
+proxmox-backup-manager user generate-token ups@pbs shutdown
+proxmox-backup-manager acl update /system/status Admin --auth-id 'ups@pbs'
+proxmox-backup-manager acl update /system/status Admin --auth-id 'ups@pbs!shutdown'
+

generate-token stampa il token ID + (ups@pbs!shutdown) e il segreto — il segreto viene mostrato una + sola volta e non può essere recuperato dopo. Inserisci l'URL API come + https://<pbs-ip>:8007, poi verifica con “Test”.

+

Tre dettagli differiscono da Proxmox VE e spiegano i comandi sopra:

+
    +
  • Servono entrambe le voci. Su PBS i permessi di un token sono calcolati + dalle sue ACL e poi intersecati con quelli del suo utente, quindi un token + non può mai superare il suo utente. Concederne solo una delle due lascia il token + senza il privilegio.
  • +
  • Il ruolo è Admin. PBS non ha un ruolo granulare di + power-management — Sys.PowerManagement è posseduto solo da + Admin. Concederlo su /system/status invece che su + / mantiene l'ambito il più stretto possibile per PBS.
  • +
  • Il campo “Nodo” è solo un'etichetta. PBS ignora il nodo nel percorso API, + quindi lo spegnimento indirizza sempre /nodes/localhost/status. Chiama la + voce come è leggibile sulla dashboard; nulla deve corrispondere all'hostname.
  • +
+
+ Valuta bene se collegare qui un Backup Server. + Il token sopra è un token Admin, e PVE-UPS deve raggiungere l'API + PBS dall'ambiente che protegge. Questa combinazione allarga il percorso da un + host di virtualizzazione compromesso verso i backup — che sono esattamente ciò che + dovrebbe sopravvivere a una tale compromissione. Per ambienti di produzione + sconsigliamo quindi di collegare PBS alla stessa istanza PVE-UPS che gestisce + gli host PVE. Alternative: dai al Backup Server un suo percorso di spegnimento UPS + (una propria istanza PVE-UPS, raggiungibile solo da un segmento di management separato), o + lascialo superare i blackout brevi sull'autonomia del suo UPS. +
+
+ +
+

5 Sorgenti UPS

+

Ogni voce UPS nella procedura guidata ha un selettore “Lettura via”. Le due + sorgenti si possono mescolare liberamente in una stessa istanza, e tutto ciò che viene + dopo — soglie, mappatura host, regole fail-safe — è identico per entrambe.

+ +

SNMP (UPS con scheda di rete)

+

Nell'interfaccia web della scheda di rete dell'UPS (di solito in Network → SNMPv1 + o SNMPv3), abilita l'accesso e concedi all'IP dell'appliance l'accesso in + lettura. Nulla viene mai scritto. Porta di default 161/UDP.

+ +

Quale MIB viene letto

+

Ogni UPS SNMP ha un'impostazione MIB. Decide quale insieme di oggetti viene + interrogato — i valori stessi, le soglie e tutto ciò che sta a valle sono gli stessi + in ogni caso.

+
    +
  • Automatico (default, e la risposta giusta per quasi ogni dispositivo): viene + letto lo standard RFC 1628, e l'UPS passa a un MIB del produttore appena + risponde su quello. Il rilevamento non costa query extra, e un aggiornamento firmware + che aggiunge RFC 1628 in seguito viene recepito da solo.
  • +
  • RFC 1628 (standard): il MIB UPS indipendente dal produttore, nient'altro.
  • +
  • APC PowerNet: il MIB delle schede di gestione di rete APC/Schneider.
  • +
  • CyberPower: il CPS-MIB delle schede di rete CyberPower (famiglia RMCARD). + Viene letto un valore per richiesta — vedi sotto.
  • +
+

Un MIB del produttore conta soprattutto per APC. Schneider supporta RFC 1628 solo sulle + Network Management Card 2 (AP9630/AP9631/AP9635) dal firmware + sumx/sy v5.1.7 in poi; le vecchie schede NMC1 + (AP9617/AP9618/AP9619) parlano solo PowerNet e non sono leggibili attraverso lo + standard. Sulle schede che supportano entrambi, PowerNet resta comunque la sorgente + migliore: riporta l'autonomia residua in centesimi di secondo anziché in minuti interi, e + riporta un self-test come stato a sé — RFC 1628 non sa distinguere un + self-test da un blackout vero.

+

Le schede CyberPower per lo più implementano anch'esse RFC 1628, ma non tutte, + e alcuni firmware non rispondono a una richiesta di più valori insieme — non con + un errore, la richiesta va semplicemente in timeout — mentre ogni valore risponde se + interrogato da solo. Per questo il MIB CyberPower viene sempre letto un valore per + richiesta: qualche centinaio di millisecondi per polling su una rete locale, e una + scheda che smette di rispondere costa comunque un solo timeout, come prima.

+

Imposta il MIB esplicitamente solo per forzarne uno, ad esempio per fissare un + dispositivo sullo standard mentre confronti le letture — o quando “Test UPS” riporta + che il polling regolare non ha avuto risposta anche se i valori rispondono uno alla + volta. Allora scegli CyberPower: Automatico parte sempre con una + richiesta di più valori, a cui una scheda del genere non risponde mai.

+ +

Server NUT (UPS USB e seriali)

+

Un UPS senza scheda di rete si raggiunge tramite + Network UPS + Tools: la macchina a cui l'UPS è collegato esegue upsd, e + PVE-UPS legge da esso su TCP 3493. Molto spesso un server del genere esiste già — + i NAS Synology e QNAP hanno una casella “server UPS”, così come TrueNAS e + OPNsense; altrimenti va bene un Raspberry Pi o un host Proxmox con il pacchetto + nut-server.

+

Servono tre campi: l'host che esegue upsd, il nome UPS come + configurato nel suo ups.conf (upsc -l <host> elenca i + nomi disponibili), e opzionalmente nome utente e password — un upsd standard + permette di leggere le variabili senza né l'uno né l'altra.

+

NAS come server NUT (QNAP, Synology)

+

Entrambi i produttori forniscono una configurazione propria e fissa — i valori non + si possono scegliere liberamente:

+ + + + + + +
NASNome UPSUtentePassword
QNAPqnapupsadmin123456
Synologyupsupsnessuna
+

Su entrambi il server UPS di rete va abilitato e l'indirizzo IP dell'appliance + deve essere nella lista dei dispositivi consentiti — altrimenti il NAS + semplicemente non risponde, che è di gran lunga la causa più comune di un test + fallito. L'impostazione sta nella configurazione UPS (QNAP: pannello di controllo, + dispositivo esterno; Synology: pannello di controllo, hardware e alimentazione); la + dicitura esatta varia tra le versioni di QTS e DSM.

+
+ PVE-UPS è un client NUT strettamente in sola lettura: chiede solo la + lista delle variabili, non invia mai comandi e non esegue mai upsmon. NUT + fa da driver del dispositivo; le soglie, la policy degli host e la decisione di + spegnimento restano in questa appliance. Non c'è nulla da configurare sugli host + protetti. +
+
+ Il protocollo NUT non è cifrato. Usalo dentro una rete fidata. Se upsd + gira su una macchina che PVE-UPS stesso spegne, assicurati che quell'host sia quello + marcato “Questo host” o abbia l'ordine di spegnimento più alto — altrimenti la + sorgente dati sparisce a metà blackout. Il conto alla rovescia a batteria in quel + caso continua comunque (vedi sezione 7), ma una sorgente ancora raggiungibile è meglio. +
+ +

Leggere il risultato del test

+

Sotto la riga di risultato, “Test UPS” offre Dettagli per oggetto — ogni + oggetto interrogato singolarmente, con il suo valore o il motivo per cui manca. Si apre + da solo ogni volta che qualcosa non va, e separa i casi che altrimenti + sembrano identici:

+
    +
  • Nulla risponde (ogni riga un errore): indirizzo, porta o credenziali + sbagliati, o un firewall blocca UDP 161 (SNMP) / TCP 3493 (NUT). Per NUT + controlla anche che la direttiva LISTEN di upsd copra questo host — di + default ascolta solo su localhost.
  • +
  • Alcuni oggetti rispondono, altri dicono noSuchObject / + noSuchInstance (SNMP) o “not provided by this driver” + (NUT): il dispositivo semplicemente non pubblica quei valori. Quando riguarda un + trigger, il test lo dice a parole sopra la tabella — ad esempio un driver + senza battery.runtime rende la soglia “Autonomia sotto (min)” + permanentemente inefficace. Usa invece il timer “A batteria per più di (s)”: gira + sull'orologio dell'appliance e funziona sempre.
  • +
  • noSuchName sotto SNMPv1: l'oggetto manca e + v1 interrompe una query multi-oggetto appena un oggetto è assente — il polling + regolare fallisce allora completamente anche se gli altri valori sono leggibili. + Passa l'UPS a v2c se lo supporta.
  • +
  • Il polling regolare non ha risposta, ma ogni oggetto risponde uno alla + volta (SNMP): la scheda non gestisce una richiesta di più valori insieme — alcuni + firmware CyberPower la lasciano semplicemente scadere. Il riepilogo lo dice; imposta il + MIB su CyberPower, che viene letto un valore per richiesta.
  • +
  • Ogni oggetto RFC 1628 mancante, ogni oggetto del produttore che + risponde: normale su una scheda APC senza supporto RFC 1628. In modalità + Automatico entrambi i MIB sono elencati, quello effettivamente in uso viene + per primo, e la riga sopra la tabella lo nomina. Nulla + da cambiare — gli oggetti contati e gli avvisi sui trigger si riferiscono sempre al + MIB in uso, non a quello perdente.
  • +
  • “stale data” (NUT): upsd risponde, ma il suo driver ha perso il contatto + con l'UPS. I valori che ancora fornisce sono vecchi, quindi PVE-UPS tratta l'UPS + come irraggiungibile — un allarme, mai uno spegnimento. Riavvia il driver NUT su + quella macchina.
  • +
  • L'oggetto della sorgente di alimentazione manca o riporta “unknown”: il + dispositivo risponde e altri valori arrivano, ma l'unico oggetto che dice se gira su + rete o a batteria (upsOutputSource, su APC + upsBasicOutputStatus) no. Da ciò non si può concludere nulla, + quindi PVE-UPS tratta l'UPS come irraggiungibile — un allarme, mai uno spegnimento, e un + conto alla rovescia a batteria in corso continua. Controlla l'impostazione MIB; se il + dispositivo davvero non lo implementa, non può essere usato come sorgente di + spegnimento.
  • +
  • ups.status non nomina né OL né OB + (NUT): lo stesso caso dal lato NUT. La variabile c'è, ma porta solo + qualcosa come CHRG, ALARM o RB — o è + vuota — e nulla di tutto ciò dice da dove arriva l'alimentazione. Trattato esattamente + come sopra: irraggiungibile, un allarme, mai uno spegnimento, e un conto alla rovescia in + corso continua. Di solito un quirk del driver; controlla il driver sul server NUT.
  • +
+
+ Tutti questi dispositivi hanno risposto. PVE-UPS lo tiene volutamente + separato da un dispositivo diventato silenzioso, perché “Spegnimento per pura + perdita di comunicazione” (vedi Trigger) scatta solo sul + silenzio. Un UPS che risponde in modo inutilizzabile non lo arma mai — e nemmeno una + voce mai interrogabile fin dall'inizio, come una scheda UPS salvata senza indirizzo. + L'allarme dice quale dei due casi è, così non ti manda a cercare in una rete che + funziona. +
+
+ +
+

6 Come funziona e logica

+

L'engine interroga tutti gli UPS in parallelo e valuta ogni UPS + indipendentemente. Ogni polling porta la propria scadenza, derivata dalle + impostazioni di timeout e tentativi di quel dispositivo, quindi un server che accetta + una connessione e poi tace non può bloccare il conto alla rovescia degli altri. Per UPS + c'è una semplice sequenza di stati:

+

+ ONLINE su rete  →  + ON_BATTERY blackout, UPS a batteria  →  + attivato appena una soglia scatta +

+

Quale host si spegne?

+

Lo decide la mappatura di ogni host verso i suoi UPS e la sua logica:

+
    +
  • AND (ups_policy = all, default): l'host si spegne solo + quando tutti gli UPS assegnati hanno attivato il trigger — esattamente il + comportamento per server con alimentatori ridondanti (un UPS per + alimentazione). Finché un UPS continua a erogare corrente, l'host resta acceso.
  • +
  • OR (ups_policy = any): l'host si spegne appena + uno degli UPS assegnati attiva il trigger — per carico separato, non + ridondante.
  • +
+

Più host dovuti vengono spenti a stadi, seguendo il campo “Ordine” — + crescente, quindi 0 parte per primo: tutto ciò che condivide un numero riceve il + comando contemporaneamente, e lo stadio successivo parte solo quando quello è + finito. L'host su cui gira l'appliance stessa (this_host) forma sempre + l'ultimo stadio indipendentemente dal numero, così può ancora spegnere gli + altri. Suggerimento: collega l'host dell'appliance a tutti gli UPS così diventa + dovuto per ultimo in modo affidabile. La procedura guidata mostra la sequenza risultante + in tempo reale sotto la lista host.

+

Ogni comando porta anche una scadenza rigida (“Timeout spegnimento host”). + Insieme significano che una macchina che smette di rispondere non può bloccare i suoi + compagni di stadio, gli stadi successivi, o il conto alla rovescia della batteria. Usa + il campo ordine quando un target deve davvero spegnersi prima di un altro — altrimenti + lasciarli uguali è sia più rapido sia più sicuro.

+

Se un UPS richiesto si riprende (la rete torna), uno spegnimento non ancora + eseguito viene annullato e lo stato si reimposta.

+

Uno spegnimento che è stato inviato resta bloccato — una macchina in fase di + spegnimento non va comandata due volte. Quel blocco viene rilasciato solo quando + ogni UPS è stato raggiungibile e su rete per il tempo impostato in “Riarmo dopo + ritorno rete (min)” (default 5, vuoto = mai). Solo allora l'appliance è pronta per il + blackout successivo: mentre è bloccata, un secondo blackout troverebbe ogni host già + marcato come attivato, e anche l'auto-test pianificato e “Ripristina cluster” stanno + fermi. Il tempo di attesa esiste perché la rete che torna non è la rete che resta — una + rete che cade due volte in un minuto non deve riarmare in mezzo. Un UPS + irraggiungibile non conta mai come “rete tornata”. Il ripristino di un cluster + preparato resta comunque manuale; il riarmo rende solo di nuovo disponibile quel + pulsante, e l'evento lo dice quando un cluster porta ancora la preparazione. Il + pulsante “Reimposta stato” fa la stessa cosa immediatamente, in qualsiasi momento.

+ +

Cluster Proxmox VE Beta

+
+ La preparazione del cluster richiede Proxmox VE 9.2 o più recente. Tutto + in questa sezione e nella successiva dipende da disarm-ha, arrivato con + la 9.2. Su una release più vecchia HA non viene disarmato, e dove HA gestisce i guest + non vengono nemmeno arrestati a livello cluster; i flag di manutenzione Ceph vengono + comunque impostati. PVE-UPS lo rileva dall'API stessa, mai da un numero di versione, + quindi funziona anche con un backport. +
+
+ Novità della 4.0.0, e marcata Beta di proposito. Il meccanismo stesso è + conservativo: ogni passo è verificato anziché presunto, la sequenza gira + sotto timeout rigidi, e i default sono quelli sicuri. Ciò che finora ha visto poca + esposizione è la pura varietà dei cluster reali. Nulla qui accade se non + lo spunti. Se lo usi, segnalazioni su ciò che trovi sono benvenute via + issue GitHub. +
+

Questa sezione copre un cluster Proxmox VE semplice. Se il cluster esegue + anche Ceph sui propri nodi, leggi poi + Cluster iperconvergenti (Ceph) — quella parte è un + intervento considerevolmente più grande, con privilegi propri.

+

Spunta “Membro del cluster” nel riquadro Cluster Proxmox VE della scheda host — + su ogni nodo del cluster. I membri sono raggruppati automaticamente dal nome + del cluster letto dall'API, quindi non serve un secondo token né una sezione di + configurazione extra, e la preparazione gira esattamente una volta per cluster, prima + che il primo nodo si spenga. Ciò che la spunta decide è chi conta come + membro: un nodo senza viene preparato attorno anziché dentro, non viene mai + trascinato da “spegni l'intero cluster come un'unità”, e i suoi interruttori non hanno + voce su ciò che fa il cluster. + Da ciò che invece non esime il nodo è la preparazione stessa, che per natura è + a livello cluster: HA viene disarmato per l'intero cluster e, con l'arresto guest + o Ceph, ogni guest al suo interno viene arrestato — inclusi quelli che girano lì. + Lascia il riquadro non spuntato su un nodo di un cluster e quella macchina resta + accesa senza HA e senza i suoi guest. L'auto-test pianificato nomina un nodo del + genere, così la discrepanza viene scoperta prima di un blackout anziché durante.

+

Gli interruttori di questo riquadro agiscono tutti sull'intero cluster — flag + di manutenzione Ceph, stato di arm dell'HA manager, l'arresto guest, e se il cluster si + spegne come un'unità — anche se si modificano per scheda host. Quindi imposta + ciascuno allo stesso modo su ogni nodo di uno stesso cluster. Dove non concordano, + PVE-UPS vale “almeno un nodo l'ha chiesto” (saltare un passo richiesto può costare + lo storage; eseguirne uno che nessuno ha chiesto costa un flag di manutenzione) e scrive + un avviso nominando entrambe le parti, così la discrepanza emerge al prossimo + auto-test anziché durante un blackout.

+

Ciò che fa su un cluster semplice è l'unica cosa che uno spegnimento per nodo non + può fare:

+
    +
  • Disarmo HA — senza, l'HA manager ripristina servizi su nodi che + si stanno spegnendo a loro volta. PVE-UPS disarma con + resource-mode=ignore; i guest poi escono dal tracciamento HA e + pve-guests li spegne nel loro ordine configurato. Questo + non è configurabile, e volutamente: sotto freeze i guest + restano gestiti da HA, pve-guests li salta e l'LRM disarmato non li + ferma più — verrebbero uccisi dallo spegnimento fisico.
  • +
+

Il passo è verificato, non presunto: il disarmo viene interrogato finché lo + stato HA riporta davvero disarmed. Gira sotto un timeout rigido, perché + avviene mentre la batteria si scarica.

+

Quel timeout (“Timeout disarmo HA / flag Ceph”, in Trigger) è il tempo + che il disarmo effettivamente riceve, non solo un freno d'emergenza esterno: il + budget viene passato di passo in passo, quindi alzarlo compra davvero più pazienza. + Il default è 60 s. Il disarmo è la parte lenta, perché lo stack HA lavora a + giri di dieci secondi e ogni nodo deve rilasciare il suo watchdog prima che lo stato + passi da disarming a disarmed. Sotto circa mezzo minuto di + solito non può essere confermato. Ogni secondo qui è uno che lo spegnimento non ha, + quindi alzalo consapevolmente — i nodi di quel cluster lo aspettano. Se il budget + finisce, l'evento nomina lo stato a cui si è fermato (“still disarming” significa dagli + più tempo; uno stack mai mosso è un problema diverso) e ciò che era già stato fatto. + Un cluster già disarmato — un secondo blackout prima di averlo ripristinato — + viene lasciato stare anziché disarmato di nuovo. In modalità dry-run viene solo + registrato; nulla viene cambiato.

+

La preparazione dice quando parte, non solo come è finita: un evento nomina + cosa sta per fare, quanti nodi del cluster hanno attivato il trigger, e quanto a lungo + i nodi possono aspettarla. Tra quella riga e il risultato, lo spegnimento dei nodi + di quel cluster sta aspettando di proposito.

+

Se la preparazione fallisce, lo spegnimento continua di default. È + l'impostazione più sicura: un disarmo fallito lascia l'HA manager armato, e un HA + manager armato ferma i guest da solo — più rumoroso, ma le macchine si spengono + comunque. L'opposto (“Annulla lo spegnimento se la preparazione del cluster fallisce”, + in Trigger) significa che una preparazione fallita lascia l'intero cluster + acceso finché la batteria è vuota.

+ +

Arrestare prima tutti i guest, senza Ceph (opzionale)

+

Lasciati a sé, i guest di un cluster semplice vengono fermati dai loro nodi man + mano che ciascuno si spegne — in ordine startup inverso, gruppo per gruppo, + con il ritardo di ogni gruppo. Con molti gruppi ordinati può durare più della batteria. + “Arresta prima tutti i guest a livello cluster” (disattivato di default) lo + sostituisce con il passo che usa la procedura Ceph: prima che il primo nodo si spenga, + a ogni VM e container del cluster tranne questa appliance viene chiesto di spegnersi — + tutti insieme, in parallelo — e il risultato viene verificato rileggendo la lista dei + guest.

+
    +
  • L'ordine di avvio viene ignorato, di proposito: il punto è finire prima + della batteria. Una dipendenza tra un'applicazione e il suo database non viene + rispettata.
  • +
  • Ha il suo budget proprio, “Timeout spegnimento guest”, e i guest ancora in + esecuzione dopo “Arresto forzato guest dopo” vengono fermati forzatamente — vedi + lo spegnimento dei guest sotto; quei dettagli valgono + invariati.
  • +
  • Dove HA gestisce i guest, HA deve essere disarmato prima (Proxmox VE 9.2+ + e l'interruttore HA), o l'HA manager li riavvierebbe alla stessa velocità con cui si + fermano. Se non può esserlo, il passo viene saltato con un avviso e i nodi fermano i + propri guest come prima.
  • +
  • Il guest proprio di questa appliance deve essere selezionato — vedi + il guest proprio dell'appliance. Finché non lo è, + nessun guest viene arrestato.
  • +
  • Richiede VM.Audit e VM.PowerMgmt (tabella sotto).
  • +
+

Con l'opzione Ceph l'arresto guest è sempre parte della procedura — la scheda + host lo mostra allora spuntato e bloccato, e togliere la spunta a Ceph ripristina + quello che il riquadro diceva prima.

+ +

Il cluster si spegne come un'unità

+

La preparazione è a livello cluster per natura — disarma HA per l'intero cluster, + e con l'arresto guest (o l'opzione Ceph) ferma ogni guest al suo interno. Lo + spegnimento stesso, però, è per host: un nodo è dovuto quando i suoi UPS lo + dicono. Le due cose non devono per forza concordare, e quando non lo fanno il risultato + è un cluster lasciato a metà.

+

È a questo che serve “Spegni l'intero cluster come un'unità” (attivo di + default): appena un nodo del cluster è dovuto, ogni suo nodo viene spento — inclusi + quelli il cui UPS non ha attivato il trigger. Vanno nel solito ordine, con + l'host dell'appliance per ultimo, e il registro eventi nomina perché ciascuno è stato + trascinato.

+

Disattivalo solo per un cluster semplice dove preferisci che HA sposti i guest + di un singolo nodo guasto sugli altri; lì uno spegnimento parziale è un buon risultato. + Con l'arresto guest dovrebbe restare attivo, o i nodi che restano accesi hanno perso i + loro guest per nulla; con Ceph deve — vedi + la sezione successiva per come appare davvero un cluster + iperconvergente spento a metà.

+
+ Un UPS che tace non attiva nulla. Se la rete di management + di un UPS muore con il blackout — una configurazione molto comune, dove lo switch è + appeso all'UPS stesso che fallisce — PVE-UPS vede un dispositivo irraggiungibile, non + un'interruzione di corrente, e il fail-safe rifiuta deliberatamente di spegnere + qualcosa per esso (“NO shutdown will be triggered”). I nodi alimentati solo da + quell'UPS restano quindi accesi. Due rimedi, e si combinano: lascia attivo + “spegni l'intero cluster come un'unità”, così quei nodi si spengono comunque + con gli altri, e imposta “Spegnimento per pura perdita di comunicazione dopo + (min)” in Trigger se vuoi che un UPS silenzioso conti come blackout da + solo. L'auto-test avvisa quando i nodi di uno stesso cluster dipendono da UPS che + possono attivarsi indipendentemente. +
+

Ogni nodo ha bisogno della propria voce host, con il proprio URL API. Lo + spegnimento raggiunge il nodo dietro l'URL API di ogni voce (vedi + sezione 4.1), quindi un nodo senza voce è un nodo che nessuno + spegne, qualsiasi cosa dicano i nomi — ed è anche un nodo che la regola “come + un'unità” non può trascinare. L'auto-test confronta quindi i nomi dei nodi del + cluster con le tue voci host e avvisa di ogni nodo che nessuno spegnerebbe, nominando + entrambe le liste. Dove la differenza è solo maiuscole/minuscole o un suffisso di + dominio, nomina il nodo probabilmente inteso; una voce semplicemente disattivata viene + riportata come disabilitata anziché come mal nominata.

+ +

Privilegi per un cluster semplice

+

Concedi solo ciò che hai attivato: ogni spunta porta il proprio privilegio, e una + spunta lasciata spenta non ti costa nulla. La tabella si legge cumulativamente, dalla + configurazione base della sezione 4 in giù. La parte Ceph ha + una tabella propria.

+ + + + + + + + +
Cosa è spuntatoServe in piùPercorso ACL
nulla (spegnimento semplice)Sys.PowerMgmt/nodes
Membro del clusterSys.Audit/
+ Disarma l'HA managerSys.Console/
+ Arresta prima tutti i guest a livello clusterVM.Audit, VM.PowerMgmt/ (conta anche su /vms)
+

Scegli la riga che corrisponde alle tue spunte ed eseguila una volta per + datacenter, nella shell di un qualsiasi nodo. Sys.PowerMgmt non compare + qui di proposito — è già su /nodes dalla configurazione base, e non c'è + motivo di allargarlo all'intero datacenter:

+
# Membro del cluster, senza sotto-opzioni — controlli di stato in sola lettura:
+pveum role add UpsCluster -privs "Sys.Audit"
+
+# ... più disarmo HA (concede accesso shell, vedi l'avviso sotto):
+pveum role add UpsCluster -privs "Sys.Audit Sys.Console"
+
+# ... più l'arresto guest a livello cluster:
+pveum role add UpsCluster -privs "Sys.Audit Sys.Console VM.Audit VM.PowerMgmt"
+

Poi collega il ruolo — entrambe le righe, vedi la nota sulla + separazione dei privilegi sotto:

+
pveum acl modify / -user ups@pve -role UpsCluster
+pveum acl modify / -token 'ups@pve!shutdown' -role UpsCluster
+

L'auto-test segue la stessa regola: chiede un privilegio solo dove la + funzionalità è attiva e questo cluster può effettivamente farla. A una release + senza disarm-ha non viene mai chiesto Sys.Console — quindi un + avviso su un privilegio mancante significa sempre qualcosa di cui hai davvero + bisogno.

+

Anche il pulsante “Test” sulla scheda host verifica questi privilegi, + appena l'opzione cluster è spuntata: nomina ognuno di quelli mancanti, riporta il + cluster trovato (nome, numero di nodi, stato Ceph e arm di HA), e lo dice se il nodo + risulta non essere affatto membro di un cluster. Un privilegio cluster mancante è + riportato come avviso, non come errore — la connessione stessa funziona comunque. + Ogni nome viene con ciò che compra (“Sys.Console (disarmo HA)”), così la + decisione se concederlo non richiede questa pagina aperta. Sotto, un pannello + dettagli elenca ogni query con il suo esito (si apre da solo quando qualcosa non + va), così si vede cosa ogni endpoint ha effettivamente risposto anziché solo la riga + di riepilogo. Una query che serve solo a un'opzione non spuntata — la lista guest + senza l'arresto guest, ad esempio — appare come “not readable (not needed)” e + lascia il pannello chiuso.

+
+ Sys.Console concede di fatto accesso shell ai nodi. + Per questo il disarmo HA è un interruttore a sé: se non vuoi concederlo, + lascia l'interruttore spento e usa la riga Sys.Audit sopra. + Nota anche che i token API hanno la separazione dei privilegi attiva di + default e hanno bisogno di una voce ACL propria — i diritti dell'utente non + bastano, ed è per questo che i due comandi acl modify sopra sono + entrambi necessari. +
+
+ Uno stack HA disarmato viene riportato anche quando nessun guest è + gestito da HA. Le due cose sono indipendenti: il disarmo ferma il fencing + a livello cluster, e resta così finché qualcuno lo riarma — quindi sia + l'auto-test sia “Ripristina cluster” si basano sullo stato di arm, non sul fatto + che HA gestisca attualmente qualcosa. +
+
+ Su Proxmox VE più vecchio della 9.2 non c'è disarm-ha. + Imposta invece l'opzione datacenter shutdown_policy=freeze; il default + conditional ripristina i servizi HA su nodi che si stanno spegnendo, + e migrate è pure peggio, perché l'LRM allora ritarda lo + spegnimento finché tutto non è migrato via. L'auto-test avvisa di entrambi. +
+

Dopo, il cluster resta preparato. Non c'è riarmo automatico: + riportare su HA mentre i nodi stanno ancora facendo boot è una decisione da valutare. + L'auto-test continua ad avvisare che HA è disarmato (e che i flag sono impostati, + dove lo erano), e la dashboard mostra un pulsante “Ripristina cluster” — riarma + HA e rimuove i flag, ciascuno verificato, e compare solo finché c'è effettivamente + qualcosa da annullare. Dato che l'appliance si spegne per ultima e torna con gli + altri, ispeziona i cluster una volta all'avvio, così i residui sono subito + sulla dashboard anziché al prossimo auto-test pianificato. Il pulsante è nascosto — e + l'API rifiuta la richiesta — mentre uno spegnimento è in corso: armare HA lì + annullerebbe la preparazione nell'unico momento in cui sta facendo il suo lavoro.

+

Su un cluster semplice, i singoli guest non vengono mai toccati da + PVE-UPS: a uno spegnimento vengono comunque fermati nel loro ordine configurato — + dall'LRM mentre HA è armato, e da pve-guests una volta disarmato — e il + problema che il disarmo HA risolve è puramente il ripristino su altri nodi + dopo. Solo l'arresto guest cambia questo — spuntato da solo, o come parte + dell'opzione Ceph.

+ +

Cluster iperconvergenti (Ceph) Beta

+
+ Questo è un intervento considerevolmente più grande della sezione + sopra. Con “Prepara il cluster Ceph” spuntato, PVE-UPS arresta ogni VM e + container del cluster prima che il primo nodo si spenga — non viene solo + riorganizzato HA. Servono anche altri tre privilegi, il guest proprio di questa + appliance va selezionato perché venga risparmiato, e quel guest non deve risiedere + su storage Ceph. Leggi questa sezione prima di attivarlo. +
+

Vale tutto ciò che dice la sezione precedente; questo le + aggiunge due passi. L'ordine è quello in cui girano, ed è portante — gli + interruttori sulla scheda host sono elencati nello stesso ordine esattamente per + questo motivo:

+
    +
  1. Disarmo HA — deve venire per primo, o l'HA manager riavvia ogni guest + che il passo 2 ferma.
  2. +
  3. Tutti i guest vengono arrestati, a livello cluster — ogni VM e container + tranne questa appliance stessa.
  4. +
  5. Flag di manutenzione Ceph — vengono impostati noout, + nobackfill, norecover e norebalance così Ceph + non inizia il healing mentre gli OSD spariscono uno a uno. Equivalente a + ceph osd set noout e simili.
  6. +
+

I passi 2 e 3 sono un'unica opzione, ed è disattivata di default: molti + cluster girano su replicazione ZFS o NFS/iSCSI condiviso, e questa è l'unica parte che + scrive in un layer di storage. Su un cluster senza Ceph viene saltata anziché tentata + e riportata come fallimento, e i suoi privilegi non sono richiesti. Il passo 2 esiste + anche da solo, per cluster senza Ceph (vedi sopra), + ma con Ceph non si può disattivare: lì non è un'opzione ma il primo passo della + procedura ufficiale, e una spunta la cui assenza blocca il cluster durante un blackout + sarebbe una trappola. La scheda host lo mostra spuntato e bloccato mentre Ceph è + attivo.

+

I flag sono verificati, non presunti: l'aggiornamento bulk è asincrono + (restituisce solo un worker id), quindi vengono riletti — con ripiego + sull'impostarli singolarmente.

+
+ Lascia attivo “spegni l'intero cluster come un'unità” qui. Un cluster + iperconvergente spento in parte non ha uno stato sensato. Un esempio reale da un + cluster a quattro nodi: un solo UPS ha attivato il trigger, quindi due nodi si sono + spenti mentre gli altri due restavano accesi. La preparazione aveva già fermato ogni + guest e disarmato HA a livello cluster, e due dei tre monitor Ceph erano sui nodi + uscenti — quindi i sopravvissuti sono rimasti lì senza guest, senza HA e senza + quorum di storage. Spegnere del tutto o per nulla un cluster iperconvergente è una + scelta reale; spegnerne metà no. +
+ +

Perché i guest devono fermarsi prima

+

Questa è la parte in cui uno spegnimento per nodo sbaglia. Lasciato a sé, ogni + nodo ferma i propri guest mentre si spegne — che suona ordinato e poi si blocca. Con + il solito size=3/min_size=2, il pool sta bene dopo la partenza del primo + nodo, ma una volta spariti gli OSD del secondo scende sotto min_size. I + guest ancora in esecuzione sul nodo sopravvissuto ora si bloccano sulla IO: il loro + spegnimento non finisce mai, quindi quel nodo non si spegne mai, e la batteria si + esaurisce con il cluster spento a metà.

+

Proxmox documenta lo stesso ordine (“Shutdown Proxmox VE + Ceph HCI cluster”): + ferma prima tutti i client Ceph, poi imposta i flag OSD, poi spegni i nodi. + PVE-UPS lo segue. A ogni guest in esecuzione viene chiesto di spegnersi insieme — fino a + otto richieste in volo, perché chiederlo a quaranta guest uno dopo l'altro spenderebbe + un connect timeout su ciascuno — e il risultato è verificato rileggendo la + lista guest del cluster, mai dedotto dalla risposta dell'API.

+

Due timeout in Trigger governano questo, e sono separati di + proposito:

+
    +
  • “Timeout spegnimento guest” (default 300 s) — il budget per + fermare i guest. Scala con quanti ne hai e quanto ci mettono; + il budget HA/Ceph accanto è misurato in giri HA di dieci secondi e varia + a malapena. Un unico numero non potrebbe servire entrambi, e fonderli farebbe + mangiare ai guest il tempo del disarmo.
  • +
  • “Arresto forzato guest dopo” (default 120 s) — quando un guest + che ignora la richiesta viene fermato forzatamente. Il valore viene passato anche a + Proxmox come timeout proprio della chiamata di spegnimento, quindi l'arresto avviene + comunque se questa appliance perde la rete subito dopo aver chiesto. Lascialo vuoto + per non forzare mai: un guest bloccato fa allora fallire la preparazione e viene + nominato nel registro eventi.
  • +
+

Guarda il totale. Tra lo scatto del trigger e l'ultimo nodo a cui viene detto di + spegnersi, il caso peggiore è il budget HA/Ceph più il budget guest più il timeout + di spegnimento per nodo — con i default, 420 s. La pagina impostazioni mostra + quel numero, e l'auto-test avvisa quando il trigger “autonomia stimata sotto” scatta + più tardi di quanto la sequenza richieda. Avvisa soltanto; non corregge mai un + trigger al posto tuo.

+

Ordine dei nodi: Proxmox consiglia di spegnere prima i nodi senza + monitor Ceph. PVE-UPS legge il monmap e ti dice quando i tuoi valori “ordine” lo + contraddicono, nel test host e nell'auto-test — ma non li riordina mai da solo. + Il tuo ordine è configurazione esplicita, e sovrascriverlo silenziosamente al momento + del blackout renderebbe bugiarda la sequenza mostrata sulla dashboard.

+

Non gestito: i guest vengono fermati in parallelo, non nel loro + ordine startup, quindi una dipendenza tra un'applicazione e il suo + database non viene rispettata. L'obiettivo qui è fermare la IO rapidamente, e la + batteria è il motivo.

+ +

Il guest proprio di questa appliance

+

“Arresta tutti i guest” non deve includere l'appliance che arresta. In + Trigger → Questa appliance, scegli dalla lista la VM o il container in + cui gira PVE-UPS. È una lista e non un campo di testo di proposito: un id sbagliato + significa che questa appliance spegne sé stessa nel mezzo di un blackout. Se + gira davvero altrove — Docker su un NAS, bare metal, un altro cluster — scegli “non è + un guest di questo cluster”.

+

La scelta fa due cose. Marca l'unico guest che non viene mai arrestato, e + imposta “Questo host” sulla scheda host del nodo su cui quel guest gira, così + quel nodo si spegne per ultimo. Quella spunta appare allora come presa dalla selezione + anziché mantenuta due volte; senza una scelta resta un interruttore manuale, come su + un host standalone. Se l'appliance viene poi migrata su un altro nodo, l'auto-test lo + dice.

+

Finché nulla è selezionato e “non è un guest di questo cluster” non è impostato, + i guest non vengono arrestati affatto. Il registro eventi lo dice ad alta voce e + lo spegnimento continua senza quel passo — che è esattamente ciò che facevano le + release precedenti. Rifiutare è l'unica risposta sicura qui: l'alternativa è + indovinare, e un'ipotesi sbagliata spegne la macchina che sta eseguendo lo + spegnimento.

+
+ Questo container non deve risiedere su storage Ceph. È l'unico guest + che deve sopravvivere al cluster che sta spegnendo. Su Ceph non ci riesce: una volta + che gli OSD dei nodi uscenti portano il pool sotto min_size, il suo stesso + disco smette di rispondere e non può più spegnere nulla — a metà del + blackout. Usa local-lvm, local-zfs o uno storage directory. + install.sh lo impone: gli storage su Ceph vengono saltati quando ne + viene scelto uno automaticamente e rifiutati quando nominati con + --storage, a meno che non passi --allow-ceph-storage. A + runtime il test host, l'auto-test e la dashboard lo dicono tutti se l'appliance è + comunque su Ceph. +
+ +

Privilegi per la parte Ceph

+

In aggiunta ai privilegi del cluster semplice:

+ + + + + + +
Cosa è spuntatoServe in piùPercorso ACL
Prepara il cluster CephSys.Modify, VM.Audit, VM.PowerMgmt/ (quelli VM contano anche su /vms)
opzionale, per una diagnosi miglioreDatastore.Audit/ o /storage
+
# Parte Ceph senza disarmo HA:
+pveum role add UpsCluster -privs "Sys.Audit Sys.Modify VM.Audit VM.PowerMgmt"
+
+# Tutto, incluso il controllo storage opzionale:
+pveum role add UpsCluster -privs "Sys.Audit Sys.Console Sys.Modify VM.Audit VM.PowerMgmt Datastore.Audit"
+

Collega il ruolo con gli stessi due comandi acl modify di sopra. A un + cluster senza Ceph non viene mai chiesto Sys.Modify, e + VM.Audit o VM.PowerMgmt solo con l'arresto guest + spuntato. Datastore.Audit è + l'eccezione che conferma la regola: compra solo il controllo se questa appliance + sta su Ceph, quindi la sua assenza viene menzionata discretamente e mai riportata come + privilegio mancante.

+
+ Perché VM.Audit conta più di quanto sembri. + /cluster/resources filtra per privilegio invece di rifiutare: un token + senza VM.Audit riceve una risposta perfettamente riuscita con una lista + guest vuota. PVE-UPS quindi non legge mai “vuota” come “nessun guest da + fermare” a meno che il token non dimostri di avere il privilegio — altrimenti + riporterebbe una preparazione riuscita mentre quaranta guest continuano a scrivere + su Ceph. +
+

“Ripristina cluster” non riavvia i guest — tornano + tramite la loro impostazione onboot quando i nodi si accendono.

+
+ +
+

7 Fail-safe e casi limite

+

Principio guida: una connessione SNMP persa non è un blackout confermato — + l'UPS può essere solo momentaneamente non reattivo, o uno switch in mezzo è giù. Di + default una perdita di comunicazione genera quindi solo un allarme, nessuno + spegnimento. Due impostazioni opzionali lo raffinano:

+
    +
  • Continua lo spegnimento alla perdita (default attivo): se il contatto + con l'UPS si perde dopo che il funzionamento a batteria era già confermato, + un conto alla rovescia a batteria avviato continua sull'orologio locale e scatta alla + scadenza. Solo questo trigger basato sul tempo sopravvive allo stato “cieco” + (autonomia e carica sono allora illeggibili). Se un trigger (es. batteria scarica o + la soglia di carica) era già scattato prima che la connessione cadesse, resta + bloccato e non viene degradato al conto alla rovescia. Sia il conto alla rovescia + sia lo stato attivato sopravvivono anche a un riavvio del servizio: sono + persistiti su disco e ripristinati all'avvio (fino a 24 h di età).
  • +
  • Spegnimento dopo X minuti di pura perdita (default spento): tratta una + pura perdita di comunicazione (senza funzionamento a batteria confermato in + precedenza) come blackout dopo X minuti. Un opt-in deliberato per ambienti dove una + perdita di contatto prolungata conta come critica. Scatta solo sul silenzio: + un dispositivo che risponde ma la cui risposta non è utilizzabile — nessuna + sorgente di alimentazione utilizzabile, nessuno degli oggetti del MIB selezionato, + un ups.status che non nomina né OL né OB, o + un server NUT che risponde ERR DATA-STALE, + DRIVER-NOT-CONNECTED, ACCESS-DENIED o + UNKNOWN-UPS — viene trattato come irraggiungibile per ogni altro + scopo, ma mai come perdita di comunicazione. La + connessione c'è chiaramente, quindi questo trigger ne resta fuori; l'allarme dice + quale dei due casi è. Lo stesso vale per una voce non interrogabile affatto + (una scheda UPS salvata senza indirizzo, ad es. da un import di backup): non ha mai + inviato un pacchetto, quindi non ha prodotto prove sull'alimentazione, e + resta ciò che è altrove — un allarme e un rifiuto di spegnere gli host + che alimenta, non un ordine di farlo.
  • +
+

Panoramica dei casi

+ + + + + + + + + + + + + + +
SituazioneComportamento
Su rete, tutto oknulla (mostra “Su rete elettrica”)
A batteria, un trigger scattaspegnimento (host a stadi per ordine, host proprio ultimo)
Contatto perso mentre è su retesolo allarme, nessuno spegnimento
Contatto perso durante un conto alla rovescia a batteriail countdown continua, spegnimento alla scadenza
Contatto perso dopo un trigger già scattatoil trigger resta bloccato (nessun downgrade)
Pura perdita di contatto con l'opt-in attivospegnimento alla scadenza della soglia
Server NUT risponde con dati obsoleticonta come irraggiungibile (allarme), mai come “su rete”
L'UPS risponde ma non riporta una sorgente utilizzabileconta come irraggiungibile (allarme), mai come “su rete”
Dry-run attivoil trigger viene rilevato + registrato, nulla viene spento
La rete tornalo stato si reimposta; uno spegnimento già inviato + viene rilasciato dopo “Riarmo dopo ritorno rete”
+
+ +
+

8 Trigger e soglie

+

Una condizione soddisfatta basta. Tutti i valori si possono cambiare nella + procedura guidata (campo vuoto = condizione disattivata); le soglie sono impostate + globalmente e si possono sovrascrivere per UPS.

+

Come viene misurato “a batteria per più di”. PVE-UPS misura il + blackout sul proprio orologio, dal polling in cui ha visto per la prima volta l'UPS + passare a batteria. Deliberatamente non usa il contatore di tempo trascorso che + l'UPS riporta: lo standard definisce quel valore solo mentre il dispositivo è a + batteria, e una scheda che conserva il dato del blackout precedente invece di + azzerarlo farebbe apparire il primissimo polling di un nuovo blackout come se la + soglia fosse già passata. Quel contatore non viene letto affatto. Il timer in corso + sopravvive a un riavvio del servizio, quindi un aggiornamento o un reboot non lo + perdono; solo dove quel timer memorizzato è inutilizzabile (mancante, o più vecchio + di un giorno) un'appliance che si avvia in un blackout già in corso ricomincia a + contare da zero — e le condizioni su autonomia, carica e “batteria scarica” + leggono valori freschi e non ne sono influenzate. La pagina di + stato indica se un tempo trascorso è stato misurato affatto.

+

Un timer ripristinato aspetta che l'UPS confermi il blackout. Questa + appliance spegne il proprio host per ultimo, quindi ogni blackout finisce con + il suo riavvio — e torna a un timer memorizzato che dice “a batteria da tre + ore”. Quello è esattamente il momento in cui uno switch sta ancora convergendo e una + scheda SNMP sta ancora avviandosi, e un timer preso per buono lì + spegnerebbe di nuovo le macchine appena risalite. Quindi nulla di ciò che viene letto + dal file memorizzato spegne qualcosa finché un polling del servizio riavviato non ha + risposto. Quella risposta decide in ogni caso: su rete, tutto ciò che è stato + ripristinato viene scartato; ancora a batteria, il trigger viene calcolato dal tempo + ripristinato intero su quello stesso polling. Un riavvio nel mezzo di un + blackout non costa quindi nulla, perché lì l'UPS è raggiungibile per definizione. + L'unico caso che perde il countdown è un UPS che non risponde mai più dopo il + riavvio — e lì l'appliance non ha affatto prove che il blackout sia ancora in + corso, che è esattamente quando non agisce. Mentre un timer è in attesa la + dashboard lo dice invece di contare alla rovescia verso uno spegnimento che non + avverrà. Un timer in attesa non conta nemmeno come blackout per i controlli che si + fermano durante uno: l'auto-test, i controlli all'avvio e + “Ripristina cluster” restano disponibili, il che conta perché un UPS ancora + silenzioso dopo il reboot dell'appliance è esattamente quando il cluster lasciato + dall'ultimo blackout va ripristinato.

+

Se una richiesta di spegnimento fallisce — un'API occupata, una + connessione caduta, o un nodo che ha semplicemente impiegato troppo a rispondere + — a quell'host viene chiesto di nuovo ai polling successivi, fino a tre + tentativi in totale, e ognuno finisce nel registro eventi. Una richiesta ripetuta a + una macchina che si sta già spegnendo non fa danni; non richiederlo la lascerebbe + accesa finché la batteria è vuota. Se la rete torna mentre un host è ancora in + ritentativo, l'episodio si chiude come ogni altro: l'appliance torna a + ONLINE e scrive un evento critico che dice che quella + macchina non è mai scesa ed è ancora in esecuzione.

+

Valori fuori da un intervallo sensato vengono corretti, non obbediti. Un + intervallo di polling pari a zero o un timeout negativo ripiegano sul default e + scrivono un evento che nomina l'impostazione. È voluto: rifiutare di caricare la + configurazione significherebbe che l'appliance non si avvia più, e non protegge + nulla.

+ + + + + + + + +
CondizioneDefault
A batteria per più di600 s
Autonomia sotto10 min
Carica sotto30 %
L'UPS segnala battery low/depletedattivo
+

Intervallo di polling: 30 s su rete, 8 s a batteria.

+

Le soglie batteria scarica, autonomia e carica scattano immediatamente — + indipendentemente dal conto alla rovescia temporale. Il countdown mostrato sulla + dashboard si riferisce esclusivamente alla condizione "a batteria per più di" e + sparisce appena l'UPS ha attivato il trigger. Se un host si spegne subito a quel + punto lo decide la sua logica AND/OR (vedi Come funziona e + logica): con AND aspetta che tutti i suoi UPS alimentanti abbiano + attivato.

+
+ +
+

9 Monitoraggio / REST

+

Due endpoint in sola lettura sono disponibili senza autenticazione e senza + segreti:

+
    +
  • GET /api/status — lo stato completo: appliance, tutti gli UPS, + gli host, lo stato dello spegnimento, i target di notifica con l'esito della loro + ultima consegna (webhooks, volutamente senza l'URL), più gli eventi + delle ultime 48 h con un riepilogo per gravità.
  • +
  • GET /api/health — 200 quando l'engine è in esecuzione + (con versione e raggiungibilità degli UPS), altrimenti 503. Ideale per + controlli di uptime. Riporta anche i target di spegnimento: hosts_total, + hosts_ok (l'ultimo auto-test ha confermato token e privilegio), + hosts_selftest_ok e hosts_selftest_at, i + target di notifica (webhooks_total, webhooks_ok — + un target conta come ok finché un invio non è dimostrabilmente fallito, quindi uno mai + provato non risulta guasto) e dry_run. Questi sono + informativi — un token rotto non porta l'endpoint a + 503, perché l'appliance stessa sta ancora facendo il suo lavoro. Allerta su + hosts_selftest_ok, webhooks_ok e dry_run + separatamente se vuoi esserne avvisato: un'appliance lasciata in dry-run passa ogni + controllo qui, risponde ok, e non spegne nulla.
  • +
+
curl http://<container-ip>:8080/api/status | jq
+# solo gli eventi critici delle ultime 48 h:
+curl -s http://<container-ip>:8080/api/status \
+  | jq '.events[] | select(.severity=="critical")'
+

Tutti gli endpoint modificanti (salvataggio procedura guidata, test + di spegnimento) sono protetti da password.

+
+ Il registro eventi, i messaggi webhook e i messaggi API sono uniformemente + in inglese — indipendentemente dalla lingua UI selezionata. Questo mantiene + analisi e monitoraggio coerenti tra le installazioni. +
+ +

Storico

+

La scheda Storico, tra Dashboard e Impostazioni, mostra ogni UPS nel + tempo: autonomia residua, carico e carica, con lo stato di alimentazione come + sfondo colorato — a batteria, spegnimento attivato, non raggiungibile, bypass. Gli + eventi del registro appaiono come marcatori: avvisi ed eventi critici sempre, quelli + informativi (l'auto-test, ad esempio) quando spuntati. Passando sul grafico si + vedono i valori di quel momento e gli eventi al marcatore.

+
    +
  • Periodi da 1 ora a 90 giorni, e “Tutto” — o un qualsiasi inizio e fine + digitati in Da e A; quei due campi mostrano sempre il periodo sullo + schermo. Trascina sul grafico per ingrandire un tratto, doppio clic per tornare al + preset; un periodo fisso non si aggiorna sotto di te.
  • +
  • Un UPS: autonomia in minuti sopra, carico e carica in percentuale sotto — + due pannelli su un asse temporale anziché un grafico con due scale. La linea + tratteggiata è il trigger “Autonomia sotto” che quell'UPS usa davvero, così vedi + quanto un blackout ci è andato vicino.
  • +
  • Tutti (con più di un UPS): una misura per ogni UPS in un unico + grafico, e una striscia di stato per UPS sotto.
  • +
  • La tabella blackout elenca ogni tratto a batteria nel periodo — inizio, + durata, autonomia e carica minime, carico massimo, se uno spegnimento è stato + attivato. Cliccando una riga il grafico zooma su quel blackout.
  • +
  • CSV scarica i valori grezzi del periodo sullo schermo.
  • +
+

Cosa viene registrato: un valore per UPS al minuto su rete, uno per polling a + batteria, e uno a ogni cambio di stato — circa 1.440 righe per UPS al giorno, pochi + MB per UPS su 90 giorni. Vive in history.db accanto al registro eventi + (in Docker, nello stesso volume), non fa parte del backup della configurazione, e + “Svuota registro” lo lascia stare. È scritto in background: un disco lento o pieno + costa lo storico, mai lo spegnimento.

+

In Impostazioni → Sistema la registrazione si può disattivare, la + retention impostare in giorni (1–365, default 90) e lo storico svuotare. Spento ferma + la registrazione e nasconde la scheda; ciò che è stato registrato resta finché non + scade. + Una nuova installazione registra dall'inizio; dopo un aggiornamento da una versione + precedente resta spento finché non lo attivi, così un aggiornamento non inizia mai + a scrivere su un disco che nessuno ha dimensionato per questo.

+ +

Notifiche webhook

+

Opzionalmente, ogni evento rilevante viene inviato via HTTP POST a un URL a + scelta (impostazioni, sezione 4). Puoi configurare tutti i webhook che vuoi — + una scheda per target, ciascuna con formato, filtro di gravità e pulsante di test + propri. Gli invii girano in parallelo e vengono riportati singolarmente, quindi un + target irraggiungibile non costa agli altri la loro notifica.

+

Un target che smette di funzionare lo dice. Un token scaduto o un + connettore cancellato fallivano in silenzio, il che significava scoprirlo durante il + blackout che non ti è mai arrivato. Il primo fallimento dopo un invio riuscito viene + scritto nel registro eventi, la scheda webhook mostra l'ultimo errore finché un invio + non riesce di nuovo, e la scheda appliance della dashboard guadagna una riga + “Notifiche” finché un qualsiasi target fallisce — la pagina + impostazioni non è dove qualcuno guarda mentre un blackout è in corso. Le notifiche + restano best-effort comunque: non vengono mai ritentate, e un target che fallisce non + ritarda né influenza mai uno spegnimento.

+

Formato — la forma del payload, in base al tuo sistema di + destinazione:

+ + + + + + + + + + + +
FormatoInvia
JSON (stato completo){subject, body, severity, status} — + status è lo snapshot completo di /api/status. Per sistemi + di ticket, monitoraggio e automazione.
Microsoft Teamsuna adaptive card (intestazione colorata per + gravità, testo del messaggio, fatti su UPS, host e modalità) nella busta + messaggio che Teams si aspetta.
Testo sempliceun breve stato leggibile come + text/plain — per qualsiasi cosa che mostri semplicemente una riga di + testo.
Slackun allegato con barra colorata per gravità e i fatti come + campi. Usa l'URL dell'integrazione “Incoming Webhooks” di un'app Slack + (https://hooks.slack.com/services/…).
Discordun embed con titolo, descrizione e campi. Usa l'URL + da canale → Modifica → Integrazioni → Webhook.
ntfyun push in testo semplice i cui header Title, + Priority e Tags vengono impostati dall'evento. Usa + l'URL del topic (https://ntfy.sh/il-tuo-topic).
Personalizzato (template)un body che scrivi tu, più il content + type — per un target a cui nessun preset si adatta. Vedi sotto.
+

Per Microsoft Teams, usa l'URL di un workflow “Posta in un canale quando + viene ricevuta una richiesta webhook” (Teams → canale → Workflows); l'URL di un + classico incoming webhook funziona ugualmente.

+

Template personalizzati. Con il formato Personalizzato fornisci body e + content type. Questi placeholder vengono sostituiti; tutto il resto è inviato + verbatim:

+ + + + + + + + + +
PlaceholderContiene
{{subject}} / {{body}}l'oggetto + dell'evento e il testo di dettaglio
{{severity}} / {{severity_upper}} + info, warning o critical
{{facts}} / {{facts_json}}il breve + riepilogo UPS/host, come righe di testo o come oggetto JSON
{{status_json}}lo snapshot completo di + /api/status come JSON
{{timestamp}} / {{version}}l'ora + dell'evento (UTC) e la versione dell'appliance
+

È una semplice sostituzione, non un linguaggio di espressioni: niente cicli e + niente condizionali. Quando il content type contiene json, i valori + vengono escapati in JSON automaticamente, quindi una virgoletta in un testo di evento + non può produrre un payload malformato.

+

Header di autenticazione (opzionale, per webhook) — nome e valore di un + header aggiuntivo, ad esempio Authorization: Bearer … per un topic + ntfy protetto o un header API-key. Il valore è trattato come segreto: non viene mai + restituito dall'API e resta invariato quando salvi senza reinserirlo.

+

Header extra (opzionale, per webhook) — un header per riga, scritto come + Nome: Valore, per target che hanno bisogno di più dell'unico header sopra. + Nextcloud Talk è il caso tipico: oltre alla sua autorizzazione Basic si aspetta + OCS-APIRequest: true e Accept: application/json. Questi + header sovrascrivono ciò che il formato scelto imposta, ma mai l'header di + autenticazione sopra. Le righe che non sono un header valido vengono ignorate.

+
+ Nessun segreto qui. A differenza dell'header di autenticazione sopra, + questi valori sono salvati e mostrati in chiaro e vengono restituiti dall'API di + configurazione. Tutto ciò che è riservato va nel campo Valore sopra. +
+

Invia dal livello — quali eventi vengono inviati. Tutto ciò che è sotto il + livello selezionato viene comunque scritto nel registro eventi, semplicemente non + attiva un messaggio:

+ + + + + + + +
LivelloInclude ad esempio
Tutti gli eventi (incluso info)in più “rete ripristinata”, + “connessione di rete ripristinata”
Avvisi e critici (default)blackout, UPS irraggiungibile, + auto-test fallito, spegnimento inviato/annullato
Solo criticispegnimento attivato, spegnimento FALLITO
+
+ Da questa versione il default è Avvisi e critici — i messaggi di puro + tutto-ok non arrivano più. Imposta il filtro su Tutti gli eventi per il + comportamento precedente. +
+

Il pulsante “Invia notifica di test” invia un messaggio di esempio con i + valori attualmente inseriti — non serve salvare prima, e il filtro di livello non si + applica. Riporta il risultato HTTP del target, quindi un URL sbagliato emerge + subito.

+

Le notifiche sono best-effort: un target irraggiungibile viene + registrato e non influenza mai la logica di spegnimento.

+
+ +
+

10 Aggiornamenti e gestione

+

Gli aggiornamenti si caricano direttamente nella web UI; un servizio companion + minimale e privilegiato li applica (l'app stessa resta senza privilegi). Dopo + l'upload, l'aggiornamento viene tipicamente applicato in pochi secondi. La web UI + permette anche di esportare/importare la configurazione, impostare server NTP e + fuso orario, e vedere/svuotare il registro eventi.

+

L'interfaccia web si aggiorna da sola in modo affidabile dopo un update: la + pagina non viene mai messa in cache, e ogni script e stylesheet che carica porta + un'impronta che cambia con il file — un browser non può quindi continuare a servire + la versione precedente. Se una scheda era aperta durante il riavvio del servizio, + in alto compare un avviso con un pulsante “Ricarica”; finché non lo usi, + quella scheda esegue ancora la vecchia interfaccia.

+

Nota: il fuso orario (sezione Sistema, es. + Europe/Rome) andrebbe impostato, altrimenti il container gira in UTC. + L'orario di avvio dell'auto-test è interpretato in quest'ora locale; dopo aver + impostato il fuso orario il servizio si riavvia brevemente.

+

Auto-test delle connessioni di destinazione

+

L'appliance verifica da sola se per host il token API e il suo privilegio di + power-management funzionano ancora — Sys.PowerMgmt su Proxmox VE, + Sys.PowerManagement su un Backup Server — così una credenziale + scaduta o revocata emerge molto prima che un blackout ne abbia bisogno. Due + impostazioni nella sezione Sistema lo controllano: un orario di + avvio (ora piena) e un intervallo da 15 minuti fino a 24 ore. Insieme + definiscono orari fissi del giorno: avvio 09:00 con intervallo di 6 ore significa + 09:00, 15:00, 21:00 e 03:00. Il default è giornaliero.

+

Un auto-test fallito viene sempre registrato e notificato. Uno riuscito viene + scritto nel registro eventi al massimo una volta al giorno (e ogni volta che si + riprende da un fallimento), così un intervallo breve non seppellisce il registro. + Mentre un UPS è a batteria l'auto-test viene saltato del tutto — durante un blackout + il countdown ha priorità. Un riavvio non ripete un auto-test già eseguito nello slot + corrente.

+

Se anche quell'unica riga al giorno è troppo — un solo amministratore che sa + quando un token è cambiato — togli la spunta a “Registra gli auto-test riusciti + nel registro eventi”. Sparisce solo la riga verde di routine; il controllo + continua a girare all'intervallo configurato, i fallimenti sono registrati e + notificati come prima, il journal di sistema registra comunque ogni esecuzione, e il + verdetto per host sulla dashboard non ne risente. Il controllo cluster condivide la + pianificazione dell'auto-test e la sua riga di log, quindi anche il + Cluster <nome>: ok per cluster tace con essa; ogni + avviso cluster viene riportato come prima. Due righe verdi sopravvivono + comunque all'interruttore: un'esecuzione richiesta da te, e quella che riporta una + ripresa da un fallimento.

+
+ Non esiste volutamente un intervallo settimanale o mensile. L'auto-test + esiste per intercettare una credenziale che si è rotta dall'ultimo blackout — un + token cambiato, una nuova regola firewall, una scheda di rete UPS sostituita — e + niente di tutto questo segue un calendario. Se il problema è il registro eventi, + silenzia la riga anziché il controllo; se vuoi lo stato monitorato attivamente, + interroga /api/health dal tuo sistema di monitoraggio (vedi + Monitoraggio), che copre anche il caso dell'appliance che + non gira affatto. +
+

Salvare le impostazioni non attiva un auto-test — è voluto, così + modificare la configurazione non martella mai l'API Proxmox. Dopo aver cambiato un + token o spuntato l'opzione cluster, usa “Esegui auto-test ora” sulla + dashboard per vedere subito il risultato invece di aspettare lo slot successivo. + Scrive sempre il suo risultato nel registro eventi, ignorando il limite di una + volta al giorno, e viene rifiutato mentre un UPS è a batteria.

+

Per gli host marcati come membri di cluster l'auto-test + controlla anche il cluster una volta: privilegi del token, quorum, flag di + manutenzione Ceph, stato di arm di HA e shutdown policy del datacenter. Un cluster + sano viene registrato come “Cluster <nome>: ok”, così “controllato e + a posto” è distinguibile da “mai controllato”. Nota il rovescio: senza la spunta + cluster niente di tutto questo gira, e il log mostra allora solo la semplice + riga per host.

+

Il risultato viene conservato anche per host: la tabella host della + dashboard mostra il problema nella sua colonna errori anche quando nulla è stato + spento, e /api/health conta quanti target hanno superato + l'ultimo controllo.

+

Aggiornamento dalla versione 2.x alla 3.x

+

Scarica il pacchetto della release corrente + (pve-usv-<versione>.tar.gz) dalla + pagina releases di GitHub e caricalo nella web UI sotto + “Aggiornamento” — la configurazione è interamente preservata. Due cambi di + comportamento: le notifiche e-mail non ci sono più (una voce SMTP configurata in + precedenza viene rimossa automaticamente al prossimo salvataggio; + il webhook resta), e i testi di eventi/webhook sono in inglese dalla 3.0.0.

+

Deployment Docker

+

Come alternativa all'installazione LXC, a ogni release viene pubblicata + un'immagine precompilata su + ghcr.io/ffind-dev/pve-ups. + L'immagine è multi-arch — linux/amd64, linux/arm64 e + linux/arm/v7 — quindi gira anche su un Raspberry Pi 3/4/5 con OS a + 64 o 32 bit; Docker sceglie la variante giusta. Un dispositivo così piccolo + consuma poco dalla batteria e resta acceso mentre gli host che protegge si + spengono. Un + docker-compose.example.yml pronto è nel + repository; l'essenziale:

+
services:
+  pve-ups:
+    image: ghcr.io/ffind-dev/pve-ups:latest
+    container_name: pve-ups
+    restart: unless-stopped
+    ports: ["8080:8080"]
+    environment:
+      - TZ=Europe/Rome
+    volumes:
+      - pve-ups-config:/etc/pve-usv    # config.yaml (segreti, 0600)
+      - pve-ups-data:/var/lib/pve-usv  # events.db, history.db + stato engine
+volumes:
+  pve-ups-config:
+  pve-ups-data:
+

Poi apri http://<docker-host>:8080 e segui la stessa + procedura guidata dell'LXC. Configurazione e registro eventi persistono tramite i + due volumi montati, quindi docker compose down non perde nulla.

+

Dato che nell'immagine non c'è un processo companion privilegiato (niente + systemd), due cose funzionano diversamente: gli aggiornamenti si applicano + scaricando un nuovo tag dell'immagine e ricreando il container + (docker compose pull && docker compose up -d) invece + dell'uploader in-app, e NTP/fuso orario sono responsabilità dell'host Docker o + dell'orchestratore anziché essere impostati dalla procedura guidata — entrambe le + aree sono nascoste nella web UI. Tutto il resto (polling UPS, spegnimento Proxmox, + soglie, webhook, auto-test) è identico.

+
+ Imposta TZ sul container. L'orario di avvio + dell'auto-test è interpretato nell'ora locale del container, e senza + TZ il container gira in UTC — il fuso orario non si può impostare + dalla web UI in modalità Docker. +
+
+ Se la tua rete ricade in 172.17.0.0/16 – + 172.31.0.0/16, sposta l'address pool di default di Docker + prima di avviare il container — Docker reclama quel range per i suoi + bridge, e il container non raggiungerebbe più un UPS o un host Proxmox al suo + interno. In /etc/docker/daemon.json, ad es. + {"bip":"10.210.0.1/24","default-address-pools":[{"base":"10.211.0.0/16","size":24}]}, + poi systemctl restart docker. +
+
+ +
+

11 Risoluzione dei problemi

+

Un Backup Server riporta “Authentication failed (token invalid?)”

+

Quasi sempre è il tipo sbagliato sulla voce host. Proxmox VE e Proxmox + Backup Server usano schemi di token diversi, e PBS non riconosce affatto quello PVE — + quindi la richiesta viene rifiutata prima ancora che il token venga guardato, per + quanto valido sia. Imposta il tipo su Proxmox Backup Server e riprova. Se il + test poi riesce ma avverte che Sys.PowerManagement non si è potuto + confermare, l'ACL manca sull'utente, sul token, o su entrambi — vedi + la sezione 4. Controlla anche che l'URL API usi la porta + 8007.

+

L'aggiornamento resta in coda

+

Se un aggiornamento caricato resta su “⏳ In coda”, il drainer della coda non è + attivo su quest'istanza. Succede una volta su macchine aggiornate da una versione + molto vecchia. Esegui una volta dentro il container (abilita il servizio in + permanenza):

+
pct exec <CTID> -- systemctl start pve-usv-agent.service
+# o direttamente dentro il container:
+systemctl start pve-usv-agent.service
+

Cluster: serve un token separato per nodo?

+

No. Utenti, token API e ACL sono salvati a livello datacenter + (/etc/pve), quindi un token creato una volta su un qualsiasi nodo vale + su tutti — eseguire i comandi della sezione 4 una seconda + volta riporterebbe solo che l'utente esiste già. Aggiungi ogni nodo con lo + stesso token ID e segreto, ma con il suo URL API.

+

UPS irraggiungibile / allarme

+

Controlla indirizzo, credenziali e il firewall tra l'appliance e la sorgente + UPS. “Testa UPS” nella procedura guidata mostra la causa esatta — apri + Dettagli per oggetto per vedere quale oggetto è fallito e perché (vedi + Sorgenti UPS). Una sola perdita di comunicazione non spegne + mai nulla (fail-safe).

+

Una soglia non scatta mai

+

Non ogni dispositivo riporta ogni valore. Esegui “Testa UPS”: se il dispositivo + non pubblica l'autonomia residua o la carica, il test lo dice sopra la tabella + oggetti, e la soglia corrispondente resta perennemente inefficace. Usa invece “A + batteria per più di (s)” — quel timer gira sull'orologio proprio + dell'appliance.

+

Su un UPS SNMP, controlla anche l'impostazione MIB. Molte schede + pubblicano nel loro MIB del produttore ciò che tralasciano dall'RFC 1628; + Automatico lo trova da solo, quindi vale la pena guardarci solo se il MIB è + stato fissato a mano (vedi Sorgenti UPS).

+

Un UPS APC non funziona / non risponde nulla di utile

+

Lascia l'impostazione MIB su Automatico — il MIB APC PowerNet viene + allora usato da solo. Serve perché Schneider supporta RFC 1628 solo sulle Network + Management Card 2 (AP9630/AP9631/AP9635) dal firmware + sumx/sy v5.1.7 in poi, mentre le schede NMC1 più + vecchie (AP9617/AP9618/AP9619) non lo implementano affatto. “Testa UPS” mostra + quale MIB è stato usato e quali oggetti ciascuno ha risposto.

+

Un UPS CyberPower riporta “nessuna risposta” anche se la scheda è + raggiungibile

+

Esegui “Testa UPS”. Se il suo riepilogo dice che il polling regolare non ha + avuto risposta mentre i valori rispondono uno alla volta, la scheda non gestisce una + richiesta di più valori insieme: imposta il MIB su CyberPower, che + viene letto un valore per richiesta. Se la scheda non risponde affatto, controlla + community/credenziali e che SNMP sia abilitato per l'IP dell'appliance sulla + scheda.

+

NUT: “does not know a UPS with that name” o non risponde nulla

+

Il nome dell'UPS deve corrispondere al nome di sezione nel + ups.conf di upsd; upsc -l <host> elenca i nomi + validi. Se non risponde affatto nulla, molto probabilmente upsd è in ascolto solo su + localhost — la sua direttiva LISTEN deve coprire l'interfaccia a cui + l'appliance si connette, e la TCP 3493 deve essere aperta.

+

SNMPv3 con crittografia fallisce

+

Se un utente authPriv riporta “Ciphering services not available” o il test + punta a crittografia mancante, all'appliance manca il pacchetto Python + cryptography, che fornisce i cifrari DES/AES. Le versioni fino alla + 3.0.0 non lo installavano. Aggiornare l'appliance risolve — il pacchetto + arriva automaticamente. Due cose da sapere: riguarda ogni protocollo di privacy, + quindi passare da DES ad AES non aiuta, e authNoPriv (autenticazione + MD5/SHA, privacy “nessuna”) continua a funzionare nel frattempo, perché + l'autenticazione non ha bisogno di quei cifrari.

+

Nomi interni: pve-usv

+

Il nome del prodotto è PVE-UPS, ma servizio e percorsi si chiamano + tecnicamente ancora pve-usv — ad es. + systemctl status pve-usv, /etc/pve-usv/config.yaml e + /var/lib/pve-usv/. È intenzionale e mantiene gli aggiornamenti delle + installazioni esistenti compatibili.

+
+ +
+

12 Changelog (in breve)

+

Punti salienti semplificati, visibili all'utente. Il changelog + tecnico completo vive nel + repository GitHub.

+ +
4.3.0 2026
+
    +
  • Scheda storico: autonomia residua, carico e carica di ogni UPS nel + tempo, con blackout, trigger e marcatori del registro eventi, zoom, un periodo + libero, una tabella blackout ed export CSV. Dopo un aggiornamento resta spenta + finché non la attivi in Impostazioni → Sistema — vedi + Storico.
  • +
  • “Ferma prima tutti i guest a livello cluster” senza Ceph: a ogni + guest viene chiesto di spegnersi insieme prima che il primo nodo scenda, invece + che nodo per nodo in ordine di startup — per cluster dove questo richiede più di + quanto duri la batteria. Vedi l'arresto + guest.
  • +
  • Schede di rete CyberPower lette tramite il loro MIB, anche schede + senza RFC 1628. “Automatico” su SNMP v2c non fallisce più su schede che + rispondono a un oggetto mancante con un errore.
  • +
  • L'immagine Docker gira su Raspberry Pi (64 e 32 bit).
  • +
+ +
4.2.0 2026
+
    +
  • Un webhook che hai spento non deve più avere un URL. Un target di + notifica preparato a metà viene conservato come bozza; solo uno attivo + insiste ancora su dove postare.
  • +
  • I webhook possono portare header extra. Target che ne richiedono più + di uno — come Nextcloud Talk, che si aspetta OCS-APIRequest e + Accept oltre alla sua autorizzazione — ora funzionano senza un proxy + davanti.
  • +
  • I password manager possono di nuovo compilare il form di login. La + pagina di accesso è un form vero con campo username, che è ciò su cui l'autofill + inline fa leva; i campi token e password nelle impostazioni sono marcati perché + un manager li lasci stare.
  • +
  • Gli auto-test riusciti si possono tenere fuori dal registro eventi + senza testare le credenziali meno spesso. Fallimenti, riprese ed “Esegui + auto-test ora” riportano sempre.
  • +
+ +
4.1.0 2026
+
    +
  • Un UPS che riporta un “tempo a batteria” vecchio non spegne più tutto in + una volta. L'appliance ora misura il blackout sul proprio orologio invece di + credere al contatore riportato dal dispositivo — un contatore che lo standard + definisce solo mentre l'UPS è a batteria e che molte schede conservano + dall'ultimo blackout. Il timer in corso sopravvive a un riavvio del + servizio.
  • +
  • Un UPS che risponde senza dire da dove viene la sua alimentazione è un + allarme, non un tutto-ok. Per SNMP e NUT allo stesso modo, una lettura così + non azzera più un countdown in corso nel mezzo di un blackout — e non conta mai + nemmeno come silenzio, quindi “Spegnimento su pura perdita di comunicazione” non + si applica a un dispositivo che sta rispondendo.
  • +
  • Uno spegnimento che fallisce viene ritentato — tre tentativi invece di + uno, così un'API Proxmox occupata o una connessione caduta non lasciano più una + macchina accesa mentre la dashboard la riporta come gestita.
  • +
  • Un cluster si porta dietro solo i nodi che lo hanno chiesto, e i tre + interruttori cluster vengono letti una volta per cluster anziché dal nodo che è + capitato a scattare per primo. Vedi la sezione 6.
  • +
  • Inoltre: le consegne webhook fallite vengono riportate invece di fallire + in silenzio, un dry-run attivo lo dice sulla dashboard, le voci che non possono + fare il loro lavoro (un host senza token, un UPS senza indirizzo) vengono + segnalate, e nessuna credenziale può raggiungere l'endpoint di stato + pubblico.
  • +
+ +
4.0.0 2026
+
    +
  • I cluster Proxmox VE vengono preparati prima dello spegnimento + Beta. Marca un nodo come membro di cluster e + PVE-UPS disarma l'HA manager una volta per cluster — e, con l'opzione Ceph, ferma + ogni guest e poi imposta i flag di manutenzione — prima che il primo nodo scenda. + Un pulsante “Ripristina cluster” sulla dashboard lo annulla dopo. Richiede + Proxmox VE 9.2 o più recente ed è opt-in ovunque; vedi la + sezione 6.
  • +
  • Un cluster viene spento come unità (attivo di default): un nodo che + diventa dovuto si porta dietro l'intero cluster, nell'ordine configurato, invece + di lasciare mezzo cluster in piedi con HA disarmato. Si può disattivare.
  • +
  • Più webhook invece di uno, ciascuno con formato, filtro e pulsante di + test propri — più nuovi formati per Slack, Discord e ntfy + e un template personalizzato. Vedi la sezione 9.
  • +
  • L'auto-test è più utile: gira automaticamente dopo che l'appliance si + riarma, “Esegui auto-test ora” lo avvia sul momento, e rinominare un host non + scarta più il suo token API. Vedi la sezione 4.
  • +
+ +
3.5.0 2026
+
    +
  • Anche un Proxmox Backup Server può ora essere spento. Ogni voce host + ha un tipo — Proxmox VE o Proxmox Backup Server — che decide lo schema di + token e il privilegio verificato. Inserire un PBS falliva come token non valido + qualunque cosa si facesse; vedi la sezione 4 per la + configurazione, incluso perché sconsigliamo di collegare PBS alla stessa + istanza in produzione.
  • +
  • Per un Backup Server il campo “Nodo” è solo un'etichetta. PBS ignora + il nome del nodo nel path API, quindi lì niente deve corrispondere + all'hostname.
  • +
  • Gli host vengono ora spenti a stadi invece che strettamente uno dopo + l'altro: le voci che condividono un “Ordine” vengono comandate insieme, l'host + proprio dell'appliance va sempre per ultimo, e ogni comando ha una deadline + rigida. Una macchina che smette di rispondere non può più ritardare le altre né + il countdown della batteria — vedi la sezione 6. La + procedura guidata mostra la sequenza risultante dal vivo sotto la lista host, + così si vede quali host vanno insieme e quali seguono.
  • +
  • Il risultato dell'auto-test viene conservato per host: un token rotto + ora appare nella tabella host della dashboard e in + /api/health, non solo nel registro eventi.
  • +
+ +
3.4.0 2026
+
    +
  • Il webhook ora parla la lingua del suo target: oltre al precedente + JSON di stato può postare una card Microsoft Teams o un breve stato in + testo semplice. Vedi Notifiche webhook.
  • +
  • Un nuovo filtro di livello decide quali eventi vengono inviati + affatto. Il default è Avvisi e critici — i messaggi di puro tutto-ok non + arrivano più; imposta il filtro su Tutti gli eventi per il comportamento + precedente.
  • +
  • “Invia notifica di test” invia un messaggio di esempio con i valori + attualmente inseriti — senza salvare prima — e riporta il risultato HTTP del + target.
  • +
  • Uno spegnimento eseguito o ritirato (shutdown sent / + shutdown aborted) conta come avviso invece che info, e viene + mostrato in ambra nel registro eventi.
  • +
+ +
3.3.0 2026
+
    +
  • Gli UPS APC sono supportati correttamente: un UPS SNMP può ora essere + letto tramite un MIB del produttore invece di RFC 1628, il primo dei quali + è APC PowerNet. Questo copre schede APC che RFC 1628 non riesce a + raggiungere affatto — le NMC1 più vecchie (AP9617/AP9618/AP9619) — oltre alle + schede NMC2 con firmware sotto sumx/sy v5.1.7. Vedi + Sorgenti UPS.
  • +
  • La nuova impostazione MIB ha default Automatico e non richiede + attenzione: le voci UPS esistenti la ricevono con l'aggiornamento, e un UPS passa + al suo MIB del produttore da solo appena risponde lì.
  • +
  • Le schede che supportano entrambi ottengono letture più accurate: + PowerNet riporta l'autonomia residua in centesimi di secondo invece che in minuti + interi, e marca un auto-test come tale — RFC 1628 non sa distinguerlo da + un blackout vero.
  • +
  • Il test UPS si spiega da solo: in modalità Automatico elenca entrambi + i MIB, dice su quale si è assestato e perché, e la dashboard nomina il MIB in + uso.
  • +
+ +
3.2.0 2026
+
    +
  • Gli UPS senza scheda di rete possono ora essere monitorati: una voce + UPS viene letta via SNMP come prima, oppure tramite un server NUT — che è + come si raggiungono gli UPS USB e seriali. I due tipi si possono mescolare in + un'istanza. Vedi Sorgenti UPS.
  • +
  • PVE-UPS resta un client NUT strettamente in sola lettura — niente + upsmon, niente script di spegnimento, niente file di config. La + decisione resta nell'appliance.
  • +
  • Il test UPS nomina le soglie morte: se un dispositivo non riporta né + autonomia residua né carica, il test lo dice invece di lasciare una soglia che + silenziosamente non scatta mai.
  • +
  • Un server NUT che serve dati obsoleti (il suo driver è crashato) conta come + irraggiungibile — un allarme, mai uno spegnimento, e mai scambiato per “su + rete”.
  • +
+ +
3.1.0 2026
+
    +
  • SNMPv3 con crittografia ora funziona. Un utente authPriv falliva con + “Ciphering services not available” fino alla 3.0.0 inclusa — una dipendenza + mancante, che l'aggiornamento installa strada facendo. Vedi + Risoluzione dei problemi.
  • +
  • Deployment Docker come alternativa all'LXC: un'immagine precompilata + viene pubblicata a ogni release. Vedi Deployment + Docker.
  • +
  • Test SNMP con dettagli per oggetto: ogni oggetto RFC 1628 con il suo + valore o il motivo per cui manca, così credenziali sbagliate, una porta bloccata + e un UPS incompleto si distinguono a colpo d'occhio.
  • +
  • Auto-test con intervallo: l'orario del giorno diventa un orario di + avvio, con ripetizione da 15 minuti fino a 24 ore. Le installazioni esistenti + mantengono la loro cadenza giornaliera.
  • +
+ +
3.0.0 2026
+
    +
  • Prima release pubblica su GitHub con il nome PVE-UPS + (nomi di servizio/percorsi restano pve-usv internamente).
  • +
  • Web UI bilingue: inglese (default) e tedesco, scelta automaticamente + dalla lingua del browser; manuale utente in entrambe le lingue.
  • +
  • Registro eventi, messaggi webhook e messaggi API uniformemente in + inglese.
  • +
  • Notifiche e-mail rimosse — il webhook copre le notifiche; le voci SMTP + esistenti vengono scartate automaticamente con l'aggiornamento.
  • +
  • Installazione e aggiornamenti direttamente via GitHub releases + (installazione one-liner, aggiornamento con upload del pacchetto nella web UI; + indolore dalla 2.x).
  • +
+
+
+
+ + + + + + diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 88a75bc..031f1bd 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -132,8 +132,21 @@ def test_source_type_keys_exist_in_both_dictionaries(): assert re.findall(r'\["([a-z]+)"', block.group(1)) == kinds -def test_manual_deep_links_resolve_in_both_manuals(): - """Every anchor the UI deep-links to must exist in manual.html *and* handbuch.html. +def _manual_files() -> set[str]: + """Every manual a shipped dictionary can point at (its `manual.file` value).""" + files = set() + for fname in DICTS: + m = re.search( + r'"manual\.file"\s*:\s*"([^"]+)"', + (WEB / "i18n" / fname).read_text(encoding="utf-8"), + ) + assert m, f"{fname}: no manual.file entry" + files.add(m.group(1)) + return files + + +def test_manual_deep_links_resolve_in_every_manual(): + """Every anchor the UI deep-links to must exist in every shipped manual. The links are built from data-manual attributes (static markup and, for the per-type host hint, from app.js), so a renamed section would silently land readers at the top @@ -148,7 +161,7 @@ def test_manual_deep_links_resolve_in_both_manuals(): anchors |= set(re.findall(r'"([a-z0-9-]+)"', line)) assert "token-pbs" in anchors and "token-pve" in anchors, "host hint links not detected" - for manual in ("manual.html", "handbuch.html"): + for manual in sorted(_manual_files()): have = set(re.findall(r'id="([a-z0-9-]+)"', (WEB / manual).read_text(encoding="utf-8"))) missing = sorted(a for a in anchors if a not in have) assert not missing, f"{manual} has no section for: {missing}"