Technical whitepaper — PodHeitor Granular Restore for Bacula

Technical whitepaper — PodHeitor Granular Restore for Bacula

Technical Whitepaper — Version 0.1.0 — May 2026

Author: Heitor Faria · Website: https://podheitor.com · Email: heitor@opentechs.lat · Phone / WhatsApp: +1 786 726-1749 | +55 61 98268-4220

Special offer. Bring your renewal proposal for any commercial enterprise backup platform — Veeam, Commvault, NetBackup, or others. We will benchmark a head-to-head proposal targeting at least 50% savings with stronger PHGR-specific functionality. Contact heitor@opentechs.lat for a written quote.

Table of contents

  1. Executive summary
  2. Introduction & market context
  3. Architecture overview
  4. Restore modes deep dive
  5. Feature matrix
  6. Installation guide
  7. Configuration reference
  8. FileSet examples
  9. Sizing & capacity planning
  10. Performance report
  11. Compatibility matrix
  12. Security
  13. Monitoring
  14. Troubleshooting guide
  15. Use cases & deployment scenarios
  16. Comparison with other approaches
  17. Roadmap
  18. Conclusion
  19. Contact information
  20. Legal / copyright

1. Executive summary

Virtual machine backups protect entire disk images — VMDK for vSphere, VHDX for Hyper-V, qcow2 for Proxmox and KVM. These image-level backups are extremely space-efficient and hypervisor-consistent, but they create a painful operational gap: when an administrator needs to recover a single deleted file, a specific configuration, or an individual directory from inside a backed-up VM, the traditional answer is to restore the entire disk image — a process measured in gigabytes of network transfer, tens of minutes of downtime, and significant storage churn — just to retrieve a file that may be a few kilobytes in size.

The PodHeitor Granular Restore for Bacula (PHGR) closes this gap. It is a Storage Daemon-side suite, written in Rust, that mounts virtual disk images (VHDX, VMDK, qcow2, VHD, raw, VDI) directly from Bacula volumes — without staging the full image to disk first — and exposes the guest filesystem (NTFS, ext4, xfs, btrfs, ReFS, FAT32, exFAT) for interactive browsing or programmatic extraction. Administrators can navigate the directory tree of any backed-up VM, select individual files or folders, and extract them in seconds.

Beyond file-level restore, PHGR delivers two additional capabilities sharing the same Rust core: Instant Recovery (IR), which boots a backed-up VM directly from a Bacula volume over NBD or iSCSI in under 60 seconds while storage migration runs in the background; and Cross-Hypervisor V2V, which converts disk format and generates native hypervisor configuration to restore a VM into a different hypervisor than the source. All three capabilities are packaged as a single modular suite targeting Bacula Community 15.0.3.

From a competitive standpoint, Granular Restore is a premium feature in Veeam Data Platform and Commvault — priced accordingly and unavailable on open-source backup infrastructure until now. PHGR delivers equivalent and in several dimensions superior functionality (Proxmox/CloudStack/Nutanix coverage, Hyper-V Instant Recovery, cross-hypervisor V2V, zero-staging architecture) on top of Bacula Community at a fraction of enterprise platform cost.


2. Introduction & market context

2.1 The virtual machine backup paradox

Image-level VM backup has become the de facto standard for protecting virtualised infrastructure. The approach captures the entire disk state — including the operating system, installed software, and data — in a single consistent snapshot that can be restored to a new VM in a predictable, repeatable way. Hypervisor-native mechanisms (Hyper-V RCT, VMware CBT, Proxmox dirty bitmaps) enable efficient incremental captures with minimal performance impact.

The operational paradox emerges the moment a specific file must be retrieved. Consider these real-world scenarios:

  • A developer on a Windows Server VM accidentally deletes a configuration file. The VM is running in production and cannot be stopped. The file is inside C:AppConfig in the most recent Hyper-V backup.
  • A compliance auditor requests a specific log file from a VM image taken three months ago. Restoring the entire VHDX (200 GB) to retrieve a 40 KB file is operationally absurd.
  • A database server VM crashes. Instant Recovery is needed — boot from backup immediately while the storage team provisions new hardware — but the backup platform only supports full restores.

These scenarios require granular access to the contents of virtual disk images. Without purpose-built tooling, administrators face an unpleasant choice: accept the full restore overhead, maintain a separate file-level backup in parallel (doubling storage costs and backup windows), or resort to ad hoc manual procedures.

2.2 Why existing approaches fall short

Tool Granular Restore from VM image Verdict
Bacula Community (native, no plugin) Full image restore required before file access Impractical for single-file recovery
Veeam Data Platform Instant File Recovery — premium feature, premium price Not available on open-source infrastructure
Commvault Granular Recovery Technology — proprietary, expensive Requires full Commvault licensing stack
NetBackup Instant Access — available in enterprise tiers only High licensing cost, complex deployment
Bareos / Amanda No native VM granular restore Full image restore only
Manual scripts (libguestfs/7z) Possible but error-prone, no catalog integration Not production-grade, no audit trail

The gap is clear: until PHGR, no open-source backup platform provided production-grade granular restore from VM image backups. PHGR fills this gap.

2.3 The PodHeitor approach

PHGR follows the same design philosophy as the broader PodHeitor plugin family: Rust-native implementation, phase-gated development with automated regression tests, Storage Daemon-side operation that avoids all data staging overhead, and a gRPC + REST API gateway that makes the suite consumable from Bacularis, PHWEB, or any scripted integration. The three capabilities — Granular Restore, Instant Recovery, and V2V conversion — share a single phgr-core crate, maximising code reuse and minimising the attack surface.


3. Architecture overview

3.1 Suite design: one project, three binaries

PHGR is structured as a Cargo workspace with a shared library crate and four deployable binaries:

Component Binary / file Role
Core library phgr-core (crate) Catalog/BVFS, volume reader, delta/CBT assembler, disk adapter, FS mounter, block exporter, V2V engine, session manager, audit
Granular Restore daemon /opt/phgr/bin/phgrd Long-running daemon that manages GR sessions (FUSE / SMB / NFS export)
Instant Recovery daemon /opt/phgr/bin/phird Long-running daemon that manages IR sessions (NBD / iSCSI / NFS-Datastore export) and background storage migration
CLI /opt/phgr/bin/phgr-cli Interactive wizard and scripted CLI for mount, ir, v2v, diff, verify operations
API gateway /opt/phgr/bin/phgr-api gRPC + REST server with OpenAPI 3.1 spec; consumed by Bacularis and PHWEB

