Neustart: auf das Herunterfahren im Gast warten statt es zu erzwingen

ACPI klappt bei Linux, bei Windows haeufig nicht - dort blockiert gern eine
Anwendung den Vorgang oder der Shutdown Event Tracker fragt nach einem Grund.
Deshalb gibt es jetzt die Wahl:

    Ich fahre im Gast herunter - pvesnap wartet und startet dann
    Proxmox herunterfahren lassen (ACPI) - Windows blockt das oft
    Gar nicht - spaeter selbst

Der erste Weg ist der verlaessliche: pvesnap zeigt, was im Gast zu tun ist,
verfolgt den Zustand und startet die Maschine von selbst wieder, sobald sie
aus ist. Abbrechen geht jederzeit, dann laeuft sie einfach weiter und die
Aenderung greift beim naechsten Start.

Dieselbe Falle steckte in der Taste h: shutdown_guest lief mit force=True und
hat den Gast nach 180 Sekunden hart abgeschaltet - ein Stromausfall, still und
ohne Rueckfrage. Die Vorgabe ist jetzt force=False, und bei Widerstand kommt
dieselbe Wahl: warten, hart ausschalten (mit ausdruecklicher Bestaetigung) oder
abbrechen.

Geprueft: der Wartebildschirm erkennt das Herunterfahren und meldet es zurueck,
der Auswahldialog zeigt alle drei Wege.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-08-09 11:01:47 +02:00
co-authored by Claude Opus 5
parent 8787304f0b
commit fb3be26c33
2 changed files with 118 additions and 15 deletions
+110 -13
View File
@@ -19,6 +19,7 @@ import curses
import logging
import os
import sys
import time
from datetime import datetime
from . import __version__
@@ -549,8 +550,7 @@ def _details(stdscr, proxmox, instance):
_do(stdscr, "Starte %s ..." % instance.label,
lambda: start(proxmox, instance))
elif key in ("h", "H"):
_do(stdscr, "Fahre %s herunter ..." % instance.label,
lambda: proxmox.shutdown_guest(instance.guest))
_herunterfahren(stdscr, proxmox, instance)
elif key in ("e", "E"):
_swap(stdscr, proxmox, instance, steckplaetze)
elif key in ("v", "V"):
@@ -564,6 +564,32 @@ def _details(stdscr, proxmox, instance):
return
def _herunterfahren(stdscr, proxmox, instance):
"""Sauber herunterfahren - und bei Widerstand nicht einfach zuschlagen."""
_wait_screen(stdscr, "Fahre %s herunter ..." % instance.label)
try:
proxmox.shutdown_guest(instance.guest)
return
except (ProxmoxError, RecoveryError) as exc:
grund = str(exc)
weg = choose(stdscr, "%s faehrt nicht herunter" % instance.label, [
("warten", "Im Gast selbst herunterfahren - pvesnap wartet"),
("hart", "Hart ausschalten - wie Stecker ziehen"),
(None, "Abbrechen, laeuft weiter"),
])
if weg == "warten":
if _warte_bis_aus(stdscr, proxmox, instance):
message(stdscr, "%s ist aus." % instance.label)
return
if weg == "hart":
if confirm(stdscr, "Wirklich hart ausschalten? Daten koennen verloren gehen."):
_do(stdscr, "Schalte %s aus ..." % instance.label,
lambda: proxmox.stop_guest(instance.guest))
return
message(stdscr, grund, error=True)
def _do(stdscr, text, action):
_wait_screen(stdscr, text)
try:
@@ -575,6 +601,81 @@ def _do(stdscr, text, action):
message(stdscr, hint, error=True)
def _warte_bis_aus(stdscr, proxmox, instance, minuten=30):
"""Warten, bis der Gast von innen heruntergefahren ist.
Der verlaesslichere Weg als ACPI: Windows ignoriert den Aus-Knopf gern,
wenn eine Anwendung den Herunterfahrvorgang blockiert oder der Shutdown
Event Tracker nach einem Grund fragt. Wer im Gast selbst herunterfaehrt,
umgeht das - und pvesnap sieht am Zustand, wann es soweit ist.
"""
curses.halfdelay(20) # get_wch wartet hoechstens 2s
begonnen = time.time()
try:
while time.time() - begonnen < minuten * 60:
try:
zustand = proxmox.guest_status(instance.guest).get("status") or "?"
except ProxmoxError:
zustand = "?"
if zustand != "running":
return True
height, width = stdscr.getmaxyx()
stdscr.erase()
fill(stdscr, 0, " Warte auf das Herunterfahren von %s" % instance.label,
curses.color_pair(C_HEADER) | curses.A_BOLD)
zeilen = [
"Bitte jetzt IM GAST herunterfahren.",
"",
" Windows Start -> Ein/Aus -> Herunterfahren",
" Linux poweroff bzw. shutdown -h now",
"",
"Sobald die Maschine aus ist, startet pvesnap sie von selbst",
"wieder - dann mit der neuen Ausstattung.",
"",
"Zustand: %s seit %d:%02d"
% (zustand, int(time.time() - begonnen) // 60,
int(time.time() - begonnen) % 60),
]
for zeile, text in enumerate(zeilen):
put(stdscr, zeile + 2, 3, truncate(text, width - 6),
curses.A_BOLD if zeile == 0 else 0)
fill(stdscr, height - 1, " q oder Esc = Abbrechen (die Maschine "
"laeuft dann einfach weiter)", curses.color_pair(C_FOOTER))
stdscr.refresh()
key = read_key(stdscr)
if key in ("q", "Q") or is_escape(key):
return False
finally:
curses.cbreak() # halbe Verzoegerung zuruecknehmen
return False
def _neu_starten(stdscr, proxmox, instance):
"""Neustart, damit vorgemerkte Aenderungen wirksam werden."""
weg = choose(stdscr, "Wie soll %s neu starten?" % instance.label, [
("warten", "Ich fahre im Gast herunter - pvesnap wartet und startet dann"),
("acpi", "Proxmox herunterfahren lassen (ACPI) - Windows blockt das oft"),
(None, "Gar nicht - spaeter selbst"),
])
if weg is None:
message(stdscr, "Eingetragen - wirksam beim naechsten Start.")
return
if weg == "acpi":
_do(stdscr, "Fahre %s herunter und wieder hoch ..." % instance.label,
lambda: proxmox.reboot_guest(instance.guest))
return
if not _warte_bis_aus(stdscr, proxmox, instance):
message(stdscr, "Abgebrochen - die Aenderung greift beim naechsten "
"Start. Dann hier mit s starten.")
return
_do(stdscr, "Ist aus - starte %s wieder ..." % instance.label,
lambda: start(proxmox, instance))
def _display(stdscr, proxmox, instance):
"""SPICE-Anzeige und USB-Weiterleitung nachtraeglich einstellen."""
if instance.type != "qemu":
@@ -604,9 +705,10 @@ def _display(stdscr, proxmox, instance):
warnung = ["Dafuer muss %s neu starten - die Grafikkarte laesst sich "
"im Betrieb nicht wechseln." % instance.label,
"",
"Der Neustart laeuft sauber: Proxmox faehrt den Gast per ACPI "
"herunter, er haengt seine Dateisysteme selbst aus, danach "
"geht er wieder hoch. Kein Stromausfall.",
"Gleich kannst du waehlen, wie: entweder du faehrst im Gast "
"selbst herunter und pvesnap startet ihn danach wieder, oder "
"Proxmox versucht es per ACPI. Der erste Weg ist der "
"verlaesslichere - Windows ignoriert den Aus-Knopf gern.",
"",
"Ein Neustart von innen im Gast wuerde nichts bringen - dabei "
"setzt sich nur die Maschine zurueck, der QEMU-Prozess laeuft "
@@ -633,12 +735,8 @@ def _display(stdscr, proxmox, instance):
if not geaendert:
message(stdscr, "War schon so eingestellt.")
return
if neustart and confirm(stdscr, "%s jetzt sauber neu starten?" % instance.label):
_do(stdscr, "Fahre %s herunter und wieder hoch ..." % instance.label,
lambda: proxmox.reboot_guest(instance.guest))
elif neustart:
message(stdscr, "Eingetragen - wirksam beim naechsten Start. Im Gast "
"herunterfahren, dann hier mit s starten.")
if neustart:
_neu_starten(stdscr, proxmox, instance)
else:
message(stdscr, "Uebernommen.")
@@ -796,8 +894,7 @@ def _overview(stdscr, proxmox):
lambda: start(proxmox, current))
reload_ = True
elif key in ("h", "H"):
_do(stdscr, "Fahre %s herunter ..." % current.label,
lambda: proxmox.shutdown_guest(current.guest))
_herunterfahren(stdscr, proxmox, current)
reload_ = True
elif key in ("x", "X"):
_destroy(stdscr, proxmox, current)