- 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.
Runs 'ssh stat -c%s' or 'ls -l' to get total bytes before starting
SCP transfer. Progress bar now shows real percentage and ETA
instead of indeterminate spinner.
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
Replaces wget with aria2c -x8 -s8 for significantly faster
downloads, especially on high-latency connections. Falls back to
single-connection if the server doesn't support range requests.
- 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.
When files are stored in /mnt/converter/in/{guid}/, the backend
needs the full relative path. The analyze endpoint now constructs
'{guid}/{filename}' before passing to the backend API.
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.
Rewrote to match actual Proxmox VE light interface:
- #f0f0f0 page background, white panels
- Dark blue header (#1b3a5c) with white text
- Light gray panel headers (#f8f8f8)
- Bootstrap-style alert colors (yellow/blue/green/red)
- Orange accent throughout
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.
Previously always showed indeterminate spinner (pct=0) because
total size was unknown. Now if the HEAD request returns
Content-Length, the progress bar fills proportionally.
The 10s HEAD request for Content-Length blocked the download endpoint
response. nginx proxy in front (bench.srv2.sechpoint.app:443 → :5000)
timed out waiting, returned empty response → 'data is null' JS error.
Now wget starts immediately, response returns instantly, and HEAD
runs in a daemon thread to populate content_length for ETA later.
The fill.style.width = pct + '%' line was lost during a previous edit,
so the progress bar element was found but never visually updated.
Events fired, label updated, but bar stayed at 0%.
Starlette's multipart parser (pure Python) is too slow for >1 GB uploads —
boundary scanning over gigabytes blocks the event loop indefinitely.
New /session/upload-raw endpoint:
- Receives raw binary body via request.stream() — no parsing overhead
- Metadata (filename, vmid, vm_name) passed in HTTP headers
- Async chunked write directly to staging — true zero-copy streaming
- Same progress logging as before
Frontend now sends File object directly via xhr.send(file)
instead of FormData — eliminates multipart encoding on the client too.
New /scp page with form for host, user, password, remote path.
Runs scp via sshpass in background with progress polling.
Files go to /mnt/converter/in/{session_id}/ for multi-user isolation.
- Added sshpass + openssh-client to frontend installer
- scp.html template with full form + progress bar
- POST /scp/start — launches background SCP via sshpass -e
- GET /scp/progress/{sid}/{file} — polls file size + speed
- 'SCP Pull (+10 GB)' button on start page