This separation provides three key advantages:

  1. Isolation. Each daemon runs as a separate systemd service. A crash in the IR daemon does not affect active GR sessions.
  2. Independent deployment. Sites that only need Granular Restore install only phgrd and phgr-cli. Instant Recovery and V2V are opt-in components.
  3. Testability. phgr-core is a pure library testable independently of any Bacula or hypervisor infrastructure. The E2E test harness (phgr-tests crate) exercises end-to-end scenarios on lab fixtures without modifying production systems.

3.2 Storage Daemon-side operation

PHGR runs on the host of the Bacula Storage Daemon (SD), not on the client (FD) host. This is an intentional architectural decision with important consequences:

  • Zero full-restore staging. PHGR reads virtual disk image data directly from Bacula volumes on the SD filesystem via lazy streaming reads. It never extracts the full VHDX or VMDK to a working directory before mounting. For a 200 GB VHDX, the working directory footprint is at most 2 GB (LRU cache + COW overlay).
  • Tool availability. The SD host is Linux. libguestfs, qemu-nbd, qemu-img, Samba, NFS, and iSCSI Target (LIO) are all natively available on Linux and trivially installable. The guest Windows or ESXi host does not need to be modified.
  • Performance. Reads from Bacula volumes are local filesystem I/O on the SD host. Network latency is not in the critical path for GR sessions.

3.3 Architecture diagram

┌────────────────────────────────────────────────────────────────────────┐
│                        PHGR Suite (SD host)                             │
│                                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │  phgr-cli    │  │  phgrd       │  │  phird       │  │  phgr-api    │ │
│  │ (interactive │  │ (GR daemon:  │  │ (IR daemon:  │  │ (gRPC+REST   │ │
│  │  + scripted) │  │  FUSE/SMB/   │  │  NBD/iSCSI/  │  │  OpenAPI)    │ │
│  │              │  │  NFS export) │  │  NFS-DS)     │  │              │ │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘ │
│         └─────────────────┴──────────────────┴─────────────────┘         │
│                                    │                                      │
│                                    ▼                                      │
│               ┌──────────────────────────────────────────┐                │
│               │          phgr-core  (Rust crate)         │                │
│               │                                          │                │
│               │  Catalog/BVFS ── Volume Reader           │                │
│               │  Delta/CBT Assembler ── Disk Adapter     │                │
│               │  FS Mounter ── Block Exporter            │                │
│               │  V2V Engine ── Session Manager ── Audit  │                │
│               └───────────────────────┬──────────────────┘                │
│                                       │                                   │
│                           reads Bacula volumes directly                   │
│                                       │                                   │
└───────────────────────────────────────┼───────────────────────────────────┘
                                        │
        ┌───────────────────────────────┴──────────────────────────────┐
        │  Bacula 15.0.3 (Director / SD / catalog PG / volumes)         │
        │  Hypervisors (Hyper-V, vSphere, Proxmox, KVM, CloudStack)     │
        │  libguestfs / qemu-img / qemu-nbd / Samba / NFS / iSCSI LIO  │
        └──────────────────────────────────────────────────────────────┘

3.4 Session management

Each GR or IR session is tracked in a dedicated working directory:

/opt/bacula/working/phgr/{session_uuid}/
  ├── session.db          # SQLite session state and file manifest
  ├── manifest.yaml       # heartbeat, mode, job IDs, mount points
  ├── cow/                # qcow2 COW overlay (IR mode)
  └── mnt/                # FUSE mount point (GR mode)

The daemon writes a heartbeat to manifest.yaml every 30 seconds. A background sweeper collects orphaned sessions (heartbeat age > 10 minutes), unmounts FUSE filesystems, takes NBD/iSCSI targets offline, and removes the working directory. systemd ExecStop hooks ensure cleanup on daemon restart.


4. Restore modes deep dive

4.1 Granular Restore (GR) — file-level access from VM disk images

How it works

The Granular Restore mode reads virtual disk image data lazily from Bacula volumes, assembles the CBT/incremental chain in memory, passes the reconstructed image to the disk adapter (libguestfs + qemu-nbd in v1.x, native Rust parsers in v2.x), and mounts the guest filesystem via FUSE or exports it via SMB or NFS. The operator can then browse the directory tree interactively and extract individual files or directories.

  Bacula volumes (SD filesystem)
    ──► phgr-core::volume_reader (lazy streaming)
    ──► phgr-core::delta (CBT chain assembly)
    ──► phgr-core::disk_adapter (libguestfs / qemu-nbd)
    ──► phgr-core::fs_mounter (FUSE / SMB / NFS)
    ──► operator browses guest filesystem, extracts files

Operating modes

Mode Description Best for Working dir footprint
browse FUSE read-only mount of guest FS, local access only Quick operator inspection < 100 MB
extract Direct extraction of specific paths to destination, no mount Scripted / ETL restore Streaming (no staging)
share-smb Browse + automatic Samba share Windows client drag-and-drop access < 100 MB
share-nfs Browse + NFS export Linux client or ESXi datastore < 100 MB

When to use

  • Recovering a deleted file from inside a VM backup without restoring the full image
  • Auditor or compliance request for a specific file from a historical backup
  • Recovering configuration files, logs, or certificates without rebooting the live VM
  • Comparing filesystem state between two backup jobs (diff mode)

Supported disk image formats

Format Hypervisor origin GR support
VHDX / VHD Hyper-V (Microsoft) Supported
VMDK vSphere / VMware Supported
qcow2 Proxmox / KVM / CloudStack / Nutanix AHV Supported
raw KVM / generic Supported
VDI VirtualBox Supported

Supported guest filesystems

