Version 0.0.15¶
Summary¶
Version 0.0.15 introduces the NetBox → Proxmox intent path (Issue #377) — an opt-in second integration direction that complements the historic read-only Proxmox → NetBox reflection — and rolls up the rest of the long-running v0.0.15 line: the Cluster HA dashboard, operational verbs, SSH-driven hardware discovery, NetBox Branching integration, a standalone scheduler container, cloud-init reflection, description-field metadata parsing, regex-based tenant assignment, default-role pinning, and an idempotent NetBox bootstrap. It pairs with backend proxbox-api 0.0.11 for the reflection surface, the new HA endpoints, and the new /intent/* surface (plan validator, CREATE/UPDATE/DELETE dispatchers, cloud-init builder, deletion-request executor, audit-journal scrubbing).
The intent path is opt-in at every level. With netbox_to_proxmox_enabled=False (default), nothing in 0.0.15 changes the historic read-only reflection behavior.
It also fixes five issues:
- Issue #352: the
FastAPIEndpointmodel could not express the combination "use HTTPS but skip certificate verification", which is the default state of the proxbox-api*-nginximage (TLS-only with a self-signed mkcert certificate). - Issue #354: IPAM
IPAddressrecords created during virtualization sync had an emptydns_name, even though Proxmox knew the guest hostname. The plugin now exposes a newoverwrite_ip_address_dns_namesetting (global + per-endpoint) so operators can opt out ofdns_namewrites; the actual hostname resolution and write live inproxbox-api 0.0.11. - Issue #391: the Virtual Machines and LXC Containers plugin pages now auto-detect whether the installed NetBox has the 4.6
VirtualMachineTyperelation. NetBox 4.5.x stays on the legacyproxmox_vm_typecustom-field path, while NetBox 4.6.x keeps native type support. - Issue #243: Proxmox cluster High-Availability state was not surfaced anywhere in NetBox. The plugin now ships a per-VM HA tab and a cluster-wide HA Status page, both backed by new read-only HA endpoints in
proxbox-api 0.0.11. - Issue #360: operators had no headless way to trigger a full Proxmox→NetBox sync — every run required a human clicking Full Update in the plugin UI, which blocked cron, systemd timers, Kubernetes CronJobs, and CI smoke checks. The plugin now ships a
python manage.py proxbox_syncDjango management command that enqueues the sameProxboxSyncJobas the UI button. - Issue #359: VM-interface MACs synced through the plugin never appeared in NetBox. The legacy inline
mac_addressfield onVMInterfaceisread_only=Trueat NetBox 4.5/4.6 (computed fromprimary_mac_address), so every MACproxbox-apiposted was silently dropped. The plugin itself ships no code change; the fix lives inproxbox-api 0.0.11, which now writes MACs viadcim.MACAddressand links them throughVMInterface.primary_mac_address. Existing v0.0.15 installs pick the fix up by upgrading the backend. - Issue #367: operators need a safe way to remove VMs that were previously discovered by Proxbox but no longer appear in the current Proxmox inventory. The plugin now exposes a default-off Delete orphan VMs setting that proxbox-api reads before running its orphan sweep.
NetBox → Proxmox Intent System (#377)¶
Twelve sub-issues land together as Sub-PRs A through L:
- #378 — Sub-PR A — Design doc. New
docs/design/netbox-to-proxmox-intent.mdcaptures the §1–§17 design from #377 verbatim. - #379 — Sub-PR B — Gate.
ProxboxPluginSettings.netbox_to_proxmox_enabled, typed-confirmation phraseallow-edit-and-add-actions,apply_destroy_confirmedflag, seven RBAC permissions registered through migration0038_intent_permissions, and a red advanced-direction warning callout on the Settings page. - #380 — Sub-PR C — Bootstrap custom fields. Migration
0039_intent_custom_fieldsregisters 10 VM CFs and 2 Branch CFs through the_v0_0_15_release_databootstrap module. Branching CFs are guarded so the migration is a no-op whennetbox_branchingis not installed. - #381 — Sub-PR D — Plan validator.
POST /intent/planon the backend plusnetbox_proxbox.intent.merge_validatorreturning aBranchActionIndicator(permitted, message)fornetbox-branchingto consume. - #382 — Sub-PR E —
post_mergehook. Newsignal_receivers.pyplusnetbox_proxbox.intent.apply_job.ProxmoxApplyJob. Receiver exceptions are fully swallowed (the merge transaction has already committed whenpost_mergefires). The run phase is a dry-run no-op in this PR. - #383 — Sub-PR F — CREATE.
apply_job.runbuildsVMIntentPayload/LXCIntentPayloadfrom NetBox state, POSTs to/intent/apply, and stampsproxbox_intent_state=appliedplusproxbox_last_apply_run_id. UPDATE/DELETE return501in this PR. - #384 — Sub-PR G — UPDATE. Adds delta builders, TOCTOU recheck via
find_vmid_record, and offline-required-key gating (QEMU_OFFLINE_REQUIRED_KEYS = {"cores","memory"}; LXC addsmp,rootfs). Running VMs are never auto-stopped. - #385 — Sub-PR H — DELETE → safe-delete. DELETE diffs create a
DeletionRequestrow, tag the Proxmox VMproxbox-pending-deletion, and capture a metadata snapshot. The plugin never calls Proxmox destroy from the merge handler. - #386 — Sub-PR I — Deletion Requests UI + executor. Approve/reject/list views, four-eyes self-approval block at model + view + API client layers, TTL cron job, and the two-and-only-two backend destroy dispatchers (
qemu_destroy.py,lxc_destroy.py). - #387 — Sub-PR J — Audit + four-eyes regression suite. Pure test PR: static destroy-gate walker, state-machine, orphan-tag, and journal-emit invariants.
- #388 — Sub-PR K — Cloud-Init.
CloudInitPayloadPydantic model andbuild_proxmox_ci_argsmap the four cloud-init CFs tociuser,sshkeys(URL-encoded),cicustom, andipconfig0. The plan validator emits a plaintext-password warning whencloud_init_user_datacontains apassword:key. Newproxbox_api/utils/log_scrubbing.pystripscipassword,password,secret, andtokenfrom every journal write. - #389 — Sub-PR L — UI / docs / polish. Plan-summary view, live SSE log widget on apply-job detail, audit-chain rendering on deletion-request detail, operator guides under
docs/operations/, version bump to0.0.15/proxbox-api 0.0.11, and the four-invariant Safety Model appended toCLAUDE.mdandAGENTS.md.
Safety Model — four mandatory invariants¶
netbox-proxbox 0.0.15 enforces four mandatory invariants on the intent path. Code or configuration that bypasses any of these is a regression.
- Default direction is Proxmox → NetBox (read-only). The intent path is opt-in at every level.
- Master flag is locked behind a typed confirmation phrase.
netbox_to_proxmox_enabled=Truerequiresnetbox_to_proxmox_typed_confirmation == "allow-edit-and-add-actions"to passProxboxPluginSettingsForm.clean(). - Every Proxmox-side DELETE goes through a
DeletionRequest. Branch merges containing DELETE diffs do not call Proxmox destroy at merge time. - Authorization permission is held separately from
intent_delete_*.netbox_proxbox.authorize_deletion_requestis independent ofintent_delete_vm/intent_delete_lxc; self-approval is rejected unlessintent_apply_authorization_self_approve_allowed=True(defaultFalse).
The 0.0.15 plugin pairs with the 0.0.11 release of proxbox-api for both the reflection-side surface and the new intent path. The published 0.0.11 backend exposes /intent/plan, /intent/apply, and /intent/deletion-requests/*; older backends that do not expose those routes return 404 Not Found to the plugin's intent clients.
#352 — Use HTTPS toggle decoupled from Verify SSL¶
Until this release, the Verify SSL flag on the FastAPI endpoint was overloaded — it controlled both the URL scheme (http vs https) and the underlying requests verify= argument. With the bundled mkcert cert, Verify SSL=True failed on cert validation; Verify SSL=False downgraded the URL to plain HTTP, which the nginx image rejects with 400 Bad Request: The plain HTTP request was sent to HTTPS port.
A new Use HTTPS field decouples scheme selection from certificate verification:
| Use HTTPS | Verify SSL | Resulting connection |
|---|---|---|
| ✗ | — | http:// (cert verification flag is unused) |
| ✓ | ✓ | https:// with strict cert verification |
| ✓ | ✗ | https:// with cert verification skipped — required for the *-nginx image with the bundled mkcert cert |
A migration backfills use_https = verify_ssl for existing rows so installs that were already on a working HTTPS-with-verified-cert setup keep working without operator intervention.
The plugin also returns a clearer error when it detects nginx's plain HTTP request was sent to HTTPS port body, prompting the operator to enable Use HTTPS.
#243 — HA tab and cluster-wide HA Status page¶
Proxmox cluster High-Availability state was previously invisible inside NetBox. Operators had to open the Proxmox web UI just to answer "is this VM HA-managed and what's its current CRM state?". This release adds two read-only views — both fetched live from proxbox-api 0.0.11 on every page render, no caching, no NetBox-side persistence, no migration.
- VM HA tab. A new tab on every
virtualization.VirtualMachinedetail page, sibling to Proxmox Config (slot weight1400). It callsGET /proxmox/cluster/ha/resources/by-vm/{vmid}and renders HA managed yes/no, group, current state / CRM state / request state, node, and the restart/relocate/failback counters. VMs that are not HA-managed get a friendly empty state with a link to the cluster page. - HA Status page. A new top-level Proxbox menu entry (
/plugins/proxbox/ha/) that callsGET /proxmox/cluster/ha/summaryonce and renders three sections: Cluster Status (per-node CRM state and quorum), HA Groups (name / nodes / restricted / nofailback), and HA Resources (every HA-managed VM/CT, sid linked to its NetBoxVirtualMachinewhen one exists with the matchingproxmox_vm_id). - Backend version awareness. When the FastAPI backend is older than
0.0.11and returns404on the new HA routes, both views render an inline "Backend does not support HA endpoints — upgrade proxbox-api to v0.0.11 or later." banner instead of a 500.
Changes¶
netbox_proxbox/views/vm_ha.py(new).ProxmoxVMHATabViewregistered viaregister_model_view(VirtualMachine, "proxmox_ha", path="proxmox-ha")withViewTab(label="HA", permission="virtualization.view_virtualmachine", weight=1400). Reusesservices.backend_context.get_fastapi_request_contextandservices._endpoint_errors.translate_request_exceptionexactly likeProxmoxVMConfigTabView.netbox_proxbox/views/ha.py(new).HAClusterViewextendingConditionalLoginRequiredMixin+RequireProxboxDashboardAccessMixin, fetching/proxmox/cluster/ha/summarywith a 15s timeout and renderingnetbox_proxbox/ha.html.- Templates.
templates/netbox_proxbox/vm_proxmox_ha.html(extendsgeneric/object.html) andtemplates/netbox_proxbox/ha.html(extendsbase/layout.html), both using NetBox's standard card / table / badge styling. urls.py. New routepath("ha/", views.HAClusterView.as_view(), name="ha").navigation.py. Newha_item = PluginMenuItem(link="plugins:netbox_proxbox:ha", link_text="HA Status")slotted between Replications and Task History under the "Proxmox Plugin" group.- Tests.
tests/test_views_vm_ha.pyandtests/test_views_ha.pyare AST-based source-contract tests — same pattern astest_views_vm_config.py— pinning theregister_model_viewarguments,ViewTabkwargs, template names, backend URLs (/proxmox/cluster/ha/resources/by-vm/and/proxmox/cluster/ha/summary), the_extract_vmidhelper, and the navigation/URL wiring. - No DB migration. No new persisted model. No
OVERWRITE_FIELDSchange. HA state is read-only.
Compatibility¶
| NetBox | netbox-proxbox | proxbox-api | netbox-sdk | proxmox-sdk |
|---|---|---|---|---|
| >=4.5.8 | v0.0.15 | v0.0.11 | v0.0.8.post1 | v0.0.3.post1 |
| >=4.5.8 | v0.0.14 | v0.0.10.post2 | v0.0.8.post1 | v0.0.3.post1 |
NetBox compatibility range: 4.5.8 – 4.6.99 (unchanged).
Changes¶
FastAPIEndpoint.use_https(new field). Controls the URL scheme. DefaultFalsefor new rows; existing rows are backfilled toverify_ssl's current value (preserving the effective scheme). Help text guides operators to enable it for the proxbox-api*-nginximage.FastAPIEndpoint.verify_ssl(semantics change). Now controls only TLS certificate verification — never the URL scheme. The default remainsTrue.- Migration
0038_fastapiendpoint_use_https. Adds the column withIF NOT EXISTSfor production-safe additive deployment, plus aRunPythonstep that copiesverify_sslintouse_httpsso existing rows do not regress. - Form, table, serializer, detail page. All surface the new field; the table includes it as a default column next to
Verify SSL. services/_endpoint_errors.py(new). Translates the two known misconfiguration errors into operator-actionable messages:400body containingplain HTTP request was sent to HTTPS port→ "Enable 'Use HTTPS' on the FastAPI endpoint."SSLError(e.g. self-signed cert) → "Uncheck 'Verify SSL' on the FastAPI endpoint."services/openapi_schema.py. Wires the translator into the/openapi.jsonand/versionfetch path the FastAPI endpoint detail page uses, so the misconfigured-image error is shown inline instead of the rawrequestsexception.utils.get_fastapi_urlandsignals._get_backend_url. Updated to derive the scheme fromuse_httpsonly, while still propagatingverify_sslseparately so the requests layer can decide cert strictness.FastAPIEndpoint.urlandFastAPIEndpoint.websocket_url. Useuse_httpsforhttp/httpsandws/wssselection, overriding the inheritedCommonProperties.url.- Docs.
docs/installation/backend-setup.mdnow spells out theUse HTTPS+Verify SSLcombination required for the*-nginximage.
#354 — IPAM dns_name populated from Proxmox guest hostnames¶
The plugin already renders IPAddress.dns_name in the IP-addresses table; the gap was on the write side. With proxbox-api 0.0.11, the backend now resolves the guest hostname (LXC: VMConfig.hostname; QEMU: agent/get-host-name, with a fallback through the network-interfaces payload) and writes it to IPAddress.dns_name on every IP create / reconcile path. This release wires the corresponding overwrite toggle through the plugin so operators can disable the write without touching the backend.
Changes¶
ProxboxPluginSettings.overwrite_ip_address_dns_name(new field, defaultTrue). When disabled, sync never changesdns_nameon existing IP addresses;dns_nameis still populated when an IP is created.ProxmoxEndpoint.overwrite_ip_address_dns_name(new tri-state field). Per-endpoint override; leave blank to inherit the global setting.- Migration
0039_pluginsettings_overwrite_ip_address_dns_name. Adds the column to bothProxboxPluginSettings(NOT NULL DEFAULT TRUE) andProxmoxEndpoint(nullable) usingSeparateDatabaseAndState+ALTER TABLE … IF NOT EXISTS. OVERWRITE_FIELDS/OVERWRITE_FIELD_GROUPS. The new flag is part of the canonical 23-flag set and lives in the IP Address group on the Settings tab.contracts/overwrite_flags.json. Updated mirror manifest (paired with the matching update inproxbox-api/contracts/overwrite_flags.json); the cross-repo drift detector test (tests/test_overwrite_flags_contract.py) covers it.- Settings + endpoint forms, serializers, tables. Surface the new flag wherever the sibling
overwrite_*flags appear, so it can be toggled from the UI and exported in CSV/JSON/YAML. sync_params._build_base_query_params. Forwards the resolvedoverwrite_ip_address_dns_namevalue to proxbox-api as a flat query-string key (the same shape every other overwrite flag uses).
#367 — Optional orphan VM cleanup toggle¶
The backend now stamps every reconciled VM with a shared run UUID and can sweep Proxbox-discovered VMs whose stamp is stale or missing. This plugin release adds the operator-facing gate for that destructive behavior.
Changes¶
ProxboxPluginSettings.delete_orphans(new field, defaultFalse). When enabled, proxbox-api full-update runs may delete Proxbox-discovered QEMU/LXC VMs that were not touched by the current sync run.- Migration
0046_pluginsettings_delete_orphans. Adds the default-off column using the establishedSeparateDatabaseAndState+ALTER TABLE ... IF NOT EXISTSpattern. - Settings form, template, and API serializer. Surface the flag in the Plugin Settings page and
/api/plugins/proxbox/settings/so proxbox-api can read it through the existing runtime settings client. - Docs. The Plugin Settings guide documents the
PROXBOX_DELETE_ORPHANSbackend env override and recommends reviewing the full-update dry-run stream before enabling deletion.
#360 — Headless proxbox_sync Django management command¶
Operators previously had no programmatic way to trigger a full Proxmox→NetBox sync — every run required a logged-in user clicking Full Update in the plugin UI. This blocked the entire operational-verbs roadmap (#14, #15, #16) for cron, systemd timers, Kubernetes CronJobs, and CI smoke checks.
This release adds a proxbox_sync Django management command that is the exact headless equivalent of clicking Full Update: it enqueues the same ProxboxSyncJob (queue default, sync_types=[SyncTypeChoices.ALL], all configured ProxmoxEndpoint rows) and writes a styled success / failure line.
# Fire-and-forget — enqueue and return immediately
python manage.py proxbox_sync
# Block until the job reaches a terminal state and mirror its exit code
python manage.py proxbox_sync --wait --timeout 7200
# Attribute the job to a specific user instead of the oldest active superuser
python manage.py proxbox_sync --user backupbot --wait
Changes¶
netbox_proxbox/management/commands/proxbox_sync.py(new). Thin wrapper aroundProxboxSyncJob.enqueue(...)with five flags —--user,--wait,--timeout,--poll-interval,--worker-grace— andCommandError-driven non-zero exits for cron/systemd-friendly failure semantics.- Pre-flight reachability. Reuses
services.backend_auth.wait_for_backend_ready(5-retry snappy CLI mode) andservices.backend_context.get_fastapi_request_contextso the command surfaces the same backend-unreachable / no-FastAPIEndpoint errors the UI does. --waitno-worker fast-fail. If--waitis set and the job stayspendingfor more than--worker-graceseconds with zero RQ workers on thedefaultqueue, the command exits non-zero with an actionable "no RQ worker is consuming thedefaultqueue" message instead of hanging.- User attribution. Defaults to the oldest active superuser;
--user USERNAMEoverrides. Missing user is aCommandError. - Tests.
tests/management/test_proxbox_sync.py(10 scenarios) plustests/management/conftest.pyinstall lightweightdjango.core.management.base/django.contrib.authstubs so the command can be exercised without bootstrapping NetBox. - Docs. New
docs/operations/headless-sync.mdpage covering flag reference, exit codes, the RQ-worker requirement, a 3-line cron example, and a 6-line systemd-timer example.
No DB migration. No model change. No new persisted state.
Upgrade Notes¶
- Run
python manage.py migrate netbox_proxboxafter upgrading; the migrations are additive and include a one-time data backfill (issue #352), a single new column onProxboxPluginSettingsandProxmoxEndpoint(issue #354), the default-offdelete_orphansflag (issue #367), and the intent migrations0038_intent_permissions,0039_intent_custom_fields,0040_apply_job_full, and0041_deletion_request_full. The custom-field migration is a no-op for the two Branch CFs whennetbox_branchingis not installed. - If you operate the proxbox-api
*-nginximage and previously could not connect, edit the FastAPI endpoint after upgrade and tick Use HTTPS (and untick Verify SSL if you use the bundled mkcert cert). - For the
dns_namefix, pair withproxbox-api ≥ 0.0.11.proxbox-api 0.0.10.post2is still wire-compatible for theUse HTTPSfix but does not populatedns_name. With an older backend, the new toggle has no effect because the backend never writesdns_name. - The
dns_namedefault is "always overwrite" to match every other overwrite flag. If you have hand-editeddns_nameon synced IPs, untick Overwrite IP address DNS name before the next sync (globally, or per Proxmox endpoint). - The default for
netbox_to_proxmox_enabledisFalse. Existing installs see no behavior change unless an operator explicitly opts in. To opt in, seedocs/operations/netbox-to-proxmox.mdanddocs/operations/deletion-requests.md. Grantingintent_delete_*andauthorize_deletion_requestto the same user is allowed by default, but the resulting four-eyes self-approval is rejected at the view layer unlessintent_apply_authorization_self_approve_allowed=True. - The
Deletion RequestsUI lives at/plugins/proxbox/intent/deletion-requests/. The apply-job UI lives at/plugins/proxbox/intent/apply-jobs/.