Technical whitepaper — PodHeitor vSphere for Bacula

Technical whitepaper — PodHeitor vSphere for Bacula

Backup, Replication & Conversion for VMware vSphere/ESXi on Bacula Community

Version 1.4.1 — April 2026 Author: Heitor Faria Copyright © 2026 Heitor Faria — All Rights Reserved


💰 Special Offer

Bring your Bacula Enterprise, Veeam, Commvault or Netbackup quote or renewal proposal. We offer at least 50% discount, with far more features.

📧 heitor@opentechs.lat | 📱 +1 786 726-1749 | +55 61 98268-4220 (WhatsApp)


Table of Contents

  1. Executive Summary
  2. Problem Statement & Market Context
  3. Use Cases
  4. Architecture
  5. Features — Backup
  6. Features — Replication & Disaster Recovery
  7. Features — Cross-Hypervisor Conversion
  8. Security Architecture
  9. Installation Guide
  10. Configuration Reference
  11. Backup Configuration Examples
  12. Replication Configuration Examples
  13. User Manual & Day-2 Operations
  14. Sizing & Requirements
  15. Compatibility Matrix
  16. Test Results
  17. Comparison with Enterprise Solutions
  18. Troubleshooting
  19. Licensing & Contact

1. Executive Summary

PodHeitor vSphere BRC is a production-ready plugin for Bacula Community Edition that delivers enterprise-grade VMware vSphere backup, replication, and cross-hypervisor conversion capabilities — at a fraction of the cost of commercial solutions.

Key Differentiators

  • Three products in one: Backup + Replication + Conversion in a single plugin
  • Bacula Community native: No Enterprise license required
  • CBT-based replication: Near real-time RPO with minimal bandwidth
  • 10-mode DR: Complete failover lifecycle management
  • Cross-hypervisor restore: vSphere ↔ Hyper-V ↔ Proxmox/KVM conversion
  • TLS-secured DR protocol: Production-ready security
  • Written in Rust: Memory-safe, high-performance, zero-overhead FFI with VDDK

Version 1.4.1 Highlights

End-to-end restore-correctness fixes — validated against ESXi 8.0.3 + Bacula 15.0.3 (job 6442 restored 2.147 GB / 4 files in 6 s, 0 FD errors; the recreated Alpine 3.21 VM booted cleanly):

Category Fix
🐛 Backup File paths no longer doubled (@vsphere/@vsphere/disk-2000.vmdk@vsphere/disk-2000.vmdk).
🐛 Restore Where= prefix from Bacula is now captured correctly: a non-empty Where routes to filesystem-restore; empty// falls through to hypervisor-restore.
🐛 Restore Directory entries no longer terminate the outer PTCOMM command loop with a spurious EOD; directories use a no-op writer.

Version 1.4.0 Highlights

Category Feature
⚖️ Licensing Plugin .so is now a pure-Rust cdylib — no Bacula AGPLv3 source linked. The C ABI is implemented through the clean-room bacula-fd-abi crate (independently reimplemented from public spec).
🛠 Build Building no longer requires the Bacula source tree. cargo build --release -p plugin-vsphere from the PodHeitor Rust cdylib workspace produces the shipped .so.
🧱 Architecture License firewall preserved: cdylib in-process, Rust backend out-of-process via PTCOMM (crash isolation + clean licensing boundary).

Version 1.3.0 Highlights

Category Feature
🔐 Security TLS encryption for DR protocol (rustls)
🔐 Security Constant-time token authentication
🌐 Network Automatic network mapping on failover (ReconfigVM SOAP)
🌐 Network Automatic IP reconfiguration on failover (CustomizeVM SOAP)
📸 Recovery Snapshot-based restore points on replica VM
🔧 Operations SIGTERM graceful shutdown for daemon mode

2. Problem Statement & Market Context

The Challenge

Organizations running VMware vSphere face critical data protection challenges:

  1. Cost: Bacula Enterprise vSphere plugin costs $$$; Veeam, Commvault, Netbackup even more
  2. Vendor Lock-in: Enterprise backup solutions lock customers into expensive perpetual renewals
  3. Feature Gaps: Bacula Community has no native vSphere backup — only filesystem-level agents
  4. DR Complexity: Setting up replication + failover typically requires separate products
  5. Multi-Hypervisor: Migrating between vSphere, Hyper-V, and KVM requires manual conversion

The Solution

PodHeitor vSphere BRC eliminates these challenges by providing:

  • Zero Enterprise licensing — works with free Bacula Community
  • All-in-one plugin — backup, replication, and conversion unified
  • Bacula-native integration — uses standard FileSet/Job/Schedule configuration
  • Automated DR — one-click failover with network remapping and IP reconfiguration
  • Open standards — VMDK, VHDX, QCOW2 conversion between hypervisors

3. Use Cases

Use Case 1: VMware VM Backup

Scenario: Organization needs image-level VM backup with CBT for efficient incrementals.

Job: Full backup Sunday, Incremental daily
RPO: 24 hours (backup) / minutes (replication)
RTO: Minutes (instant restore) to hours (full restore)

Use Case 2: Disaster Recovery

Scenario: Production site failure — need to boot VMs at DR site immediately.

Normal operation: CBT push every 5 minutes
Planned failover: Sync → graceful shutdown → boot replica
Unplanned failover: Boot replica immediately from last sync point
Failback: Reverse-replicate once production is restored

Use Case 3: Migration Between Hypervisors

Scenario: Migrating from VMware to Proxmox/KVM due to Broadcom licensing changes.

Backup: VMware VM → Bacula storage
Restore: Bacula → Proxmox (automatic VMDK→QCOW2 conversion)

Use Case 4: Test/Dev Environment Cloning

Scenario: Clone production VMs to isolated test network.

