-
-
Notifications
You must be signed in to change notification settings - Fork 29
Proxmox and Cloud Director Developer Guide
Since 3.1.0 Β· PR #167 by Andrew Turbay (@turbay-a) Β· User guides: Proxmox VE Β· VMware Cloud Director
How the two hypervisor syncs work, and what changed when the PR was merged, and why. Each change is written as the PR did X, the problem was Y, now it does Z, because most of them guard against something that only shows up on a real install, often as data quietly deleted. The rules are marked in the code with TRAP: comments (Code traps):
git grep -n "TRAP:" -- includes/proxmox_sync.php includes/vcloud_sync.php includes/hypervisor_http.php
This is Andrew's third contribution, after Telegram and Teams and Mattermost, and it shows real operational experience. The rules about what not to delete came from a sync he'd run in production, and they're the right instincts. Kept exactly as he built them:
-
Several servers of each, each with its own row, schedule and data, keyed by server:
(connection_id, vmid),(connection_id, vm_uuid). - Read-only. Nothing ever writes to Proxmox or Director.
-
Credentials encrypted at rest with
encryptValue(), never returned to the browser (has_passwordonly), and a blank or masked field on edit keeps the stored one. -
IPs from the VM's own NICs only. The QEMU guest agent reports
docker0,vethβ¦and bridges too; only interfaces whose MAC is one of the VM's configured NICs count. - An offline node keeps its VMs, and the safety guard: fewer than half of the known VMs seen means nothing is deleted.
- Director's XML read by local element name, so provider and tenant namespaces both parse.
-
The data shaped like vCenter's (
details_jsonuses theraw_datashape), so the Servers page's detail view shows Proxmox and Director VMs with no special cases. - A Source column on Servers, two settings tabs, two capabilities marked sensitive, schema in all three places, foreign keys in Database Verification, and every string translated into 14 languages.
| File | ||
|---|---|---|
| βοΈ | includes/hypervisor_http.php |
New at merge. HypervisorHttp: https only, the shared CA bundle, the test hook |
| βοΈ | includes/proxmox_sync.php |
Login (token or ticket), the sync, proxmoxSaveConnection(), proxmoxConnectionOut()
|
| βοΈ | includes/vcloud_sync.php |
Session, paged inventory, NICs and disks, edge gateways, logout, vcdSaveConnection()
|
| π |
api/assets/proxmox_connections.php, vcloud_connections.php
|
List, save, delete. assets.proxmox / assets.vcloud
|
| π |
api/assets/proxmox_sync.php, vcloud_sync.php
|
Test and Sync now for one server. Same capabilities |
| π |
api/assets/proxmox_vms.php, vcloud_vms.php
|
Show VMs (and edge gateways). Same capabilities |
| π | api/assets/sync_hypervisors.php |
New at merge. The Servers page's sync: every active server, on module access |
| π | api/assets/get_servers.php |
Servers list: vCenter, Proxmox and Director rows in one shape |
| β° |
cron/proxmox_sync.php, cron/vcloud_sync.php
|
Scheduled; CLI only; each server on its own interval |
| π₯οΈ |
asset-management/settings/index.php, assets/js/proxmox-settings.js, vcloud-settings.js
|
The two settings tabs |
| π§ͺ | tests/hypervisor-sync.php |
New at merge. 48 checks against a stand-in of each API, rolled back |
| ποΈ |
proxmox_connections, proxmox_nodes, proxmox_vms, vcloud_connections, vcloud_vms, vcloud_edge_gateways
|
Install-wide, like vCenter's servers
|
HypervisorHttp::request() is the only cURL call in either sync.
HTTPS only. The PR's address check was #^https?://#, so http://pve:8006 saved and synced. The problem: every Proxmox request carries the API token secret in a header, and every Director login carries the password in Basic auth, so plain http hands them to anyone on the path. The error message even said "must start with https://"; the regex disagreed. Now HypervisorHttp::validateHost() refuses http:// when a server is saved, and request() refuses it again, in case a row got one some other way.
The CA bundle. The PR set CURLOPT_SSL_VERIFYPEER from the server's "Verify certificate" tick, but no CURLOPT_CAINFO. On WAMP, and on some Linux and Docker hosts, PHP has no default bundle, so a perfectly valid certificate fails with "unable to get local issuer certificate". That's GH #129 again, which sslApplyCurl() exists to prevent. Now a ticked box calls sslApplyCurl($ch, true): verification forced on, with the install's bundle, like every other integration. An unticked box turns verification off for that server only, which Proxmox's default self-signed certificate usually needs.
The test hook. HypervisorHttp::$testTransport lets the test answer as Proxmox or Director. Same idea as MessagingProvider::$testTransport and TeamsProvider::$testKeys; never set outside tests/.
// includes/proxmox_sync.php β the lists that came back
$list = proxmoxTryGet($c, $auth, '/nodes/' . rawurlencode($nodeName) . '/' . $segment);
if (!is_array($list)) {
$summary['skipped_nodes'][] = $nodeName . '/' . $segment;
continue;
}
$readLists[$nodeName . '|' . $type] = true;The PR removed a VM when it was missing and its node "answered", meaning it was online in /nodes. The problem: a node can be online while its qemu or lxc list fails (a timeout, a permission on that node). That node still counted as scanned, so every VM on it looked gone and was deleted. The safety guard didn't catch it either: it compared VMs seen across the whole cluster with the known ones, so one small node failing in a big cluster stayed under the 50% line. Now removal is scoped to (node, type) lists that came back, and the guard compares, within those lists, how many known VMs are still there. Rows are upserted before the removal looks, so a VM that moved node is already recorded on its new one.
Proven by a control: with the PR's rule put back, tests/hypervisor-sync.php fails "a node whose qemu list failed keeps its VMs", with 2 VMs deleted that still existed.
Test and the node list. Test read the node list with proxmoxTryGet(), which swallows errors. With a token there's no separate login call, so the node list is the first request, and an unreachable server came back as "Logged in, but the node list could not be read. Check the user's permissions". That's wrong, and it sends an admin into Proxmox permissions for a typo in the address. Now Test and the sync read the node list with proxmoxCall(), and the real reason ("Could not reach the Proxmox server: Could not resolve hostβ¦") is what the admin sees. Found by testing the real endpoint with a server that doesn't exist.
Paging. The PR stopped reading the VM list when $page * 128 >= total, assuming each page held the 128 it asked for. The problem: Director caps a page at its own maximum (restapi.queryservice.maxPageSize, which an administrator can lower). On such a system page 1 came back short, the loop stopped, the list counted as complete, and every VM after page 1 was deleted, on every sync. Now the loop counts the records that actually arrived and stops at total. The stand-in pages by 4 while being asked for 128, which is what found this.
Edge gateways. The PR read one page of 128 gateways and then deleted every gateway not on it. Over 128 gateways, the rest were deleted each sync; and a failed read with a partial list did the same. Now every page is read (pageCount), and gateways are removed only after a complete read.
Logging out. The PR logged in on every Test and every sync and never logged out, so each scheduled run left a live session on Director until it timed out: one more per server per interval. Now vcdLogout() deletes the session after Test, after a sync, and after a sync that failed part-way.
A colon in the user name is refused when saving: Basic auth splits user@org:password at the first colon.
The PR's Sync button fetched the server lists from proxmox_connections.php and vcloud_connections.php and synced each one from the browser. The problem: those endpoints need the settings permissions, so for anyone who only uses the Servers page they returned 403. Both sources were skipped silently, and the button reported success. vCenter had settled this question: get_vcenter.php syncs on module access, because the Servers page is operational and a sync changes no configuration. Now sync_hypervisors.php does the same for every active Proxmox and Director server in one call. Adding, changing, testing and "Show VMs" still need the capabilities.
Every sync on an install without vCenter showed a red "vCenter settings not configured". get_vcenter.php now says not_configured: true, and the page skips that line when other sources exist.
The Source badge used fixed colours; it now uses the theme tokens (--accent-soft, --surface-2β¦) so it works in dark mode, as do the status badges in both settings tabs. The empty table said Click "Sync vCenter"; a new string names the new button. (It's a new key rather than a changed one, so the existing translations don't drift.)
-
Director's Show VMs printed "undefined". The table was copied from Proxmox's and still read
vmid,vm_typeandnode_name, which Director VMs don't have. It now shows Name, vApp and Organization VDC, and the edge gateways, which were synced and returned but never displayed. -
A foreign key named twice.
freeitsm.sqlcalled one keyfk_vcloud_edge_connection; Database Verification looks forfk_vcloud_edge_gateways_connection. On every fresh install it wouldn't find it and would add a second, identical key. The schema now uses Verification's name. - Deleting a server also deletes its rows explicitly, not only through the cascade. Verification adds the foreign keys to an existing install only when it runs.
-
Saving moved into the includes (
proxmoxSaveConnection(),vcdSaveConnection()), so the rules are in one place and the test runs the real ones. Editing a server that doesn't exist is refused. -
Help. The PR's two sections were English written into the page, between sections 1 and 2, with no number and no menu entry. They're now sections 11 and 12, after Servers, in
lang/en/asset-management.php(help.proxmox.*,help.vcloud.*), with the full guides here on the wiki instead ofdocs/. The menu had two entries numbered 15; it now counts 1 to 19. Two menu items printed&because their strings already held&; they're echoed without double escaping. -
Proxmox VE 9 removed
VM.Monitor. Guest-agent reads now needVM.GuestAgent.Audit. The docs give both.
Proxmox: log in (token header PVEAPIToken=user@realm!id=secret, or a ticket from /access/ticket sent as the PVEAuthCookie) β /nodes β /cluster/status for the name β for each online node, qemu and lxc lists β each VM's config, and for a running VM the guest agent's interfaces β upsert β remove what a list that came back no longer has, unless the guard fires β record the result on the server row.
Director: POST /cloudapi/1.0.0/sessions with Basic user@org:password; the token comes back in X-VMWARE-VCLOUD-ACCESS-TOKEN β /api/query?type=vm page by page β for each VM, networkConnectionSection and virtualHardwareSection/disks (ResourceType 17 is a disk) β upsert β remove only after a complete read, unless the guard fires β edge gateways, every page β log out β record the result.
tests/hypervisor-sync.php: 48 checks, everything in one transaction that's rolled back. The stand-ins hold a small cluster and a small Director in arrays, with switches to make one call fail. What it proves: https only; the secret encrypted, never returned, kept on a blank or masked edit; both Proxmox logins; IPs from the VM's own NICs; container ip= and DHCP; a failed qemu list keeps that node's VMs while its lxc list still applies; offline nodes; the guard; Director paging past its own page cap; a failed page removes nothing; edge gateways across pages; logout; an unreachable server named as such.
Not run against a real server. Proxmox was run against a real cluster by the contributor before the merge changes; Director has never met a real one. The first live run of each is worth watching.
See also: Proxmox VE Β· VMware Cloud Director Β· Code traps Β· Assets
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96