# Proxmox Backup Server deployment runbook

Stand up PBS-ANA as the fleet's primary vzdump target, then PBS-NH3 as
a DR mirror that pulls nightly from PBS-ANA. Migrate all 5 hypervisors
(pfi-pve, nh3-pve, esh-pve, esh-pve-nas, sfsrv-ana) off their current
local-dump vzdump targets onto PBS-ANA.

Architecture decision + rationale: see `STATUS.md` and the earlier
session transcript. Short version: one primary at ANA, one-way sync to
NH3, ANA owns retention policy.

**Estimated total time: 4–6 hours spread across sittings.** Each phase
below is a self-contained work unit with a clear done-state; you can
stop after any phase without leaving the fleet in a bad state.

## Phase 0 — Prerequisites (one-time, before Phase 1)

### 0.1. Check current NFS exports

```bash
ssh -t 10.250.50.50 'sudo exportfs -v'
```

The existing broad exports (e.g. `/mnt/backup` → `10.0.0.0/8`) cover
pfi-pve but use `root_squash` — which will **block** PBS from writing
its datastore metadata. We need a dedicated, no-squash export scoped
to the PBS-ANA VM's IP.

### 0.2. Create a dedicated export for PBS-ANA

Assign PBS-ANA its LAN IP (let's say `10.250.50.90` — Phase 1.1 uses
this), then on the Debian NAS:

```bash
ssh -t 10.250.50.50 '
  sudo mkdir -p /mnt/backup/pbs-ana &&
  sudo chown root:root /mnt/backup/pbs-ana &&
  sudo chmod 755 /mnt/backup/pbs-ana &&

  echo "/mnt/backup/pbs-ana  10.250.50.90(rw,sync,no_root_squash,no_subtree_check,sec=sys)" \
    | sudo tee /etc/exports.d/pbs-ana.exports > /dev/null &&

  sudo exportfs -ra &&
  sudo exportfs -v | grep pbs-ana
'
```

Should print an `/mnt/backup/pbs-ana` line with `no_root_squash`.

The existing `cockpit-file-sharing.exports` is managed by Cockpit's
File Sharing module; keep the PBS-specific export in a separate file
so Cockpit doesn't clobber it.

### 0.3. Decide datastore paths

- **PBS-ANA datastore path:** `/mnt/backup/pbs-ana/` on the NAS, mounted
  at `/mnt/pbs-datastore/` inside the PBS-ANA VM (Phase 1.3).
- **PBS-NH3 datastore path:** local-only on nh3-pve, mounted at
  `/mnt/pbs-datastore/` inside the PBS-NH3 VM (Phase 5.3).

### 0.4. Side-note: `/mnt/pve-VMStorage` is exported to `<world>`

Not blocking PBS work but worth tightening during a Cockpit pass —
reduce to `10.0.0.0/8` or more specific. Open item, not blocker.

### 0.5. ZFS case-insensitivity breaks NFSv4 writes

The Debian NAS's `/mnt/backup` is a ZFS dataset with
`casesensitivity=insensitive`. ZFS + NFSv4 + case-insensitive dataset
is a known-bad combo: lookups/readdir work, `create`/`write` ops fail
with EACCES even for root with `no_root_squash`.

**Workaround:** mount with NFSv3 instead of NFSv4. Update the VM's
`/etc/fstab` filesystem type to `nfs` and options to `vers=3`. PBS's
access pattern (dedupe chunks are writen-once/read-many) works fine
over NFSv3.

Example `/etc/fstab` line that works:

```
10.250.50.50:/mnt/backup/pbs-ana  /mnt/pbs-datastore  nfs  defaults,_netdev,bg,hard,timeo=600,retrans=2,vers=3  0  0
```

**Cleaner long-term fix** (not required, but nicer): create a
case-sensitive child dataset for PBS's data:

```
sudo zfs create -o casesensitivity=sensitive -o mountpoint=/mnt/backup/pbs-ds \
     NASPool/backupStore/pbs-ds
```

