Skip to content

๐Ÿ’พ Automated "Plug-and-Run" Offline Cold Backup Pipeline โ€‹

This runbook documents the fully automated, zero-command offline cold backup architecture for PVE-1 (pve, 192.168.0.100), backing up the Immich photo library and homelab database dumps to a physically isolated 2TB Western Digital hard drive upon hardware insertion.


๐Ÿ›๏ธ Architecture & Operational Workflow โ€‹

The cold backup pipeline implements the final tier of the homelab 3-2-1 backup strategy (air-gapped offline storage resistant to ransomware and physical disasters). The drive remains stored in a drawer until plugged in.


๐Ÿ’ฝ Hardware & Storage Specification โ€‹

PropertyValueNotes
Target HostPVE-1 (pve, 192.168.0.100)Proxmox VE 9.2.11 / Debian 12
Drive ModelWestern Digital 2TB (WDC_WD20SDZW-11JJ8S0)5400 RPM 2.5" HDD
Drive SerialWD-WXL1EC84X4KR (USB: 57584C314543383458344B52)Unique hardware identifier
By-ID Path/dev/disk/by-id/ata-WDC_WD20SDZW-11JJ8S0_WD-WXL1EC84X4KRPartition: ...-part1
Filesystemext4 (GPT)Mount point: /mnt/cold_backup
Source Datasets1. /tank/media_root/backups/
2. /tank/media_root/media/photos/
High-value air-gapped data
Workload HostLXC 131 (docker-media)Immich stack & database backups on mass storage
Telegram Bot@proxmox_pve_botToken: 7245922764:AAGyR...
Home Assistant Webhookhttp://192.168.0.199/api/webhook/cold_backup_statusAutomation: automation.cold_backup_status_via_webhook

โš™๏ธ Key Pipeline Capabilities โ€‹

1. Pre-Flight SMART Drive Health Checks โ€‹

Before starting any rsync operations, the script queries disk hardware status via smartctl:

  • Overall Self-Assessment: Passed vs Failed.
  • Critical Attributes: Reallocated Sector Count (ID 5), Current Pending Sectors (ID 197), Offline Uncorrectable (ID 198).
  • Drive Telemetry: Temperature (ยฐC) and Power-On Hours.
  • Any detected hardware anomalies are immediately flagged in both Telegram notifications and Home Assistant entities.

2. ๐Ÿ›ก๏ธ Bit Rot Protection & Checksum Integrity โ€‹

  • Pre-Sync Verification: Validates existing SHA256 checksums in /mnt/cold_backup/.checksums/ against database dumps and a representative sampling of photos before syncing new data.
  • Post-Sync Generation: Generates updated SHA256 manifests (backups_manifest.sha256, photos_sample.sha256) for newly synced files.
  • Audit Logging: Appends integrity verification results to /mnt/cold_backup/.checksums/bitrot_audit.log.

3. ๐Ÿ  Home Assistant Integration & Dashboard Entities โ€‹

The script dispatches real-time JSON payloads to http://192.168.0.199/api/webhook/cold_backup_status on start, finish, failure, and physical disconnect.

Home Assistant EntityHelper TypePurpose / Value
input_boolean.cold_backup_runninginput_booleanActive execution state (on / off)
input_boolean.cold_backup_drive_connectedinput_booleanPhysical USB drive attached (on / off)
input_boolean.cold_backup_warninginput_booleanRaised on SMART or Bit Rot warnings (on / off)
input_text.cold_backup_statusinput_textHigh-level status text (e.g. Idle (Safe to Unplug), Running)
input_text.cold_backup_drive_serialinput_textConnected drive serial (WD-WXL1EC84X4KR)
input_text.cold_backup_smart_healthinput_textSMART health & temperature (PASSED (31ยฐC))
input_text.cold_backup_bit_rot_statusinput_textIntegrity verification summary (โœ… 18 files verified (0 errors))
input_text.cold_backup_durationinput_textLast backup duration (2m 4s)
input_text.cold_backup_net_changeinput_textStorage change delta (+84.9 GiB)
input_text.cold_backup_free_spaceinput_textFree space remaining on drive (1.7T)
input_datetime.cold_backup_last_completedinput_datetimeTimestamp of last successful backup
automation.homelab_cold_backup_reminderautomationWeekly reminder (Sun 18:00) if backup > 30 days old

4. Drive State Persistence & History Comparison โ€‹

Upon mounting /mnt/cold_backup:

  • Reads .backup_state.json on the drive and calculates elapsed time (e.g. 1d 4h ago).
  • Persists updated metadata JSON on completion and appends to .backup_history.log.

