added proxmox power button plugin 0.1

This commit is contained in:
2026-08-05 04:46:26 +02:00
parent 28fa9afa28
commit c8f5baedde
15 changed files with 817 additions and 1 deletions

139
README.md
View File

@@ -1 +1,138 @@
# netbox-proxmox-power-button
# 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": 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`:
```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 <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.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).