SPICE-Anzeige und USB-Weiterleitung fuer Wiederherstellungen

Der Anlass: ein Software-Schutzmodul am USB-Port. Normalerweise haengt so ein
Dongle an einem USB-Server im Netz - eine abgeschottete Wiederherstellung hat
aber keins. Ueber SPICE kommt das Geraet vom Rechner des Bedieners, nicht ueber
das Gastnetz. Die Maschine bleibt also ohne Netzwerk und hat trotzdem ihren
Dongle.

  --spice        SPICE-Anzeige (vga: qxl), zusaetzlich zu noVNC
  --usb <n>      so viele USB-Weiterleitungen
  --iso <volid>  Abbild als CD einlegen
  spice <vmid>   Verbindungsdatei fuer remote-viewer (.vv)

Zwei Dinge zeigte erst der Test, beide haetten die Sache still unbrauchbar
gemacht:

  vga: qxl zerstoert den geladenen Arbeitsspeicher. qxl hat 64 MB
  Grafikspeicher, die Vorgabe 16 - beim Laden kommt "Size mismatch:
  vga.vram", und die Maschine bleibt angehalten stehen. Wieder mit TASK OK.
  Wird jetzt abgelehnt, mit den drei Auswegen in der Meldung. Der dauerhafte
  ist, am Original 'vga: qxl' zu setzen - dann tragen alle kuenftigen
  Snapshots es mit und beides geht gleichzeitig.

  USB ohne SPICE ist wirkungslos. QEMU legt die usbredir-Geraete zwar an, aber
  ohne SPICE-Server gibt es keinen Kanal dafuer - spiceproxy sagt schlicht "no
  spice port". --usb schaltet die Anzeige deshalb mit ein.

Ausserdem korrigiert: in der .vv-Datei darf das Zertifikat nicht in echte
Zeilenumbrueche uebersetzt werden. virt-viewer erwartet die Zeichenfolge \n als
zwei Zeichen in einer einzigen Zeile; sonst steht nur die erste hinter "ca=".

