Files
netbox-proxmox-power-button/README.md

6.1 KiB

proxmox_power_button

NetBox plugin that adds Start / Stop / Reboot buttons to the Virtual Machine detail page and drives the corresponding Proxmox VE VM, reflecting the power state back into the NetBox VM status.

Installation

pip install netbox-proxmox-power-button

The distribution is netbox-proxmox-power-button; the importable package — and the name that goes into NetBox's PLUGINS — is proxmox_power_button:

# configuration/plugins.py
PLUGINS = ["proxmox_power_button"]

PLUGINS_CONFIG = {
    "proxmox_power_button": {
        "verify_ssl": False,
        "stop_mode": "shutdown",
        "reboot_mode": "reboot",
    },
}

Then run ./manage.py migrate (creates the custom fields) and restart NetBox and its worker.

Behaviour

  • Buttons live in the VM detail page's button bar, before "Add Components" (injected via {% plugin_buttons %}, no custom page).
  • Start — green, mdi-play. Shown only when the VM is not active. On success sets NetBox status → active.
  • Stop — red, mdi-stop. Shown only when the VM is active. On success sets NetBox status → offline.
  • Reboot — orange, mdi-sync. Shown only when the VM is active, to the left of Stop. Leaves status unchanged.
  • NetBox status is updated only after Proxmox confirms the command. If Proxmox is unconfigured/unreachable, the action shows an error and changes nothing (no 500).
  • After a start, the plugin waits 3s and re-queries Proxmox; the status is set to active only if the VM reports running. Stop/reboot are not verified inline (they take too long to settle).

Auditing

One changelog entry per state change, each carrying a readable message (visible in the VM's Changelog tab and in /core/changelog/):

operation changelog entry object saved
start (confirmed running) "Powered on via Proxmox (confirmed running)" yes (status)
stop "Powered off via Proxmox (shutdown sent)" yes (status)
reboot "Rebooted via Proxmox" no — entry written directly
start sent, not yet running none (no state change) no
failure none (no state change) no

Every outcome — including the two "none" rows above — is written to the proxmox_power_button logger (visible in docker compose logs netbox), e.g.:

INFO  … proxmox_power_button user=admin vm=jellyfin: powered on (confirmed running)
ERROR … proxmox_power_button user=admin vm=jellyfin: start failed: VMID 20100 not found …

Data model (custom fields, auto-created by migration)

  • VirtualMachine.vmid — integer, required, min 100. Unique within a cluster (enforced on create, on VMID change, and when moving the VM to another cluster — a move into a cluster that already has that VMID is rejected).
  • Cluster.endpoint — text: host, host:port, or https://host:port.
  • Cluster.token — text: user@realm!tokenid=secret.

The client resolves the target VM by vmid via cluster/resources, so it works for both QEMU and LXC.

Settings (PLUGINS_CONFIG["proxmox_power_button"])

key default meaning
verify_ssl False verify Proxmox TLS cert
stop_mode "shutdown" shutdown (graceful ACPI) or stop (hard)
reboot_mode "reboot" reboot (graceful) or reset (hard)

Notes / caveats

  • Token is stored in a plain-text custom field and is visible to anyone who can view the cluster. Use a scoped, least-privilege Proxmox API token and restrict cluster view permissions. NetBox has no "secret" custom-field type.
  • Requires NetBox ≥ 4.5.0. The data migration depends on extras.0134_owner and virtualization.0052_gfk_indexes, which first appear in 4.5.0; on 4.4 or older migrate fails with NodeNotFoundError. Bump min_version together with those pins if you ever retarget them. Verified against 4.5.8.
  • Making vmid required means existing VMs without a VMID will fail validation on their next edit until one is set.

Releasing to PyPI

Everything runs through the makefile:

make venv              # one-off: build+twine in .venv-publish (PEP 668-safe)
make bump V=0.2.0      # writes the version to BOTH places, then verifies
make build             # clean + sdist + wheel
make check             # lists both artifacts, asserts contents, twine check
make testpypi          # optional dry run against TestPyPI
make publish           # build + check + upload (asks you to type the version)
make tag               # git tag <version>

Uploads authenticate with username __token__ and a pypi-… API token (~/.pypirc, or TWINE_USERNAME/TWINE_PASSWORD). A version number is burned permanently on upload — it can never be reused, even after deleting the release; hence publish refuses to run without a passing check and a typed confirmation.

The version lives in two places: pyproject.tomlversion, and proxmox_power_button/__init__.pyProxmoxPowerButtonConfig.version. They are not single-sourced on purpose — importing the package to read a version would drag in netbox, absent in a build environment. make bump writes both and make version fails loudly if they ever drift.

make check asserts three things that a broken build would otherwise hide until someone installs the package: the wheel carries the button template (templates/ has no __init__.py, so without [tool.setuptools.package-data] +MANIFEST.in you get a wheel that raises TemplateDoesNotExist on every VM page), the migration, and the licence.

Licence

GPL-2.0-or-later. See LICENSE for the full text; every source file carries an SPDX-License-Identifier: GPL-2.0-or-later header, which is what expresses the "or later" option (the GPL-2 text alone does not).