# 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 ```bash 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`: ```python # configuration/plugins.py PLUGINS = ["proxmox_power_button"] PLUGINS_CONFIG = { "proxmox_power_button": { "verify_ssl": True, # set False only for self-signed lab certs "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` | `True` | verify Proxmox TLS cert (set `False` only for self-signed lab certs) | | `stop_mode` | `"shutdown"` | `shutdown` (graceful ACPI) or `stop` (hard) | | `reboot_mode` | `"reboot"` | `reboot` (graceful) or `reset` (hard) | ## Security - **Token is stored in a plain-text custom field.** It is visible to anyone who can *view* the cluster — in the UI, in REST API responses for the cluster, and in change-log data. NetBox has no "secret" custom-field type, so: - restrict Cluster **view** permissions to administrators only; - use a least-privilege Proxmox API token: only `VM.Audit` + `VM.PowerMgmt`, scoped to the specific VMs or pool this plugin should manage — never a `root@pam` token with datacenter-wide rights. Anyone who can read the field can extract the token and drive the hypervisor directly. - **Permissions.** The buttons render only for users with `virtualization.change_virtualmachine`, and the action endpoint enforces NetBox's **object-scoped** permissions: a user whose change permission is constrained (e.g. to one tenant's VMs) gets a 404 on any VM outside that scope — the same behaviour as core NetBox object views. - **TLS verification is on by default.** The Proxmox API token crosses this connection on every power action; leaving `verify_ssl` at `True` prevents token theft via MitM. Only set it to `False` for self-signed lab certificates. - **Error detail goes to the log, not the browser.** Failed actions show a generic message; the full Proxmox/network error (which may contain internal hostnames, URLs, or response bodies) is written to the `proxmox_power_button` logger. ## Notes / caveats - **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`: ```bash 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 ``` 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.toml` → `version`, and `proxmox_power_button/__init__.py` → `ProxmoxPowerButtonConfig.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).