Filesystem OS Read ACL preservation
NTFS Windows (all versions) Supported Partial (POSIX approximation)
ext4 Linux Supported Yes
ext3 / ext2 Linux Supported Yes
xfs Linux / RHEL Supported Yes
btrfs Linux / openSUSE Supported Yes
ReFS Windows Server 2016+ Supported Partial
FAT32 / exFAT UEFI ESP / removable Supported N/A

4.2 Instant Recovery (IR) — boot VM directly from Bacula backup

How it works

The Instant Recovery mode exposes the reconstructed disk image as a block device target over NBD (Proxmox/KVM/ESXi 7+), iSCSI (Hyper-V), or NFS Datastore (vSphere classic). The hypervisor boots the VM using this block endpoint. All reads are served by phird streaming data on-demand from Bacula volumes. A Copy-on-Write overlay absorbs writes during the recovery period. A background storage migration thread copies blocks to the target storage and completes the migration without interrupting the running VM.

  Bacula volumes ──► phird lazy streaming ──► qcow2 COW overlay
                                                    │
                          ┌─────────────────────────┼─────────────────────────┐
                          │                         │                         │
                     NBD export              iSCSI export (LIO)     NFS-Datastore export
                          │                         │                         │
                   Proxmox/KVM               Hyper-V                    vSphere
                   VM boots ≤ 60s          VM boots ≤ 90s             VM boots ≤ 60s
                          │
              background: migrate blocks to local-lvm → session COMPLETE

IR modes by hypervisor

Hypervisor Export protocol Target boot time (p95) Status
Proxmox / KVM NBD (qemu-nbd) ≤ 60 s Phase 5
vSphere / ESXi NFS Datastore (NFSv3) ≤ 60 s Phase 5
Hyper-V iSCSI Target (Linux LIO) ≤ 90 s Phase 5
Nutanix AHV NBD / Prism API ≤ 90 s Phase 7
CloudStack / KVM NBD ≤ 60 s Phase 7

4.3 Cross-Hypervisor V2V — restore to a different hypervisor

How it works

The V2V mode converts disk format (via qemu-img) and translates the VM configuration descriptor to the format required by the target hypervisor. A Hyper-V VHDX backup can be restored as a Proxmox qcow2 VM with correct vCPU, RAM, and network configuration. The entire pipeline — disk extraction from Bacula, format conversion, config generation, VM registration — is orchestrated by a single phgr-cli command.

  phgr-cli v2v --from @hyperv/MyVM --to proxmox/pve --jobid 1234 --target-storage local-lvm
      │
      ├─ 1. Extract VHDX from Bacula volume (lazy read + CBT assembly)
      ├─ 2. Convert VHDX → qcow2 via qemu-img
      ├─ 3. Parse VMCX config → unified VM descriptor → Proxmox qm config
      ├─ 4. Register VM on Proxmox (qm create + import disk)
      └─ 5. Start VM (optional) — validated boot

5. Feature matrix

Feature Granular Restore Instant Recovery V2V
VHDX support (Hyper-V) Yes Yes Yes
VMDK support (vSphere) Yes Yes Yes
qcow2 support (Proxmox/KVM) Yes Yes Yes
NTFS guest filesystem Yes Yes Yes
ext4 guest filesystem Yes Yes Yes
xfs guest filesystem Yes Yes Yes
btrfs guest filesystem Yes Yes Yes
ReFS guest filesystem Yes Yes Yes
FAT32 / exFAT Yes Yes Yes
CBT incremental chain assembly Yes Yes Yes
Zero full-image staging Yes Yes No
FUSE local mount Yes No No
SMB share export Yes No No
NFS export Yes Yes (datastore) No
NBD block export No Yes No
iSCSI export (Hyper-V IR) No Yes No
COW overlay (writes during IR) No Yes No
Background storage migration No Yes No
Cross-hypervisor conversion No Partial (on-the-fly) Yes
Diff between two backup jobs Yes No No
Sandbox boot verify Yes No No
Malware scan (ClamAV/YARA) Yes (optional) No No
HMAC audit log Yes Yes Yes
Prometheus metrics Yes Yes Yes
gRPC + REST API Yes Yes Yes
Bacularis integration Yes Yes Yes
RPM package Yes Yes Yes
DEB package Yes Yes Yes
Concurrent sessions Yes (≥ 8) Yes (≥ 2) Yes
Session orphan cleanup Yes Yes Yes

6. Installation guide

6.1 Prerequisites

  • Bacula Community 15.0.3 or later is installed; Storage Daemon is running on a Linux host
  • OS: RHEL/OL/Rocky 9+ or Ubuntu 22.04+/Debian 12+
  • glibc 2.34+
  • Required system packages (installed automatically by RPM/DEB pre-requires):
# EL9 / OL9
dnf install libguestfs guestfs-tools qemu-img qemu-nbd kpartx fuse3 
            samba samba-common nfs-utils iscsi-initiator-utils targetcli ntfs-3g

# Ubuntu 22.04 / Debian 12
apt install libguestfs-tools qemu-utils fuse3 samba nfs-kernel-server 
            open-iscsi targetcli-fb ntfs-3g
  • User bacula exists and has read access to Bacula SD volume directories
  • bconsole is available and can connect to the Bacula Director (required for BVFS catalog queries)

6.2 RPM installation (EL9 / OL9 / RHEL9 / Rocky 9)

# 1. Install the base package (installs phgr-core + phgr-cli)
dnf install podheitor-phgr-0.1.0-1.el9.x86_64.rpm

# 2. Install GR daemon (optional — required for persistent GR sessions + API)
dnf install podheitor-phgr-gr-0.1.0-1.el9.x86_64.rpm

# 3. Install IR daemon (optional — required for Instant Recovery)
dnf install podheitor-phgr-ir-0.1.0-1.el9.x86_64.rpm

# 4. Enable and start daemons
systemctl enable --now phgrd phird

# 5. Verify installation
/opt/phgr/bin/phgr-cli --version
/opt/phgr/bin/phgr-cli health

6.3 DEB installation (Ubuntu 22.04 / Debian 12)

# 1. Install base package
apt install ./podheitor-phgr-core_0.1.0-1_amd64.deb

# 2. Install GR daemon
apt install ./podheitor-phgr-gr_0.1.0-1_amd64.deb

# 3. Install IR daemon
apt install ./podheitor-phgr-ir_0.1.0-1_amd64.deb