Mode: failover-test (non-destructive)
Network: Isolated test VLAN
Result: Production unaffected, test VM available

Use Case 5: Compliance & Multi-Site Backup

Scenario: Regulatory requirement for offsite backup copies.

Primary: Local backup to disk/tape
Secondary: CBT replication to remote DR site
Tertiary: Cross-restore to different hypervisor at third site

4. Architecture

Component Overview

┌─────────────────────────────────────────────────────────────┐
│                     Bacula Director                          │
│  (Job scheduling, catalog, policy management)               │
└─────────────────┬───────────────────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────────────────┐
│                   Bacula File Daemon (FD)                    │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  podheitor-vsphere-fd.so (Rust cdylib, v1.4.1+)      │   │
│  │  • Loaded by Bacula FD via dlopen                    │   │
│  │  • Implements Bacula FD plugin C ABI through         │   │
│  │    independently-reimplemented bacula-fd-abi crate   │   │
│  │  • No Bacula AGPLv3 source linked                    │   │
│  │  • Spawns Rust backend via PTCOMM pipe                │   │
│  └──────────────────┬───────────────────────────────────┘   │
│                     │ stdin/stdout pipe                      │
│  ┌──────────────────▼───────────────────────────────────┐   │
│  │  podheitor-vsphere-backend (Rust binary)              │   │
│  │                                                       │   │
│  │  ┌─────────────────────────────────────────────────┐  │   │
│  │  │  Backup Module        │  Restore Module         │  │   │
│  │  │  • VADP workflow      │  • Full VM restore      │  │   │
│  │  │  • CBT tracking       │  • Single disk restore  │  │   │
│  │  │  • Snapshot lifecycle  │  • Cross-restore (VHDX) │  │   │
│  │  ├─────────────────────────────────────────────────┤  │   │
│  │  │  Replication Module (1500+ lines)               │  │   │
│  │  │  • CBT push (async replication)                 │  │   │
│  │  │  • Seed (initial full sync)                     │  │   │
│  │  │  • 10-mode failover lifecycle                   │  │   │
│  │  │  • TLS-encrypted DR protocol                    │  │   │
│  │  │  • DR receiver daemon                           │  │   │
│  │  ├─────────────────────────────────────────────────┤  │   │
│  │  │  vSphere API Client                             │  │   │
│  │  │  • SOAP/XML over HTTPS                          │  │   │
│  │  │  • Property collector                           │  │   │
│  │  │  • Task manager                                 │  │   │
│  │  │  • ReconfigVM (net_map)                         │  │   │
│  │  │  • CustomizeVM (Re-IP)                          │  │   │
│  │  ├─────────────────────────────────────────────────┤  │   │
│  │  │  VDDK Integration (FFI)                         │  │   │
│  │  │  • VixDiskLib_Open/Read/Write                   │  │   │
│  │  │  • QueryAllocatedBlocks                         │  │   │
│  │  │  • Transport: NBD, NBDSSL, HotAdd, SAN          │  │   │
│  │  └─────────────────────────────────────────────────┘  │   │
│  └───────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                  │                            │
                  ▼                            ▼
┌─────────────────────────┐  ┌─────────────────────────────┐
│   VMware vSphere API     │  │  DR Receiver (remote host)  │
│   (SOAP over HTTPS)      │  │  TLS-encrypted TCP          │
│   ESXi / vCenter         │  │  PSK authentication         │
└─────────────────────────┘  │  Snapshot management         │
                              │  Delta application           │
                              └─────────────────────────────┘

Data Flow — Backup

1. Bacula FD invokes plugin
2. Plugin creates VM snapshot (quiesced)
3. VDDK opens VMDK via chosen transport
4. CBT provides changed block list (incremental)
5. Blocks streamed to Bacula SD via FD pipe
6. Snapshot deleted after backup
7. Job metadata stored in Bacula catalog

Data Flow — CBT Replication

1. Plugin queries CBT delta since last sync
2. Changed blocks read via VDDK
3. Delta sent to DR receiver via TLS-encrypted TCP
4. Receiver creates restore point snapshot on replica
5. Receiver writes delta to replica VMDK via VDDK
6. Replication state persisted to /var/lib/podheitor/replication/

Data Flow — Failover

Planned Failover:
1. Final CBT sync (catch up)
2. Shutdown source VM
3. Apply net_map (ReconfigVM)
4. Apply Re-IP (CustomizeVM)
5. Power on replica
6. Update replication state

Unplanned Failover:
1. Apply net_map on replica
2. Apply Re-IP on replica
3. Power on replica immediately
4. (no final sync — uses last available restore point)

5. Features — Backup

Full Backup

  • Image-level VM backup via VMware VADP API
  • Quiesced snapshots for application consistency
  • Multi-disk support (all virtual disks captured)
  • Metadata preservation (VM config, hardware version, guest OS)

Incremental Backup

  • Changed Block Tracking (CBT) for efficient incremental backups
  • Only modified blocks transferred — typically 1-5% of total disk
  • Dramatic reduction in backup window and storage

Differential Backup

  • Captures all changes since last Full backup
  • Useful for environments with high change rates

Transport Modes

Mode Description Use Case
NBD Network Block Device (unencrypted) Lab/test environments
NBDSSL NBD with SSL encryption Standard production use
HotAdd Attach VMDK directly to FD VM FD running as VM on same ESXi
SAN Direct SAN access to VMDK LUN LAN-free backup, highest performance

Multi-VM Support

  • Backup multiple VMs with separate Jobs
  • Host include/exclude filters for bulk operations
  • Disk include/exclude for selective backup

6. Features — Replication & Disaster Recovery

CBT-Based Replication

  • Asynchronous push-based replication using VMware CBT
  • RPO measured in minutes (configurable push interval)
  • Bandwidth-efficient — only changed blocks transmitted
  • Optional bandwidth throttling (max_bandwidth_mbps)

