- Backend: None = keep current size (no auto-shrink); only resize when value explicitly provided
- Frontend: Target Disk Size field shows detected size, disabled (not submitted)
- Checkbox 'Enable disk resize' unlocks the field for editing
- Storage pool discovery now uses pvesm status (reliable) with Custom... option
- discover_all_disks returns all found disk images sorted by size
- Analysis shows all disks with radio button for boot disk selection
- Confirm form passes boot disk + additional disks to job payload
- GET /api/v1/sessions scans tmp/ for session dirs with staged files
- Index page shows resumable sessions with filenames and sizes
- Resume button skips download and starts analysis with stored session_id
- session_id carried through analysis → confirm → job for correct file paths
- New GET /api/v1/storage/pools returns available lvmthin/zfspool/rbd/dir pools
- Analysis result template now shows dropdown when >1 pool available
- Falls back to text input with single pool or on discovery failure
- Backend /api/v1/analyze now returns 202 with analysis_id immediately
- New GET /api/v1/analyze/{id} for polling analysis status
- Background thread handles extraction, disk discovery, OS/EFI detection
- Nested archive extraction: handles chained zips and split zips (.z01-.zNN)
- Frontend polls /session/analyze/status/{id} every 2s until complete
- SCP page now has complete confirm form flow with job polling
1. Removed qemu-utils from required packages — Proxmox ships its own
qemu-img and installing Debian's version breaks VMs. Added preflight
check: if qemu-img missing, exits with clear error message.
2. Installer now prompts for host storage path (default:
/mnt/pve/hosted-thin). Uses it for the LXC bind mount mp0.
The LXC always sees /mnt/converter internally — no symlinks needed.
Deletes intermediate files as conversion progresses:
- Archive (.7z/.zip) after extraction
- Source VMDK after qemu-img convert
- Local QCOW2 after qm disk import
Reduces peak storage from zip+VMDK+QCOW2 to just the largest
single file — critical for the 94 GB rootfs with 87 GB images.
Replaces flat 30 GB default with proportional sizing:
- 500 GB → 50 GB, 200 GB → 20 GB, 80 GB → 20 GB
- Less aggressive for large disks, safer for GRUB boot
- Update UI hint and README to reflect new rule
- Backend cleanup_staging accepts session_id, deletes whole
STAGING_ROOT/{guid}/ dir when provided
- CleanupRequest model gets session_id field
- Frontend cleanup proxy passes session_id from body
- api_client passes session_id to backend
- reuseImage JS includes session_id in cleanup payload
1. submit_job() was creating empty tmp/out/{vmid}/ dirs via the old
flat STAGING_OUT path. Removed — _process_job handles this via
_staging_out() in the session-aware path.
2. Copy (re-convert) flow was hardcoding boot_type=uefi. Now uses
auto_detect_boot=true with boot_type=legacy as fallback, matching
the behavior of the original analyze→convert flow.
extract_if_needed now extracts relative to the source file's parent
directory instead of /mnt/converter/tmp/in/. This correctly handles
files in tmp/{guid}/in/ paths.
Archives like Debian_13_VMG.7z contain nested single-child dirs:
Debian_13_VMG_LinuxVMImages.COM/Debian_13_VMG_LinuxVMImages.COM/
_flatten_nested() collapses these chains until files sit directly
in the parent, eliminating the double-nesting issue.
Each browser gets a UUID stored in localStorage, set as a cookie,
and attached to every API call. Files go to session-specific dirs:
/mnt/converter/in/{guid}/ — uploads, downloads, SCP pulls
/mnt/converter/out/{guid}/ — converted QCOW2s
Changes:
- base.html: generates UUID via crypto.randomUUID(), stores in
localStorage + cookie, exposes as window.VM_BENCH_SID
- Frontend: all endpoints accept session_id, _staging() helper
creates session-aware paths on demand
- JavaScript: session_id appended to all FormData, set as
X-Session-ID header on raw uploads
- Backend models: JobSubmissionRequest.session_id field added
- Provisioner: _staging_in/_staging_out helpers, source path
resolution uses session-aware directory
- Converter: extract_if_needed skips re-extraction if dir exists
When reusing a source image (Copy flow), the archive was already
extracted in /mnt/converter/in/. The extraction step failed with
'Destination path already exists' from 7z.
Now extract_if_needed checks if the expected output directory already
contains disk images, and returns it directly without re-extracting.
After conversion completes, the user sees a form with VM Name and ID
prefilled, and three buttons:
- Clone (instant): qm clone --full via backend, takes seconds
- Copy (re-convert): shows CPU/RAM/Disk fields, re-runs full pipeline
- Clean Up & Finish: deletes staging, returns to start page
Backend: new /api/v1/clone endpoint → provisioner.clone_vm()
Frontend: new /session/clone proxy, api_client.clone_vm()
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.
qemu-img convert is the longest step but previously had no progress
updates — bar stuck at 10% until conversion completed.
Now _convert_with_progress() runs qemu-img in background and polls
output file size every 2s, updating progress with GiB done/total.
Progress flow for single disk:
5% queued → 10% starting → 10-55% converting (real-time)
→ 60% shrinking → 70% creating VM → 75-85% importing
→ 95% configuring → 100% done
The job submission receives the original filename (e.g. Debian_13_VMG.7z)
but qemu-img convert needs the actual VMDK path inside the archive.
Now reuse extract_if_needed() + discover_disk() from converter module
to locate the real disk image before conversion.
Replace stub provisioner with real Proxmox integration:
- qemu-img convert: source → QCOW2 in /mnt/converter/out/{vmid}/
- virt-resize --shrink --resize-force: auto-shrink >30 GiB disks
- qm create: VM with cores, RAM, network, SCSI controller
- qm importdisk: import each QCOW2 into Proxmox storage
- qm set: attach disks, configure boot (OVMF/SeaBIOS), EFI disk, serial
- Auto-destroy existing VM with same ID before recreating
- Background thread execution with in-memory status tracking
- Full logging throughout the conversion pipeline
Archives like 64bit.7z that already contain a 64bit/ top-level dir
were extracted into STAGING_IN/64bit/, creating 64bit/64bit/ nesting.
Now extract into STAGING_IN root, then:
- If the archive produced a single dir matching the stem, use it
- Otherwise, collect scattered files into a stem-named subdirectory
Removes the need for nested path: 64bit/Debian.vmdk instead of
64bit/64bit/Debian.vmdk
Both frontend and backend now write rotating log files to
/mnt/converter/logs/ (shared between host and LXC):
/mnt/converter/logs/vm-bench.log (frontend)
/mnt/converter/logs/vm-bench-backend.log (backend)
- RotatingFileHandler: 10 MB per file, 5 backups
- Console handler still writes to systemd journal
- Logs/ directory is gitignored and auto-created on startup
- Install script creates logs/ directory
Backend had zero logging. Frontend logs were fine (logging.basicConfig
in app.py). Now both services write structured logs to systemd journal:
journalctl -u vm-bench -f # frontend (LXC)
journalctl -u vm-bench-backend -f # backend (srv2)
{job_id} in f-strings was interpreted as Python variable instead
of FastAPI path parameter. Double braces {{job_id}} produce
literal {job_id} in the route string.
Large-file handling:
- Set TMPDIR=/mnt/converter/in in service to spool uploads to
shared storage instead of 24 GB LXC rootfs (critical for >24GB)
- Chunked upload streaming (8 MiB) with progress logging every 1 GiB
- Pre-flight disk space check via Content-Length header
- Clean up partial files on upload/download failure
- Download timeout extended to 7200s (2 hours) for 88 GB images
- Switched wget from --show-progress to --progress=dot:giga
(compact output, won't fill memory on large transfers)
- uvicorn --timeout-keep-alive 300 on both frontend and backend
VM name:
- Added vm_name field to initial session form (step 1)
- Falls back to auto-generated 'os_type-vmid' if left blank
- Pre-filled & editable in confirm form (step 2)