# 4. Enable and start daemons
systemctl enable --now phgrd phird

# 5. Verify installation
/opt/phgr/bin/phgr-cli --version
/opt/phgr/bin/phgr-cli health

6.4 sudoers configuration

The daemons run as bacula:bacula and require a small set of privileged operations. The RPM/DEB automatically installs a minimal sudoers allowlist:

# /etc/sudoers.d/phgr (installed automatically)
bacula ALL=(ALL) NOPASSWD: /usr/bin/smbcontrol reload-config
bacula ALL=(ALL) NOPASSWD: /usr/sbin/exportfs -ra
bacula ALL=(ALL) NOPASSWD: /usr/bin/targetcli *
bacula ALL=(ALL) NOPASSWD: /usr/bin/fusermount -z *

6.5 Quick smoke test

# List VMs backed up in the last 7 days
phgr-cli catalog list-vms --days 7

# Mount a specific backup for file browsing
phgr-cli mount --client myserver --jobid 4200 --vm "MyVM" --mode browse

# Navigate the mounted filesystem
ls /opt/bacula/working/phgr/&lt;session-uuid&gt;/mnt/

# Extract a single file
phgr-cli extract --session &lt;uuid&gt; --path /etc/nginx/nginx.conf --dest /tmp/recovered/

7. Configuration reference

7.1 Main configuration file — /etc/phgr/phgr.toml

Parameter Default Description
working_dir /opt/bacula/working/phgr Base directory for session working directories
bconsole_bin /opt/bacula/bin/bconsole Path to bconsole binary
bconsole_conf /opt/bacula/etc/bconsole.conf Path to bconsole configuration
direct_pg false Use direct PostgreSQL connection for BVFS (faster; requires PG credentials)
pg_dsn (empty) PostgreSQL DSN when direct_pg = true
cache_size_mb 256 LRU disk block cache size in MB per session
sweeper_interval_secs 300 Orphan session sweeper interval (seconds)
heartbeat_secs 30 Session heartbeat interval (seconds)
max_sessions 8 Maximum concurrent sessions across all modes
nbd_listen_addr 127.0.0.1 NBD export bind address (IR mode)
nbd_base_port 10809 Base port for NBD exports (incremented per session)
iscsi_iqn_prefix iqn.2026-01.com.podheitor iSCSI IQN prefix for target names
metrics_listen (empty) Prometheus metrics bind address; empty = disabled
audit_log /var/log/phgr/audit.log Path to HMAC-chained audit log
audit_hmac_key_file /etc/phgr/audit.key Path to HMAC key file (mode 600, bacula-owned)

7.2 phgr-cli command reference

Command Description
phgr-cli catalog list-vms List VMs with available backups in the Bacula catalog
phgr-cli catalog list-jobs List backup jobs for a specific VM
phgr-cli mount Start a GR session (interactive wizard or flags)
phgr-cli extract Extract specific file paths from an active session
phgr-cli diff Compare filesystems between two backup jobs
phgr-cli verify Boot-test a backup image in an isolated sandbox
phgr-cli ir start Start an Instant Recovery session
phgr-cli ir status Show IR session status and migration progress
phgr-cli v2v Convert and restore VM to a different hypervisor
phgr-cli session list List all active sessions
phgr-cli session cleanup Terminate and clean up a specific session
phgr-cli health Check daemon health and version

7.3 FileSet Plugin string note

PHGR does not require its own FileSet — it consumes backups created by the existing PodHeitor hypervisor plugins (Hyper-V, Proxmox, vSphere). The GR/IR operations are initiated post-backup via phgr-cli or the API, referencing existing Job IDs.


8. FileSet examples

8.1 Hyper-V VM backup (using existing PodHeitor Hyper-V plugin)

FileSet {
  Name = "HyperV-VMs-Full"
  Include {
    Plugin = "podheitor-hyperv: vm=MyWindowsServer include_vss=true compress=zstd"
  }
}

After this job completes, PHGR can mount the resulting VHDX for granular restore:

# Interactive wizard
phgr-cli mount --client hyperv-host --vm "MyWindowsServer" --mode share-smb

# Scripted extraction
phgr-cli mount --client hyperv-host --jobid 5001 --vm "MyWindowsServer" 
               --mode extract --path "C:AppConfigapp.xml" --dest /tmp/recovered/

8.2 Proxmox VM backup (using existing PodHeitor Proxmox plugin)

FileSet {
  Name = "Proxmox-VMs-CBT"
  Include {
    Plugin = "podheitor-proxmox: vmid=200 compress=zstd cbt=true"
  }
}

After this job completes, PHGR can mount the resulting qcow2 for granular restore or trigger Instant Recovery:

# Granular restore — browse guest ext4 filesystem
phgr-cli mount --client pve-host --jobid 5200 --vm 200 --mode browse

# Instant Recovery — boot VM on Proxmox directly from Bacula backup
phgr-cli ir start --jobid 5200 --vm 200 --target proxmox/192.168.194.101 
                  --storage local-lvm --node pve

8.3 V2V cross-hypervisor restore

# Restore a Hyper-V-backed VM into Proxmox
phgr-cli v2v --from "@hyperv/MyWindowsServer" --jobid 5001 
             --to proxmox/192.168.194.101 --storage local-lvm --node pve

# Restore a Proxmox VM into Hyper-V
phgr-cli v2v --from "@proxmox/200" --jobid 5200 
             --to hyperv/192.168.15.84 --path "C:VMs"

8.4 Diff between two backup jobs

# Show files changed between job 5001 (Monday) and job 5008 (Friday)
phgr-cli diff --jobid-a 5001 --jobid-b 5008 --vm "MyWindowsServer" --path "C:App"

9. Sizing & capacity planning

9.1 Memory requirements

Scenario phgrd RSS phird RSS Per-session overhead
Idle (daemons started, no sessions) ~50 MB ~50 MB
1 GR session (browse mode) ~150 MB +100 MB
4 GR sessions concurrent ~500 MB +100 MB each
1 IR session (NBD, 256 MB LRU) ~350 MB +300 MB
2 IR sessions concurrent ~700 MB +300 MB each
Maximum (8 sessions, v1.0 target) ≤ 2 GB total daemon RSS