tools/get-guest-tools.sh holt virtio-win auf einen ISO-Storage. Fuer den
Dongle wird davon nichts gebraucht - die Tools sind nur fuer Zwischenablage,
Aufloesung und Mauszeiger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-08-03 12:52:00 +02:00
co-authored by Claude Opus 5
parent a1076c6641
commit e88a80cb25
3 changed files with 290 additions and 1 deletions
+64 -1
View File
@@ -27,7 +27,8 @@ from .curses_util import (C_DIM, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL, C_WARN
keybar, message, prompt, put, read_key)
from .proxmox import Proxmox, ProxmoxError, pvesh_available
from .recovery import (RecoveryError, Spec, create,
eject_transfer, insert_transfer, transfer_state,
eject_transfer, insert_transfer, spice_file,
transfer_state,
destroy, find_orphans, flatten, flatten_cost, flatten_hint,
human_bytes, linked_volumes, list_instances, plan,
remove_orphans, start)
@@ -165,6 +166,13 @@ def _fields(guest):
if guest.type == "qemu":
rows.append(("resume", "Arbeitsspeicher", "choice", RESUME_CHOICES))
rows.append(("transfers", "Transfer-Laufwerke", "transfers", ()))
if guest.type == "qemu":
rows.append(("spice", "SPICE-Anzeige", "choice",
((False, "nein - nur noVNC im Browser"),
(True, "ja - zusaetzlich remote-viewer"))))
rows.append(("usb", "USB-Weiterleitung", "choice",
((0, "keine"), (1, "1 Anschluss"), (2, "2 Anschluesse"),
(4, "4 Anschluesse"))))
rows += [
("newid", "Neue VMID", "text", ()),
("node", "Node", "text", ()),
@@ -214,6 +222,10 @@ def _options(win, guest, spec):
"sie standen - ohne Crash-Recovery.",
"transfers": "Vorbereitete Austauschmedien. Anlegen und befuellen "
"in der Uebersicht mit v .",
"spice": "Zweiter Zugang ueber remote-viewer. Achtung: vertraegt "
"sich nicht mit geladenem Arbeitsspeicher.",
"usb": "Ein USB-Geraet vom eigenen Rechner in den Gast reichen - "
"etwa einen Dongle. Laeuft ueber SPICE, nicht ueber das Netz.",
"newid": "VMID der neuen Maschine.",
"node": "Auf welchem Node sie laufen soll.",
"start": "Sofort hochfahren.",
@@ -321,6 +333,14 @@ def summary_lines(plan_):
% (human_bytes(plan_.vmstate_bytes) or "?")
if plan_.resume else "vorhanden, wird aber nicht geladen"),
curses.color_pair(C_OK) if plan_.resume else 0))
if plan_.spice:
lines.append(("Anzeige: noVNC und SPICE (vga: qxl)",
curses.color_pair(C_OK)))
if plan_.usb:
lines.append(("USB: %d Weiterleitung(en) ueber SPICE" % plan_.usb,
curses.color_pair(C_OK)))
if plan_.iso:
lines.append(("CD: %s" % plan_.iso, 0))
for volume in plan_.transfers:
lines.append(("Transfer: %-14s %8s im Gast als %s"
% (volume.name, human_bytes(volume.size), volume.label),
@@ -449,6 +469,9 @@ def _details(stdscr, proxmox, instance):
if instance.type == "lxc":
lines.append((" oder auf dem Host: pct enter %d" % instance.vmid,
curses.color_pair(C_MARK)))
else:
lines.append((" SPICE (remote-viewer): pvesnap-recovery spice %d"
% instance.vmid, curses.color_pair(C_MARK)))
if instance.resumed:
lines.append(("", 0))
@@ -713,6 +736,9 @@ def _spec_from_args(args, mode):
spec.net = args.net or ""
spec.resume = args.resume
spec.transfers = list(args.transfer or [])
spec.spice = bool(getattr(args, "spice", False))
spec.usb = int(getattr(args, "usb", 0) or 0)
spec.iso = getattr(args, "iso", "") or ""
spec.memory = args.memory or 0
spec.cores = args.cores or 0
spec.name = args.name or ""
@@ -877,6 +903,28 @@ def cmd_cleanup(proxmox, args):
return 1 if failed else 0
def cmd_spice(proxmox, args):
instance = _find_instance(proxmox, args.vmid)
text = spice_file(proxmox, instance)
if args.output:
with open(args.output, "w", encoding="utf-8") as handle:
handle.write(text)
os.chmod(args.output, 0o600)
print(args.output)
print("\n Auf dem eigenen Rechner oeffnen:", file=sys.stderr)
print(" scp root@%s:%s . && remote-viewer %s"
% (instance.node, args.output, os.path.basename(args.output)),
file=sys.stderr)
else:
sys.stdout.write(text)
print("\n Umleiten und mit remote-viewer oeffnen:", file=sys.stderr)
print(" pvesnap-recovery spice %d -o vm.vv" % instance.vmid,
file=sys.stderr)
print(" Das Kennwort darin ist ein Einmal-Kennwort und laeuft bald ab.",
file=sys.stderr)
return 0
def cmd_console(proxmox, args):
instance = _find_instance(proxmox, args.vmid)
print(instance.console_url())
@@ -911,6 +959,14 @@ def _add_create_options(parser):
help="Arbeitsspeicher des Snapshots laden")
parser.add_argument("--no-resume", dest="resume", action="store_false",
help="kalt starten, auch wenn der Snapshot RAM enthaelt")
parser.add_argument("--spice", action="store_true",
help="SPICE-Anzeige einschalten (vga: qxl) - vertraegt "
"sich nicht mit geladenem Arbeitsspeicher")
parser.add_argument("--usb", type=int, default=0, metavar="ANZAHL",
help="so viele USB-Weiterleitungen ueber SPICE, z.B. "
"fuer einen Dongle (geht auch mit RAM-Zustand)")
parser.add_argument("--iso", metavar="VOLID",
help="Abbild als CD einlegen, z.B. local:iso/virtio-win.iso")
parser.add_argument("--transfer", action="append", metavar="NAME", default=[],
help="vorbereitetes Transfer-Laufwerk anhaengen "
"(mehrfach moeglich)")
@@ -978,6 +1034,12 @@ def build_parser():
sub = subs.add_parser(name, help=helptext)
sub.add_argument("vmid", type=int)
spice = subs.add_parser("spice",
help="SPICE-Verbindungsdatei fuer remote-viewer")
spice.add_argument("vmid", type=int)
spice.add_argument("-o", "--output", metavar="DATEI",
help="hineinschreiben statt auf die Ausgabe")
flatten_ = subs.add_parser(
"flatten", help="vom Quell-Snapshot loesen - fuer den Dauerbetrieb")
flatten_.add_argument("vmid", type=int)
@@ -1020,6 +1082,7 @@ def main(argv=None):
"flatten": lambda: cmd_flatten(proxmox, args),
"cleanup": lambda: cmd_cleanup(proxmox, args),
"console": lambda: cmd_console(proxmox, args),
"spice": lambda: cmd_spice(proxmox, args),
"start": lambda: cmd_start(proxmox, args),
"stop": lambda: cmd_stop(proxmox, args),
}