# Proxmox Manager Agent

> Manage a Proxmox VE homelab (single-node or small cluster) from an AI agent via scoped SSH access. Covers VM and LXC lifecycle (create, start, stop, snapshot, destroy), storage management (ISO upload, LVM-thin/ZFS/dir pools), backup/restore (vzdump, PBS), network bridges, firewall, cluster operations, and the PVE 9.x gotchas (binary path splits, --is_mountpoint no re-index, NVMe-shuffle, broken D…

- **Type:** Skill
- **Install:** `agentstack add skill-kevwillow-proxmox-manager-agent-skill-proxmox-manager-agent-skill`
- **Verified:** Pending review
- **Seller:** [kevwillow](https://agentstack.voostack.com/s/kevwillow)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kevwillow](https://github.com/kevwillow)
- **Source:** https://github.com/kevwillow/proxmox-manager-agent-skill

## Install

```sh
agentstack add skill-kevwillow-proxmox-manager-agent-skill-proxmox-manager-agent-skill
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Proxmox Manager Agent

Operational skill for managing a Proxmox VE host (single-node or small cluster)
from any AI agent — Hermes, Claude Code, Codex, OpenCode, or any headless
runner — via scoped SSH access. The security model (dedicated agent user,
sudoers allowlist, key-only auth, IP allowlist, audit trail, one-command
revoke) lives in the `scoped-agent-ssh-access` skill. **This skill assumes that
is already set up.** This skill is the *operational* side: which commands to
run, in what order, with what pitfall-avoidance.

## When to Use

- User says "manage my Proxmox," "spin up a VM," "add an ISO," "backup the VMs,"
  "create an LXC," "add storage," "snapshot before I upgrade," "what's running
  on my proxmox," or any equivalent.
- A task touches any of: `qm`, `pct`, `pvesh`, `pvesm`, `vzdump`, `pve-firewall`,
  `ha-manager`, `pvecm`, `qemu-img`, on a PVE 8.x or 9.x host.

**Don't use for:** generic Linux sysadmin (use scoped-agent-ssh-access
directly), non-Proxmox virtualization (KVM/libvirt without PVE wrapper, Xen,
ESXi), or anything where the user has not yet established scoped SSH access —
that's a prerequisite, not a part of this skill.

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ Agent host (where the AI runs)                                  │
│   ~/.ssh/-     (private key, 600 perms)            │
│   HERMES_HOME=/home//.hermes                             │
│   skill: proxmox-manager-agent  (this file)                     │
└────────────────┬────────────────────────────────────────────────┘
                 │ SSH (key-only, scoped user, IP-restricted)
                 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Proxmox VE host (PVE 8.x or 9.x on Debian 12/13)                │
│   user: zen-agent       (or claude-svc, codex-bot, etc.)        │
│   sudoers: /etc/sudoers.d/  (NOPASSWD allowlist only)    │
│   sshd: /etc/ssh/sshd_config.d/99-.conf  (Match block)   │
│   audit: /var/log/auth.log   (every sudo invocation logged)     │
└─────────────────────────────────────────────────────────────────┘
```

The agent never logs in as root. It never has a root password. Every action
goes through the scoped user with a sudoers allowlist. A leaked key still
requires the right source IP and the agent user still exists with the same
blast radius.

## The Command Surface

### Binary locations (PVE 8/9)

| Binary | Path | Purpose |
|---|---|---|
| `qm` | `/usr/sbin/qm` | KVM/QEMU VM management |
| `pct` | `/usr/sbin/pct` | LXC container management |
| `pvesh` | **`/usr/bin/pvesh`** | Proxmox API CLI (preferred for scripted ops) |
| `pvesm` | `/usr/sbin/pvesm` | Storage manager |
| `vzdump` | `/usr/sbin/vzdump` | Backup |
| `qemu-img` | `/usr/sbin/qemu-img` | Raw disk image operations |
| `pveum` | `/usr/sbin/pveum` | User/permission manager |
| `pvecm` | `/usr/sbin/pvecm` | Cluster manager |
| `ha-manager` | `/usr/sbin/ha-manager` | HA (high availability) manager |
| `pve-firewall` | `/usr/sbin/pve-firewall` | Firewall rules compiler |
| `pveam` | `/usr/bin/pveam` | Appliance manager (templates, ISOs) |
| `qmrestore` | `/usr/sbin/qmrestore` | Restore from vzdump backup |

**The `pvesh` and `pvesm` paths are the gotcha.** Most Proxmox docs and
tutorials list them in `/usr/sbin/`, but on PVE 8/9 they live in `/usr/bin/`.
A sudoers rule with the wrong path silently falls through to "ask for a
password" — see `scoped-agent-ssh-access` for the diagnose-and-fix recipe.

Always verify on a fresh host before writing sudoers:
```bash
ssh zen-agent@ 'for b in qm pct pvesh pvesm vzdump pveam ha-manager pve-firewall qemu-img pveum pvecm; do echo -n "$b: "; command -v $b || echo MISSING; done'
```

### Output formats

`pvesh` returns JSON for nested objects and a tabular format for lists.
Both are stable across PVE 8/9.

```bash
sudo -n pvesh get /version
# {"release":"9.2","repoid":"b9984c6d90a4bd80","version":"9.2.2"}

sudo -n pvesh get /nodes
# ┌────────┬────────┬───────┬─...
# │ node   │ status │   cpu │...
# ╞════════╪════════╪═══════╪═...
# │ apollo │ online │ 0.17% │
# └────────┴────────┴───────┴─...
```

`qm list` and `pct list` return empty when there are no VMs/containers.
Empty output is not an error — verify with `sudo -n -l` to distinguish
"command worked, no VMs yet" from "command was denied."

## Whitelist & Blacklist Commands

This is the core of the safe-by-default design. The agent's blast radius
is determined entirely by what's in its sudoers file. Every command below
is either explicitly allowed (whitelist) or explicitly denied (blacklist,
via NOT being in the allowlist).

### Recommended sudoers allowlist (whitelist)

This template goes in `/etc/sudoers.d/` on the Proxmox host.
Adjust paths and command list to match the actual PVE install.

```sudoers
# /etc/sudoers.d/zen-agent
# Whitelist for an AI agent managing this Proxmox host.
# Each line is one allowed command pattern. Anything NOT in this list
# is denied by sudo at runtime, with an audit log entry.

# --- Proxmox management ---
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/qm *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/pct *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/pvesh *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/pvesm *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/vzdump *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/qmrestore *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/qemu-img *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/pveam *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/ha-manager *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/pve-firewall *

# --- Storage observation ---
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/df *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/du *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/lsblk *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/find *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/ls *
zen-agent ALL=(ALL) NOPASSWD: /usr/sbin/blkid *

# --- Service observation ---
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/journalctl *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-units *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *

# --- Network observation ---
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/ip *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/ss *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/ping *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/resolvectl *

# --- User/mount management (for storage workflow) ---
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/mount *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/umount *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/mkdir *

# --- Read-only sysinfo ---
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/uname *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/uptime *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/free *
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/cat /etc/fstab
zen-agent ALL=(ALL) NOPASSWD: /usr/bin/cat /etc/network/interfaces

# --- Audit: every invocation logged ---
Defaults!zen-agent LOG_INPUT
Defaults!zen-agent LOG_OUTPUT
Defaults log_output
Defaults!zen-agent env_reset
```

A copy of this file is at `templates/zen-agent-sudoers` — copy to
`/etc/sudoers.d/zen-agent` on the Proxmox host, validate with
`visudo -c -f /etc/sudoers.d/zen-agent`, then test with
`sudo -n -l` as zen-agent to see the loaded rules.

### Explicit blacklist (commands the sudoers MUST NOT include)

Even though sudoers defaults to deny for anything not whitelisted, be
deliberate about these. The blast radius of each is large, the recovery
is hard or impossible, and there is no operational reason for the agent
to need them.

| Command | Reason to deny |
|---|---|
| `reboot`, `shutdown`, `poweroff`, `halt` | Takes the host down. Agent should ASK, user should run. |
| `mkfs.ext4`, `mkfs.vfat`, `mkfs.xfs`, `mkfs.btrfs` | Destroys filesystem contents. Agent gives instructions, user runs. |
| `dd` | Byte-level disk destruction. Almost no legitimate use case. |
| `parted`, `fdisk`, `sfdisk` | Interactive or scriptable partition table rewrites. Agent identifies, user runs. |
| `cryptsetup` | Disk encryption setup/destruction. |
| `mount /dev/nvme*n* ...` with literal device path | Use only UUID= in fstab — but the agent should not be writing fstab directly anyway. |
| `pvesm remove` without explicit `--nostart 1` | Destroys storage pool metadata. |
| `qm destroy`, `qm stop`, `pct destroy`, `pct stop` | **See "Soft blacklist" below** — present in sudoers, but agent's safety policy refuses without user confirmation. |
| `apt`, `apt-get`, `dpkg` | Package management. Agent should never install packages without explicit user instruction, and even then the user runs the command. |
| `visudo` | Modifying sudoers is a privilege escalation vector. User-only. |
| `useradd`, `userdel`, `usermod`, `passwd` | Account management. User-only. |
| `iptables`, `nft` directly | Use `pve-firewall` API instead. |
| `rm -rf /` | Obviously. Don't include `rm` at all if avoidable — agents rarely need to delete files. |

The pattern: **observe freely, manage Proxmox state freely, but
destructive disk operations and host-state changes require a human in the
loop.** This keeps the blast radius at "VM/container state" not "host
state."

### Soft blacklist (in sudoers, but agent refuses without explicit user OK)

These are commands that Proxmox admins legitimately need, and the sudoers
file permits them, but the agent's safety policy refuses to invoke without
asking first. The agent has them in a *runtime* blocklist, not a sudoers
blocklist — defense in depth.

| Command | Why the agent pauses to ask |
|---|---|
| `qm destroy ` | Deletes the VM and its disks. Irreversible. |
| `qm stop ` | Hard power-off. Use `qm shutdown` first. |
| `pct destroy ` | Deletes the container. Irreversible. |
| `pct stop ` | Hard stop. Use `pct shutdown` first. |
| `pvesm remove ` | Removes storage registration. Doesn't touch data but breaks the web UI. |
| `vzdump --remove 0` (with no other flags) | Can wipe the entire backup retention. |
| `qm snapshot --vmstate 0` on a running VM | Memory snapshot of a live VM. Heavy IO. |
| `ha-manager add` / `ha-manager remove` | HA changes affect failover behavior. |

The agent's exact wording when refusing:
> "I won't run `qm destroy 100` without explicit confirmation — this deletes
> the VM and its disks irrecoverably. Reply `yes destroy 100` if you want me
> to proceed."

This matches the trust model in `scoped-agent-ssh-access`: sudoers says
yes, the agent layer says "ask first."

### How the agent should *think* about the whitelist

When the user says "spin up a VM," the agent's actual command sequence is:

1. **Verify sudoers covers what we need:** `sudo -n -l | grep -E '^.*NOPASSWD.*qm '` — if this returns nothing, stop and tell the user to install the sudoers file.
2. **Verify SSH works with key-only:** `ssh -o BatchMode=yes zen-agent@ 'whoami'` — if this prompts for a password, stop.
3. **Verify audit log exists:** `sudo -n -l | grep -i 'log_'` — if no logging is configured, the sudoers install is incomplete; fix before continuing.
4. **Only then** start running `qm`, `pct`, `pvesh` commands.

If any of those checks fail, the right action is **stop and ask the user
to install the sudoers file**, not to fall back to running things as root.

## Common Workflows

### Reading host state

```bash
# Node health (CPU, RAM, uptime, kernel)
sudo -n pvesh get /nodes//status

# Storage status
sudo -n pvesm status

# Cluster resources (across all nodes if clustered)
sudo -n pvesh get /cluster/resources --type node

# All VMs on the cluster
sudo -n pvesh get /cluster/resources --type vm

# All LXC containers
sudo -n pvesh get /cluster/resources --type ct
```

The `scripts/proxmox-status.sh` script runs all of these in one shot and
prints a clean summary. Run from the agent host:

```bash
scp scripts/proxmox-status.sh zen-agent@:/tmp/
ssh zen-agent@ 'bash /tmp/proxmox-status.sh'
```

### Uploading an ISO from a USB or local path

```bash
# Mount the source
sudo -n mount /dev/sdX1 /mnt/source

# Verify perms (sudoers must include /usr/bin/ls)
sudo -n ls /mnt/source/

# Copy. Proxmox default 'local' storage is /var/lib/vz/template/iso
sudo -n cp /mnt/source/*.iso /var/lib/vz/template/iso/

# Verify via pvesh
sudo -n pvesh get /nodes/localhost/storage/local/content --content iso
```

**Always check `df -h /` before a bulk copy.** Ventoy USBs can easily hold
30–60GB; the default `local` storage backing `/var/lib/vz` may not have that
much free. The `scripts/iso-import.sh` wrapper enforces this with a hard
pre-flight abort.

**Strongly prefer copying ISOs to a dedicated NVMe storage pool
(`/mnt/iso`) rather than `/var/lib/vz/template/iso/` on the boot disk.**
A 9-ISO Ventoy stick can easily total 30-60GB; the Proxmox boot disk is
typically 60-70GB total, so a single copy fills it from 8% to 100% and
breaks the system. See *Adding dedicated storage pools* below.

### Adding dedicated storage pools

When the host has additional NVMe drives that should be Proxmox storage,
the full pipeline is: wipe → partition → format → mount → register with
pvesm. The agent drives this end-to-end with the user running the
destructive steps (the agent's safety policy blocks `mkfs`, `parted`, `dd`
by design — see *Explicit blacklist* above).

**Step 1: Identify drives and confirm scope.**

```bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL,SERIAL,WWN
```

Show the user the boot disk's serial and mark it OUT-OF-SCOPE explicitly.
Reference drives by **serial** (e.g. `S413NS0RC03383M`), NOT by
`/dev/nvmeXnY` — NVMe device numbers shuffle across reboots. The boot disk
in the typical homelab config is identifiable by containing `/boot/efi`
and LVM volumes (`pve-root`, `pve-swap`, `pve-data`).

**Step 2: User partitions (one fdisk at a time).** Agent instructs:

```bash
sudo fdisk /dev/nvmeXnY    # target drive confirmed by serial
# In fdisk:
g                              # new GPT table (destroys existing)
n                              # new partition
1                              # partition 1
[Enter]                        # default first sector
[Enter]    # or +119G          # full disk, or specify size for splits
w                              # write and exit
```

**Step 3: User formats.**

```bash
sudo mkfs.ext4 -L  /dev/nvmeXnYp1
```

**Step 4: User mounts.**

```bash
sudo mkdir -p /mnt/
sudo mount /dev/nvmeXnYp1 /mnt/
df -h /mnt/     # verify the FULL partition size, NOT 458MB
```

**Step 5: Agent adds UUID-based fstab entry (NEVER `/dev/nvmeXnYp1`).**

```bash
UUID=$(blkid -s UUID -o value /dev/nvmeXnYp1)
echo "UUID=$UUID /mnt/ ext4 defaults,nofail 0 2" >> /etc/fstab
mount -a
df -h | grep 
```

NVMe device numbers (`nvme0n1`, `nvme1n1`, ...) are assigned by kernel
discovery order and can shuffle on every reboot, especially after disks are
added/removed. The physical identity (model, serial, WWN) is stable; the
device path is not. UUIDs are stable. Always use `UUID=` or `LABEL=` in
`/etc/fstab` — never `/dev/nvmeXnYp1`. After a reboot, verify with
`df -h /mnt/` that the right contents are at the right mount point.

**Step 6: Agent registers with Proxmox.**

```bash
sudo -n pvesm add dir  --path /mnt/ --content  --is_mountpoint yes
```

After files are copied into the path, force Proxmox to scan and index the
contents:

```bash
sudo -n pvesm set  --is_mountpoint no
sudo -n pvesm list    # verify files are indexed
```

There is no `pvesm scan ` subcommand on PVE 9.x — its purpose was
folded into the `set` command's `--is_mountpoint` toggle. Proxmox doesn't
auto-scan mount-point-backed storage, which is why `pvesm list` returns
empty even when `ls` shows files at the path. The web UI's "Create VM →
ISO" dropdown won't show the storage's contents until `pvesm list` does.

The `scripts/add-nvme-storage.sh` wrapper runs this full pipeline with
explicit prompts at every destructive step.

### Creating a VM from an ISO

```bash
# 1. Pick a VMID (Proxmox convention: 100+ for VMs, 200+ for LXC)
VMID=100

# 2. Create with sensible defaults
sudo -n qm create $VMID \
    --name "ubuntu-test" \
    -

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kevwillow](https://github.com/kevwillow)
- **Source:** [kevwillow/proxmox-manager-agent-skill](https://github.com/kevwillow/proxmox-manager-agent-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kevwillow-proxmox-manager-agent-skill-proxmox-manager-agent-skill
- Seller: https://agentstack.voostack.com/s/kevwillow
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
