updates
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
CLAUDE.md
|
||||||
+23
-24
@@ -61,6 +61,7 @@ class VMConfig:
|
|||||||
memory: int = 1024
|
memory: int = 1024
|
||||||
cpus: int = 2
|
cpus: int = 2
|
||||||
disk: str = ""
|
disk: str = ""
|
||||||
|
disk_fmt: str = "qcow2"
|
||||||
cdrom: str = ""
|
cdrom: str = ""
|
||||||
arch: str = "x86_64"
|
arch: str = "x86_64"
|
||||||
network: str = "user"
|
network: str = "user"
|
||||||
@@ -189,7 +190,7 @@ class VMManager:
|
|||||||
vm = self.vms.get(name)
|
vm = self.vms.get(name)
|
||||||
if not vm:
|
if not vm:
|
||||||
return "Not found"
|
return "Not found"
|
||||||
if vm.status == "running":
|
if vm.status in ("running", "paused"):
|
||||||
return "Stop VM first"
|
return "Stop VM first"
|
||||||
del self.vms[name]
|
del self.vms[name]
|
||||||
self._save()
|
self._save()
|
||||||
@@ -199,7 +200,7 @@ class VMManager:
|
|||||||
vm = self.vms.get(name)
|
vm = self.vms.get(name)
|
||||||
if not vm:
|
if not vm:
|
||||||
return "Not found"
|
return "Not found"
|
||||||
if vm.status == "running":
|
if vm.status in ("running", "paused"):
|
||||||
return "Stop VM first"
|
return "Stop VM first"
|
||||||
vm.config = cfg
|
vm.config = cfg
|
||||||
self._save()
|
self._save()
|
||||||
@@ -221,7 +222,7 @@ class VMManager:
|
|||||||
cmd += ["-drive", f"if=pflash,format=raw,readonly=on,file={ovmf}"]
|
cmd += ["-drive", f"if=pflash,format=raw,readonly=on,file={ovmf}"]
|
||||||
|
|
||||||
if cfg.disk:
|
if cfg.disk:
|
||||||
cmd += ["-drive", f"file={cfg.disk},format=qcow2,if=virtio"]
|
cmd += ["-drive", f"file={cfg.disk},format={cfg.disk_fmt},if=virtio"]
|
||||||
|
|
||||||
if cfg.cdrom:
|
if cfg.cdrom:
|
||||||
cmd += ["-cdrom", cfg.cdrom]
|
cmd += ["-cdrom", cfg.cdrom]
|
||||||
@@ -285,8 +286,8 @@ class VMManager:
|
|||||||
vm = self.vms.get(name)
|
vm = self.vms.get(name)
|
||||||
if not vm:
|
if not vm:
|
||||||
return "Not found"
|
return "Not found"
|
||||||
if vm.status == "running":
|
if vm.status in ("running", "paused"):
|
||||||
return "Already running"
|
return "Already running or paused"
|
||||||
cmd, sock = self.build_cmd(vm.config)
|
cmd, sock = self.build_cmd(vm.config)
|
||||||
try:
|
try:
|
||||||
Path(sock).unlink(missing_ok=True)
|
Path(sock).unlink(missing_ok=True)
|
||||||
@@ -610,7 +611,6 @@ class VMManager:
|
|||||||
# strip ANSI escape sequences (e.g. cursor movement, colour codes)
|
# strip ANSI escape sequences (e.g. cursor movement, colour codes)
|
||||||
import re as _re
|
import re as _re
|
||||||
text = _re.sub(r'\x1b\[[0-9;]*[A-Za-z]', '', text)
|
text = _re.sub(r'\x1b\[[0-9;]*[A-Za-z]', '', text)
|
||||||
text = _re.sub(r'\x1b\[[0-9;]*m', '', text)
|
|
||||||
text = text.replace("(qemu)", "").strip()
|
text = text.replace("(qemu)", "").strip()
|
||||||
return text or "(ok)"
|
return text or "(ok)"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -627,6 +627,7 @@ class VMManager:
|
|||||||
vm = self.vms.get(name)
|
vm = self.vms.get(name)
|
||||||
if vm:
|
if vm:
|
||||||
vm.status = "paused"
|
vm.status = "paused"
|
||||||
|
self._save_runtime()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def monitor_resume(self, name: str) -> str:
|
def monitor_resume(self, name: str) -> str:
|
||||||
@@ -636,6 +637,7 @@ class VMManager:
|
|||||||
vm = self.vms.get(name)
|
vm = self.vms.get(name)
|
||||||
if vm:
|
if vm:
|
||||||
vm.status = "running"
|
vm.status = "running"
|
||||||
|
self._save_runtime()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def monitor_reset(self, name: str) -> str:
|
def monitor_reset(self, name: str) -> str:
|
||||||
@@ -713,18 +715,13 @@ class VMManager:
|
|||||||
|
|
||||||
# probe with qemu-img info
|
# probe with qemu-img info
|
||||||
info = self.disk_info(disk_path)
|
info = self.disk_info(disk_path)
|
||||||
# build config — use detected format in extra_args if not qcow2
|
|
||||||
fmt = info.get("format", "qcow2") if "error" not in info else "qcow2"
|
fmt = info.get("format", "qcow2") if "error" not in info else "qcow2"
|
||||||
|
|
||||||
cfg = VMConfig(
|
cfg = VMConfig(
|
||||||
name = vm_name,
|
name = vm_name,
|
||||||
disk = disk_path,
|
disk = disk_path,
|
||||||
extra_args = f"-drive file={disk_path},format={fmt},if=virtio" if fmt != "qcow2" else "",
|
disk_fmt = fmt,
|
||||||
)
|
)
|
||||||
# if format is not qcow2, use raw drive and clear the standard disk field
|
|
||||||
if fmt != "qcow2":
|
|
||||||
cfg.disk = ""
|
|
||||||
cfg.extra_args = f"-drive file={disk_path},format={fmt},if=virtio"
|
|
||||||
|
|
||||||
self.vms[vm_name] = VMState(config=cfg)
|
self.vms[vm_name] = VMState(config=cfg)
|
||||||
self._save()
|
self._save()
|
||||||
@@ -1066,6 +1063,7 @@ def vm_form_modal(stdscr, cfg: Optional[VMConfig] = None) -> Optional[VMConfig]:
|
|||||||
memory = int(values["memory"] or 1024),
|
memory = int(values["memory"] or 1024),
|
||||||
cpus = int(values["cpus"] or 2),
|
cpus = int(values["cpus"] or 2),
|
||||||
disk = values["disk"].strip(),
|
disk = values["disk"].strip(),
|
||||||
|
disk_fmt = cfg.disk_fmt,
|
||||||
cdrom = values["cdrom"].strip(),
|
cdrom = values["cdrom"].strip(),
|
||||||
arch = values["arch"],
|
arch = values["arch"],
|
||||||
network = values["network"],
|
network = values["network"],
|
||||||
@@ -1382,7 +1380,7 @@ def disk_mgmt_modal(stdscr, mgr: VMManager, vm_state) -> str:
|
|||||||
else:
|
else:
|
||||||
if not cfg.disk:
|
if not cfg.disk:
|
||||||
cfg.disk = path_raw
|
cfg.disk = path_raw
|
||||||
mgr.update(cfg.name, cfg)
|
mgr._save()
|
||||||
msg = f"Created {path_raw} ({gb} GiB)"
|
msg = f"Created {path_raw} ({gb} GiB)"
|
||||||
info = _load_info()
|
info = _load_info()
|
||||||
|
|
||||||
@@ -1664,7 +1662,8 @@ def portfwd_modal(stdscr, mgr: VMManager, vm_state) -> str:
|
|||||||
|
|
||||||
def _save():
|
def _save():
|
||||||
cfg.portfwds = [dict(r) for r in rules]
|
cfg.portfwds = [dict(r) for r in rules]
|
||||||
mgr.update(cfg.name, cfg)
|
err = mgr.update(cfg.name, cfg)
|
||||||
|
return err
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
win.erase()
|
win.erase()
|
||||||
@@ -1734,8 +1733,10 @@ def portfwd_modal(stdscr, mgr: VMManager, vm_state) -> str:
|
|||||||
|
|
||||||
if _is_esc(ch):
|
if _is_esc(ch):
|
||||||
_flush_esc(win)
|
_flush_esc(win)
|
||||||
_save()
|
err = _save()
|
||||||
_close_modal(win, stdscr)
|
_close_modal(win, stdscr)
|
||||||
|
if err:
|
||||||
|
return f"Error: {err}"
|
||||||
return f"Saved {len(rules)} rule(s)." if rules else "No rules."
|
return f"Saved {len(rules)} rule(s)." if rules else "No rules."
|
||||||
|
|
||||||
elif ch == curses.KEY_UP:
|
elif ch == curses.KEY_UP:
|
||||||
@@ -2214,7 +2215,7 @@ class TUI:
|
|||||||
# tabs: 0=Info 1=Command 2=Console 3=Disk 4=Snapshots 5=Monitor
|
# tabs: 0=Info 1=Command 2=Console 3=Disk 4=Snapshots 5=Monitor
|
||||||
self.tab = 0
|
self.tab = 0
|
||||||
self.log_off = 0
|
self.log_off = 0
|
||||||
self.msg = "Ready | n=new Tab=switch tab q=quit"
|
self.msg = "Ready"
|
||||||
self.msg_ok = True
|
self.msg_ok = True
|
||||||
self.last_poll = 0.0
|
self.last_poll = 0.0
|
||||||
|
|
||||||
@@ -2254,7 +2255,8 @@ class TUI:
|
|||||||
("s","start"), ("k","stop"), ("F","force kill"),
|
("s","start"), ("k","stop"), ("F","force kill"),
|
||||||
("g","ACPI"), ("z","pause"), ("~","monitor ~"),
|
("g","ACPI"), ("z","pause"), ("~","monitor ~"),
|
||||||
("d","disk"), ("p","snaps"), ("f","portfwd"),
|
("d","disk"), ("p","snaps"), ("f","portfwd"),
|
||||||
("x","eject"), ("q","quit"),
|
("x","eject"),
|
||||||
|
("Tab","switch tab"), ("q","quit"),
|
||||||
]
|
]
|
||||||
hy = sh - len(hints) - 2
|
hy = sh - len(hints) - 2
|
||||||
for key, act in hints:
|
for key, act in hints:
|
||||||
@@ -2379,7 +2381,7 @@ class TUI:
|
|||||||
elif base < y0 + h and cfg.network == "user":
|
elif base < y0 + h and cfg.network == "user":
|
||||||
try:
|
try:
|
||||||
scr.addstr(base, x0, f"{'Port Fwds':<16}", curses.color_pair(6))
|
scr.addstr(base, x0, f"{'Port Fwds':<16}", curses.color_pair(6))
|
||||||
scr.addstr(base, x0 + 16, "(none — press F to add)", curses.color_pair(6))
|
scr.addstr(base, x0 + 16, "(none — press f to add)", curses.color_pair(6))
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -2445,7 +2447,7 @@ class TUI:
|
|||||||
try:
|
try:
|
||||||
scr.addstr(row, x0, "No disk configured.", curses.color_pair(6))
|
scr.addstr(row, x0, "No disk configured.", curses.color_pair(6))
|
||||||
row += 1
|
row += 1
|
||||||
scr.addstr(row, x0, "Press 'm' to open Disk Management and create one.",
|
scr.addstr(row, x0, "Press 'd' to open Disk Management and create one.",
|
||||||
curses.color_pair(8))
|
curses.color_pair(8))
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
@@ -2456,7 +2458,7 @@ class TUI:
|
|||||||
if not p.exists():
|
if not p.exists():
|
||||||
addrow("Status", "FILE NOT FOUND", curses.color_pair(5))
|
addrow("Status", "FILE NOT FOUND", curses.color_pair(5))
|
||||||
try:
|
try:
|
||||||
scr.addstr(row+1, x0, "Press 'm' > Create to make the disk image.",
|
scr.addstr(row+1, x0, "Press 'd' > Create to make the disk image.",
|
||||||
curses.color_pair(8))
|
curses.color_pair(8))
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
@@ -2481,7 +2483,7 @@ class TUI:
|
|||||||
try:
|
try:
|
||||||
scr.addstr(row, x0, "-" * min(w-1, 50), curses.color_pair(6))
|
scr.addstr(row, x0, "-" * min(w-1, 50), curses.color_pair(6))
|
||||||
row += 1
|
row += 1
|
||||||
scr.addstr(row, x0, "Press 'm' to manage: create / resize / convert / delete",
|
scr.addstr(row, x0, "Press 'd' to manage: create / resize / convert / delete",
|
||||||
curses.color_pair(8))
|
curses.color_pair(8))
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
@@ -2615,9 +2617,6 @@ class TUI:
|
|||||||
|
|
||||||
if ch in (ord("q"), ord("Q")):
|
if ch in (ord("q"), ord("Q")):
|
||||||
return False
|
return False
|
||||||
if _is_esc(ch):
|
|
||||||
return False
|
|
||||||
|
|
||||||
elif ch == curses.KEY_DOWN:
|
elif ch == curses.KEY_DOWN:
|
||||||
self.sel = clamp(self.sel + 1, 0, max(0, len(names)-1))
|
self.sel = clamp(self.sel + 1, 0, max(0, len(names)-1))
|
||||||
self.log_off = 0
|
self.log_off = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user