9.2 CPU requirements

Scenario Recommended cores
GR browse session (idle reads) 1
GR extract (sequential read throughput) 2
IR session serving NBD reads 2
4 concurrent GR + 2 IR sessions 8
V2V conversion (qemu-img) 4 (conversion-bound)

9.3 Disk space — installation footprint

File Approximate size
phgrd ~8 MB
phird ~8 MB
phgr-cli ~6 MB
phgr-api ~7 MB
Total installation footprint ~30 MB

9.4 Working directory footprint

Mode Working dir footprint
GR browse / extract < 100 MB (LRU cache only)
GR share-smb / share-nfs < 100 MB
IR (any hypervisor) < 500 MB (LRU + COW overlay writes)
V2V (60 GB VHDX) ~60 GB during conversion; removed after registration
Sandbox verify < 1 GB (ephemeral qemu runtime)

Key point. For GR and IR modes, the working directory footprint is orders of magnitude smaller than the backed-up disk image. A 500 GB VHDX can be accessed granularly with less than 100 MB of local working space.

9.5 Performance targets (v1.0 SLOs)

Operation Metric Target
GR session open (BVFS list) p95 latency ≤ 5 s
GR guest FS mount p95 wall time ≤ 30 s
GR file extraction (sequential) Sustained throughput ≥ 250 MB/s
IR boot-ready (Linux guest) p95 wall time ≤ 60 s
IR boot-ready (Windows guest) p95 wall time ≤ 120 s
IR NBD read throughput Sustained ≥ 100 MB/s
V2V 60 GB VHDX → qcow2 Total time on 10 GbE ≤ 25 min
Orphan session cleanup Upper bound ≤ 30 s

10. Performance report

All measurements were taken in a controlled lab environment (Oracle Linux 9, VM 105, 192.168.15.105) running Bacula Community 15.0.3 with NVMe-backed SD volumes. Hypervisors tested: Proxmox 8.2, Hyper-V (Windows Server 2025), vSphere 8.0.

10.1 Phase 0 — bootstrap & lab validation

Metric Result
cargo check –workspace Passes in < 2 min on VM 105
libguestfs mount test (VHDX small NTFS) Passes — file list correct
libguestfs mount test (qcow2 ext4) Passes — file list correct
Golden disk fixtures generated 4 fixtures (NTFS, ext4, XFS/LVM, btrfs)

10.2 Phase 1 — Core: Catalog + Volume + Delta + Disk + FS-mount

Test Result
T-CORE-001: open_session + list_files(“/etc/”) ≤ 5 s p95 — PASS
T-CORE-002: Full + 2 Inc CBT chain, SHA256 vs golden Byte-identical — PASS
T-CORE-003: 4 concurrent sessions, no deadlock RSS < 600 MB — PASS
Unit coverage — phgr-core ≥ 70%

10.3 Phase 2 — CLI + API

Test Result
T-CLI-001: Enterprise SIR parity (T1–T5) PASS
T-CLI-002: JSON mode SMB share, Windows VM access PASS
T-API-001: Bacularis gRPC integration, file tree display PASS
List dir (1k entries) latency ≤ 200 ms p95 — PASS

10.4 Phase 3 — Daemon + Sessions + Audit

Test Result
T-DAEMON-001: kill -9 + restart, session cleanup ≤ 30 s PASS
T-AUDIT-001: HMAC chain validation All entries valid — PASS
T-METRIC-001: Prometheus metrics exposed 4 metrics visible — PASS
Unattended install from clean SD host Daemon up in ≤ 60 s — PASS

10.5 Phase 5 — Instant Recovery

Test Target Result
T-IR-PVE-001: Hyper-V backup → IR on Proxmox, NBD ≤ 60 s boot PASS — 38 s
T-IR-VMW-001: IR on vSphere via NFS Datastore ≤ 60 s boot PASS — 44 s
T-IR-HYP-001: Hyper-V backup → IR on Hyper-V, iSCSI ≤ 90 s boot PASS — 71 s
T-IR-CROSS-001: VHDX backup → NBD on Proxmox (on-the-fly) ≤ 60 s boot PASS — 52 s
NBD read throughput (NVMe volume) ≥ 100 MB/s PASS — 187 MB/s
2 IR sessions concurrent, no corruption PASS PASS

10.6 Phase 6 — V2V Cross-Hypervisor

Test Result
T-V2V-001: Hyper-V → Proxmox Linux VM Boot OK — PASS
T-V2V-002: Proxmox → Hyper-V Linux VM Boot OK — PASS
T-V2V-003: VMware (VMDK fixture) → Proxmox Boot OK — PASS
60 GB VHDX V2V time on 10 GbE link 18 min — PASS (target ≤ 25 min)

10.7 Test suite summary

Phase Tests added Cumulative total
Phase 0 (bootstrap) 4 4
Phase 1 (core) 18 22
Phase 2 (CLI + API) 14 36
Phase 3 (daemon + audit) 12 48
Phase 4 (GR advanced: diff, verify, malware) 16 64
Phase 5 (Instant Recovery) 22 86
Phase 6 (V2V cross-hypervisor) 18 104
Phase 7 (Nutanix / CloudStack / K8s PVC) 14 118
Phase 8 (DR drill automation) 12 130

11. Compatibility matrix

11.1 Operating system (SD host)

OS Architecture Status
RHEL 9 x86_64 Supported
Oracle Linux 9 x86_64 Supported
Rocky Linux 9 x86_64 Supported
AlmaLinux 9 x86_64 Supported
Ubuntu 22.04 LTS x86_64 Supported
Debian 12 x86_64 Supported
RHEL 8 / CentOS 8 x86_64 Not tested (glibc < 2.34)
Ubuntu 20.04 x86_64 Not tested (glibc < 2.34)
ARM64 / aarch64 any Not yet available

11.2 Virtual disk image formats

Format Hypervisor GR IR V2V (source) V2V (target)
VHDX Hyper-V Supported Supported Supported Supported
VHD Hyper-V (legacy) Supported Supported Supported No
VMDK vSphere / VMware Supported Supported Supported Supported
qcow2 Proxmox / KVM / CloudStack Supported Supported Supported Supported
raw KVM / generic Supported Supported Supported Supported
VDI VirtualBox Supported No Supported No

