A bar widget for Omarchy Quattro showing Proxmox VE guest status, CPU/memory/disk usage, power actions (start/stop/shutdown/ reboot), and console access (LXC shell, VM SPICE/noVNC) — at a glance.
Forked from g-desoutter/omarchy-plugin-proxmox (the read-only status widget) and extended with actions, console, host stats, and multi-cluster support.
The bar shows running / total. Click it for a panel listing every guest
with live metrics, action buttons, and console access. The widget turns to
the bar's alert colour when the API is unreachable, or when a guest you
explicitly watch is not running.
![]() |
![]() |
- Status: guest list (LXC + QEMU) with CPU/RAM, sorted running-first, plus a host-level CPU/RAM/disk summary for the node itself
- Actions: start / graceful shutdown / forced stop / reboot, with a
two-click confirmation on anything destructive — via a Proxmox API token
scoped to
VM.PowerMgmtonly, no SSH involved - Console:
- LXC → opens a terminal with
pct enterover SSH - VM with SPICE configured → generates a ticket via the API and opens it
with
remote-viewer(fromvirt-viewer) - VM without SPICE → falls back to noVNC in a browser app window, no guest-side configuration needed
- "Terminal on host" button for a direct SSH shell on the node
- LXC → opens a terminal with
- Multi-cluster:
allowMultipleis enabled — add one widget instance per Proxmox cluster/host
Status polling and actions use different API tokens with different roles, on purpose:
- Polling (continuous, every
intervalseconds) uses a token scoped toPVEAuditor— read-only. A leaked polling token exposes visibility only. - Actions (only invoked on an explicit click) use a separate token
scoped to a custom role containing only
VM.PowerMgmt— it can start/stop/reboot guests and nothing else: no config read/write, no console, no datastore access, no user/ACL management.
Console access is the one feature that genuinely needs SSH (there is no API
equivalent for an interactive LXC shell or generating a local terminal
session), scoped to whatever user you configure in sshUser — separate
from both API tokens.
curl,jq,sshon the machine running Omarchyvirt-viewer(forremote-viewer, SPICE consoles) — optional, only needed for VMs listed inspiceVms- A Proxmox VE cluster reachable from that machine
On your Proxmox node:
pveum user add omarchy@pve --comment "Omarchy bar widget"
pveum user token add omarchy@pve bar --privsep 1
pveum acl modify / --users 'omarchy@pve' --roles PVEAuditor
pveum acl modify / --tokens 'omarchy@pve!bar' --roles PVEAuditorThe token secret is displayed once, at creation. Copy it now.
Both ACL lines are required. With privilege separation enabled, a token's
effective permissions are the intersection of the user's and the
token's — granting the role to only one of them yields no access at all,
and /cluster/resources returns an empty list rather than a 403.
Same user, a second token, with a custom role containing only
VM.PowerMgmt — not one of the built-in roles, which all bundle other
privileges (config access, snapshots, migration, etc.) you don't want to
hand to a bar widget:
pveum role add OmarchyPowerMgmt --privs VM.PowerMgmt
pveum user token add omarchy@pve actions --privsep 1
pveum acl modify / --users 'omarchy@pve' --roles OmarchyPowerMgmt
pveum acl modify / --tokens 'omarchy@pve!actions' --roles OmarchyPowerMgmtSame intersection rule as above applies — both ACL lines are required.
If you don't want power actions at all, skip this step and leave
actionCredentialsFile unset: the buttons will just fail cleanly on click.
install -d -m 700 ~/.config/omarchy/proxmox
cat > ~/.config/omarchy/proxmox/credentials <<'EOF'
PVE_TOKEN_ID='omarchy@pve!bar'
PVE_TOKEN_SECRET='<secret from step 1>'
EOF
cat > ~/.config/omarchy/proxmox/actions-credentials <<'EOF'
PVE_TOKEN_ID='omarchy@pve!actions'
PVE_TOKEN_SECRET='<secret from step 2>'
EOF
chmod 600 ~/.config/omarchy/proxmox/credentials ~/.config/omarchy/proxmox/actions-credentialsSingle quotes matter: the token id contains !, which bash would otherwise
treat as history expansion.
Never put a token in shell.json. That file is plugin configuration,
not a secret store, and it's the kind of file people keep in a dotfiles
repo. The widget only ever learns the path to each credentials file.
Proxmox generates its own PKI at install time. Copy the cluster CA to the system trust store so TLS verification succeeds without special-casing:
# Print it on the node, paste it locally
cat /etc/pve/pve-root-ca.pem
sudo cp pve-root-ca.pem /etc/ca-certificates/trust-source/anchors/pve.crt
sudo trust extract-compatVerify — a 401 means the TLS handshake succeeded and only authentication
is missing, which is what you want at this point:
curl -s -o /dev/null -w '%{http_code}\n' https://your-node.example.com:8006/api2/json/versionThe endpoint host must match the certificate's CN or a SAN entry. Check what the node presents with:
openssl s_client -connect <node>:8006 </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltNameIf it doesn't (self-signed certs on some setups only cover the default
hostname, e.g. pve/pve.local), use the node's IP as the endpoint
instead of a custom hostname, or point caBundle at a local copy of the CA
rather than touching the system trust store.
Only needed if you want the console/terminal features. Passwordless SSH as
sshUser (root by default) to each Proxmox node:
ssh-copy-id root@your-node.example.comThis grants the widget a full root shell on the host when you click console/terminal buttons — that's inherent to what an interactive LXC shell requires, not something a scoped API token can replace. If you're not comfortable with that, skip this step: status and actions work independently of console access.
omarchy plugin add https://github.com/boyoyooo/proxmarchy.git --enableSettings are inline on the widget's entry in ~/.config/omarchy/shell.json:
{
"id": "io.github.boyoyooo.proxmarchy",
"endpoint": "https://your-node.example.com:8006",
"credentialsFile": "/home/you/.config/omarchy/proxmox/credentials",
"actionCredentialsFile": "/home/you/.config/omarchy/proxmox/actions-credentials",
"label": "homelab",
"watchedGuests": [500],
"sshUser": "root",
"spiceVms": [110],
"terminal": "xdg-terminal-exec",
"browser": "chromium"
}| Key | Default | Description |
|---|---|---|
endpoint |
https://proxmox.example.com:8006 |
PVE API base URL. Must match the certificate CN or a SAN. |
credentialsFile |
~/.config/omarchy/proxmox/credentials |
Read-only PVEAuditor token, for polling. |
actionCredentialsFile |
(unset) | Scoped VM.PowerMgmt token, for start/stop/shutdown/reboot. Leave unset to disable actions. |
caBundle |
(system trust store) | Absolute path to a CA bundle, if you did not install the CA system-wide. |
interval |
15 |
Refresh interval in seconds. Values below 10 gain nothing — see below. |
label |
(the endpoint) | Name shown in the panel header. |
watchedGuests |
[] |
VMIDs whose shutdown is abnormal. Empty means never alert on a stopped guest. |
sshUser |
root |
SSH user for console/terminal access only — never used for actions. |
spiceVms |
[] |
VMIDs to open with SPICE (remote-viewer) instead of the noVNC fallback. |
terminal |
xdg-terminal-exec |
Terminal launcher command, for LXC console and host terminal. |
browser |
chromium |
Browser command, for the noVNC fallback. |
allowMultiple is enabled, so you can add one widget per cluster/host.
A stopped guest is not inherently a problem — lab VMs and templates are meant to be off. The widget cannot know your intent, so you declare it: list only the VMIDs whose absence is genuinely abnormal. Everything else is ignored, and the widget stays quiet.
Refresh interval. pvestatd refreshes /cluster/resources roughly
every 10 seconds, so polling faster than that re-reads the same sample. The
widget floors the interval at 10s for this reason. Guest status changes,
unlike metrics, are immediate — stopping a VM is reflected on the next
poll, or instantly via the panel's refresh button.
Memory figures. mem is memory as seen by the hypervisor, not pressure
inside the guest. A Linux guest filling its page cache, or a Windows guest
caching aggressively, will read high without being short of RAM. Don't size
guests from this number; check Available Memory inside the guest instead.
CPU figures. cpu is already normalised across all vCPUs assigned to
the guest, so 100% means the guest is saturating its allocation — not one
core.
Guest OS icons. The OS type comes from each VM's ostype config field,
which is declarative rather than detected — a guest created with the wrong
value shows the wrong icon. It is cached for 24 hours under
$XDG_CACHE_HOME/omarchy-proxmox, so the extra request happens once per
guest per day. Containers always show the container icon.
SPICE requires VirtIO GPU display. A VM set to "Standard Display" will
not offer a SPICE console — switch it to "VirtIO GPU" in the VM's Display
settings, or leave it out of spiceVms to use the noVNC fallback instead.
- The poller issues read-only
GETrequests throughcurl, using a token scoped toPVEAuditor. It never writes to the cluster. - The actions script issues
POST .../status/{start,stop,shutdown,reboot}requests, using a separate token scoped to a custom role containing onlyVM.PowerMgmt. It cannot read VM config, manage snapshots, touch storage, or do anything outside power state changes — even if there were a bug in this script's own code, that's the ceiling on what it could do. - The console/terminal features use SSH as
sshUser(root by default) — this is the one place the plugin has full host access, because an interactive LXC shell has no API equivalent. It's opt-in: skip step 5 in Setup and console buttons simply won't work, everything else still does.
Like every Omarchy plugin, this runs unsandboxed inside the long-lived
shell process, with your user's permissions. Read the scripts in bin/
before enabling the plugin.
omarchy plugin remove io.github.boyoyooo.proxmarchyThis only removes the plugin files — it does not revoke the credentials or trust it created during Setup. To fully clean up, also do the following.
Proxmox side (on each host you configured):
pveum user delete omarchy@pve # removes both API tokens (bar, actions) and their ACLs
pveum role delete OmarchyPowerMgmt # only if you set up the actions/power-management tokenIf you enabled console access (SSH), Setup step 5 (ssh-copy-id) installed
your local public key in that node's /root/.ssh/authorized_keys — remove
it there too (passwordless root access otherwise survives the plugin):
ssh root@your-node.example.com "sed -i '/<a distinctive fragment of your pubkey>/d' ~/.ssh/authorized_keys"Local side (on the machine that ran Omarchy):
rm -rf ~/.config/omarchy/proxmox # the two plaintext token files from Setup step 3
rm -rf ~/.cache/omarchy-proxmox # cached guest OS-type lookups
sudo rm -f /etc/ca-certificates/trust-source/anchors/pve.crt # the cluster CA trusted in Setup step 4
sudo trust extract-compat
ssh-keygen -R <your-proxmox-host> # accepted host key, if you used console accessMIT — see LICENSE. Includes code from g-desoutter/omarchy-plugin-proxmox (MIT).


