SPICE und USB lassen sich nachtraeglich einstellen
Bisher liess sich beides nur beim Anlegen waehlen. Wer erst spaeter merkt, dass
er den Dongle braucht, musste die Maschine wegwerfen und neu bauen. Jetzt geht
es in der Detailansicht auf Taste p, mit dem Ist-Zustand im Dialog.
Was dabei geht und was nicht, ist am laufenden System durchgemessen:
SPICE ein/aus Neustart noetig - die Grafikkarte laesst sich einer
laufenden Maschine nicht unterschieben
USB dazu/weg sofort, solange der SPICE-Kanal schon steht
USB ohne SPICE schaltet SPICE mit ein, sonst waeren die Anschluesse
wirkungslos
Vor einem noetigen Neustart wird gewarnt, und bei einer Maschine mit geladenem
Arbeitsspeicher besonders deutlich: der ist danach endgueltig weg, weil
Proxmox ihn schon beim ersten Start freigibt. Ein zweiter startet kalt, offene
Programme und ungespeicherte Daten sind verloren.
Beim Abschalten wird der vga-Eintrag entfernt statt auf "std" gesetzt - dann
steht die Maschine wieder genau so da wie vorher, ohne eine Zeile, die es nie
gab.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
56003a71ab
commit
91c23ce2c0
@@ -1242,6 +1242,84 @@ def spice_file(proxmox, instance):
|
|||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
USB_KEY = re.compile(r"^usb\d+$")
|
||||||
|
|
||||||
|
|
||||||
|
def display_state(proxmox, instance):
|
||||||
|
"""(SPICE an?, Anzahl USB-Weiterleitungen) aus der Konfiguration."""
|
||||||
|
try:
|
||||||
|
config = proxmox.guest_config(instance.guest)
|
||||||
|
except ProxmoxError:
|
||||||
|
return False, 0
|
||||||
|
spice = str(config.get("vga") or "").startswith("qxl")
|
||||||
|
usb = sum(1 for key, value in config.items()
|
||||||
|
if USB_KEY.match(key) and str(value).strip().lower() == "spice")
|
||||||
|
return spice, usb
|
||||||
|
|
||||||
|
|
||||||
|
def set_display(proxmox, instance, spice, usb, progress=None):
|
||||||
|
"""SPICE-Anzeige und USB-Weiterleitung nachtraeglich einstellen.
|
||||||
|
|
||||||
|
Liefert (geaendert, neustart_noetig). Die Anzeige selbst laesst sich nur
|
||||||
|
beim Start festlegen - qxl bringt eine andere Grafikkarte mit, die kann
|
||||||
|
man einer laufenden Maschine nicht unterschieben. USB-Anschluesse dagegen
|
||||||
|
sind steckbar, solange der SPICE-Kanal schon steht.
|
||||||
|
"""
|
||||||
|
def step(text):
|
||||||
|
log.info("%s", text)
|
||||||
|
if progress:
|
||||||
|
progress(text)
|
||||||
|
|
||||||
|
if instance.type != "qemu":
|
||||||
|
raise RecoveryError("SPICE und USB-Weiterleitung gibt es nur bei "
|
||||||
|
"virtuellen Maschinen, nicht bei Containern.")
|
||||||
|
usb = max(0, min(14, int(usb or 0)))
|
||||||
|
if usb and not spice:
|
||||||
|
spice = True
|
||||||
|
step("SPICE wird mit eingeschaltet - ohne den Kanal waeren die "
|
||||||
|
"Anschluesse wirkungslos")
|
||||||
|
|
||||||
|
config = proxmox.guest_config(instance.guest)
|
||||||
|
hat_spice = str(config.get("vga") or "").startswith("qxl")
|
||||||
|
vorhanden = sorted(key for key, value in config.items()
|
||||||
|
if USB_KEY.match(key) and str(value).strip().lower() == "spice")
|
||||||
|
|
||||||
|
aenderungen = {}
|
||||||
|
entfernen = []
|
||||||
|
if spice and not hat_spice:
|
||||||
|
aenderungen["vga"] = "qxl"
|
||||||
|
elif not spice and hat_spice:
|
||||||
|
# Eintrag entfernen statt auf "std" setzen: dann steht die Maschine
|
||||||
|
# wieder genau so da wie vorher, ohne eine Zeile, die es nie gab.
|
||||||
|
entfernen.append("vga")
|
||||||
|
entfernen.extend(vorhanden) # ohne Kanal sind sie sinnlos
|
||||||
|
vorhanden = []
|
||||||
|
|
||||||
|
if spice:
|
||||||
|
for _ in range(usb - len(vorhanden)):
|
||||||
|
slot = _free_usb_slot(dict(config, **aenderungen))
|
||||||
|
aenderungen[slot] = "spice"
|
||||||
|
config = dict(config, **{slot: "spice"})
|
||||||
|
entfernen.extend(vorhanden[usb:])
|
||||||
|
|
||||||
|
if not aenderungen and not entfernen:
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
if entfernen:
|
||||||
|
step("Entferne %s" % ", ".join(entfernen))
|
||||||
|
proxmox.set_guest_config(instance.guest, {"delete": ",".join(entfernen)})
|
||||||
|
if aenderungen:
|
||||||
|
step("Setze %s" % ", ".join("%s=%s" % kv for kv in sorted(aenderungen.items())))
|
||||||
|
proxmox.set_guest_config(instance.guest, aenderungen)
|
||||||
|
|
||||||
|
# Die Grafikkarte wechselt erst beim naechsten Start. USB-Anschluesse
|
||||||
|
# stecken sofort, aber nur wenn der SPICE-Kanal schon lief.
|
||||||
|
laeuft = proxmox.guest_status(instance.guest).get("status") == "running"
|
||||||
|
neustart = laeuft and ("vga" in aenderungen or "vga" in entfernen
|
||||||
|
or (spice and not hat_spice))
|
||||||
|
return True, neustart
|
||||||
|
|
||||||
|
|
||||||
def _default_name(guest, plan_):
|
def _default_name(guest, plan_):
|
||||||
base = re.sub(r"[^A-Za-z0-9-]", "-", guest.name or "vm%d" % guest.vmid)[:40]
|
base = re.sub(r"[^A-Za-z0-9-]", "-", guest.name or "vm%d" % guest.vmid)[:40]
|
||||||
return ("%s-wdh" % base).strip("-")
|
return ("%s-wdh" % base).strip("-")
|
||||||
|
|||||||
+65
-2
@@ -30,7 +30,8 @@ from . import transfer, transfer_ui
|
|||||||
from .recovery import (RecoveryError, Spec, create, destroy, eject_transfer,
|
from .recovery import (RecoveryError, Spec, create, destroy, eject_transfer,
|
||||||
find_orphans, flatten, flatten_cost, flatten_hint,
|
find_orphans, flatten, flatten_cost, flatten_hint,
|
||||||
human_bytes, insert_transfer, linked_volumes,
|
human_bytes, insert_transfer, linked_volumes,
|
||||||
list_instances, node_address, plan, remove_orphans,
|
display_state, list_instances, node_address, plan,
|
||||||
|
remove_orphans, set_display,
|
||||||
spice_file, start, transfer_state)
|
spice_file, start, transfer_state)
|
||||||
from .snapfs import list_snapshots
|
from .snapfs import list_snapshots
|
||||||
from .util import truncate
|
from .util import truncate
|
||||||
@@ -526,7 +527,7 @@ def _details(stdscr, proxmox, instance):
|
|||||||
# ausgeworfenes Laufwerk beim Host liegt und genau jetzt befuellt
|
# ausgeworfenes Laufwerk beim Host liegt und genau jetzt befuellt
|
||||||
# werden will.
|
# werden will.
|
||||||
footer = ("s Start | h Stop | e Auswerfen/Einklinken | v Laufwerke | "
|
footer = ("s Start | h Stop | e Auswerfen/Einklinken | v Laufwerke | "
|
||||||
"f Loesen | x Verwerfen | q zurueck")
|
"p SPICE/USB | f Loesen | x Verwerfen | q zurueck")
|
||||||
key = _pages(stdscr, lines, "Wiederherstellung %s" % instance.label, footer)
|
key = _pages(stdscr, lines, "Wiederherstellung %s" % instance.label, footer)
|
||||||
|
|
||||||
if key in ("q", "\x1b") or is_escape(key):
|
if key in ("q", "\x1b") or is_escape(key):
|
||||||
@@ -541,6 +542,8 @@ def _details(stdscr, proxmox, instance):
|
|||||||
_swap(stdscr, proxmox, instance, steckplaetze)
|
_swap(stdscr, proxmox, instance, steckplaetze)
|
||||||
elif key in ("v", "V"):
|
elif key in ("v", "V"):
|
||||||
transfer_ui.screen(stdscr, browse=_browse_transfer)
|
transfer_ui.screen(stdscr, browse=_browse_transfer)
|
||||||
|
elif key in ("p", "P"):
|
||||||
|
_display(stdscr, proxmox, instance)
|
||||||
elif key in ("f", "F"):
|
elif key in ("f", "F"):
|
||||||
_flatten(stdscr, proxmox, instance, linked)
|
_flatten(stdscr, proxmox, instance, linked)
|
||||||
elif key in ("x", "X"):
|
elif key in ("x", "X"):
|
||||||
@@ -559,6 +562,66 @@ def _do(stdscr, text, action):
|
|||||||
message(stdscr, hint, error=True)
|
message(stdscr, hint, error=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _display(stdscr, proxmox, instance):
|
||||||
|
"""SPICE-Anzeige und USB-Weiterleitung nachtraeglich einstellen."""
|
||||||
|
if instance.type != "qemu":
|
||||||
|
message(stdscr, "Bei Containern gibt es kein SPICE - das ist eine "
|
||||||
|
"reine Sache virtueller Maschinen.", error=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
spice, usb = display_state(proxmox, instance)
|
||||||
|
ziel = choose(stdscr, "SPICE-Anzeige (jetzt: %s)" % ("ein" if spice else "aus"),
|
||||||
|
[(True, "ein - zusaetzlich remote-viewer, noetig fuer USB"),
|
||||||
|
(False, "aus - nur noVNC im Browser")])
|
||||||
|
if ziel is None:
|
||||||
|
return
|
||||||
|
anzahl = usb
|
||||||
|
if ziel:
|
||||||
|
anzahl = choose(stdscr, "USB-Weiterleitung (jetzt: %d)" % usb,
|
||||||
|
[(0, "keine"), (1, "1 Anschluss"), (2, "2 Anschluesse"),
|
||||||
|
(4, "4 Anschluesse")])
|
||||||
|
if anzahl is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
laeuft = proxmox.guest_status(instance.guest).get("status") == "running"
|
||||||
|
if laeuft and ziel != spice:
|
||||||
|
# Die Grafikkarte laesst sich nur beim Start wechseln. Und der
|
||||||
|
# geladene Arbeitsspeicher ist danach weg - Proxmox gibt ihn schon
|
||||||
|
# beim ersten Start frei, ein zweiter startet also kalt.
|
||||||
|
warnung = ["Dafuer muss %s neu starten - die Grafikkarte laesst sich "
|
||||||
|
"im Betrieb nicht wechseln." % instance.label]
|
||||||
|
if instance.resumed:
|
||||||
|
warnung.append("ACHTUNG: der geladene Arbeitsspeicher ist danach "
|
||||||
|
"endgueltig weg. Die Maschine bootet kalt, offene "
|
||||||
|
"Programme und ungespeicherte Daten sind verloren.")
|
||||||
|
warnung.append("Im Gast vorher sauber herunterfahren, was noch laeuft.")
|
||||||
|
key = _pages(stdscr, [(line, curses.color_pair(C_WARN) | curses.A_BOLD)
|
||||||
|
for eintrag in warnung for line in _wrap(eintrag, 74)],
|
||||||
|
"Neustart noetig", "j = trotzdem | andere Taste = abbrechen")
|
||||||
|
if key not in ("j", "J", "y", "Y"):
|
||||||
|
return
|
||||||
|
|
||||||
|
progress = _Progress(stdscr, "Stelle Anzeige um")
|
||||||
|
try:
|
||||||
|
geaendert, neustart = set_display(proxmox, instance, ziel, anzahl,
|
||||||
|
progress=progress)
|
||||||
|
except (RecoveryError, ProxmoxError) as exc:
|
||||||
|
_pages(stdscr, [(line, curses.color_pair(C_WARN))
|
||||||
|
for line in _wrap(str(exc), 74)], "Fehlgeschlagen")
|
||||||
|
return
|
||||||
|
if not geaendert:
|
||||||
|
message(stdscr, "War schon so eingestellt.")
|
||||||
|
return
|
||||||
|
if neustart and confirm(stdscr, "%s jetzt neu starten?" % instance.label):
|
||||||
|
_do(stdscr, "Starte %s neu ..." % instance.label,
|
||||||
|
lambda: (proxmox.stop_guest(instance.guest),
|
||||||
|
start(proxmox, instance))[1])
|
||||||
|
elif neustart:
|
||||||
|
message(stdscr, "Eingetragen - wirksam beim naechsten Start.")
|
||||||
|
else:
|
||||||
|
message(stdscr, "Uebernommen.")
|
||||||
|
|
||||||
|
|
||||||
def _swap(stdscr, proxmox, instance, steckplaetze):
|
def _swap(stdscr, proxmox, instance, steckplaetze):
|
||||||
"""Transfer-Laufwerk im laufenden Betrieb abziehen oder wieder einklinken."""
|
"""Transfer-Laufwerk im laufenden Betrieb abziehen oder wieder einklinken."""
|
||||||
wechselbar = [(e, drin) for e, drin in steckplaetze if e.get("kind") != "bind"]
|
wechselbar = [(e, drin) for e, drin in steckplaetze if e.get("kind") != "bind"]
|
||||||
|
|||||||
Reference in New Issue
Block a user