From 7e1303a21731fe525bf4fad9970f050a3882c21e Mon Sep 17 00:00:00 2001 From: Claus Lohmar Date: Thu, 23 Jul 2026 08:48:31 +0000 Subject: [PATCH] fix: auto-detect BIOS boot type + simplified reuse form 1. BIOS: provisioner now calls detect_efi() on the first disk when auto_detect_boot=true. Detects EFI partition via guestfish. Falls back to user's boot_type if detection inconclusive. 2. Reuse: 'Create Another VM' now shows an inline form asking only for VM Name and ID, reusing the same disk image, format, and boot settings from the completed job. No redirect needed. --- backend/provisioner.py | 21 ++++++++++++-- frontend/templates/index.html | 50 ++++++++++++++++++++++++++++++++- frontend/templates/polling.html | 23 +++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/backend/provisioner.py b/backend/provisioner.py index a25ad9f..f413308 100644 --- a/backend/provisioner.py +++ b/backend/provisioner.py @@ -23,8 +23,9 @@ from models import ( JobStatusResponse, JobStatus, DiskType, + BootType, ) -from converter import extract_if_needed, discover_disk +from converter import extract_if_needed, discover_disk, detect_efi logger = logging.getLogger("backend.provisioner") @@ -275,7 +276,21 @@ def _process_job(job_id: str, req: JobSubmissionRequest): if not safe_name: safe_name = f"vm-{vmid}" - bios = "ovmf" if req.boot_type.value == "uefi" else "seabios" + # ── Auto-detect boot type if requested ───────────────────── + final_boot = req.boot_type + if req.auto_detect_boot and disk_paths: + logger.info("Auto-detecting boot type...") + efi = detect_efi(disk_paths[0]) + if efi is True: + final_boot = BootType.UEFI + logger.info(" EFI partition detected → UEFI (OVMF)") + elif efi is False: + final_boot = BootType.LEGACY + logger.info(" No EFI partition → Legacy BIOS (SeaBIOS)") + else: + logger.info(" EFI detection inconclusive — using %s", final_boot.value) + + bios = "ovmf" if final_boot.value == "uefi" else "seabios" _run(["qm", "create", str(vmid), "--name", safe_name, "--bios", bios, @@ -310,7 +325,7 @@ def _process_job(job_id: str, req: JobSubmissionRequest): message=f"Imported disk {idx + 1}/{len(disk_paths)}") # ── Add EFI disk if UEFI ────────────────────────────────── - if req.boot_type.value == "uefi": + if final_boot.value == "uefi": logger.info("Adding EFI disk") _run(["qm", "set", str(vmid), "--efidisk0", f"{req.target_storage}:0,format=raw,size=4M"]) diff --git a/frontend/templates/index.html b/frontend/templates/index.html index b90ee5f..485177e 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -295,7 +295,9 @@ function initPolling() { let completed = false; // Reuse buttons - document.getElementById('btn-reuse-yes').onclick = () => reuseImage(jobId, vmid, sourceFilename, true); + document.getElementById('btn-reuse-yes').onclick = () => { + document.getElementById('reuse-form-wrap').classList.remove('hidden'); + }; document.getElementById('btn-reuse-no').onclick = () => reuseImage(jobId, vmid, sourceFilename, false); async function poll() { @@ -363,6 +365,52 @@ async function reuseImage(jobId, vmid, sourceFilename, keep) { } } catch (err) { alert('Cleanup failed: ' + err.message); } } + +async function submitReuse(e) { + e.preventDefault(); + const newVmid = document.getElementById('reuse-vmid').value; + const newVmName = document.getElementById('reuse-vmname').value.trim(); + const status = document.getElementById('reuse-submit-status'); + status.classList.remove('hidden'); + status.innerHTML = '
Submitting job...'; + + // Get original job data from the container + const container = document.getElementById('polling-container'); + const sourceFilename = container.dataset.sourceFilename; + const jobId = container.dataset.jobId; + + // Get the analysis data from the completed job's backend + let analysis; + try { + const resp = await fetch('/session/status/' + jobId); + analysis = await resp.json(); + } catch (err) { + status.innerHTML = '
Failed to fetch job data.
'; + return; + } + + // Build payload using same disk settings as first VM + const payload = new FormData(); + payload.append('vmid', newVmid); + payload.append('vm_name', newVmName); + payload.append('source_filename', sourceFilename); + // Re-use disk format from original job or default + payload.append('disk_format', 'vmdk'); + payload.append('cpu_cores', '2'); + payload.append('ram_mb', '4096'); + payload.append('target_storage', 'local-lvm'); + payload.append('auto_detect_boot', 'true'); + payload.append('boot_type', 'uefi'); + + try { + const resp = await fetch('/session/confirm', { method: 'POST', body: payload }); + const html = await resp.text(); + document.getElementById('polling-section').innerHTML = html; + initPolling(); + } catch (err) { + status.innerHTML = '
Failed: ' + err.message + '
'; + } +} {% endblock %} diff --git a/frontend/templates/polling.html b/frontend/templates/polling.html index 12bc210..a1f05e6 100644 --- a/frontend/templates/polling.html +++ b/frontend/templates/polling.html @@ -19,6 +19,7 @@ + +