5. Graceful Container Quiescing (LXC 132) โ€‹

  • Gracefully initiates shutdown (pct shutdown 132 --timeout 60).
  • Explicitly polls and verifies the container status reaches status: stopped before synchronization proceeds.
  • Restores Immich (pct start 132) immediately after sync completes.
  • Error traps guarantee container restart even if an unexpected error occurs.

6. ๐Ÿ›ก๏ธ Defense Safeguards (Data Loss & Loop Prevention) โ€‹

  • Guard 1 (Source Sanity Check): Before running rsync, verifies that source directories exist, are readable, and contain at least 3 files. If a source dataset is empty (e.g. from an unmounted mount or accidental deletion), backup is immediately aborted to prevent wiping the destination cold drive.
  • Guard 2 (ZFS Storage Pool Validation): Validates that ZFS pool tank health is ONLINE and dataset tank/media_root is actively mounted (zfs get mounted tank/media_root == yes) before running any operations.
  • Guard 3 (3-Hour Cooldown Safeguard & Insertion Debounce):
    • Insertion Debounce: 10-second debounce lock (/run/cold_backup_insert.lock) prevents USB bounce/flapping triggers when plugging in or wiggling cables.
    • 3-Hour Cooldown: Upon mounting, reads .backup_state.json. If a successful backup was completed within the last 3 hours (< 10,800s), the script sends an informational Telegram & Home Assistant notification, unmounts, spins the drive down into 0 RPM standby (hdparm -y), and exits cleanly without stopping containers or writing data. Overridable with --force or -f.
  • Guard 4 (Max Deletion Cap): Runs a prospective dry-run rsync check to count file deletions. If deletions exceed MAX_DELETION_LIMIT (default 250 files), the backup halts immediately with a critical alert listing a sample of deleted files and providing the exact CLI inspection command (rsync -aAX -n --delete -i ... | grep '^\*deleting'). Overridable with --force-deletion or --max-deletions=N.
  • Guard 5 (Safe Recovery / Restore Mode): Dedicated non-destructive --restore mode that reverses the sync direction (/mnt/cold_backup/ -> /tank/media_root/) without --delete, verifies SHA256 checksums first, and quiesces LXC 132 during photo/backup restoration.

๐Ÿš€ Deployment Instructions (On PVE-1 Host) โ€‹

Run the following commands directly on PVE-1 (192.168.0.100) as root:

bash
# 1. Install prerequisites (if not present)
apt-get update && apt-get install -y rsync curl udisks2 hdparm smartmontools

# 2. Copy script to /usr/local/bin and set permissions
cp scripts/cold_backup.sh /usr/local/bin/cold_backup.sh
chmod 755 /usr/local/bin/cold_backup.sh

# 3. Copy systemd services and reload daemon
cp scripts/systemd/cold-backup.service /etc/systemd/system/cold-backup.service
cp scripts/systemd/cold-backup-unplug.service /etc/systemd/system/cold-backup-unplug.service
systemctl daemon-reload

# 4. Copy udev rules and reload udev
cp scripts/udev/99-cold-backup.rules /etc/udev/rules.d/99-cold-backup.rules
udevadm control --reload-rules && udevadm trigger

# 5. Create mount point directory
mkdir -p /mnt/cold_backup

๐Ÿงช CLI Testing & Operations Reference โ€‹

bash
# Full Production Run (Default behavior upon plug-in)
/usr/local/bin/cold_backup.sh

# Fast Test Mode (Syncs small backup dumps only, skips LXC quiesce, keeps mounted)
/usr/local/bin/cold_backup.sh --test

# Dry Run Simulation (Simulates sync, zero disk writes)
/usr/local/bin/cold_backup.sh --dry-run

# Bypass 3-Hour Cooldown (Force immediate backup run)
/usr/local/bin/cold_backup.sh --force

# Override Deletion Cap (For intentional bulk deletions)
/usr/local/bin/cold_backup.sh --force-deletion

# Restore all targets from cold backup drive to ZFS (/tank/media_root)
/usr/local/bin/cold_backup.sh --restore

# Restore only Immich photos from cold backup drive
/usr/local/bin/cold_backup.sh --restore-photos

# Restore only system and database dumps
/usr/local/bin/cold_backup.sh --restore-backups

๐Ÿ”” Telegram Notifications Reference โ€‹

1. Drive Connected (First Message โ€” Includes Last Backup Time) โ€‹

๐Ÿ”Œ [PVE-1] Cold Backup Drive Connected

๐Ÿ“ฆ Drive: WD 2TB HDD (WD-WXL1EC84X4KR)
โš™๏ธ Mode: PRODUCTION MODE
โฐ Connected At: 2026-10-03 14:00:00 BST
โฎ๏ธ Last Backup: 3 days ago (2026-09-30 18:30:24 BST)
๐Ÿ“ Device: /dev/sde

โณ Running SMART health assessment & bit rot integrity audit...