11.3 Hypervisors (Instant Recovery targets)

Hypervisor IR export protocol GR browse IR V2V target
Proxmox 7.x / 8.x NBD Supported Supported Supported
KVM / libvirt NBD Supported Supported Supported
vSphere / ESXi 7.0+ NFS Datastore Supported Supported Supported
Hyper-V (Windows Server 2019+) iSCSI (Linux LIO) Supported Supported Supported
Nutanix AHV NBD / Prism API Supported Phase 7 Phase 7
CloudStack / KVM NBD Supported Phase 7 Phase 7

11.4 Bacula versions

Bacula version Status
Community 15.0.3 Supported (validated)
Community 15.0.x (future) Expected compatible
Community 14.x Not supported
Bacula Enterprise Not required; PHGR targets Bacula Community

11.5 System libraries

Library Minimum version Notes
glibc 2.34 Provided by RHEL 9 / Ubuntu 22.04+
libguestfs 1.48 Available in standard EL9 / Ubuntu 22.04 repos
libfuse3 3.10 Required for FUSE mount mode
qemu-nbd 6.2 Included in qemu-img package

12. Security

12.1 Least-privilege daemon model

Both daemons (phgrd and phird) run as bacula:bacula by default. Required privileges are granted via Linux capabilities in the systemd unit files — not by running as root:

# /etc/systemd/system/phgrd.service (excerpt)
User=bacula
Group=bacula
AmbientCapabilities=CAP_SYS_ADMIN CAP_DAC_READ_SEARCH
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_READ_SEARCH CAP_NET_BIND_SERVICE

Operations that still require elevated privilege (Samba reload, NFS exportfs, iSCSI targetcli) are delegated to a minimal sudo allowlist — never using NOPASSWD: ALL.

12.2 Session token isolation

Each session is identified by a UUID v7 token used as the working directory name and as an API bearer token. Session tokens are ephemeral (no persistence across daemon restarts). An operator with a session token can access only the FUSE mount or block endpoint for that specific session, not other sessions or Bacula volumes.

12.3 HMAC-chained audit log

Every session event (open, file list, extract, export start/stop, cleanup) is written to /var/log/phgr/audit.log with an HMAC-SHA256 entry that chains to the previous entry’s hash. This creates a tamper-evident log suitable for LGPD, GDPR, and PCI-DSS compliance reviews. The HMAC key is stored in a mode-600 file readable only by the bacula user.

12.4 Network security

NBD exports bind to 127.0.0.1 by default. For remote IR (hypervisor on a different host), the operator configures nbd_listen_addr to the private interface IP. iSCSI exports use CHAP authentication (auto-generated per session). No listening port is opened by default until an IR session is started.

12.5 Malware scan integration

When the optional malware scan feature is enabled (malware_scan = true in phgr.toml), the GR exporter invokes ClamAV or YARA rules against the extracted files before publishing an SMB or NFS share. Any detection halts the export and alerts the operator via standard Bacula messaging. This is an optional defence layer aligned with OWASP best practices for data recovery pipelines.

12.6 State directory permissions

/opt/bacula/working/phgr/          # mode 0750 bacula:bacula
/var/log/phgr/                     # mode 0750 bacula:bacula
/etc/phgr/                         # mode 0750 bacula:bacula
/etc/phgr/audit.key                # mode 0600 bacula:bacula

13. Monitoring

13.1 Prometheus metrics endpoint

When metrics_listen is set in /etc/phgr/phgr.toml, both daemons expose a /metrics endpoint in Prometheus text format:

# /etc/phgr/phgr.toml
metrics_listen = "0.0.0.0:9283"

13.2 Exposed metrics

Metric Type Description
phgr_active_sessions Gauge Number of currently active sessions (GR + IR)
phgr_sessions_total Counter Total sessions started since daemon start
phgr_extracts_total Counter Total individual file extractions
phgr_extract_bytes_total Counter Total bytes extracted across all sessions
phgr_extract_duration_seconds Histogram Per-extraction duration
phgr_ir_sessions_total Counter Total Instant Recovery sessions started
phgr_ir_migration_progress_ratio Gauge Background storage migration progress (0–1) per IR session
phgr_nbd_read_bytes_total Counter Total bytes served via NBD
phgr_session_open_duration_seconds Histogram Time to open session (BVFS list + disk mount)
phgr_orphan_cleanups_total Counter Total orphan sessions cleaned up by sweeper
phgr_errors_total Counter Total error events by error code

13.3 Prometheus scrape configuration

scrape_configs:
  - job_name: 'podheitor-phgr'
    static_configs:
      - targets: ['sd-host:9283']
    scrape_interval: 30s

13.4 OpenTelemetry tracing

PHGR supports OpenTelemetry tracing (opt-in via environment variable). When enabled, traces are exported to the configured collector, providing end-to-end visibility into session open time, disk adapter operations, and block export latency:

# Enable OTel tracing
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 systemctl restart phgrd

13.5 Bacula job status codes

GR and IR operations initiated via phgr-cli report status through the standard Bacula messaging system when a Restore Job is associated. Standalone operations report via phgr-cli exit codes:

  • 0 — session completed successfully
  • 1 — session completed with warnings (check audit log)
  • 2 — session failed (daemon log at /var/log/phgr/phgrd.log)

14. Troubleshooting guide

14.1 Common errors

phgrd / phird fails to start: libguestfs not found

phgrd: failed to initialise disk adapter: libguestfs not available

Cause. libguestfs is not installed or not in the library path.
Fix:

# EL9
dnf install libguestfs guestfs-tools
# Ubuntu 22.04
apt install libguestfs-tools

BVFS query returns empty results

phgr-cli: catalog: BVFS returned 0 results for jobid 5001

Cause. The Bacula catalog BVFS index has not been updated for this job, or bconsole cannot connect to the Director.
Fix:

# Force BVFS update in bconsole
*run job=RefreshBVFS yes
# Or verify bconsole connectivity
/opt/bacula/bin/bconsole -c /opt/bacula/etc/bconsole.conf &lt;&lt;&lt; "version"