10-Mode DR Lifecycle

# Mode Description Destructive Source Impact
1 replication-status Query replication state and last sync No None
2 cbt-push Push CBT deltas to replica No None
3 seed Initial full disk sync (create replica) No None
4 failover-test Boot replica on isolated network No None
5 failover-undo Power off test replica No None
6 failover-planned Sync → shutdown source → boot replica Yes Shutdown
7 failover-unplanned Boot replica immediately No None
8 failover-permanent Convert replica to production Yes N/A
9 failback Reverse-replicate from replica to source Yes Restored
10 reprotect Re-establish forward replication No None

Network Mapping (net_map)

  • Automatically reconfigures VM NICs on failover
  • Maps source networks to target networks at DR site
  • Uses VMware ReconfigVM_Task SOAP API
  • Applied on: planned, unplanned, permanent failover

IP Reconfiguration (Re-IP)

  • Automatically sets IP address, subnet, gateway, DNS on failover
  • Uses VMware CustomizeVM_Task SOAP API (GuestOS Customization)
  • Format: "nic_index:ip/prefix:gateway:dns1,dns2"
  • Requires VMware Tools on guest OS

Restore Points

  • Snapshot created on replica before each delta application
  • Named PodHeitor_RP_<timestamp>
  • Configurable retention (dr_restore_points, default: 5)
  • Enables point-in-time recovery from replica

DR Receiver Daemon

  • Persistent daemon mode for receiving replicated data
  • Listens on configurable TCP port (default: 9102)
  • PSK-based authentication with constant-time comparison
  • SIGTERM graceful shutdown handler
  • TLS encryption (optional, recommended for production)

7. Features — Cross-Hypervisor Conversion

Supported Conversions

Source Target Format Conversion
VMware vSphere (VMDK) Hyper-V (VHDX) Automatic
VMware vSphere (VMDK) Proxmox/KVM (QCOW2) Automatic
Hyper-V (VHDX) VMware vSphere (VMDK) Automatic
Hyper-V (VHDX) Proxmox/KVM (QCOW2) Automatic
Proxmox/KVM (QCOW2) VMware vSphere (VMDK) Automatic

How It Works

  1. Backup captures VM at image level (VMDK native format)
  2. Bacula stores the backup data in its standard storage
  3. On restore, the target plugin detects the source hypervisor
  4. Automatic format conversion (VMDK ↔ VHDX ↔ QCOW2)
  5. VM configuration adapted to target hypervisor (hardware, drivers)

8. Security Architecture

Transport Security

  • Backup: NBDSSL (SSL-encrypted) or SAN (dedicated storage network)
  • DR Protocol: TLS 1.3 via rustls (when certificate/key configured)
  • vSphere API: HTTPS with certificate validation

Authentication

  • vSphere: Username/password via SOAP SessionManager
  • DR Protocol: Pre-shared key (PSK) with constant-time comparison
  • Constant-time auth: XOR-based comparison prevents timing attacks

Data Protection

  • No secrets stored in code — credentials via Bacula FileSet configuration
  • DR auth tokens rotatable without service restart
  • TLS certificate/key paths configurable via plugin options

Compliance Features

  • Structured logging (level + component + message)
  • Audit trail via Bacula catalog (job history, metadata)
  • Replication state persistence for DR compliance verification

9. Installation Guide

9.1 Prerequisites

Requirement Details
OS (File Daemon host) Oracle Linux 9 / RHEL 9 / Rocky Linux 9 / AlmaLinux 9 — x86_64
Bacula Community 15.0.x installed and running on the same host
VDDK Shipped as podheitor-vixdisklib-runtime RPM — no manual tar extraction
Network TCP 443 to ESXi/vCenter; TCP 9102 (default) between primary FD and DR receiver
VMware Tools Installed in guest VMs (required for quiesce and Re-IP)
CBT Enabled on every VM to be protected (see §9.5)
Disk space ≥ 500 MB free for backend binary + VDDK runtime; see §13 for backup storage

The File Daemon host needs outbound TCP 443 to the ESXi/vCenter API, and (for replication) outbound TCP 9102 to the DR receiver. No inbound ports are required on the primary FD.

9.2 Release artifacts

Everything ships as signed RPMs inside releases/:

File Purpose
podheitor-vsphere-1.4.1-1.el9.x86_64.rpm Rust cdylib plugin (.so) + Rust backend + CBT/replication state dirs
podheitor-vixdisklib-runtime-9.0.1-1.el9.x86_64.rpm VMware VDDK 9.0.1 runtime libraries, self-contained via $ORIGIN RPATH

No tar extraction, no manual ld.so.conf edits, no LD_LIBRARY_PATH exports — all of that is handled by the RPMs’ %post scriptlets.

9.3 Standard install (RPM)

Order does not matter; either RPM can be installed first.

# 1. Plugin RPM
sudo rpm -Uvh releases/podheitor-vsphere-1.4.1-1.el9.x86_64.rpm

# 2. VDDK runtime RPM
sudo rpm -Uvh releases/podheitor-vixdisklib-runtime-9.0.1-1.el9.x86_64.rpm

# 3. Restart Bacula FD
sudo systemctl restart bacula-fd

The plugin RPM installs:

Path Contents
/opt/bacula/plugins/podheitor-vsphere-fd.so Rust cdylib plugin loaded by Bacula FD
/opt/bacula/bin/podheitor-vsphere-backend Rust backend binary
/opt/bacula/working/podheitor/cbt/ CBT state (change-ids per VM)
/var/lib/podheitor/replication/ Replication state + restore-point metadata

The VDDK runtime RPM installs:

Path Contents
/usr/lib/vmware-vix-disklib/lib64/ libvixDiskLib.so* + bundled OpenSSL/curl/z (all patched with RPATH=$ORIGIN)
/usr/lib/vmware-vix-disklib/bin64/ vixDiskCheck, vmware-vdiskmanager, vddkReporter
/usr/lib/vmware-vix-disklib/include/ VDDK headers (for development only)
/usr/lib/vmware-vix-disklib/doc/ VDDK HTML documentation

9.4 OpenSSL 3.4.0 conflict — prevention and recovery

Older installers created /etc/ld.so.conf.d/vmware-vddk.conf pointing at /usr/lib/vmware-vix-disklib/lib64. On EL9 hosts whose system OpenSSL has been updated past 3.4.0, every system binary (rpm, dnf, flatpak, …) then loaded VMware’s bundled (older) libcrypto.so.3 and failed with:

flatpak: /usr/lib/vmware-vix-disklib/lib64/libcrypto.so.3: 
    version `OPENSSL_3.4.0' not found (required by /lib64/librpmio.so.9)

The current podheitor-vixdisklib-runtime RPM prevents this by patching every shared object with RPATH=$ORIGIN at build time, so VDDK finds its siblings without any global loader hint. The %post scriptlet also removes stale entries:

/etc/ld.so.conf.d/vmware-vddk.conf
/etc/ld.so.conf.d/vmware-vix-disklib.conf
/etc/ld.so.conf.d/vixlib*.conf
/etc/ld.so.conf.d/flatpack*.conf
/etc/ld.so.conf.d/flatpak-vddk*.conf

To recover a host that is already broken (and cannot even run dnf), run — offline is fine:

sudo rm -f /etc/ld.so.conf.d/vmware-vddk.conf 
           /etc/ld.so.conf.d/vmware-vix-disklib.conf 
           /etc/ld.so.conf.d/*vixlib*.conf 
           /etc/ld.so.conf.d/*flatpack*.conf
sudo ldconfig
# Verify the system libcrypto is back in front:
ldconfig -p | grep 'libcrypto.so.3 .*=> /lib64'
# Then dnf/rpm/flatpak work again:
sudo dnf check-update

9.5 Enable Changed Block Tracking (CBT)

CBT is mandatory for efficient incrementals and for replication.

Via govc:

export GOVC_URL="https://root:<PASSWORD>@<ESXI_IP>/sdk"
export GOVC_INSECURE=true

govc vm.change -vm <VM_NAME> -e ctkEnabled=TRUE
govc vm.change -vm <VM_NAME> -e scsi0:0.ctkEnabled=TRUE
# Repeat for each virtual disk (scsi0:1, scsi0:2, …)

Via vSphere Client: VM → Edit SettingsVM OptionsAdvancedConfiguration Parameters → set ctkEnabled=TRUE and scsi<N>:<M>.ctkEnabled=TRUE per disk, then take and delete a snapshot to materialize the change log.

9.6 Configure Bacula

Add the plugin directory to the FD (usually already set):

# /opt/bacula/etc/bacula-fd.conf
FileDaemon {
  Name = vsphere-fd
  Plugin Directory = /opt/bacula/plugins
  Plugin Names = "podheitor-vsphere"
  ...
}

Then add FileSets / Jobs / Schedules on the Director (see §11 and §12). Restart both daemons:

sudo systemctl restart bacula-fd
sudo systemctl restart bacula-dir

9.7 Smoke test

# VDDK visible to the backend?
/opt/bacula/bin/podheitor-vsphere-backend --version
# Should print the build version and exit 0.

# Run a dry-run backup of a single VM:
echo "status" | bconsole
run job=Backup-<VM_NAME> yes
# watch progress:
status client=vsphere-fd

9.8 Optional: build from source

For developers / git clones:

sudo ./install.sh

install.sh will: detect and install the VDDK runtime RPM from releases/, remove legacy flatpack/vixlib/vmware-vddk entries under /etc/ld.so.conf.d/, build the Rust backend with cargo --release, and install the resulting binary + .so into /opt/bacula/.

9.9 Uninstall

sudo systemctl stop bacula-fd
sudo rpm -e podheitor-vsphere podheitor-vixdisklib-runtime
sudo systemctl start bacula-fd

State directories (/opt/bacula/working/podheitor/, /var/lib/podheitor/) are preserved across upgrades and removed only on full rpm -e.


10. Configuration Reference

Plugin String Format

Plugin = "podheitor-vsphere: key1=value1 key2=value2 ..."

All options can be specified directly in the FileSet Plugin directive.

Complete Options Table — Backup

Option Type Required Default Description
host string Yes ESXi or vCenter hostname/IP
username string Yes vSphere login name
password string Yes vSphere login password
datacenter string Yes Datacenter managed object name
vm string Yes VM name to protect
transport string No nbdssl nbd, nbdssl, hotadd, san
quiesce bool No true Quiesce guest filesystem
timeout int No 3600 Timeout in seconds
include_disk multi No all Include specific disks
exclude_disk multi No none Exclude specific disks
keep_cbt bool No false Keep CBT after backup
abort_on_error bool No false Abort on first error
snapshot_delete_delay int No 0 Seconds to wait before deleting snapshot
force_san bool No false Force SAN transport
debug bool No false Enable debug logging
config_file string No External configuration file

Complete Options Table — Restore

Option Type Required Default Description
new_vm_name string No original Name for restored VM
power_on bool No false Power on after restore
datastore string No original Target datastore name
overwrite bool No false Overwrite existing VM
no_network bool No false Disconnect NICs
thin_provisioned bool No false Use thin disk provisioning
resource_pool string No default Target resource pool
network_name string No original Target network
guest_id_override string No auto Override guest OS type
hw_version string No auto Override HW version
controller_type string No auto auto, ide, scsi, nvme

Complete Options Table — Replication

Option Type Required Default Description
mode string Yes Replication mode (see modes table)
dr_host string Yes* DR receiver hostname/IP
dr_port int No 9102 DR receiver TCP port
dr_auth_token string Yes* Pre-shared authentication token
dr_restore_points int No 5 Max snapshots on replica
dr_replica_datastore string No auto Datastore for replica
push_interval int No 300 Push interval (seconds)
push_apply_remote bool No true Apply deltas remotely
max_retries int No 3 Max retries on failure
retry_delay_sec int No 30 Delay between retries
alert_after_failures int No 3 Alert threshold
net_map multi No Network mapping rules
reip multi No IP reconfiguration rules
storage_map multi No Datastore mapping rules
max_bandwidth_mbps int No 0 Bandwidth limit (0=unlimited)
test_failover_network string No Isolated test network
failover_vm string No auto Target VM for failover
dr_tls_cert string No TLS certificate path
dr_tls_key string No TLS private key path
dr_tls_insecure bool No false Accept self-signed certs

Required for modes that communicate with DR receiver (cbt-push, seed, failover-, failback, reprotect).


11. Backup Configuration Examples

Example 1: Basic Full + Incremental Backup

# FileSet
FileSet {
  Name = "vSphere-Backup-VM1"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
              password=MyP@ss datacenter=ha-datacenter vm=production-web
              transport=nbdssl"
  }
}

# Full backup weekly, Incremental daily
Schedule {
  Name = "vSphere-Schedule"
  Run = Full sun at 02:00
  Run = Incremental mon-sat at 22:00
}

# Backup Job
Job {
  Name = "Backup-Production-Web"
  Type = Backup
  Client = vsphere-fd
  FileSet = "vSphere-Backup-VM1"
  Storage = File1
  Pool = File
  Schedule = "vSphere-Schedule"
  Messages = Standard
}

Example 2: Multiple VMs

FileSet {
  Name = "vSphere-Backup-All"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
              password=MyP@ss datacenter=ha-datacenter vm=web-server
              transport=nbdssl"
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
              password=MyP@ss datacenter=ha-datacenter vm=db-server
              transport=nbdssl quiesce=true"
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root
              password=MyP@ss datacenter=ha-datacenter vm=app-server
              transport=nbdssl"
  }
}

Example 3: SAN (LAN-Free) Backup

FileSet {
  Name = "vSphere-SAN-Backup"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=vcenter.prod.local username=admin@vsphere.local
              password=VcP@ss datacenter=Production vm=critical-db
              transport=san force_san=true"
  }
}

Example 4: Selective Disk Backup

FileSet {
  Name = "vSphere-Selective"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
              datacenter=ha-datacenter vm=multi-disk-vm transport=nbdssl
              exclude_disk=Hard disk 3"
  }
}

Example 5: Restore to New VM

# bconsole commands
restore jobid=<BACKUP_JOBID> where=/
  # Then add these override options at the restore prompt:
  # podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
  #   datacenter=ha-datacenter new_vm_name=restored-vm power_on=yes
  #   datastore=datastore2 thin_provisioned=yes

12. Replication Configuration Examples

Example 1: CBT Push Replication (Incremental)

# FileSet for CBT push
FileSet {
  Name = "vSphere-Repl-Push"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
              datacenter=ha-datacenter vm=production-web transport=nbdssl
              mode=cbt-push dr_host=192.168.2.50 dr_port=9102
              dr_auth_token=MySecureToken2026 dr_tls_insecure=yes"
  }
}

# Schedule: push every 15 minutes
Schedule {
  Name = "Repl-Push-15min"
  Run = Incremental hourly at 0:00
  Run = Incremental hourly at 0:15
  Run = Incremental hourly at 0:30
  Run = Incremental hourly at 0:45
}

# Job
Job {
  Name = "Replicate-Production-Web"
  Type = Backup
  Client = vsphere-fd
  FileSet = "vSphere-Repl-Push"
  Storage = File1
  Pool = File
  Schedule = "Repl-Push-15min"
}

Example 2: Initial Seed

FileSet {
  Name = "vSphere-Seed"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
              datacenter=ha-datacenter vm=production-web transport=nbdssl
              mode=seed dr_host=192.168.2.50 dr_port=9102
              dr_auth_token=MySecureToken2026"
  }
}

Job {
  Name = "Seed-Production-Web"
  Type = Backup
  Client = vsphere-fd
  FileSet = "vSphere-Seed"
  Storage = File1
  Pool = File
}

Example 3: Planned Failover with Network Mapping

FileSet {
  Name = "vSphere-Failover-Planned"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss
              datacenter=ha-datacenter vm=production-web transport=nbdssl
              mode=failover-planned
              dr_host=192.168.2.50 dr_port=9102 dr_auth_token=MySecureToken2026
              net_map=Production_Network=DR_Network
              reip=0:192.168.2.10/24:192.168.2.1:8.8.8.8,8.8.4.4"
  }
}

Example 4: DR Receiver

FileSet {
  Name = "vSphere-DR-Receiver"
  Include {
    Options { signature = MD5 }
    Plugin = "podheitor-vsphere: host=192.168.2.50 username=root password=DrP@ss
              datacenter=ha-datacenter vm=production-web-replica transport=nbdssl
              mode=dr-receiver dr_port=9102 dr_auth_token=MySecureToken2026
              dr_tls_cert=/etc/podheitor/tls/server.crt
              dr_tls_key=/etc/podheitor/tls/server.key"
  }
}

Example 5: Complete DR Setup

# === PRIMARY SITE (Production) ===

# 1. Initial seed
Job { Name = "Seed-WebVM"  ... FileSet = "vSphere-Seed" }

# 2. Continuous replication (every 5 min)
Job { Name = "Repl-WebVM"  ... FileSet = "vSphere-Repl-Push" Schedule = "Every5Min" }

# 3. Status check (hourly)
Job { Name = "Status-WebVM" ... FileSet = "vSphere-Status" Schedule = "Hourly" }

# === DR SITE (Standby) ===

# 4. DR Receiver (long-running)
Job { Name = "DR-Receiver"  ... FileSet = "vSphere-DR-Receiver" }

# === FAILOVER JOBS (manual trigger) ===

# 5. Test failover
Job { Name = "Test-Failover-WebVM" ... mode=failover-test }

# 6. Undo test
Job { Name = "Undo-Failover-WebVM" ... mode=failover-undo }

# 7. Planned failover (during maintenance window)
Job { Name = "Planned-Failover-WebVM" ... mode=failover-planned
      net_map=... reip=... }

# 8. Emergency failover
Job { Name = "Emergency-Failover-WebVM" ... mode=failover-unplanned
      net_map=... reip=... }

# 9. Permanent failover
Job { Name = "Permanent-Failover-WebVM" ... mode=failover-permanent
      net_map=... reip=... }

# 10. Failback (after production restored)
Job { Name = "Failback-WebVM" ... mode=failback }

# 11. Re-protect
Job { Name = "Reprotect-WebVM" ... mode=reprotect }

13. User Manual & Day-2 Operations

This section walks through the everyday tasks an operator performs once the plugin is installed and the FileSets in §11–§12 have been added to bacula-dir.conf.

13.1 Common bconsole commands

Task Command (in bconsole)
List configured backup jobs show jobs
List backup jobs of a client list jobs client=vsphere-fd
Run a job on demand run job=Backup-Production-Web yes
Run with explicit level run job=Backup-Production-Web level=Full yes
See running job(s) status client=vsphere-fd
Tail messages messages
Find files in catalog list files jobid=<JOBID>
Cancel a stuck job cancel jobid=<JOBID>
Purge & delete a backup delete jobid=<JOBID>

13.2 Running a backup

echo "run job=Backup-Production-Web yes" | bconsole
# Then watch:
echo "status client=vsphere-fd" | bconsole
echo "messages" | bconsole

For a forced Full (e.g. after enabling CBT for the first time):

run job=Backup-Production-Web level=Full yes

After the first Full, subsequent Incrementals will only carry changed blocks reported by CBT (typically 1–5 % of the disk per day).

13.3 Restoring a VM (interactive)

* restore
Select item:  5 (Select the most recent backup for a client)
Defined Clients:
  1: vsphere-fd
Select Client (File Daemon) resource:  1
The defined FileSet resources are:
  1: vSphere-Backup-VM1
Select FileSet:  1
... bconsole picks the latest Full + chain of Incrementals
... it then drops you into the file selection tree.
$ mark *
$ done
Run Restore job
   JobName:    RestoreFiles
   Bootstrap:  /opt/bacula/working/...
   Where:      /tmp/bacula-restores
   Plugin Options: *None*
OK to run? (yes/mod/no): mod
Parameters to modify:
   ...
   12: Plugin Options
Select parameter to modify (1-13): 12
Please enter a Plugin Options string: podheitor-vsphere: 
        host=192.168.1.100 username=root password=MyP@ss 
        datacenter=ha-datacenter new_vm_name=production-web-restored 
        datastore=datastore2 thin_provisioned=yes power_on=yes
OK to run? (yes/mod/no): yes

The plugin re-uses the original VM hardware version, network adapter type, and guest OS unless you override via hw_version, network_name, guest_id_override, or controller_type.

13.4 Single-disk restore

Use include_disk (or exclude_disk) on the restore plugin string to restrict which VMDK is restored:

podheitor-vsphere: host=192.168.1.100 username=root password=MyP@ss 
        datacenter=ha-datacenter new_vm_name=onlydata 
        include_disk="Hard disk 2" datastore=datastore2 power_on=no

13.5 Cross-hypervisor restore (vSphere → Proxmox / Hyper-V)

Cross-restore is just a regular restore that targets a non-VMware host. Format conversion (VMDK ↔ VHDX ↔ QCOW2) is automatic — see §7.

# Restore a VMware backup onto a Proxmox host (FD running on PVE node)
restore client=proxmox-fd jobid=<BACKUP_JOBID>
   Plugin Options: podheitor-proxmox: node=pve1 storage=local-lvm 
                   new_vm_name=migrated-web vmid=120 power_on=no

13.6 Replication day-2 workflow

Once the seed (mode seed) has finished and you have a recurring job in cbt-push mode, replication is fully automatic. Day-2 operations are:

Operation When bconsole
Check status Anytime run job=Status-WebVM yes
Test failover (non-destructive) DR drill run job=Test-Failover-WebVM yes
Undo test After drill run job=Undo-Failover-WebVM yes
Planned failover Maintenance window run job=Planned-Failover-WebVM yes
Unplanned failover Outage run job=Emergency-Failover-WebVM yes
Permanent failover Permanent migration run job=Permanent-Failover-WebVM yes
Failback to source Source restored run job=Failback-WebVM yes
Re-protect (restart fwd repl) After failback run job=Reprotect-WebVM yes

13.7 DR receiver lifecycle

The DR receiver runs as a long-lived job (mode=dr-receiver) on the DR site’s Bacula FD. Recommended setup:

Job {
  Name = "DR-Receiver-Always-On"
  Type = Backup
  Client = vsphere-dr-fd
  FileSet = "vSphere-DR-Receiver"
  Schedule = "Boot"          # starts on FD boot
  Max Run Sched Time = 0     # never time out
  Allow Duplicate Jobs = no
  Reschedule On Error = yes
  Reschedule Interval = 60
  Reschedule Times = 0       # forever
}

SIGTERM (e.g. bconsole: cancel jobid=<n>) triggers a graceful shutdown — in-flight deltas are flushed and the snapshot lock on the replica is released before exit.

13.8 TLS certificates for the DR protocol

For production, generate a server cert for the DR receiver host:

openssl req -x509 -newkey rsa:4096 -nodes -days 3650 
    -keyout /etc/podheitor/tls/server.key 
    -out    /etc/podheitor/tls/server.crt 
    -subj   "/CN=vsphere-dr-fd.dr.local"
chmod 600 /etc/podheitor/tls/server.key

Reference both files via dr_tls_cert= and dr_tls_key= on the mode=dr-receiver FileSet, and remove dr_tls_insecure=yes on the sender side once the cert is trusted.

13.9 Token rotation

Tokens are read from the Plugin string at job start, so rotation is:

  1. Generate a new token (openssl rand -hex 32).
  2. Update both dr_auth_token= strings (sender + receiver FileSets).
  3. bconsole: reload.
  4. Cancel the running DR receiver job — Bacula will reschedule it

automatically (per §13.7) and the new token takes effect.

13.10 Log locations

File What
/opt/bacula/working/bacula-fd.log Bacula FD log (plugin output multiplexed)
/opt/bacula/working/podheitor/cbt/<vm>.json Per-VM CBT change-IDs
/var/lib/podheitor/replication/<vm>.json Replication state, last sync time, restore points
journalctl -u bacula-fd -e systemd-captured stderr

Add debug=true to a Plugin string for verbose backend tracing (routed to the Bacula FD log via PTCOMM).


14. Sizing & Requirements

Hardware Requirements

Component Minimum Recommended High Performance
CPU 2 cores 4 cores 8+ cores
RAM 4 GB 8 GB 16+ GB
Disk (FD) 20 GB 50 GB 100+ GB
Network 1 Gbps 10 Gbps 25 Gbps

Storage Sizing

Scenario Estimate
Full backup ~1:1 with used data (compression by Bacula SD)
Incremental 1-5% of total disk per day (typical)
Replica Equal to source VM disk size
Restore points ~1-5% per snapshot (depends on change rate)

Network Bandwidth

Scenario Bandwidth Needed
Daily backup (100 GB VM) ~5 GB incremental → ~11 Mbps sustained
Replication (100 GB VM, 5-min RPO) ~100 MB/interval → ~3 Mbps sustained
Full seed (100 GB VM) 100 GB → ~2.2 hrs at 100 Mbps

Concurrent VM Limits

Configuration Recommended Max
2 CPU / 4 GB RAM 2-3 concurrent VMs
4 CPU / 8 GB RAM 5-10 concurrent VMs
8 CPU / 16 GB RAM 15-20 concurrent VMs

15. Compatibility Matrix

VMware Compatibility

Component Version Tested Notes
ESXi 7.0 All update levels
ESXi 8.0 All update levels
ESXi 8.0U3e Primary test platform
vCenter 7.0 Optional — standalone ESXi supported
vCenter 8.0 Optional
VDDK 8.0.x Minimum recommended
VDDK 9.0.1 Primary test platform

Operating System Compatibility (FD Server)

OS Version Tested Notes
Oracle Linux 9.x Primary test platform (9.5)
RHEL 9.x Same binary as OL9
Rocky Linux 9.x Same binary as OL9
AlmaLinux 9.x Same binary as OL9

Bacula Compatibility

Component Version Tested
Bacula Community 15.0.3
Bacula Community 15.0.x Expected compatible

Guest OS Support

Guest OS Backup Restore Quiesce CBT Re-IP
Linux (any) ✅* ✅*
Windows Server ✅* ✅*
Windows 10/11 ✅* ✅*
FreeBSD ⚠️
Other ⚠️

*Requires VMware Tools installed in the guest.


16. Test Results

Test Environment

Component Details
ESXi 8.0U3e, standalone, IP 192.168.15.91
FD Server Oracle Linux 9.5, Bacula 15.0.3
VDDK 9.0.1
Test VMs Alpine (381 MB), TinyCore, CirrOS, Multi-Disk

Backup & Restore Tests

# Test Result Details
1 Full backup (NBDSSL) ✅ PASS All 4 VMs
2 Incremental backup (CBT) ✅ PASS Only changed blocks
3 Full VM restore ✅ PASS Original name + new name
4 Restore to different datastore ✅ PASS Thin provisioned
5 Cross-restore (vSphere → Hyper-V) ✅ PASS VMDK → VHDX
6 Cross-restore (Hyper-V → vSphere) ✅ PASS VHDX → VMDK

Replication Tests (April 20, 2026)

# Test JobId Status Data
1 Replication Status 881 ✅ T 870 B
2 CBT Push (Incremental) 882 ✅ T 377 MB
3 Failover Test 883 ✅ T 137 B
4 Failover Undo 884 ✅ T 137 B
5 Failover Planned 885 ✅ T 140 B
6 Failover Undo (2) 887 ✅ T 137 B
7 Failover Unplanned 889 ✅ T 142 B
8 Failover Permanent 892 ✅ T 142 B
9 Seed (Full Sync) 893 ✅ T 377 MB
10 Failback 894 ✅ T 2.1 GB
11 Reprotect 895 ✅ T 1,795 B
12 Final Status 897 ✅ T 870 B

Result: 12/12 tests PASSED — 100% success rate

Restore Validation Matrix (v1.4.1, April 29 2026)

End-to-end restore-path lab on ESXi 8.0.3 (192.168.15.91, no vCenter) + Bacula 15.0.3 FD/SD/Director (192.168.15.105). Source VM vm-alpine1 (Alpine 3.21, ext4 with SYSLINUX VBR; live snapshot).

# Path JobId Result Notes
1 Full backup (NBDSSL + CBT enable) 6425 ✅ T changeId persisted: …/85
2 Full → filesystem-restore (Where=/tmp/restore) 6427 ✅ T 4 files / 2.147 GB / 6 s, 0 FD errors
3 Restored VMDK byte-correctness mount -o ro,loop,noload; /etc/alpine-release = 3.21.0
4 Incremental backup (CBT) 6429 ✅ T changeId persisted: …/86
5 Incremental → filesystem-restore 6431 ✅ T matches Full restore byte-for-byte
6 Hypervisor-restore safety (source VM exists) 6434/6435 ✅ refused “VM already exists; use overwrite=yes or different where name”
7 Backup with new_vm_name=vm-alpine1-restored baked in 6440 ✅ T catalog-frozen plugin string
8 Hypervisor-restore (recreate on ESXi via NBD/SSL) 6442 ✅ T created moref=71, VMDK uploaded
9 Recreated VM power-on / boot bootTime set, uptimeSeconds=118, CPU 29 MHz, mem 220 MB

Result: 9/9 paths PASSED. All three v1.4.1 fixes (path doubling, Where= capture, directory-writer EOD) confirmed against live workload.


17. Comparison with Enterprise Solutions

Feature Comparison

Feature PodHeitor BRC Bacula Enterprise Veeam B&R Commvault
Image Backup
CBT Support
Incremental
SAN Transport
Full VM Restore
Single-Disk Restore
Cross-Hypervisor ⚠️ ⚠️
VM Replication
DR Failover ✅ (10 modes)
Network Mapping
Re-IP on Failover
TLS DR Protocol N/A
Bacula Community N/A N/A
Cost $$ $$$$ $$$$$ $$$$$

Cost Advantage

Bring your Bacula Enterprise, Veeam, Commvault or Netbackup quote or renewal proposal. We offer at least 50% discount, with far more features.


18. Troubleshooting

18.1 OPENSSL_3.4.0 not found from rpm / dnf / flatpak

Cause: a legacy /etc/ld.so.conf.d/ entry is forcing every process to load VMware’s older libcrypto.so.3. See §9.4 for the one-liner recovery.

18.2 libvixDiskLib.so not found in standard paths

The backend searches:

  1. /usr/lib/vmware-vix-disklib/lib64
  2. /usr/lib64/vmware-vix-disklib
  3. /opt/bacula/lib/vix

If the VDDK runtime RPM is installed, path #1 will contain libvixDiskLib.so. If you installed VDDK manually in an unusual location, set the env var VDDK_LIB_PATH=/full/path/libvixDiskLib.so in the FD service unit:

sudo systemctl edit bacula-fd
# add:
# [Service]
# Environment=VDDK_LIB_PATH=/opt/vddk/lib64/libvixDiskLib.so
sudo systemctl daemon-reload && sudo systemctl restart bacula-fd

18.3 Backup runs but produces 0 bytes on Incremental

CBT is not enabled or was reset. Verify:

govc vm.info -vm.ipath /ha-datacenter/vm/<VM_NAME> | grep -i ctk
# Must show ctkEnabled = true

Then take + delete a snapshot to materialize the change log, and run a fresh Full.

18.4 dlopen(libvixDiskLib.so): symbol lookup error

The VDDK .so did not find one of its bundled siblings (libcrypto.so.3, libssl.so.3, libcurl.so.4, …). Confirm the RPATH was applied:

readelf -d /usr/lib/vmware-vix-disklib/lib64/libvixDiskLib.so.9 | grep PATH
# Expected:
#   0x0000000000000001 (RUNPATH)            Library runpath: [$ORIGIN]

If missing, re-install podheitor-vixdisklib-runtime — the RPM’s %install applies patchelf --set-rpath '$ORIGIN' to every .so in lib64/.

18.5 Replication delta applies but VM won’t boot after failover

Re-check that the net_map= / reip= on the failover FileSet point to networks and IPs that actually exist at the DR site. An unmapped port group causes VMware to silently disconnect the NIC at power-on.

18.6 “Authentication failed” on DR receiver

dr_auth_token= must match byte-for-byte on sender and receiver. The comparison is constant-time (XOR-based) to prevent timing attacks, so the only way it fails is a real mismatch. If you recently rotated the token, see §13.9.

18.7 Backups slow or stuck in connecting

Check transport:

  • nbdssl over WAN with high latency — switch to san if possible,

or hotadd if the FD is itself a VM on the same ESXi cluster.

  • Firewall between FD and ESXi — the NBD port (902) must be open on

top of 443 when using nbd / nbdssl.


19. Licensing & Contact

License

Copyright © 2026 Heitor Faria — All Rights Reserved

This software is proprietary. All source code, binaries, and documentation are the exclusive property of Heitor Faria. Unauthorized copying, distribution, modification, or reverse engineering is strictly prohibited.

Contact

Heitor Faria

  • 📧 Email: heitor@opentechs.lat
  • 📱 Phone: +1 786 726-1749
  • 📱 WhatsApp: +55 61 98268-4220

Sales Inquiries

For pricing, licensing, proof-of-concept, and technical demonstrations, contact us directly. Volume discounts and multi-year agreements available.

Special Offer: Bring your existing Bacula Enterprise, Veeam, Commvault, or Netbackup quote or renewal proposal. We guarantee at least 50% discount, with far more features.


PodHeitor vSphere BRC Plugin — Whitepaper v1.4.1 — April 2026 VMware, vSphere, ESXi, VADP, VDDK are trademarks of Broadcom/VMware. Bacula is a trademark of Kern Sibbald. All other trademarks are property of their respective owners.

Disponível em: pt-brPortuguês (Portuguese (Brazil))enEnglishesEspañol (Spanish)

Leave a Reply