# Provisioning Pipeline

This page walks through what `POST /api/vm/install` does, stage by stage, from the request to an SSH-ready VM.

## Stage Overview

Each SSE event's `step` field reads `<stage> > <step>`.

| Stage | Step | Action |
|---|---|---|
| `preparation` | `checking VMID` | Runs [VMID allocation](/vmid-ip-allocation) when `id` is omitted; an explicit `id` is used as is, without an availability check |
| | `assigning IP` | First three octets of `GATEWAY` + VMID, with `/24` |
| | `validating CPU and RAM` | CPU ≤ 0 → 1; above `VM_MAX_CPU` is clamped. RAM < 512 → 512; above `VM_MAX_RAM` is clamped |
| | `validating disk size` | Parses the number after stripping a trailing `G`; < 16 → `16G`; ≥ `VM_MAX_DISK` → the cap |
| | `setting default config values` | Appends `-<vmid>` to the name, fills the default user and password |
| | `checking storage pool` | `pvesm status` confirms `ASSIGN_STORAGE` is active and of type `dir` / `zfspool` / `lvmthin` / `nfs` |
| `OS preparation` | `getting OS image` | Resolves the image URL from `os` / `version` |
| | `validating OS image URL` | `HEAD` request must return 200 within 15 seconds |
| | `downloading OS image` | Downloads to `/tmp`; skipped when the file already exists (`using OS image`) |
| `SSH preparation` | `checking SSH key` | Generates a key with `ssh-keygen -t ed25519` when the main node has none |
| `VM creation` | `creating VM` | `qm create` with the [cluster baseline](/cpu-baseline) CPU type, or `kvm64` when detection fails |
| | `importing disk image` | `qm importdisk` into `ASSIGN_STORAGE` |
| `VM initialization` | `initializing configuration` | `qm set` in order: SSH keys, password, `--ciupgrade 0`, OS tag, `scsi0`, cloud-init disk, boot order; `qm resize` to the target size (3 attempts, 5 seconds apart); finally `ipconfig0` |
| | `migrating VM` | Runs `qm migrate --with-local-disks` only when the request sets `node` |
| | `waiting for ready` | After boot, tries SSH every 5 seconds, up to 60 times |
| | `SSH initialization` | Runs the [OS init script](/os-init-scripts) inside the VM and relays each output line as SSE |
| | `rebooting VM` | `qm reboot`, then waits for SSH again |
| | `finalizing` | Reports total duration, VMID, IP, and user |

## `qm create` Arguments

| Argument | Value |
|---|---|
| `--cores` | Requested `cpu` (after clamping) |
| `--cpu` | Cluster baseline, or `kvm64` |
| `--memory` | Requested `ram` (after clamping) |
| `--scsihw` | `virtio-scsi-pci` |
| `--ostype` | `l26` |
| `--agent` | `1` |
| `--net0` | `virtio,bridge=vmbr0` |
| `--serial0` | `socket` |
| `--numa` / `--balloon` | Per the [ballooning rule](/configuration#ballooning-rule) |

`--ciupgrade 0` turns off cloud-init's boot-time package upgrade so it does not fight the init script for the package lock.

## Failure Handling

| Failure point | Handling |
|---|---|
| Preparation, image, SSH key, `qm create` | Sends an `error` event and stops; no VM is left (not created yet, or creation failed) |
| `importing disk image`, `initializing configuration`, `SSH initialization` | Force-stops and runs `qm destroy --purge --skiplock` to remove the partial VM |
| Migrate, start, or reboot command fails | Stops immediately; **the VM stays** in the cluster |
| SSH wait times out | Sends an `error` event and stops; **the VM stays** so you can debug it on the node |

Success or failure, the handler always ends with `event: close`.

## Image Cache

Downloads are stored in `/tmp` under version-specific names (e.g. `/tmp/debian-12-generic-amd64.qcow2`) and reused by later installs of the same version. Delete the file manually to pick up a newer upstream image.
