Transfer-Laufwerke: nur noch exFAT, Oberflaeche und Anbindung
Ein einziges Dateisystem, mit Absicht: exFAT koennen Windows und Linux beide
von Haus aus, es kennt keine Besitzrechte auf dem Datentraeger und hat anders
als FAT32 keine 4-GB-Grenze je Datei. Die Auswahl faellt damit weg - eine
Entscheidung weniger, wenn es schnell gehen muss.
Neu in der Oberflaeche:
Taste v Uebersicht der Laufwerke mit Zustand
Enter darauf haengt am Host ein und startet den Commander - links das
Laufwerk, rechts der eigene Rechner
n / u / l anlegen, aushaengen, loeschen
Optionsmaske Mehrfachauswahl statt Groessenangabe
--transfer NAME dasselbe auf der Kommandozeile, mehrfach moeglich
Am laufenden System durchgespielt: Laufwerk angelegt, vom Host ein Skript
hineingelegt, Wiederherstellung damit gestartet, im Gast gelesen, ausgefuehrt
und geschrieben, Wiederherstellung verworfen - das Laufwerk lebte samt
Ergebnis weiter.
Zwei Rechtefallen dabei gefunden und geschlossen:
Der Gast konnte lesen, aber nicht schreiben. exFAT hat keine Besitzrechte
auf der Platte, die entstehen beim Einhaengen - mit der Vorgabe gehoert
alles root, und der root eines unprivilegierten Containers ist auf dem Host
uid 100000. Wird jetzt mit umask=0000 eingehaengt.
Ein durchgereichtes Laufwerk galt als "am Host eingehaengt", weil beim
Container genau das der Fall ist. Wer das sah, haette es aushaengen und dem
laufenden Gast den Boden wegziehen koennen. Die Belegung wird jetzt auch aus
Container-Konfigurationen gelesen, der Gast hat Vorrang in der Anzeige, und
Aushaengen wird abgelehnt, solange jemand darauf arbeitet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6279a20d4a
commit
2b895e6f4f
+49
-4
@@ -35,6 +35,7 @@ import subprocess
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from . import transfer
|
||||
from .proxmox import Guest, ProxmoxError
|
||||
|
||||
log = logging.getLogger("pvesnap.recovery")
|
||||
@@ -477,6 +478,7 @@ class Spec:
|
||||
exchange_storage: str = ""
|
||||
exchange_fs: str = DEFAULT_EXCHANGE_FS
|
||||
exchange_dir: str = "" # nur LXC: vorhandenes Host-Verzeichnis
|
||||
transfers: list = field(default_factory=list) # Namen vorhandener Laufwerke
|
||||
memory: int = 0 # 0 = wie im Snapshot
|
||||
cores: int = 0
|
||||
name: str = ""
|
||||
@@ -504,6 +506,7 @@ class Plan:
|
||||
resume: bool = False
|
||||
exchange_kind: str = "" # "disk" | "bind" | ""
|
||||
exchange_detail: str = ""
|
||||
transfers: list = field(default_factory=list) # [transfer.Volume]
|
||||
warnings: list = field(default_factory=list)
|
||||
# Was nicht nur unschoen, sondern gefaehrlich ist. Hierfuer genuegt ein
|
||||
# beilaeufiges "ja" nicht - siehe cmd_create() und die Oberflaeche.
|
||||
@@ -531,6 +534,7 @@ class Instance:
|
||||
volumes: list = field(default_factory=list)
|
||||
protected: list = field(default_factory=list) # [[volid, snapname]]
|
||||
exchange: dict = field(default_factory=dict)
|
||||
transfers: list = field(default_factory=list)
|
||||
resumed: bool = False
|
||||
status: str = ""
|
||||
|
||||
@@ -555,14 +559,15 @@ class Instance:
|
||||
def to_dict(self):
|
||||
return {k: getattr(self, k) for k in
|
||||
("vmid", "type", "node", "name", "source", "source_node", "snapshot",
|
||||
"mode", "created", "volumes", "protected", "exchange", "resumed")}
|
||||
"mode", "created", "volumes", "protected", "exchange", "transfers",
|
||||
"resumed")}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
known = {k: data.get(k) for k in
|
||||
("vmid", "type", "node", "name", "source", "source_node", "snapshot",
|
||||
"mode", "created", "volumes", "protected", "exchange", "resumed")
|
||||
if data.get(k) is not None}
|
||||
"mode", "created", "volumes", "protected", "exchange", "transfers",
|
||||
"resumed") if data.get(k) is not None}
|
||||
known["vmid"] = int(known.get("vmid") or 0)
|
||||
return cls(**known)
|
||||
|
||||
@@ -796,6 +801,14 @@ def plan(proxmox, guest, snapname, spec):
|
||||
result.exchange_detail = "%s auf %s, %s, im Gast als weitere Platte" % (
|
||||
human_bytes(size_kb * 1024), storage, spec.exchange_fs)
|
||||
|
||||
# -- Transfer-Laufwerke ------------------------------------------------
|
||||
for name in spec.transfers:
|
||||
volume = transfer.get(name) # wirft, wenn es das nicht gibt
|
||||
if not volume.free:
|
||||
raise RecoveryError("Transfer-Laufwerk %r ist gerade %s."
|
||||
% (name, volume.state_text))
|
||||
result.transfers.append(volume)
|
||||
|
||||
# -- Hinweise ----------------------------------------------------------
|
||||
if result.dropped and not spec.keep_binds:
|
||||
result.notes.append("Nicht uebernommen werden Einbindungen von Host-"
|
||||
@@ -936,6 +949,23 @@ def create(proxmox, plan_, progress=None):
|
||||
exchange = {"kind": "bind", "path": host_dir, "key": slot,
|
||||
"own": not spec.exchange_dir}
|
||||
|
||||
# -- Transfer-Laufwerke --------------------------------------------
|
||||
attached = []
|
||||
for volume in plan_.transfers:
|
||||
if guest.type == "lxc":
|
||||
# Beim Container wird der Mountpunkt des Hosts durchgereicht.
|
||||
# Ein Bind-Mount ist dasselbe Dateisystem an zwei Stellen, kein
|
||||
# zweites Einhaengen - das ist unbedenklich.
|
||||
step("Reiche Transfer-Laufwerk %r durch" % volume.name)
|
||||
path = transfer.host_mount(volume.name)
|
||||
slot = _free_mp_slot(new_config)
|
||||
new_config[slot] = "%s,mp=/mnt/%s" % (path, volume.name)
|
||||
else:
|
||||
step("Haenge Transfer-Laufwerk %r an" % volume.name)
|
||||
slot = _free_disk_slot(new_config)
|
||||
new_config[slot] = transfer.drive_string(volume.name)
|
||||
attached.append(volume.name)
|
||||
|
||||
# -- restliche Anpassungen -----------------------------------------
|
||||
if spec.memory:
|
||||
new_config["memory"] = spec.memory
|
||||
@@ -957,7 +987,8 @@ def create(proxmox, plan_, progress=None):
|
||||
name=str(new_config.get("name") or new_config.get("hostname") or ""),
|
||||
source=guest.vmid, source_node=guest.node, snapshot=plan_.snapshot,
|
||||
mode=spec.mode, created=int(time.time()), volumes=created,
|
||||
protected=protected, exchange=exchange, resumed=plan_.resume)
|
||||
protected=protected, exchange=exchange, resumed=plan_.resume,
|
||||
transfers=attached)
|
||||
_registry_add(instance)
|
||||
return instance
|
||||
|
||||
@@ -980,6 +1011,12 @@ def create(proxmox, plan_, progress=None):
|
||||
pass
|
||||
for path in made_dirs:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
for volume in plan_.transfers:
|
||||
try:
|
||||
transfer.host_umount(volume.name)
|
||||
transfer.release(volume.name)
|
||||
except transfer.TransferError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
@@ -1458,6 +1495,14 @@ def destroy(proxmox, instance, progress=None, keep_snapshot_protection=False):
|
||||
except RecoveryError as exc:
|
||||
log.warning("Schutz von %s@%s blieb: %s", volid, snapname, exc)
|
||||
|
||||
for name in instance.transfers or []:
|
||||
step("Gebe Transfer-Laufwerk %r wieder frei" % name)
|
||||
try:
|
||||
transfer.host_umount(name)
|
||||
transfer.release(name)
|
||||
except transfer.TransferError as exc:
|
||||
log.warning("%s: %s", name, exc)
|
||||
|
||||
exchange = instance.exchange or {}
|
||||
if exchange.get("kind") == "bind" and exchange.get("own") and exchange.get("path"):
|
||||
step("Entferne Austauschverzeichnis %s" % exchange["path"])
|
||||
|
||||
Reference in New Issue
Block a user