Install
openclaw skills install pve-builderProxmox VE VM builder with cloud-init automation, config-driven hardware defaults, validation, and static IP support
openclaw skills install pve-builderAgent runs on your local machine - NOT on Proxmox
Forbidden:
Must Do:
Generates Proxmox VM creation commands with cloud-init configuration, SSH key management, and optional data disks. All hardware defaults are config-driven via pve-env.md.
IMPORTANT: Commands are output as text for you to copy/paste into Proxmox shell. The agent does NOT execute any Proxmox commands.
pve-env.md in the skill directory.gitignore excludes pve-env.md| Section | Keys | Purpose |
|---|---|---|
| Proxy | Proxy Required, HTTP Proxy, HTTPS Proxy, Proxy CA Certificate | Network proxy for apt inside VMs |
| SSH | Default User, Key Path, Key Type | Default SSH user, key storage location, key type |
| Network | Default Bridge, Default VLAN, DNS Server, Use DHCP Default, Network Interface | Default network settings and interface type |
| Storage | Default Storage, Template Path, Default OS Disk Size, Auto-Format Data Disks, Data Disk Interface, Default Cloud Image | Storage defaults and cloud image path |
| Node | Default Node, BIOS Type, Machine Type, CPU Type, OS Type, SCSI Controller, Onboot | Hardware defaults for VM creation |
| Workload Presets | Preset table (RAM/CPU/Disk) | Recommended specs per workload type |
| Package Defaults | Package Update, Base Packages | Always-installed package list |
The workflow uses section-based numbered prompts with continuous numbering across sections:
=== VM Specs ===
1. CPU cores 2. CPU sockets 3. RAM in GB 4. OS disk size
=== Network ===
5. Bridge 6. VLAN 7. DHCP?
[if static:] 8. IP 9. Gateway 10. DNS
=== User & Disks ===
11. SSH user 12. Add data disks? 13. Format? 14. Count 15.x: Disk sizes
15. Proxy? 16. Extra packages 17. SSH key directory
Steps:
pve-env.md (error if missing)Template Path + Default Cloud Image)openssl rand -base64 12 | tr -d '/+=' | head -c16. Use this in chpasswd. If the user explicitly provides a password, use theirs instead. Always show the password in the final output summary.pvesh get /cluster/nextid on the Proxmox node and paste back the result. Use that VMID in all generated commands. Never hardcode a VMID — always get the next ID from the cluster. Add a # Replace VMID=... if already taken comment in the output.qm start) and Post-boot cleanup (delete the snippets YAML — only run after the VM is verified up).Before generating commands, the agent validates that the target storage, bridge, and cloud image exist on the Proxmox node.
~/.pve-builder/validation.jsonIf no valid cache exists, the agent shows these commands for the user to run on the Proxmox node:
echo "=== Storage ==="; pvesm status
echo "=== Bridge ==="; ip -br link show
echo "=== Image ==="; ls -la <image-path>
echo "=== END ==="
Results are parsed:
pvesm statusOn failure: Agent aborts and reports which check(s) failed. On success: Results are cached with node/storage/bridge/timestamp.
/var/lib/vz/snippets/<VMNAME>-user-data.yaml and attached via --cicustom "user=local:snippets/<VMNAME>-user-data.yaml". The local:snippets/ storage path maps to /var/lib/vz/snippets/ on the Proxmox node. Never use /var/lib/vz/template/cloud-init/ — that directory is not a recognized Proxmox snippets storage target.--cicustom reads the snippets file at every boot. The snippets YAML must exist on the node when the VM starts — cloud-init won't apply without it. Do NOT delete the snippets file before the first boot.mkdir -p /var/lib/vz/snippets at the top of the generated commands.--ide2 and --citype, add --cicustom to wire the user-data: qm set $VMID --cicustom "user=local:snippets/${VMNAME}-user-data.yaml".--cicustom, regenerate the cloud-init drive before starting: qm cloudinit update $VMID.true when a password is configured (so the password actually works over SSH). Set false only for SSH-key-only VMs.~/.ssh/pve-builder/)set -e or be in the main command block. It should be in a separate "Post-boot" section that runs only after the VM is verified running and cloud-init applied. If the user deletes the YAML before the first boot, cloud-init fails silently.echo "VMID: <id>" in the verify/final section so the user has the VMID referencedNever present commands to the user without running this validation first. Generate → validate → fix → present.
Build the complete command set internally (do not show yet).
Generate a small bash validation script, set the variables to match the VM specs, and run it locally with exec. If it fails, fix the draft and re-run until it passes. Only then show the commands.
#!/bin/bash
# Pre-flight: verify parameters match intended spec
# SET THESE to match the VM being built:
VMID="1024"; VMNAME="MB-TBD"; RAM_MB="12288"; CORES="2"; SOCKETS="1"
OS_DISK_SIZE="25G"; BRIDGE="vmbr0"; VLAN_TAG="160"; STORAGE="Data"
IMAGE="/mnt/pve/ISO/template/iso/ubuntu-24.04-server-cloudimg-amd64.img"
CITYPE="nocloud"; VM_GB="12"
PASS=true
# RAM must be multiple of 256
(( RAM_MB % 256 != 0 )) && { echo "FAIL: RAM $RAM_MB not mult. of 256"; PASS=false; }
# Cores/sockets positive
(( CORES < 1 )) && { echo "FAIL: CORES < 1"; PASS=false; }
(( SOCKETS < 1 )) && { echo "FAIL: SOCKETS < 1"; PASS=false; }
# VLAN in valid range
(( VLAN_TAG < 1 || VLAN_TAG > 4094 )) && { echo "FAIL: VLAN out of range"; PASS=false; }
# Disk size format
[[ ! "$OS_DISK_SIZE" =~ ^[0-9]+[G]$ ]] && { echo "FAIL: OS_DISK_SIZE format"; PASS=false; }
# Valid CITYPE
case "$CITYPE" in nocloud|configdrive2|opennebula) ;; *) echo "FAIL: CITYPE=$CITYPE invalid"; PASS=false ;; esac
# Image path not empty
[ -z "$IMAGE" ] && { echo "FAIL: IMAGE path empty"; PASS=false; }
# RAM_MB must match VM_GB * 1024
EXPECTED_MB=$((VM_GB * 1024))
[ "$RAM_MB" -ne "$EXPECTED_MB" ] && { echo "FAIL: RAM_MB=$RAM_MB != VM_GB=$VM_GB (expected $EXPECTED_MB)"; PASS=false; }
$PASS && echo "PREFLIGHT OK" || { echo "PREFLIGHT FAILED"; exit 1; }
The agent:
exec bash /tmp/preflight.shPREFLIGHT OKmkdir -p /var/lib/vz/snippets/var/lib/vz/snippets/$VMNAME-user-data.yamlssh_pwauth: true if password configured, false if SSH-only--citype nocloud (NOT cloud-config)qm create with no --nodeqm importdisk references correct image--scsi0 attaches imported disk--ide2 <storage>:cloudinit attached--cicustom "user=local:snippets/${VMNAME}-user-data.yaml" addedqm cloudinit update $VMID after cicustom set--ipconfig0 ip=dhcp (DHCP) or --ipconfig0 ip=<CIDR>,gw=<GW> (static) — this actually configures the network inside the guest--net0 virtio,bridge=<bridge>,tag=<vlan>--boot order=scsi0qm resize $VMID scsi0 <size> after importecho "VMID: $VMID" in outputqm config $VMID for reviewqm not vm, $VMID not $VMSimple network config: qm set --ipconfig0 ip=dhcp
Do NOT use --nameserver with DHCP — let the DHCP lease provide DNS. Only set --nameserver when using static IP.
When DHCP is declined, the agent prompts for:
10.0.12.50/24)10.0.12.1)Generated commands:
qm set --ipconfig0 ip=10.0.12.50/24,gw=10.0.12.1qm set --nameserver 8.8.8.8All VMs get base packages from pve-env.md (deduplicated with any extra packages).
If proxy is configured, apt proxy is automatically enabled in cloud-init.
openssl rand -base64 12 | tr -d '/+=' | head -c16. Use in both chpasswd in cloud-init YAML and embed in command output. If the user provides an explicit password, use theirs instead — but always show it back in output so they have it. Set ssh_pwauth: true unless the user explicitly says SSH-only.Key Path in pve-env.md (default ~/.ssh/pve-builder); permissions 700 on base dir--cicustom warning: snippets file must survive first boot. Split output into Setup and Post-boot sections. VMID auto-detection via cluster/nextid. Fix workflow numbering.openssl rand -base64 12), user override option, always display password in output/var/lib/vz/snippets/ + --cicustom, qm cloudinit update, ssh_pwauth conditionalmkdir -p for required dirs in generated commands, command self-review checklist, --citype nocloud enforced, automatic disk resize after importdiskThis file is yours to evolve. As you learn who you are, update it.