FUSE mount fails: permission denied

phgr-cli: fuse: mount failed: Operation not permitted

Cause. CAP_SYS_ADMIN capability not granted to the daemon, or FUSE device not accessible to bacula user.
Fix:

ls -la /dev/fuse                  # should be crw-rw-rw- or bacula in group fuse
systemctl show phgrd | grep Cap   # verify AmbientCapabilities includes CAP_SYS_ADMIN
systemctl daemon-reload &amp;&amp; systemctl restart phgrd

IR session: NBD client times out

qm set 9999 -scsi0 nbd:...: timeout connecting to NBD server

Cause. The nbd_listen_addr is 127.0.0.1 (default) but the Proxmox host is remote, or firewall blocks the NBD port.
Fix:

# In /etc/phgr/phgr.toml, set the SD host's private IP:
nbd_listen_addr = "192.168.15.105"
# Open the NBD port range in firewall:
firewall-cmd --add-port=10809-10820/tcp --permanent &amp;&amp; firewall-cmd --reload

iSCSI CHAP authentication failure (Hyper-V IR)

Hyper-V: failed to connect iSCSI target: authentication failure

Cause. The CHAP credentials generated for this IR session were not applied correctly to the Hyper-V initiator.
Fix. Retrieve CHAP credentials from the session manifest and apply manually:

phgr-cli session show --uuid &lt;session-uuid&gt; | grep chap
# Then set in Hyper-V iSCSI Initiator → Discovery → CHAP

libguestfs fails on NTFS: ntfs-3g not found

libguestfs: error: ntfs-3g is not installed

Fix:

# EL9
dnf install ntfs-3g
# Ubuntu 22.04
apt install ntfs-3g

14.2 Log locations

Log Path
phgrd daemon log /var/log/phgr/phgrd.log
phird daemon log /var/log/phgr/phird.log
HMAC audit log /var/log/phgr/audit.log
Session-specific log /opt/bacula/working/phgr/{uuid}/session.log
libguestfs trace (when LIBGUESTFS_DEBUG=1) stderr of phgrd process → journald

14.3 Enabling debug logging

# Increase daemon log verbosity
PHGR_LOG=debug systemctl restart phgrd

# Enable libguestfs trace (very verbose — use only for disk adapter issues)
LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1 systemctl restart phgrd

# Debug a specific CLI command
phgr-cli --log-level debug mount --jobid 5001 --vm "MyVM" --mode browse

15. Use cases & deployment scenarios

15.1 File recovery from a deleted VM file (production scenario)

Scenario. A developer on a Windows Server 2022 VM accidentally deletes C:AppConfigproduction.xml on a Friday afternoon. The VM is in production. The most recent Hyper-V backup (VHDX) was taken 4 hours earlier.

Solution with PHGR.

  • Operator runs: phgr-cli mount --client hyperv-host --vm "AppServer" --mode share-smb
  • A Samba share is created pointing to the VHDX filesystem: sd-hostPHGR-{uuid}CAppConfigproduction.xml
  • Operator copies the file from the share directly to the live VM — no full restore, no downtime, no reboot
  • Session is closed with phgr-cli session cleanup --uuid {uuid}
  • Total recovery time: under 5 minutes from alert to file restored

15.2 Compliance audit — specific file from historical backup

Scenario. A compliance auditor requests a copy of /etc/sudoers from a Linux VM as it was on a specific date three months ago. The VM has been patched multiple times since then.

Solution with PHGR.

  • Operator identifies the backup Job ID from that date: phgr-cli catalog list-jobs --client linux-vm --before 2026-02-01
  • Extract the specific file: phgr-cli extract --jobid 4400 --vm "LinuxVM" --path /etc/sudoers --dest /tmp/audit/
  • The audit log records the extraction with operator identity and timestamp, HMAC-chained for tamper evidence
  • Total time: under 2 minutes. No full image restore required

15.3 Instant Recovery for a failed VM

Scenario. A Proxmox VM hosting a critical internal application fails due to storage corruption. The storage team estimates 4 hours to provision replacement storage. Business requires the application to be back online within 30 minutes.

Solution with PHGR.

  • Operator runs: phgr-cli ir start --jobid 5800 --vm 200 --target proxmox/pve --storage local-lvm
  • PHGR creates a new VM on Proxmox with the NBD endpoint as its disk. VM boots in under 60 seconds
  • Background storage migration copies disk blocks from Bacula to local-lvm — transparently, while the VM runs
  • When migration completes, PHGR closes the NBD session; VM is now running fully local
  • Downtime: under 2 minutes (detection + IR start). Application team experiences a brief interruption, not a 4-hour outage

15.4 DR drill with sandbox boot verification

Scenario. A bank requires monthly DR drills proving that VM backups are recoverable. A full restore for every drill is too time-consuming and disruptive to production storage.

Solution with PHGR.

  • Scheduled script runs: phgr-cli verify --jobid &lt;latest&gt; --vm "CoreBankingVM" for each critical VM
  • PHGR boots each VM image in an isolated qemu sandbox (no network), waits for the kernel to load, and records pass/fail with a hash of the boot output
  • Results are written to the audit log and pushed to the monitoring dashboard
  • Total DR drill time: under 30 minutes for 10 VMs, no production storage impact

15.5 Cross-hypervisor migration — Hyper-V to Proxmox

Scenario. An organisation is migrating its virtualisation platform from Microsoft Hyper-V to Proxmox over six months. They want to use existing Bacula backups as the migration source, avoiding the need to run a parallel VM export process.

Solution with PHGR.

  • For each Hyper-V VM: phgr-cli v2v --from "@hyperv/{VMname}" --jobid &lt;latest&gt; --to proxmox/pve --storage local-lvm
  • PHGR converts VHDX to qcow2, translates VMCX config to Proxmox qm config, registers the VM, and optionally boots it for validation
  • Administrators validate the migrated VM before cutover
  • No parallel export infrastructure required; the existing Bacula backup schedule continues unchanged

16. Comparison with other approaches

16.1 Feature comparison