Then export/mount `/mnt/backup/pbs-ds` via NFSv4 as originally planned.
Case-sensitivity can't be changed on an existing dataset, which is why
the parent is stuck on case-insensitive.

## Phase 1 — Provision PBS-ANA (VM on pfi-pve)

**Target state:** a fresh Debian 12 VM on pfi-pve running
`proxmox-backup-server`, with a datastore configured on the NFS-mounted
NAS path. Reachable at `pbs-ana.phasefinal.com` (or by IP).

### 1.1. Create a Debian 12 VM on pfi-pve

Via the pve web UI or CLI. Starting specs:

| Setting | Value |
|---|---|
| Name | `pbs-ana` |
| VMID | next available (probably 114 given pfi-pve's fleet) |
| ISO | debian-12.x.x-amd64-netinst |
| CPU | 4 cores |
| RAM | 8 GB (can be tight at scale; bump if needed) |
| Disk | 32 GB on `local-lvm` (for OS only — datastore is NFS) |
| Network | vmbr0, firewall on |
| Onboot | yes |

Install Debian 12 minimal (no desktop, SSH server, standard system utilities).

Once up, set a static IP in the `servers/` LAN range (e.g.
`10.250.50.90`) or DHCP-reserved for stability.

### 1.2. Install PBS package

SSH in as root:

```bash
# Add the PBS no-subscription repo
cat > /etc/apt/sources.list.d/pbs-no-subscription.list <<'EOF'
deb http://download.proxmox.com/debian/pbs bookworm pbs-no-subscription
EOF

wget https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg \
  -O /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg

apt update
apt full-upgrade -y
apt install -y proxmox-backup-server
```

Reboot once the PBS kernel is installed (PBS kernel is Proxmox's
patched one, like PVE hosts):

```bash
systemctl reboot
```

After reboot, verify the web UI is reachable:

```
https://10.250.50.90:8007/
```

Default login: `root` / (the Debian root password you set at install).

### 1.3. Mount NAS datastore path

On the PBS VM:

```bash
# Create mount point
mkdir -p /mnt/pbs-datastore

# Add persistent NFS mount
cat >> /etc/fstab <<'EOF'
10.250.50.50:/mnt/backup/pbs-ana  /mnt/pbs-datastore  nfs4  defaults,_netdev,bg,hard,timeo=600,retrans=2,vers=4.2  0  0
EOF

# Create the dir on NAS side if it doesn't exist
ssh ana-nas 'mkdir -p /mnt/backup/pbs-ana && chown root:root /mnt/backup/pbs-ana'

# Mount
mount -a
mount | grep pbs-datastore
# Should show the NFS mount active
df -h /mnt/pbs-datastore
```

### 1.4. Create PBS datastore

In the PBS web UI: **Datastore → Add Datastore**:

| Field | Value |
|---|---|
| Name | `backups` |
| Backing Path | `/mnt/pbs-datastore` |
| Retention (can set later) | — |
| GC Schedule | Sunday 06:00 |
| Prune Schedule | Daily 05:30 |

The first time PBS writes to the datastore it initializes `.chunks/`,
`.lock`, etc. Takes a few seconds.

### 1.4b. Create per-hypervisor namespaces

VMIDs are NOT globally unique across PVE hosts (esh-pve and sfsrv-ana
both have VM 100 for instance). Without namespaces, their backups
collide under the same `/vm/100/` path in the datastore. Create a
namespace per hypervisor up front:

`proxmox-backup-manager` doesn't manage namespaces — they're created
through the API/web UI or the `proxmox-backup-client` tool.

**Easiest: web UI.** Datastore → `backups` → Content → top of pane
there's a namespace selector with an **Add NS** button. Add one per
hypervisor.

**Scripted via `proxmox-backup-client`** (run on PBS-ANA):

```bash
export PBS_REPOSITORY='root@pam@localhost:backups'
export PBS_PASSWORD='<root-pam-password>'
for ns in pfi-pve nh3-pve esh-pve esh-pve-nas sfsrv-ana; do
  proxmox-backup-client namespace create "$ns"
done
proxmox-backup-client namespace list
```

**Scripted via API** (when ssh access is more convenient than shell
on PBS-ANA):

```bash
TOKEN='<fleet-vzdump-secret>'
for ns in pfi-pve nh3-pve esh-pve esh-pve-nas sfsrv-ana; do
  curl -sk \
    -H "Authorization: PBSAPIToken=root@pam!fleet-vzdump:$TOKEN" \
    -X POST \
    https://10.250.50.90:8007/api2/json/admin/datastore/backups/namespace \
    -d "{\"ns\":\"$ns\"}"
done
```

Each PVE client later (Phase 2.1, 3.1, 4) sets its Namespace field to
its own hostname when configuring the PBS storage.

### 1.4c. Create a verify job

New snapshots land unverified — PBS treats "backup completed" and
"backup verified" as separate states. A verify job hash-checks chunk
data (not just the manifest), flips snapshots to verified, and catches
bitrot on aging data.

Web UI: **Datastore → backups → Verify Jobs → Add**:

| Field | Value |
|---|---|
| Schedule | `sat 23:00` |
| Ignore verified snapshots | ✓ |
| Re-verify after (days) | `30` |
| Max depth | blank (unlimited) |
| Namespace | blank (root — recurses into all namespaces) |
| Comment | `fleet verify — new + 30d re-check` |

Rationale for these defaults:

- **`sat 23:00`** — sits between the daily 03:00 backup window and the
  Sunday 06:00 GC run, so verify never fights GC for I/O, and every
  week's new backups get verified before pruning decisions happen.
- **`ignore-verified: true`** + **`outdated-after: 30d`** — efficient
  steady state. First run after a backup night does the new snapshots
  only; a 30-day rolling re-verify catches silent chunk corruption.
- **unlimited depth, root namespace** — one job covers all 5
  hypervisor namespaces. Split into per-namespace jobs only if you
  want per-hypervisor visibility into verify failures (not necessary
  for a fleet this size).

Verify runs are I/O-heavy on the datastore — on the NFS-backed
PBS-ANA, expect a full-datastore verify to take hours once the
datastore grows. The `ignore-verified` flag keeps incremental verify
cheap; only the 30-day-aged portion is re-read each run.

### 1.5. Create an API token for hypervisors to use

PBS web UI: **Configuration → Access Control → API Token → Add**:

| Field | Value |
|---|---|
| User | `root@pam` |
| Token Name | `fleet-vzdump` |
| Privilege Separation | **off** (so the token inherits root perms) |

Save the token secret — this is the only time PBS shows it. Store in
your password manager alongside the other fleet creds.

Token identifier looks like: `root@pam!fleet-vzdump`
Secret looks like: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`

### 1.6. Note PBS fingerprint

For client connections:

```bash
# On PBS VM
proxmox-backup-manager cert info | grep -i fingerprint
```

Save this fingerprint — each PVE client needs it to verify PBS's cert.

### Phase 1 done-state

- PBS-ANA reachable at `https://<ip>:8007/`
- Datastore `backups` created and healthy
- API token `root@pam!fleet-vzdump` issued
- Cert fingerprint recorded

## Phase 2 — Migrate pfi-pve's vzdump jobs onto PBS-ANA

**Target state:** pfi-pve sends its nightly backups to PBS-ANA.

### 2.1. Add PBS-ANA as storage on pfi-pve

Web UI: **Datacenter → Storage → Add → Proxmox Backup Server**:

| Field | Value |
|---|---|
| ID | `pbs-ana` |
| Server | PBS-ANA IP |
| Username | `root@pam!fleet-vzdump` |
| Password | the token secret from 1.5 |
| Datastore | `backups` |
| **Namespace** | **`pfi-pve`** (match the hypervisor name — see 1.4b) |
| Fingerprint | from 1.6 |
| Content | VZDump backup file |
| Nodes | `pve` (pfi-pve) |

Click Add. Storage should show as available within seconds.

> **Critical:** every hypervisor gets its own namespace value matching
> its own name — `nh3-pve`, `esh-pve`, `esh-pve-nas`, `sfsrv-ana`.
> Prevents VMID collisions (not-globally-unique VMIDs would otherwise
> land in the same `/vm/<vmid>/` path).

### 2.2. Test backup of one VM

Pick a small VM (e.g. an idle LXC like ana-filebot, VMID 112). Web UI:
**select VM → Backup → Backup Now → Storage: pbs-ana**.

First backup is a full dump. For a small LXC, a few minutes. Verify:
- Backup completes with green OK status
- In PBS UI: **Datastore → backups** shows the snapshot under
  `ct/112/` or `vm/112/`

### 2.3. Migrate the scheduled backup job

Web UI: **Datacenter → Backup**. Edit the existing pfi-pve backup job
(`backup-4b911ca7-92df` from the audit).

| Change | From | To |
|---|---|---|
| Storage | `pve-truenas` (or current) | `pbs-ana` |
| Mode | snapshot | snapshot |
| Compression | zstd | zstd |

Save. Next scheduled run (03:00 by default) fires against PBS-ANA.

### 2.4. Run it manually to verify

Web UI: **backup job → Run Now**. Watch each VM's backup complete.
Larger VMs (PFI-Postgres 8GB RAM, PFI-Mongo, etc.) take 10-30 minutes
each on first full backup. Subsequent backups will be minutes due to
dirty-bitmap incrementals.

### 2.5. Keep legacy target for overlap

**Don't delete the old `pve-truenas` backup storage yet.** Wait ~1 week
of successful PBS runs before retiring it. Double-coverage period is
cheap insurance.

### Phase 2 done-state

- pfi-pve's 11 guests have first PBS snapshots
- Scheduled job uses PBS-ANA
- Old `pve-truenas` target still exists (for safety) but no new jobs
  write to it

## Phase 3 — Onboard esh-pve, esh-pve-nas, sfsrv-ana

**Target state:** all three remaining ANA/ESH hypervisors use PBS-ANA.

### 3.1. Repeat Phase 2.1 on each hypervisor

On each of `esh-pve`, `esh-pve-nas`, `sfsrv-ana`:

- Web UI: Datacenter → Storage → Add → Proxmox Backup Server (same
  fields as Phase 2.1; same token is fine — it's fleet-wide).

### 3.2. Configure vzdump jobs

`esh-pve` and `esh-pve-nas` already have vzdump jobs hitting local
dump dirs. Edit them per Phase 2.3.

`sfsrv-ana` has **no existing vzdump jobs** (per audit). Create one:

Web UI: Datacenter → Backup → Add:

| Field | Value |
|---|---|
| Storage | `pbs-ana` |
| Schedule | `03:00` (daily) |
| Selection | All |
| Retention | keep-daily 7, weekly 4, monthly 12, yearly 3 |
| Compression | zstd |
| Mode | snapshot |
| Email | your address, notifications on failure |

### 3.3. First backups

Fire manual runs from each hypervisor. ESH site backups cross-WAN to
ANA — first full dumps will take a while. Schedule overnight if
bandwidth is an issue during the day.

### 3.4. Verify on PBS-ANA side

Web UI on PBS: **Datastore → backups → Content**. Should show
namespaces / groups for guests from all 4 hypervisors by hostname-ish
prefixes (PBS uses VMID + node).

### Phase 3 done-state

- All 3 hypervisors have working PBS backups
- sfsrv-ana specifically goes from ZERO backup coverage to full
  vzdump coverage (biggest single gain of this migration)

## Phase 4 — Onboard nh3-pve

**Target state:** nh3-pve uses PBS-ANA as backup target despite the
WAN hop.

Same process as Phase 3. The only extra considerations:

- **Bandwidth:** first full backups of NH3 VMs cross the WAN to ANA.
  Estimate size and run during off-hours. Incrementals after that
  should be small.
- **Verify WAN stability:** if the link flaps during a backup, PBS
  retries automatically but a multi-hour drop will fail. Don't start
  a 50 GB backup during known-unstable windows.

### Phase 4 done-state

- All 5 hypervisors back up to PBS-ANA
- Verified: ~24 guests total across the fleet, all with recent
  PBS snapshots

## Phase 5 — Provision PBS-NH3 (DR mirror)

**Target state:** PBS-NH3 running on nh3-pve, ready to pull from
PBS-ANA.

### 5.1. Create Debian VM on nh3-pve

Same pattern as Phase 1.1:

| Setting | Value |
|---|---|
| Name | `pbs-nh3` |
| VMID | next available on nh3-pve |
| CPU | 2 cores |
| RAM | 4 GB |
| Disk | 32 GB OS + data disk (see 5.3) |
| Network | vmbr0 |

### 5.2. Install PBS

Same as Phase 1.2.

### 5.3. Datastore backing — Synology NFS (chosen 2026-04-22)

PBS-NH3 mounts a Synology NFS share rather than using a local virtual
disk. Simpler storage admin; tradeoff is that PBS-NH3 now shares its
failure domain with the other NH3 backup paths (nh3-docker restic,
nh3-dev restic, Backrest repos). Acceptable for DR purposes because
PBS-ANA remains primary.

**Synology-side export setup (DSM):**

1. Create a dedicated share, e.g. `pbs-nh3`, on the target volume.
2. **Control Panel → Shared Folder → [share] → Edit → NFS Permissions → Add/Edit:**
   | Field | Value |
   |---|---|
   | Hostname/IP | PBS-NH3 VM IP |
   | Privilege | Read/Write |
   | Squash | **No mapping** (Synology's label for `no_root_squash`) |
   | Security | sys |
   | Enable asynchronous | on |
   | Allow connections from non-privileged ports | on |

3. Verify no **Advanced Permissions** ACL denies `root` write — those
   override NFS perms and cause silent write failures.

**Client-side mount (on PBS-NH3 VM):**

Use **NFSv3**, not NFSv4 — see the gotcha below. Synology's "Advanced
Permissions" layer an NFSv4 ACL on top of POSIX mode that is invisible
to `ls` but denies writes to unprivileged users (including the
`backup` uid-34 that PBS runs as), even when the directory mode is
777. Root bypasses this via `no_root_squash`, which is why a root
`touch` succeeds but the datastore init fails.

```bash
mkdir -p /mnt/pbs-datastore

cat >> /etc/fstab <<'EOF'
10.100.50.50:/volume1/pbs-nh3  /mnt/pbs-datastore  nfs  defaults,_netdev,bg,hard,timeo=600,retrans=2,vers=3  0  0
EOF

mount -a
df -h /mnt/pbs-datastore

# Two-layer smoke test: root AND the backup user that PBS runs as.
# The backup-user check is the one that actually matters — if root
# works but backup doesn't, you're hitting the ACL override.
touch /mnt/pbs-datastore/root-write-test && rm /mnt/pbs-datastore/root-write-test
sudo -u backup touch /mnt/pbs-datastore/backup-user-test && rm /mnt/pbs-datastore/backup-user-test
```

Ensure NFSv3 is reachable on the Synology side: **Control Panel →
File Services → NFS → Advanced** — confirm "Minimum NFS Protocol" is
3 (not 4.0+). Default is usually 3; only an issue if someone
hardened it previously.

**Fallback if NFSv3 still fails the backup-user check:** the deny is
a Synology-local syno_acl (`ls -la` on the Synology shows POSIX mode
`d---------+` with a `+` for extended ACL). DSM "Enable Advanced
Permissions" unchecked does NOT reset this once the archive flag
`has_ACL,is_support_ACL` is set on the share.

Diagnose from the Synology shell:

```
sudo synoacltool -get /volume1/<share>
sudo ls -la /volume1/<share>/
```

A `+` after the permission string + a `group:administrators:allow:...`
ACL entry + POSIX `d---------` is the smoking gun: only members of
the `administrators` group have access, which is why root (with
`no_root_squash`) writes but uid-34 (backup) doesn't.

**Fix (confirmed working 2026-04-22):** keep **Squash: `No mapping`**
(i.e. `no_root_squash`) AND flatten the share to Linux/POSIX mode
with `chmod 777`. This drops the syno_acl entirely — verifiable by
`synoacltool -get` returning "It's Linux mode" and `ls -la`
showing `drwxrwxrwx` with NO trailing `+`.

On the Synology shell:

```bash
sudo chmod 777 /volume1/<share>

# Verify pure POSIX, no ACL
sudo synoacltool -get /volume1/<share>   # should say "It's Linux mode"
ls -la /volume1/<share>/                 # should show drwxrwxrwx (no '+')
```

Pure POSIX 777 is a cleaner long-term config than the ACL-grant
approach — fewer permission-translation layers between NFSv3 and
Btrfs, and no chance of ACL inheritance surprises on PBS-created
subdirectories.

**Why `all_squash` alone doesn't work:** it lets backup-user writes
through (because the ACL-granted admin gets the mapped uid), but
breaks PBS's `chown()` during init. Squashed-admin doesn't have
CAP_CHOWN on the Synology side → EPERM. Only real root (via
`no_root_squash`) can chown to uid 34.

**Why squash alone fails:** even with `all_squash + anonuid=1024`
letting backup-user writes succeed (because admin is in
`administrators` ACL), PBS's datastore-init calls `chown` on
newly-created paths. Squashed-admin doesn't have CAP_CHOWN on the
Synology side → EPERM. Only a real root (via `no_root_squash`) can
chown to uid 34.

**Smoke-test all three paths after the fix:**

```bash
sudo touch /mnt/pbs-datastore/t && rm /mnt/pbs-datastore/t && echo ROOT_OK
sudo -u backup touch /mnt/pbs-datastore/t && rm /mnt/pbs-datastore/t && echo BACKUP_OK
touch /mnt/pbs-datastore/t && chown 34:34 /mnt/pbs-datastore/t && rm /mnt/pbs-datastore/t && echo CHOWN_OK
```

All three must pass before PBS will init cleanly.

**Historical note:** both PBS instances ended up on NFSv3 for
unrelated reasons — the ANA side because of ZFS case-insensitivity,
the NH3 side because of Synology ACL override. NFSv3 is the safer
default for PBS-on-NFS regardless of backend.

### 5.4. Create datastore

Web UI: Datastore → Add, name `backups-mirror`, path `/mnt/pbs-datastore`.

### 5.5. Create a verify job on the mirror

Same pattern as Phase 1.4c on PBS-ANA — verification doesn't replicate
across PBS instances, so the mirror needs its own job to catch bitrot
on the local datastore disk.

Web UI: **Datastore → backups-mirror → Verify Jobs → Add**:

| Field | Value |
|---|---|
| Schedule | `sun 12:00` |
| Ignore verified snapshots | ✓ |
| Re-verify after (days) | `30` |
| Max depth | blank (unlimited) |
| Namespace | blank |
| Comment | `mirror verify — catches DR-side bitrot` |

Schedule sits after the 06:00 sync job finishes, so newly-synced
snapshots get verified same day.

### Phase 5 done-state

- PBS-NH3 reachable, datastore ready
- No sync job yet — that's Phase 6

## Phase 6 — Sync PBS-ANA → PBS-NH3

**Target state:** PBS-NH3 nightly pulls everything from PBS-ANA.

### 6.1. Create a remote entry on PBS-NH3

PBS-NH3 web UI: **Configuration → Remotes → Add**:

| Field | Value |
|---|---|
| Remote ID | `pbs-ana` |
| Host | PBS-ANA IP |
| User ID | `root@pam!fleet-vzdump` (or a dedicated read-only token — see 6.3) |
| Password | token secret |
| Fingerprint | PBS-ANA's cert fingerprint |

### 6.2. Create a sync job

Web UI: **Datastore → backups-mirror → Sync Jobs → Add**:

| Field | Value |
|---|---|
| Remote | `pbs-ana` |
| Remote Datastore | `backups` |
| Sync Direction | Pull |
| Schedule | `*-*-* 06:00:00` (after primary backups at 03:00) |
| Max Depth | unlimited |
| Owner | `root@pam` |

Save. Run once manually to confirm.

First run transfers everything the primary has. This is one-time heavy
— schedule on a weekend if bandwidth is a concern.

### 6.3. (Optional) tighten the token

The `fleet-vzdump` token has write permissions on PBS-ANA. For
sync-only access, create a second read-only token on PBS-ANA
(`Datastore.Audit` + `Remote.Audit`) and use that on the PBS-NH3
remote config. Reduces blast radius if PBS-NH3 ever compromised.

### 6.4. Set retention on PBS-NH3

Shorter than primary (it's DR, not archival):

Web UI: Datastore → backups-mirror → Prune & GC → Edit:

| Field | Value |
|---|---|
| keep-last | 7 |
| keep-daily | 7 |
| keep-weekly | 4 |

### Phase 6 done-state

- Nightly pull from PBS-ANA → PBS-NH3 runs cleanly
- PBS-NH3 holds last 7 days of backups (shorter retention, DR focus)

## Phase 7 — Burn-in

**Target state:** ~1 week of clean operation before retiring legacy targets.

Daily checks:

```bash
# PBS-ANA datastore growth is sensible
ssh root@pbs-ana 'df -h /mnt/pbs-datastore'

# PBS-NH3 sync job succeeded overnight
ssh root@pbs-nh3 'journalctl -u proxmox-backup-proxy --since yesterday | grep -i sync'

# Every hypervisor's last backup is ≤24h old
# (Run refresh-proxmox-info.sh and inspect the backup-coverage section)
scripts/refresh-proxmox-info.sh all
```

Watch for:

- Failed backup jobs (email alerts if enabled)
- Sync job duration growing unexpectedly (bandwidth issue)
- Datastore approaching capacity (bump retention or storage)
- VMs that don't dirty-bitmap incremental (missing QEMU Guest Agent,
  shut-down VMs always do full dumps)

## Phase 8 — Retire legacy vzdump targets

**Target state:** no more old-style local-dump backups running; just PBS.

Only after Phase 7 passes:

- On each hypervisor: remove old vzdump jobs that targeted local dump
  dirs.
- Optionally delete old VMA files from local dump storage to reclaim
  space (before deleting, verify PBS has the same vintage).
- Update `servers/*-pve/proxmox-details.txt` by re-running
  `scripts/refresh-proxmox-info.sh all` — the `backup coverage` section
  should now show every guest covered by the PBS-ANA job.

## Phase 9 — Update this workspace

- Update `STATUS.md` coverage table to show PBS deployment state.
- Update memory `project_backup_pipeline_gaps.md` — PBS closes several
  of the open gaps.
- Update `README.md` backup pipeline section to describe the new
  topology.
- Add an entry for PBS-ANA and PBS-NH3 in `servers/` (with README +
  ssh-target). They're first-class infrastructure now.
- Homepage Infra-ANA card pointing at PBS-ANA Web UI; Infra-NH3 for
  PBS-NH3.

## Rollback posture

At any phase, you can stop and leave the fleet in its current state:

- Phase 1 incomplete: no impact, PBS-ANA just sits idle.
- Phase 2+ incomplete: affected hypervisors still have their old
  vzdump jobs running. Until Phase 8, old targets are kept as
  redundant. Rolling back one hypervisor means deleting its
  PBS storage entry and re-enabling its old job.
- Phase 6+ incomplete: PBS-ANA still provides primary backup coverage.
  DR mirror just isn't built yet.