2. Drive Connected (3-Hour Cooldown Active โ€” Backup Skipped) โ€‹

๐Ÿ”Œ [PVE-1] Cold Backup Drive Connected โ€” Cooldown Active

๐Ÿ“ฆ Drive: WD 2TB HDD (WD-WXL1EC84X4KR)
โฐ Detected At: 2026-10-03 14:00:00 BST
โฎ๏ธ Last Backup: 45 min(s) ago (2026-10-03 13:15:00 BST)
โณ Cooldown: 3-Hour Safeguard Active (2h 15m 0s remaining)

๐Ÿ›‘ Backup skipped to prevent unnecessary duplicate runs. Drive will safely unmount and spin down.
๐Ÿ’ก To override & run anyway: /usr/local/bin/cold_backup.sh --force

3. Pre-Flight Checks Passed โ€” Backup Starting โ€‹

๐Ÿš€ [PVE-1] Pre-Flight Checks Passed โ€” Backup Starting

๐Ÿ“ฆ Drive: WD 2TB HDD (WD-WXL1EC84X4KR)
โš™๏ธ Mode: Full Live Backup
โฐ Detected At: 2026-10-03 14:00:00 BST
โฎ๏ธ Last Backup: 3 days ago (2026-09-30 18:30:24 BST)
๐Ÿฉบ SMART Health: โœ… PASSED (23ยฐC | Power-On: 38919h | Realloc: 0)
๐Ÿ›ก๏ธ Bit Rot Audit: โœ… 240 files verified (0 errors)
๐Ÿ“‚ Backup Targets:
โ€ข ๐Ÿ—„๏ธ System & DB Backups: /tank/media_root/backups
โ€ข ๐Ÿ“ธ Immich Photos: /tank/media_root/media/photos

โณ Quiescing containers & syncing data to cold storage...

4. Backup Completed โ€‹

โœ… [PVE-1] Cold Backup Complete!

๐Ÿ”Œ Drive: WD 2TB HDD (WD-WXL1EC84X4KR)
โฑ๏ธ Total Duration: 2m 4s
๐Ÿฉบ SMART Health: โœ… PASSED (32ยฐC | Realloc: 0)
๐Ÿ›ก๏ธ Bit Rot Integrity: โœ… 18 files verified (0 errors)

๐Ÿ“Š Disk Space & Delta:
โ€ข Before: 1.8T free (520M used / 1%)
โ€ข After: 1.7T free (85.4G used / 5%)
โ€ข Net Change: +84.9 GiB

๐Ÿ“‹ Results:
โ€ข ๐Ÿ—„๏ธ System & DB Backups: โœ… Synced (0m 5s)
โ€ข ๐Ÿ“ธ Immich Photos: โœ… Synced (1m 58s)
โ€ข ๐Ÿ”„ Immich (LXC 132): Quiesced & restored online

๐Ÿ’พ Drive State: Metadata, history & checksum manifests saved to cold storage.
๐Ÿ”’ Safety: Filesystem flushed, cleanly unmounted, and drive spun down.

๐ŸŸข SAFE TO UNPLUG: Safe to disconnect the drive and return to cold storage.

5. Physical Disconnect โ€‹

๐Ÿ”Œ [PVE-1] Cold Backup Drive Unplugged

๐Ÿ“ฆ Drive: WD 2TB HDD (WD-WXL1EC84X4KR)
๐Ÿ”’ Status: Physically disconnected from host USB port.
๐ŸŸข Cold Storage: Drive safely isolated offline.

6. Deletion Guard Alert (Data Protection Triggered) โ€‹

๐Ÿšจ [PVE-1] Cold Backup ABORTED โ€” Deletion Guard Triggered!

โš ๏ธ Target: ๐Ÿ“ธ Immich Photos (/mnt/cold_backup/photos)
๐Ÿ—‘๏ธ Prospective Deletions: 312 files (Safety Limit: 250)
๐Ÿ›ก๏ธ Action: Backup halted immediately to protect cold drive archives.

๐Ÿ“„ Sample Files to be Deleted:
โ€ข photos/2026/08/IMG_0192.jpg
โ€ข photos/2026/08/IMG_0193.jpg
โ€ข photos/2026/08/IMG_0194.jpg
... and 309 more file(s)

๐Ÿ” Inspect All Deleted Files on PVE:
rsync -aAX -n --delete -i --exclude=thumbs/** --exclude=encoded-video/** /tank/media_root/media/photos/ /mnt/cold_backup/photos/ | grep '^\*deleting'

๐Ÿ’ก To Override & Accept Deletions:
/usr/local/bin/cold_backup.sh --force-deletion
Or raise threshold: /usr/local/bin/cold_backup.sh --max-deletions=500


Authoritative operational repository and DR hub.