Commit graph

37 commits

Author SHA1 Message Date
e3bc7929c6 fix: storage pool list now filters by content type — only pools with 'images' support are shown 2026-07-27 07:08:02 +00:00
7eb2d6bb45 feat: storage pool dropdown populated from /etc/pve/storage.cfg
- 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
2026-07-27 06:55:10 +00:00
878bc66da8 feat: add .ova support — OVA files are tar archives containing .vmdk disks 2026-07-26 07:42:06 +00:00
1ecaefe1d8 feat: async analysis with polling + nested archive extraction
- 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
2026-07-26 00:13:14 +00:00
f1978008bd fix: use relative paths for STAGING_ROOT and LOG_DIR so backend works on host regardless of install location 2026-07-25 17:50:13 +00:00
f7c6527d3d chore: add AGENTS.md, move install.sh to root, rename frontend install.sh → setup.sh 2026-07-25 17:40:59 +00:00
root
ef2d74a62b fix: remove qemu-utils from installer + configurable storage path
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.
2026-07-25 11:23:26 +00:00
b33ab1605e fix: auto-cleanup staging files to minimize disk usage
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.
2026-07-24 10:06:12 +00:00
cd8d86a98c fix: auto-shrink to 10% of virtual size, min 20 GB
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
2026-07-23 12:45:32 +00:00
6db69f434c fix: Clean Up now deletes entire session directory tmp/{guid}/
- 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
2026-07-23 10:54:42 +00:00
90bf8e4842 fix: remove unused out_dir from submit_job + Copy flow uses legacy fallback
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.
2026-07-23 10:52:20 +00:00
6617aca341 fix: purge all old staging path references — full code review cleanup
Bugs found and fixed:
- converter.py: STAGING_IN.iterdir() → extract_base.iterdir()
  (checked wrong dir when source was in session subdir)
- vm-bench.service: TMPDIR=/mnt/converter/in → /mnt/converter/tmp
  (old path poisoned all temp file writes)
- backend/app.py: removed unused STAGING_IN constant
- converter.py: removed unused STAGING_OUT constant
- install.sh: removed creation of old /in and /out dirs
2026-07-23 10:36:03 +00:00
1b145adea7 fix: extraction uses file's parent dir as base, not hardcoded STAGING_IN
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.
2026-07-23 10:19:43 +00:00
7a024ca166 fix: correct staging path resolution — tmp/{guid}/in not tmp/in/{guid}
Frontend now passes '{guid}/in/filename' to backend.
Backend extract_if_needed uses STAGING_ROOT (tmp/) as base.
Provisioner staging helpers build tmp/{guid}/in + tmp/{guid}/out.
2026-07-23 10:14:19 +00:00
9dcde5d022 fix: flatten nested single-child directories after extraction
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.
2026-07-23 10:09:25 +00:00
a28e2af674 refactor: move staging to /mnt/converter/tmp/{guid}/in + out 2026-07-23 10:07:57 +00:00
e53d05f7dc fix: add missing _staging_in helper + STAGING_IN constant 2026-07-23 09:36:50 +00:00
63659e4f93 feat: session GUID isolation for multi-user support
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
2026-07-23 09:32:16 +00:00
7ded831354 fix: skip re-extraction if source directory already 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.
2026-07-23 09:28:02 +00:00
9eb0f15261 feat: Clone (qm clone) + Copy (re-convert) buttons on completion
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()
2026-07-23 09:02:57 +00:00
7e1303a217 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.
2026-07-23 08:48:31 +00:00
80c5e2ceda fix: sanitize VM name + use qm disk import (newer Proxmox syntax)
- VM name now sanitized: spaces→hyphens, lowercase, strip invalid chars
- qm importdisk → qm disk import (newer Proxmox VE command)
2026-07-23 08:25:46 +00:00
6ba7944bb2 fix: add unrar-free, use 7z for .zip extraction (more reliable)
- Added unrar-free package to backend installer
- Added .rar to archive extension detection
- Changed .zip extraction from unzip → 7z (x -y)
  7z handles .zip, .7z, and other formats with one tool
