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
195 lines
6.7 KiB
Markdown
195 lines
6.7 KiB
Markdown
# VM Bench — Proxmox Image Conversion Frontend
|
|
|
|
Web GUI and REST API for converting VMware disk images (VMDK, VHD, etc.) into
|
|
Proxmox-compatible QCOW2 disks and provisioning VMs with automatic OS and boot
|
|
type detection.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Browser vm-bench LXC Proxmox Host (srv2)
|
|
(this container)
|
|
──→ :5000 ──→ frontend/app.py ──→ :9000 backend/app.py
|
|
│
|
|
├─ qemu-img (format, size)
|
|
├─ guestfish (OS, EFI detection)
|
|
├─ 7z/unzip (archive extraction)
|
|
└─ qm create (VM provisioning)
|
|
```
|
|
|
|
- **Frontend** (`vm-bench` LXC) — FastAPI + Jinja2 web UI on port 5000
|
|
- **Backend** (`srv2` Proxmox host) — FastAPI REST API on port 9000
|
|
- **Shared storage** — `/mnt/converter/in` (staging) and `/mnt/converter/out` (output)
|
|
|
|
## Directory Layout
|
|
|
|
```
|
|
/mnt/converter/
|
|
├── frontend/ # Web UI (runs on vm-bench LXC)
|
|
│ ├── app.py # FastAPI app, routes, session handling
|
|
│ ├── api_client.py # Typed REST client → backend
|
|
│ ├── requirements.txt
|
|
│ ├── templates/ # Jinja2 HTML templates
|
|
│ │ ├── base.html
|
|
│ │ ├── index.html # New session form
|
|
│ │ ├── _analysis.html # Analysis result + confirm form
|
|
│ │ └── polling.html # Progress bar + job status
|
|
│ └── static/
|
|
│ └── proxmox.css
|
|
├── backend/ # REST API (deploy to Proxmox host srv2)
|
|
│ ├── app.py # FastAPI app, routes
|
|
│ ├── models.py # Pydantic request/response models
|
|
│ ├── converter.py # Archive extraction, disk probing, OS/EFI detection
|
|
│ ├── provisioner.py # VM provisioning (qm create/importdisk)
|
|
│ ├── requirements.txt
|
|
│ ├── install.sh # Systemd installation script
|
|
│ └── vm-bench-backend.service
|
|
├── open-api.yaml # API spec (single source of truth)
|
|
├── in/ # Shared staging directory (gitignored)
|
|
└── out/ # Shared output directory (gitignored)
|
|
```
|
|
|
|
## Features
|
|
|
|
- **File upload or URL download** — ingest `.vmdk`, `.vhd`, `.7z`, `.zip`, `.tar.gz` archives
|
|
- **Automatic archive extraction** — 7z, unzip, tar, gunzip
|
|
- **Disk analysis** — detects format, virtual size, guest OS, EFI bootability
|
|
- **VM provisioning** — creates Proxmox VM with automatic disk import and boot config
|
|
- **Progress polling** — real-time job status with progress bar
|
|
- **Staging reuse** — keep source files to create multiple VMs from one image
|
|
|
|
## Prerequisites
|
|
|
|
### Backend (Proxmox host `srv2`)
|
|
|
|
- Python 3.13+
|
|
- `qemu-img` (from `qemu-utils`)
|
|
- `guestfish` (from `libguestfs-tools`)
|
|
- `7z`, `unzip`, `tar` (for archive extraction)
|
|
- `systemd` (for service management)
|
|
|
|
### Frontend (`vm-bench` LXC)
|
|
|
|
- Python 3.13+
|
|
- `wget` (for URL downloads)
|
|
- `systemd` (for service management)
|
|
- Network access to backend at `10.2.0.2:9000`
|
|
|
|
## Installation
|
|
|
|
### 1. Backend (on Proxmox host `srv2`)
|
|
|
|
```bash
|
|
# The backend code is shared via /mnt/converter/backend/
|
|
cd /mnt/converter/backend
|
|
bash install.sh
|
|
```
|
|
|
|
This installs Python dependencies, copies the systemd service, and starts
|
|
`vm-bench-backend` on port 9000. Verify:
|
|
|
|
```bash
|
|
curl http://127.0.0.1:9000/api/v1/health
|
|
# → {"status":"ok"}
|
|
```
|
|
|
|
### 2. Frontend (on `vm-bench` LXC)
|
|
|
|
```bash
|
|
# Install dependencies
|
|
cd /mnt/converter/frontend
|
|
pip3 install --break-system-packages -r requirements.txt
|
|
|
|
# Copy service file and start
|
|
cp /etc/systemd/system/vm-bench.service /etc/systemd/system/
|
|
systemctl daemon-reload
|
|
systemctl enable --now vm-bench
|
|
```
|
|
|
|
The frontend serves on port 5000. Open `http://<vm-bench-ip>:5000` in a browser.
|
|
|
|
## API Reference
|
|
|
|
All endpoints are under `/api/v1/`. Full specification in [`open-api.yaml`](open-api.yaml).
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/v1/health` | Health check |
|
|
| `POST` | `/api/v1/analyze` | Probe source image (format, size, OS, EFI) |
|
|
| `POST` | `/api/v1/jobs` | Submit conversion + provisioning job |
|
|
| `GET` | `/api/v1/jobs/{id}` | Poll job status and progress |
|
|
| `POST` | `/api/v1/jobs/{id}/cleanup` | Delete or preserve staging files |
|
|
|
|
### Example: Analyze a source image
|
|
|
|
```bash
|
|
curl -X POST http://10.2.0.2:9000/api/v1/analyze \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"vmid": 21050, "source_filename": "64bit/Debian 12.11.0 (64bit).vmdk"}'
|
|
|
|
# Response:
|
|
# {
|
|
# "vmid": 21050,
|
|
# "filename": "Debian 12.11.0 (64bit).vmdk",
|
|
# "disk_format": "vmdk",
|
|
# "disk_size_gb": 7.5,
|
|
# "os_type": "Debian",
|
|
# "efi_detectable": true
|
|
# }
|
|
```
|
|
|
|
### Example: Submit a conversion job
|
|
|
|
```bash
|
|
curl -X POST http://10.2.0.2:9000/api/v1/jobs \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"vmid": 21050,
|
|
"vm_name": "debian-test",
|
|
"boot_disk": {
|
|
"disk_type": "image_file",
|
|
"source_filename": "64bit/Debian 12.11.0 (64bit).vmdk",
|
|
"format": "vmdk"
|
|
},
|
|
"cpu_cores": 2,
|
|
"ram_mb": 4096,
|
|
"target_storage": "local-lvm"
|
|
}'
|
|
|
|
# Response:
|
|
# { "job_id": "job_21050_1737480000", "vmid": 21050, "status": "queued", ... }
|
|
```
|
|
|
|
## Usage Flow
|
|
|
|
1. **Open the web UI** → new session form
|
|
2. **Upload or download a source image** → file lands in `/mnt/converter/in/`
|
|
3. **Backend analyses the image** → shows format, size, OS, EFI status
|
|
4. **Configure VM settings** → name, CPU, RAM, storage, boot type
|
|
5. **Submit job** → backend converts, shrinks (if needed), creates VM
|
|
6. **Poll progress** → real-time status bar updates every 2 seconds
|
|
7. **Reuse or cleanup** → create another VM from same source, or delete staging files
|
|
|
|
## Configuration
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `BACKEND_URL` | `http://10.2.0.2:9000` | Backend API base URL (set in systemd unit) |
|
|
| `VM_ID_MIN` | `21000` | Minimum allowed Proxmox VM ID |
|
|
| `VM_ID_MAX` | `21100` | Maximum allowed Proxmox VM ID |
|
|
|
|
## Development
|
|
|
|
**Source of truth**: `open-api.yaml` — always derive models from this file.
|
|
|
|
**Backend tool requirements** (on srv2):
|
|
- `qemu-img` — disk format and size detection
|
|
- `guestfish` / `virt-inspector` — OS type and EFI boot detection
|
|
- `7z`, `unzip`, `tar`, `gunzip`, `bunzip2`, `xz` — archive extraction
|
|
|
|
**Provisioner note**: `backend/provisioner.py` is currently a stub with simulated
|
|
progress. Replace with real `qm create`, `qm importdisk`, and `qm set` commands
|
|
when deploying to the Proxmox host.
|
|
|
|
**Staging files**: `/mnt/converter/in/` and `/mnt/converter/out/` are gitignored —
|
|
they contain user data, not code.
|