--- name: manage-proxmox-vm version: 1.0.0 description: Full QEMU VM lifecycle — create, list, start, stop, shutdown, destroy, clone, update config tags: [proxmox, vm, qemu, api, shell] --- # Manage Proxmox VM Assumes `my_auth`, `my_base_url`, `my_curl` are set (see `auth-proxmox-api` skill). Endpoints: see `docs/api-endpoints.md`. Config fields: see `docs/qemu-config-fields.md`. ## Create from ISO ```sh fn_create_qemu() { ( my_target_node="${1}" my_vmid="${2}" my_name="${3}" my_cores="${4}" my_memory="${5}" # MB my_disk_gb="${6}" # e.g. 20 my_storage_pool="${7}" # e.g. pool-ex1 my_iso="${8:-}" # e.g. local:iso/ubuntu-24.04.iso my_task_result="$( ${my_curl} -H "${my_auth}" \ -X POST "${my_base_url}/nodes/${my_target_node}/qemu" \ --data-urlencode "vmid=${my_vmid}" \ --data-urlencode "name=${my_name}" \ --data-urlencode "cores=${my_cores}" \ --data-urlencode "sockets=1" \ --data-urlencode "cpu=host" \ --data-urlencode "memory=${my_memory}" \ --data-urlencode "ostype=l26" \ --data-urlencode "scsi0=${my_storage_pool}:${my_disk_gb},discard=on,ssd=1" \ --data-urlencode "scsihw=virtio-scsi-pci" \ --data-urlencode "bootdisk=scsi0" \ --data-urlencode "net0=virtio,bridge=vmbr0" \ --data-urlencode "onboot=1" \ --data-urlencode "pool=${PROXMOX_RESOURCE_POOL:-}" \ ${my_iso:+--data-urlencode "ide2=${my_iso},media=cdrom"} )" my_task_id="$(printf '%s' "${my_task_result}" | jq -r '.data' | grep '^UPID:' || true)" if test -z "${my_task_id}"; then printf '%s' "${my_task_result}" | jq '.' >&2 echo "Error: VM create failed" >&2 return 1 fi echo "${my_task_id}" ); } ``` ## Create Cloud-Init VM Boots from a pre-imported disk image (not an ISO). ```sh fn_create_cloudinit_vm() { ( my_target_node="${1}" my_vmid="${2}" my_name="${3}" my_cores="${4}" my_memory="${5}" # MB my_disk_gb="${6}" my_storage_pool="${7}" my_ci_user="${8}" my_ci_pubkey="${9}" my_ip_config="${10:-ip=dhcp}" # e.g. "ip=10.11.0.51/24,gw=10.11.0.1" ${my_curl} -H "${my_auth}" \ -X POST "${my_base_url}/nodes/${my_target_node}/qemu" \ --data-urlencode "vmid=${my_vmid}" \ --data-urlencode "name=${my_name}" \ --data-urlencode "cores=${my_cores}" \ --data-urlencode "sockets=1" \ --data-urlencode "cpu=host" \ --data-urlencode "memory=${my_memory}" \ --data-urlencode "ostype=l26" \ --data-urlencode "scsi0=${my_storage_pool}:${my_disk_gb},discard=on" \ --data-urlencode "scsihw=virtio-scsi-pci" \ --data-urlencode "ide2=${my_storage_pool}:cloudinit" \ --data-urlencode "boot=order=scsi0" \ --data-urlencode "serial0=socket" \ --data-urlencode "vga=serial0" \ --data-urlencode "net0=virtio,bridge=vmbr0" \ --data-urlencode "ipconfig0=${my_ip_config}" \ --data-urlencode "ciuser=${my_ci_user}" \ --data-urlencode "sshkeys=${my_ci_pubkey}" \ --data-urlencode "onboot=1" \ --data-urlencode "pool=${PROXMOX_RESOURCE_POOL:-}" | jq -r '.data' ); } ``` ## List (TSV) ```sh fn_qemu_list() { ( my_node="${1:-${PROXMOX_TARGET_NODE}}" ${my_curl} -H "${my_auth}" "${my_base_url}/nodes/${my_node}/qemu" | jq -r '.data[] | [.vmid, .name, .status, .cpus, .maxmem, .maxdisk] | @tsv' ); } ``` ## Status ```sh fn_qemu_status() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" \ "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/status/current" | jq -r '.data | [.vmid, .name, .status, .uptime, .cpu, .mem] | @tsv' ); } ``` ## Start / Shutdown / Stop / Reset ```sh fn_qemu_start() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" -X POST \ "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/status/start" | jq -r '.data' ); } fn_qemu_shutdown() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" -X POST \ "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/status/shutdown" | jq -r '.data' ); } fn_qemu_stop() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" -X POST \ "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/status/stop" | jq -r '.data' ); } fn_qemu_reset() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" -X POST \ "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/status/reset" | jq -r '.data' ); } ``` ## Destroy ```sh fn_qemu_destroy() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" \ -X DELETE "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}?purge=1&destroy-unreferenced-disks=1" | jq -r '.data' ); } ``` ## Clone / Template ```sh fn_qemu_clone() { ( my_node="${1}"; my_src_vmid="${2}"; my_new_vmid="${3}"; my_new_name="${4}" ${my_curl} -H "${my_auth}" \ -X POST "${my_base_url}/nodes/${my_node}/qemu/${my_src_vmid}/clone" \ --data-urlencode "newid=${my_new_vmid}" \ --data-urlencode "name=${my_new_name}" \ --data-urlencode "full=1" | jq -r '.data' ); } fn_qemu_template() { ( my_node="${1}"; my_vmid="${2}" ${my_curl} -H "${my_auth}" \ -X POST "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/template" | jq -r '.data' ); } ``` ## Update Config ```sh fn_qemu_config_set() { ( my_node="${1}"; my_vmid="${2}" shift 2 ${my_curl} -H "${my_auth}" \ -X PUT "${my_base_url}/nodes/${my_node}/qemu/${my_vmid}/config" \ "$@" ); } ``` ## Notes - VMID must be unique cluster-wide; use `/cluster/nextid` or `fn_resources_next_index` (see `manage-proxmox-ct` skill). - `cpu=host` gives best performance but prevents live migration between different CPU families. - `serial0=socket` + `vga=serial0` are required for cloud-init headless console. - Run quota pre-flight before creating (see `manage-proxmox-pool` skill). - Poll UPIDs with `fn_wait_status` (see `auth-proxmox-api` skill).