- Added unrar handler for .rar files
2026-07-21 19:37:27 +00:00
5e786533b1 fix: add /usr/sbin to PATH in service files (qm, virt-resize, etc.)
Proxmox tools (qm, pct) and libguestfs tools (virt-resize,
guestfish) live in /usr/sbin which wasn't in the service PATH.
2026-07-21 19:24:13 +00:00
9a6d2df78b fix: real-time progress during qemu-img convert, smoother percentages
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
2026-07-21 19:14:32 +00:00
affcde7afb fix: provisioner now extracts archives and discovers disks before conversion
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.
2026-07-21 19:11:19 +00:00
56131d6346 feat: real Proxmox VM provisioning — qemu-img, virt-resize, qm commands
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
2026-07-21 19:05:07 +00:00
7808b16946 fix: prevent double-nested directory on archive extraction
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
2026-07-21 18:58:13 +00:00
0e7f8c81f3 feat: shared file-based logging to /mnt/converter/logs/
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
2026-07-21 18:52:58 +00:00
42d3cdf1fe fix: add logging to backend — analyze, job submit, status, cleanup
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)
2026-07-21 18:46:12 +00:00
10199f896b fix: escape f-string braces in backend route decorators
{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.
2026-07-21 18:17:41 +00:00
509eb97b57 fix: large-file support + user-defined VM name
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)
2026-07-21 17:59:31 +00:00
4b50b185fa feat: full deployment scripts — backend + LXC creation, frontend installer
backend/install.sh (run on srv2):
- Creates /mnt/converter directory tree, clones/updates git repo
- Installs 10 system packages (qemu-img, guestfish, archive tools)
- Installs Python deps + vm-bench-backend systemd service
- Interactive LXC creation: prompts for container ID, IP, gateway,
  bridge, MAC (all with defaults)
- Creates vm-bench LXC: 3 cores, 6GB RAM, 24GB rootfs, nesting,
  AppArmor unconfined, cgroup access, bind mount /mnt/converter

frontend/install.sh (run inside LXC):
- Installs python3, pip3, wget, curl
- Installs Python deps (fastapi, uvicorn, jinja2, etc.)
- Verifies backend connectivity, installs vm-bench systemd service

frontend/vm-bench.service: systemd unit bundled in repo

README: replaced Prerequisites/Installation with full Deployment
section covering both steps, container config table, and re-deploy
2026-07-21 17:51:53 +00:00
c887ccd5a3 fix: uniform /api/v1/ prefix, real EFI detection, spec alignment
Backend:
- All routes now consistently under /api/v1/ (health, analyze, jobs)
- Replace dummy EFI heuristic with actual guestfish-based detect_efi()
- Add path traversal validation on analyze source_filename
- Use typed response models (HealthResponse, CleanupResponse) everywhere
- Clean up unused imports (Path, ErrorResponse)

Models:
- Align AnalyzeResponse with open-api.yaml (add vmid, remove bootable/error,
  make efi_detectable nullable, deduplicate CleanupResponse)

Converter:
- Add detect_efi(disk_path) using guestfish list-filesystems

Frontend:
- Update api_client paths to /api/v1/health and /api/v1/analyze
- Remove unsupported 'network' field from job payload

Spec:
- Promote inline HealthResponse/CleanupResponse to named schemas
- Move /analyze and /health under /api/v1/ prefix

Docs:
- Add README.md with architecture, install, API reference, usage flow
2026-07-21 17:40:08 +00:00
be756e33bf chore: add install.sh for backend systemd setup 2026-07-21 15:01:02 +00:00
439b0c51a1 chore: add systemd service file for backend deployment 2026-07-21 14:56:02 +00:00
a8099f504c chore: initial commit — vm-bench frontend + backend 2026-07-21 14:53:29 +00:00