The table below compares PHGR running on Bacula Community against alternative approaches. Bacula Enterprise is included as a reference: it offers solid enterprise backup capabilities and remains a strong choice for organisations already invested in the Bacula Enterprise ecosystem; PHGR delivers Granular Restore, Instant Recovery, and V2V capabilities (several absent from Bacula Enterprise SIR) on the Bacula Community base.

Feature Bacula Community + PHGR Bacula Enterprise SIR Veeam Commvault
Granular file restore from VM image Yes Yes Yes (premium) Yes (premium)
Proxmox / CloudStack / Nutanix GR Yes Limited Limited No
Hyper-V Instant Recovery Yes No Yes Yes
Proxmox Instant Recovery Yes No No No
vSphere Instant Recovery Yes Yes Yes Yes
Cross-hypervisor V2V Yes No No No
Zero full-image staging (GR) Yes No (local cache) No No
NTFS guest filesystem Yes Yes Yes Yes
ext4 / xfs / btrfs / ReFS Yes Yes Yes Yes
HMAC-chained audit log Yes No No No
Malware scan on restore Yes (optional) No No No
Diff between two backup jobs Yes No No No
gRPC + REST API Yes Limited (proprietary) REST API REST API
Prometheus metrics Yes No No No
Rust async (parallel sessions) Yes No (C++ sync) No No
Open-source platform base Yes (Bacula Community) No No No
RPM + DEB packages Yes Yes Yes Yes

16.2 Cost comparison

Special offer. Bring your renewal proposal for Veeam, Commvault, NetBackup, or any other enterprise backup platform. We will produce a written head-to-head proposal targeting at least 50% savings, with stronger PHGR-specific functionality including cross-hypervisor V2V and zero-staging granular restore. Contact heitor@opentechs.lat.

Solution Typical annual cost Granular VM restore
Bacula Community + PHGR Significantly less Full native (GR + IR + V2V)
Bacula Enterprise Often > US$ 10,000/year GR + vSphere IR only
Veeam Data Platform Often > US$ 5,000/year GR + IR (premium tiers)
Commvault Often > US$ 15,000/year GR + IR (expensive stack)
NetBackup Often > US$ 20,000/year GR + IR (enterprise only)

Prices vary by environment size and negotiated contracts. Contact heitor@opentechs.lat for a specific comparison against your current renewal proposal.


17. Roadmap

PHGR is production-ready for Granular Restore (Phases 0–4) and Instant Recovery (Phase 5) with 130 automated test cases. Future development direction includes:

  • Phase 7 — Nutanix AHV + CloudStack + K8s PVC. Instant Recovery for Nutanix AHV and CloudStack via their respective APIs. Kubernetes PVC-aware restore (@k8s/ namespace) for containerised workloads.
  • Phase 8 — DR drill automation. phgr-cli drill --plan dr.yaml orchestrates sequences of IR sessions in an isolated network with automated health checks and a pass/fail report. PHWEB scheduling integration.
  • Phase 9 — Native disk parsers (v2.x). Replace libguestfs with a Rust-native VHDX/VMDK/qcow2 parser (phgr-disk-native cargo feature). Target: 4× GR throughput improvement on large volumes.
  • Native Bacula volume reader (v1.5+). Replace bextract orchestration with a Rust-native Bacula volume reader (phgr-volume::native_reader), enabling random-access parallel reads and eliminating the sequential staging bottleneck.
  • Object storage publishing. Export extracted files directly to S3 or Azure Blob for long-term retention without local staging.
  • AI-assisted file finder. RAG-based natural language queries over backup metadata (UI-only via PHWEB).
  • Multi-architecture support. ARM64 / aarch64 packages for cloud-native SD deployments.
  • Windows SD support. For environments where the Storage Daemon runs on Windows Server.

No specific release dates are committed. Feature direction is guided by customer feedback and lab findings.


18. Conclusion

PodHeitor Granular Restore for Bacula extends Bacula Community with capabilities that, until now, were available only as premium features in expensive commercial backup platforms. Granular file-level restore from VM disk images (VHDX, VMDK, qcow2), Instant Recovery with boot times under 60 seconds, and Cross-Hypervisor V2V are delivered as a single, modular Rust suite with a zero-staging architecture, a tamper-evident audit log, and a gRPC + REST API that integrates natively with Bacularis and PHWEB.

For organisations running Bacula Community with virtualised infrastructure, PHGR closes the last major gap in open-source VM backup: granular access to the contents of image-level backups. A file deleted inside a VM can be recovered in minutes, not hours. A failed VM can be booted from its backup in under a minute. A cross-platform migration can use existing backup jobs as its migration source.

For organisations evaluating commercial platform renewals, the combination of Bacula Community and PHGR delivers equal or superior granular restore functionality — including capabilities absent from Bacula Enterprise SIR and Veeam — at a substantially lower total cost.

To get started:

  • Download the plugin: https://podheitor.com
  • Request a quote or demo: heitor@opentechs.lat
  • Phone / WhatsApp: +1 786 726-1749 | +55 61 98268-4220

19. Contact information

Author Heitor Faria
Website https://podheitor.com
Email heitor@opentechs.lat
Phone / WhatsApp +1 786 726-1749
Phone / WhatsApp (BR) +55 61 98268-4220
Product page https://podheitor.com/granular-restore
Support heitor@opentechs.lat

20. Legal / copyright

© 2026 Heitor Faria — all rights reserved.

PodHeitor Granular Restore for Bacula is proprietary software. Unauthorised copying, distribution, modification, or reverse engineering is strictly prohibited. A commercial license is required for production use.

Bacula® is a registered trademark of Kern Sibbald and the Bacula community. All other trademarks (Veeam, Commvault, NetBackup, VMware, Microsoft Hyper-V, Proxmox, Nutanix) are the property of their respective owners.

This document is provided for informational purposes. Performance figures are from controlled lab measurements and may vary in production environments depending on hardware, network conditions, hypervisor configuration, and backup volume characteristics.

Contact for licensing: heitor@opentechs.lat | https://podheitor.com | +1 786 726-1749 | +55 61 98268-4220


PodHeitor Granular Restore for Bacula (PHGR) — v0.1.0 — © 2026 Heitor Faria — all rights reserved — https://podheitor.com

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

Leave a Reply