Introduces an opt-in reverse DNS management subsystem backed by a PowerDNS
Authoritative HTTP API. Runs via a companion WHMCS addon module
(modules/addons/VirtFusionDns) that holds settings and a Test Connection
page; the server module reads those settings from tbladdonmodules and
short-circuits when the addon is absent or disabled, so provisioning is
unaffected for operators who don't use the feature.
Lifecycle hooks:
- createAccount creates PTRs for every assigned IP (forward DNS must
already resolve to the IP — FCrDNS enforcement)
- renameServer updates only PTRs whose content matched the old hostname,
preserving client-custom records
- terminateAccount deletes all PTRs before the local state is purged
- TestConnection merges PowerDNS health check with the existing VirtFusion
check
- A DailyCronJob hook reconciles missing PTRs additive-only (never
overwrites)
Client UI: new "Reverse DNS" panel on the service overview with one
editable PTR input per assigned IP, per-row status badges, and
forward-DNS rejection on save. Admin services tab gets a parallel
widget with Reconcile (additive) and Reconcile (force reset) buttons.
New subsystem at lib/PowerDns/:
- Client.php PowerDNS API wrapper (X-API-Key, listZones/getZone/
patchRRset/notifyZone), auto-NOTIFY on successful PATCH
- Config.php Loads + decrypts addon settings from tbladdonmodules
- IpUtil.php PTR-name generation (IPv4 + IPv6), zone matching,
RFC 2317 classless parsing
- Resolver.php FCrDNS verification via dns_get_record with CNAME-chain
following and per-(hostname,ip) caching
- PtrManager.php Orchestrator: syncServer, deleteForServer, listPtrs,
setPtr, reconcile, reconcileAll
Security hardening helpers added to Module and applied to the rDNS
endpoints:
- requirePost() HTTP method gate (405 on non-POST mutations)
- requireSameOrigin() Origin/Referer check against WHMCS host (CSRF
defence against cross-site form POST)
- requireServiceStatus() tblhosting.domainstatus filter (Active for
writes, Active+Suspended for reads)
RFC 2317 classless delegations (e.g. 64/64.113.0.203.in-addr.arpa.)
supported with alignment validation: rejects misaligned start addresses
that don't correspond to any real delegation boundary.
PowerDNS zone IDs containing '/' are URL-encoded as '=2F' per the
PowerDNS API convention. PATCH success triggers PUT /zones/{id}/notify
so slaves pick up the SOA-bumped serial immediately.
Includes IPv4 + IPv6 support, per-IP write rate limit (10s), fresh
IP-ownership re-verification on every client write (defends against
stale-ownership after IP reassignment), and audit logging of every
successful edit to the WHMCS module log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
VirtFusion Direct Provisioning Module for WHMCS — a PHP module that integrates WHMCS with the VirtFusion control panel API for automated VPS provisioning, management, and client self-service. No build system or package manager; the module is pure PHP installed by copying modules/servers/VirtFusionDirect/ into a WHMCS installation.
Development & Testing
There is no automated test suite, linter, or build step. Testing is manual:
- Test connection: WHMCS Admin → System Settings → Servers → Test Connection button
- Dry run validation:
VirtFusionDirect_validateServerConfig()tests configuration without creating a server - Module logging: WHMCS Admin → Utilities → Logs → Module Log captures all API calls and responses
- Server object viewer: Admin services tab shows full JSON response from VirtFusion API
Development Rules
- Error handling: Always use try...catch blocks around API calls, database operations, and any code that may throw exceptions. Never let exceptions bubble up unhandled to the user. Log caught exceptions via
Log::insert(). - Ownership validation: Every client-facing action MUST verify service ownership via
validateUserOwnsService()before performing any operation. Server IDs must be cross-referenced against the authenticated client to prevent cross-customer data access. - Security: All input must be validated server-side. Never trust client-side validation alone. Cast IDs to
(int), validate strings with regex, escape output withhtmlspecialchars(). - Control flow: Every
$vf->output()call in switch cases must be followed bybreak. Do not rely onexit()insideoutput()for flow control. - HTTP methods: Read-only operations use GET. State-mutating operations (power, rebuild, rename, password reset, credit, VNC toggle) use POST with data in the request body.
- Caching: Use the
Cacheclass for slow-changing API responses. Never cache real-time data (server status, VNC sessions, login tokens) or mutation responses.
Release Process
Releases are triggered by pushing a git tag:
git tag v1.1.0
git push origin v1.1.0
The publish-release.yml workflow creates a GitHub/Gitea release with auto-generated notes from the commit log. Use conventional commits for clear changelogs:
fix:→ patch-level changefeat:→ feature additionrefactor:→ code improvement without behavior change
Architecture
Namespace: WHMCS\Module\Server\VirtFusionDirect
Entry Points
| File | Purpose |
|---|---|
modules/servers/VirtFusionDirect/VirtFusionDirect.php |
WHMCS module interface — non-namespaced functions (VirtFusionDirect_CreateAccount(), etc.) that delegate to library classes |
modules/servers/VirtFusionDirect/client.php |
Client-facing AJAX API — authenticated by WHMCS session + service ownership validation. POST for mutations, GET for reads. |
modules/servers/VirtFusionDirect/admin.php |
Admin-facing AJAX API — requires WHMCS admin authentication |
modules/servers/VirtFusionDirect/hooks.php |
WHMCS hooks — checkout validation (OS selection), OS gallery + SSH key UI injection, slider UI for configurable options, daily PowerDNS reconciliation |
modules/addons/VirtFusionDns/VirtFusionDns.php |
Optional companion addon — holds PowerDNS settings and provides a Test Connection admin page. See "Reverse DNS (PowerDNS)" below. |
Core Classes (in lib/)
| Class | Role |
|---|---|
Module |
Base class with API integration, auth checks, and all feature methods (power, network, VNC, backup, resource, self-service, traffic, rename, password reset). Contains resolveServiceContext() for DRY service lookups and groupOsTemplates() for shared OS category logic. |
ModuleFunctions |
Extends Module. Service lifecycle: create, suspend, unsuspend, terminate, change package, usage updates, client area rendering. |
ConfigureService |
Extends Module. Order-time operations: package discovery, OS template fetching, server build initialization, SSH key retrieval and creation. |
Database |
Static methods for mod_virtfusion_direct table operations and WHMCS DB queries. Auto-creates/migrates schema on first use. |
Curl |
HTTP client wrapper with Bearer token auth, SSL verification, 30s timeout. Methods: get, post, put, patch, delete. Single-use — each instance makes one request. |
Cache |
Two-tier caching: Redis (if ext-redis available) with atomic filesystem fallback. TTLs: OS templates 10min, traffic/backups 2min, packages 10min. |
ServerResource |
Transforms VirtFusion API response into flat key-value format for Smarty templates. Only reads interfaces[0]; for rDNS use PowerDns\IpUtil::extractIps() which walks all interfaces. |
AdminHTML |
Static methods generating admin services tab HTML (server ID editor, JSON viewer, action buttons, rdnsSection() widget). |
Log |
Thin wrapper around WHMCS module logging. |
PowerDns\Client |
PowerDNS HTTP API wrapper (X-API-Key auth): ping, listZones, getZone, patchRRset, notifyZone. PATCH success triggers an automatic NOTIFY so slaves pick up the SOA bump immediately. |
PowerDns\Config |
Loads settings from tbladdonmodules (module="virtfusiondns") and decrypts apiKey via WHMCS decrypt(). isEnabled() gates every PowerDNS call site. |
PowerDns\IpUtil |
Pure helpers: ptrNameForIp (v4/v6 nibble reversal), expandIpv6, extractIps (all interfaces), findZoneAndPtrName (standard + RFC 2317 classless), parseClasslessZone. |
PowerDns\Resolver |
Forward-DNS verification via dns_get_record() with up-to-5-hop CNAME following. Cached per (hostname, ip) pair. |
PowerDns\PtrManager |
Orchestrator: syncServer, deleteForServer, listPtrs, setPtr, reconcile, reconcileAll. Per-request zone cache. 10s per-IP write rate limit. Enforces FCrDNS before writes. |
Class Hierarchy
ModuleFunctions and ConfigureService both extend Module. Most business logic lives in Module — it handles API calls, auth, validation, and all feature-specific operations. The resolveServiceContext() method provides a standardized way to look up service → WHMCS service → control panel → curl client in a single call, eliminating boilerplate across all API methods.
Client-Side
templates/overview.tpl— Smarty template for client area (server info, power, network, rebuild with OS gallery, resources with traffic chart, VNC toggle, self-service billing, billing overview, backups timeline, server rename, password reset)templates/js/module.js— Vanilla JS + jQuery handling AJAX calls, DOM updates, status badges, power actions, all management UIs. Key helpers:vfUrl()(URL builder),vfShowAlert()(alert display),vfRenderOsGallery()(accordion gallery),vfDrawTrafficChart()(canvas chart)templates/js/keygen.js— Client-side SSH Ed25519 key generator using Web Crypto API (loaded on checkout page)templates/css/module.css— Cross-theme styles with Bootstrap 3/4/5 dual class support (panel card,panel-body card-body)
Removed Features
- Firewall — Removed (non-functional; rulesets must be created in VirtFusion admin panel)
- IP add/remove buttons — Removed; IPs are managed by VirtFusion during provisioning
- Upgrade/Downgrade link — Removed from resources panel
Data Flow: Server Creation
- WHMCS calls
VirtFusionDirect_CreateAccount()→ModuleFunctions::createAccount() - Checks/creates VirtFusion user via external relation ID (WHMCS client ID)
- Reads configurable options (Package, Location, IPv4, Memory, CPU, Bandwidth, etc.)
- Dry-run validation → actual API POST to
/servers - Stores server ID in
mod_virtfusion_directtable - Updates WHMCS hosting record (IP, username, password, domain)
- If the PowerDNS addon is enabled, calls
PowerDns\PtrManager::syncServer()to write PTRs (non-blocking; failures log but never fail provisioning) - Calls
ConfigureService::initServerBuild()with selected OS + SSH key
Custom fields (Initial Operating System, Initial SSH Key) are auto-created by Database::ensureCustomFields() on module load for all products using this module. No manual SQL setup required.
Reverse DNS (PowerDNS)
Opt-in integration via the companion VirtFusionDns addon module. Loose-coupled: the server module never requires addon code at runtime; it queries the addon's tbladdonmodules row and short-circuits when enabled=0 or the addon isn't activated. Activate via WHMCS Admin → Addon Modules → VirtFusion DNS.
Settings (tbladdonmodules, module="virtfusiondns"): enabled (yesno), endpoint (e.g. https://ns1.example.com:8081), apiKey (encrypted by WHMCS), serverId (usually localhost), defaultTtl (3600), cacheTtl (60).
Lifecycle hooks:
createAccount→ sync PTRs to server hostname (forward DNS must match before each write)renameServer→ update only PTRs whose current content equals the old hostname (preserves client-custom PTRs)terminateAccount→ delete every PTR beforeDatabase::deleteSystemService()VirtFusionDirect_TestConnection→ merged VirtFusion + PowerDNS health checkDailyCronJob→PtrManager::reconcileAll()— additive-only (never overwrites)
Client-facing actions (client.php): rdnsList, rdnsUpdate. Admin (admin.php): rdnsStatus, rdnsReconcile (accepts force=1 for explicit reset).
Client UI: Reverse DNS panel in templates/overview.tpl (rendered by vfLoadRdns() / vfRenderRdnsPanel() / vfUpdateRdns() in module.js). Admin services tab gets a status widget via AdminHTML::rdnsSection().
FCrDNS rule: Every PTR write (auto or client-initiated) requires the hostname's forward DNS (A/AAAA) to already resolve to the target IP. On mismatch, auto-sync logs and skips; client edits return a 400 with guidance.
Zone handling: Zones are operator-managed — the module never creates zones. Zone discovery uses GET /zones (cached for cacheTtl) + longest-suffix match. RFC 2317 classless delegations (X/Y.octet.octet.octet.in-addr.arpa.) are supported: both CIDR-prefix (0/26) and block-size (64/64) conventions are parsed, and PTRs are written with the classless sub-zone label in the record name.
SOA / NOTIFY: PowerDNS auto-bumps SOA serials when soa_edit_api=INCREASE is set on the zone. After every successful PATCH the module issues an explicit PUT /zones/{id}/notify so slaves refresh immediately rather than waiting for the next scheduled poll.
Safety properties:
- PowerDNS failures never block VirtFusion operations (try/catch at every call site)
- Cron is additive-only — never auto-overwrites a PTR
- Admin Reconcile button supports
force=1for explicit reset to hostname - Client edits are IP-ownership-checked against a fresh VirtFusion fetch (not cached
server_object), defending against reassigned-IP stale-ownership - Per-IP write rate limit (10s, via
Cache) prevents save-button abuse
Configurable Option Mapping
Custom option names can be mapped in config/ConfigOptionMapping.php (copy from -example.php). Default mapping keys: packageId, hypervisorId, ipv4, storage, memory, traffic, cpuCores, networkSpeedInbound, networkSpeedOutbound, networkProfile, storageProfile.
Security Patterns
- All PHP files start with
if (!defined("WHMCS")) die()to prevent direct access (except entry points usinginit.php) - Client endpoints validate WHMCS session AND service ownership before any operation
- API tokens stored encrypted in WHMCS server password field (decrypted via
localAPI('DecryptPassword')) - Input validation: type casting (
(int)), regex filtering,filter_var()for IP addresses - Output escaping:
htmlspecialchars()in PHP,$('<span>').text()in jQuery,{escape:'htmlall'}in Smarty - SSL verification enabled on all API calls (
CURLOPT_SSL_VERIFYPEER+CURLOPT_SSL_VERIFYHOST = 2) - Server rename validated both client-side and server-side with RFC 1123 regex
VirtFusion API Compatibility
- API reference (OpenAPI spec): https://docs.virtfusion.com/api/openapi.yaml
- Base features: VirtFusion v1.7.3+
- VNC console: v6.1.0+
- Resource modification: v6.2.0+
- Self-service billing: Requires self-service feature enabled in VirtFusion
- OS icon path:
{baseUrl}/img/logo/{icon_filename}(public, no auth required)
PowerDNS API Compatibility
- API reference: https://doc.powerdns.com/authoritative/http-api/
- Tested against: PowerDNS Authoritative 4.8+
- Auth:
X-API-Keyheader (not Bearer) - Required endpoints:
GET /servers/{id},GET /servers/{id}/zones,GET /servers/{id}/zones/{zone},PATCH /servers/{id}/zones/{zone},PUT /servers/{id}/zones/{zone}/notify - Zone ID URL encoding:
/in zone names (RFC 2317) must be encoded as=2Fnot%2F— handled byClient::zoneIdEncode() api-allow-from: must include the WHMCS host's IP (PowerDNS's own ACL)- Recommended zone config:
soa_edit_api: INCREASEfor automatic serial bumping on API-driven changes
Product Config Options
| Option | Name | Description | Default |
|---|---|---|---|
| configoption1 | Hypervisor Group ID | VirtFusion hypervisor group for server placement | 1 |
| configoption2 | Package ID | VirtFusion package defining server resources | 1 |
| configoption3 | Default IPv4 | Number of IPv4 addresses to assign (0-10) | 1 |
| configoption4 | Self-Service Mode | 0=Disabled, 1=Hourly, 2=Resource Packs, 3=Both | 0 |
| configoption5 | Auto Top-Off Threshold | Credit balance below which auto top-off triggers | 0 |
| configoption6 | Auto Top-Off Amount | Credit amount to add on auto top-off | 100 |
WHMCS Compatibility
- WHMCS 8.x+ (tested 8.0–8.10)
- PHP 8.0+ with cURL extension
- Redis extension optional (improves caching performance, falls back to filesystem)
- All WHMCS themes supported (Six, Twenty-One, Lagom, custom) via Bootstrap 3/4/5 dual classes