<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FBackupSystem</id>
	<title>Services/BackupSystem - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FBackupSystem"/>
	<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/BackupSystem&amp;action=history"/>
	<updated>2026-07-06T06:58:20Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.wilsoz.com/index.php?title=Services/BackupSystem&amp;diff=15&amp;oldid=prev</id>
		<title>Wikibot: Migrated from services/BACKUP_SYSTEM.md</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/BackupSystem&amp;diff=15&amp;oldid=prev"/>
		<updated>2026-07-05T18:16:21Z</updated>

		<summary type="html">&lt;p&gt;Migrated from services/BACKUP_SYSTEM.md&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;As of: 2026-07-05 — migrated from `services/BACKUP_SYSTEM.md`&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
= Backup System — Restic + Timeshift =&lt;br /&gt;
&lt;br /&gt;
Complete setup &amp;amp; operations guide for Stronghold&amp;#039;s dual-tier backup system: Timeshift (local snapshots) + Restic (remote encrypted backups).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Criticality:&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;CRITICAL — The foundation.&amp;#039;&amp;#039;&amp;#039; All disaster recovery depends on backups. Without them, data loss is permanent. This system enables recovery of every service on Stronghold.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Stronghold uses a &amp;#039;&amp;#039;&amp;#039;two-tier backup strategy&amp;#039;&amp;#039;&amp;#039; for redundancy and speed:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Timeshift&amp;#039;&amp;#039;&amp;#039; (local, fast)&lt;br /&gt;
** RSYNC-based filesystem snapshots stored on &amp;lt;code&amp;gt;/raid1/timeshift/&amp;lt;/code&amp;gt;&lt;br /&gt;
** 8 snapshots retained (mix of daily, weekly, monthly)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Purpose:** Fast recovery of individual files or full system (within days)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Speed:** ~5-30 minutes to restore (depends on size)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Limitation:** If &amp;lt;code&amp;gt;/raid1&amp;lt;/code&amp;gt; is damaged, all local snapshots are lost&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Restic&amp;#039;&amp;#039;&amp;#039; (remote, encrypted)&lt;br /&gt;
** Incremental encrypted backups sent to Hetzner Storage Box&lt;br /&gt;
** Daily backups at 03:00 CST, retention: 7 daily + 4 weekly + 6 monthly&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Purpose:** Off-site redundancy; survives hardware failure&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Coverage:** All service data, configs, media, databases&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Speed:** ~30 minutes to backup, 1-2 hours per terabyte for restoration&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;#039;Limitation:** Requires Hetzner SSH access and Restic repo password&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Recovery decision tree:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Drive works, need recent files?&amp;#039;&amp;#039;&amp;#039; → Restore from Timeshift (fast)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Drive works, lost some service?&amp;#039;&amp;#039;&amp;#039; → Restore from Timeshift or Restic (both available)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Drive failed, building new hardware?&amp;#039;&amp;#039;&amp;#039; → Restore from Restic (only source of truth)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Service Information ==&lt;br /&gt;
&lt;br /&gt;
=== Timeshift ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Storage location&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/raid1/timeshift/&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Snapshot mode&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| RSYNC (not btrfs)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Device&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/dev/md1&amp;lt;/code&amp;gt; (RAID-1 array)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Current snapshots&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 8 (mix of daily D, weekly W, monthly M, backfill B)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Retention policy&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Auto: Last 5 daily, 1 weekly, 1 monthly; manual cleanup for older&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Snapshot schedule&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Daily at 00:00 (D), weekly at 10:00 (W), manual on demand&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Excluded from snapshots&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/var/lib/docker/volumes&amp;lt;/code&amp;gt; ⚠️, &amp;lt;code&amp;gt;/proc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/sys&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/tmp&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/var/tmp&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Restic ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Container name&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;restic-backup&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Image&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;restic/restic:latest&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Compose file&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/home/mnw/Developer/SystemResiliency/backup-stack/docker-compose.yml&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Network mode&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;host&amp;lt;/code&amp;gt; (direct network access)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Backup schedule&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 03:00 CST daily (systemd timer &amp;lt;code&amp;gt;backup.timer&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Hetzner server&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;u540853.your-storagebox.de:23&amp;lt;/code&amp;gt; (SSH port 23, not 22)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Repo path&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;sftp:u540853@u540853.your-storagebox.de:./backups&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Encryption&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| AES-256, password-based (stored in &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;HETZNER_PASSWORD&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Deduplication&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Enabled (saves 20-30% space)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Compression&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| zstd (default, good balance)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Retention&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| 7 daily, 4 weekly, 6 monthly snapshots&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Database dumps&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| PostgreSQL dumps staged to &amp;lt;code&amp;gt;/library/backup-stack/dumps/&amp;lt;/code&amp;gt; before backup&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;System-state dump&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;/etc&amp;lt;/code&amp;gt; subset + user crontabs + user/system systemd units + dpkg selections tar&amp;#039;d to &amp;lt;code&amp;gt;/library/backup-stack/dumps/system-state-YYYYMMDD.tar.gz&amp;lt;/code&amp;gt; before backup (covers schedulers/configs that live outside &amp;lt;code&amp;gt;/data/*&amp;lt;/code&amp;gt; and would otherwise be lost if &amp;lt;code&amp;gt;/etc&amp;lt;/code&amp;gt; or a user home got wiped)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Initial Setup from Scratch ==&lt;br /&gt;
&lt;br /&gt;
=== Timeshift (Local Snapshots) ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Timeshift is likely already installed and running on your system.&amp;#039;&amp;#039;&amp;#039; To verify or install:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Check if Timeshift is installed&lt;br /&gt;
which timeshift&lt;br /&gt;
&lt;br /&gt;
# If not, install (Ubuntu/Debian)&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timeshift&lt;br /&gt;
&lt;br /&gt;
# Check current snapshots&lt;br /&gt;
sudo timeshift --list&lt;br /&gt;
&lt;br /&gt;
# Create first snapshot manually (if none exist)&lt;br /&gt;
sudo timeshift --create --comments &amp;quot;Initial snapshot before disaster recovery setup&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Configuration&amp;#039;&amp;#039;&amp;#039; (in Timeshift GUI or config):&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Storage location:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;/raid1/timeshift/&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Device:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;/dev/md1&amp;lt;/code&amp;gt; (RAID-1 array)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Snapshot mode:&amp;#039;&amp;#039;&amp;#039; RSYNC&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Auto-backup:&amp;#039;&amp;#039;&amp;#039; Enabled, daily at 00:00&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Retention:&amp;#039;&amp;#039;&amp;#039; At least 5 daily snapshots kept&lt;br /&gt;
&lt;br /&gt;
=== Restic (Remote Encrypted Backups) ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Set Up Hetzner Storage Box ====&lt;br /&gt;
&lt;br /&gt;
If not already done:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Create Hetzner account&amp;#039;&amp;#039;&amp;#039; at https://www.hetzner.com/storage/storage-box&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Order Storage Box&amp;#039;&amp;#039;&amp;#039; (recommended: smallest size starts at €3.81/month)&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;In Hetzner Console:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Go to Storage Box settings&lt;br /&gt;
** Enable &amp;quot;External reachability&amp;quot; (required for SSH/SFTP)&lt;br /&gt;
** Upload your SSH public key (&amp;lt;code&amp;gt;~/.ssh/id_rsa.pub&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Note your credentials:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Server: &amp;lt;code&amp;gt;u540853.your-storagebox.de&amp;lt;/code&amp;gt; (your number varies)&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; Port: &amp;#039;&amp;#039;&amp;#039;23** (not 22!)&lt;br /&gt;
** Username: &amp;lt;code&amp;gt;u540853&amp;lt;/code&amp;gt; (same as your account number)&lt;br /&gt;
&lt;br /&gt;
==== 2. Set Up SSH Access ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Create SSH key if you don&amp;#039;t have one&lt;br /&gt;
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa&lt;br /&gt;
&lt;br /&gt;
# Test SSH connection to Hetzner (port 23!)&lt;br /&gt;
ssh -p 23 u540853@u540853.your-storagebox.de &amp;quot;echo &amp;#039;SSH working!&amp;#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Should print: SSH working!&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. Copy Backup Stack to /library ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# From your project directory&lt;br /&gt;
cp -r /home/mnw/Developer/SystemResiliency/backup-stack /library/&lt;br /&gt;
&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
&lt;br /&gt;
# Create .env from example&lt;br /&gt;
cp .env.example .env&lt;br /&gt;
&lt;br /&gt;
# Edit with your Hetzner credentials and restic password&lt;br /&gt;
nano .env&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;.env file contents:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Hetzner Storage Box&lt;br /&gt;
HETZNER_REPO=&amp;quot;sftp:u540853@u540853.your-storagebox.de:./backups&amp;quot;&lt;br /&gt;
HETZNER_PASSWORD=&amp;quot;&amp;lt;generate-strong-restic-password-32-chars&amp;gt;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Alerts&lt;br /&gt;
NTFY_TOKEN=&amp;quot;&amp;lt;from Vaultwarden if you have one&amp;gt;&amp;quot;&lt;br /&gt;
NTFY_TOPIC=&amp;quot;backup-alerts&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. Initialize Restic Repository ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Init Hetzner repository (will encrypt with password)&lt;br /&gt;
docker exec -e RESTIC_PASSWORD=&amp;#039;&amp;lt;same-as-HETZNER_PASSWORD&amp;gt;&amp;#039; restic-backup \&lt;br /&gt;
  restic init --repo sftp:u540853@u540853.your-storagebox.de:./backups&lt;br /&gt;
&lt;br /&gt;
# Output should show: &amp;quot;created restic repository&amp;quot; with repository ID&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. Install Systemd Timers ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Copy timer definitions&lt;br /&gt;
sudo cp /library/backup-stack/systemd/*.service /etc/systemd/system/&lt;br /&gt;
sudo cp /library/backup-stack/systemd/*.timer /etc/systemd/system/&lt;br /&gt;
&lt;br /&gt;
# Reload systemd&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
# Enable and start backup timer&lt;br /&gt;
sudo systemctl enable --now backup.timer&lt;br /&gt;
sudo systemctl enable --now backup-validate-daily.timer&lt;br /&gt;
sudo systemctl enable --now backup-validate-weekly.timer&lt;br /&gt;
&lt;br /&gt;
# Check status&lt;br /&gt;
systemctl list-timers | grep backup&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. Test the Backup ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run a manual backup to verify everything works&lt;br /&gt;
docker exec restic-backup /scripts/run-backup.sh&lt;br /&gt;
&lt;br /&gt;
# Watch the logs&lt;br /&gt;
docker logs -f restic-backup&lt;br /&gt;
&lt;br /&gt;
# Should show:&lt;br /&gt;
# - Database dumps created&lt;br /&gt;
# - Restic scanning files&lt;br /&gt;
# - Upload to Hetzner&lt;br /&gt;
# - Success message&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Disaster Mode A: Local Recovery (Fast) ==&lt;br /&gt;
&lt;br /&gt;
=== Scenario: Lost a single file or small data ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example:&amp;#039;&amp;#039;&amp;#039; Accidentally deleted a recipe from Mealie 2 days ago.&lt;br /&gt;
&lt;br /&gt;
==== Recovery Steps: ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. List available snapshots&lt;br /&gt;
sudo timeshift --list&lt;br /&gt;
&lt;br /&gt;
# 2. Mount the snapshot you want to restore from&lt;br /&gt;
# (Example: restore from 2026-05-11 daily snapshot)&lt;br /&gt;
sudo timeshift --mount /run/timeshift/latest&lt;br /&gt;
&lt;br /&gt;
# 3. File is now accessible at:&lt;br /&gt;
#    /run/timeshift/latest/library/mealie-app/mealie-data/recipes/&lt;br /&gt;
# Copy the file back to current system&lt;br /&gt;
cp /run/timeshift/latest/library/mealie-app/mealie-data/recipes/recipe.json \&lt;br /&gt;
   /library/mealie-app/mealie-data/recipes/&lt;br /&gt;
&lt;br /&gt;
# 4. Unmount&lt;br /&gt;
sudo timeshift --umount&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario: Need to restore entire service from snapshot ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example:&amp;#039;&amp;#039;&amp;#039; Immich database corrupted, want to restore from snapshot.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Stop the service&lt;br /&gt;
cd /library/immich-app&lt;br /&gt;
docker compose down&lt;br /&gt;
&lt;br /&gt;
# 2. Mount snapshot&lt;br /&gt;
sudo timeshift --mount&lt;br /&gt;
&lt;br /&gt;
# 3. Restore the entire service directory&lt;br /&gt;
sudo rm -rf /library/immich-app&lt;br /&gt;
sudo cp -r /run/timeshift/latest/library/immich-app /library/&lt;br /&gt;
&lt;br /&gt;
# 4. Fix permissions (Docker runs as specific user)&lt;br /&gt;
sudo chown -R mnw:mnw /library/immich-app&lt;br /&gt;
&lt;br /&gt;
# 5. Restart service&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# 6. Unmount snapshot&lt;br /&gt;
sudo timeshift --umount&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario: Full system restoration (if OS is still usable) ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example:&amp;#039;&amp;#039;&amp;#039; Stronghold is working but some system files are corrupted.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# List snapshots from a few days ago&lt;br /&gt;
sudo timeshift --list&lt;br /&gt;
&lt;br /&gt;
# Restore from a known-good snapshot&lt;br /&gt;
# This restores EVERYTHING except /home, /var/lib/docker, /opt, /etc/hosts, etc.&lt;br /&gt;
sudo timeshift --restore --snapshot &amp;quot;2026-05-11_10-00-01&amp;quot; --skip-grub&lt;br /&gt;
&lt;br /&gt;
# Reboot after restoration&lt;br /&gt;
sudo reboot&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Disaster Mode B: Full Recovery (After Drive Failure) ==&lt;br /&gt;
&lt;br /&gt;
=== Scenario: Hard drive failed, new hardware deployed ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Prerequisites:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* New hardware with OS installed (Ubuntu/Debian)&lt;br /&gt;
* Docker and docker-compose installed&lt;br /&gt;
* SSH key for Hetzner auth&lt;br /&gt;
* Restic password (from your secure notes)&lt;br /&gt;
&lt;br /&gt;
==== Recovery Steps: ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Create directories&lt;br /&gt;
mkdir -p /raid1&lt;br /&gt;
mkdir -p /library&lt;br /&gt;
mkdir -p /home/mnw/Developer/SystemResiliency&lt;br /&gt;
&lt;br /&gt;
# 2. Copy backup-stack to new system&lt;br /&gt;
scp -r username@old-system:/library/backup-stack /library/&lt;br /&gt;
&lt;br /&gt;
# 3. Update .env with correct passwords/credentials&lt;br /&gt;
nano /library/backup-stack/.env&lt;br /&gt;
&lt;br /&gt;
# 4. Start the Restic container&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# 5. List available backups in Hetzner&lt;br /&gt;
docker exec restic-backup restic snapshots --repo sftp:u540853@u540853.your-storagebox.de:./backups&lt;br /&gt;
&lt;br /&gt;
# 6. Restore the full /library directory from latest snapshot&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/restore \&lt;br /&gt;
  --repo sftp:u540853@u540853.your-storagebox.de:./backups \&lt;br /&gt;
  --include &amp;quot;/data/*&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# 7. Copy restored data to /library&lt;br /&gt;
sudo cp -r /tmp/restore/data/* /library/&lt;br /&gt;
&lt;br /&gt;
# 8. Restore /home/mnw&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/restore \&lt;br /&gt;
  --repo sftp:u540853@u540853.your-storagebox.de:./backups \&lt;br /&gt;
  --include &amp;quot;/data/home&amp;quot;&lt;br /&gt;
&lt;br /&gt;
sudo cp -r /tmp/restore/data/home/* /home/&lt;br /&gt;
&lt;br /&gt;
# 9. Fix permissions&lt;br /&gt;
sudo chown -R mnw:mnw /library&lt;br /&gt;
sudo chown -R mnw:mnw /home/mnw&lt;br /&gt;
&lt;br /&gt;
# 10. Restore PostgreSQL databases&lt;br /&gt;
# Each service (Authentik, Nextcloud, etc.) has a dump at /library/backup-stack/dumps/&lt;br /&gt;
# Use the restore procedure from each service&amp;#039;s documentation&lt;br /&gt;
&lt;br /&gt;
# Example: Restore Authentik PostgreSQL&lt;br /&gt;
docker exec authentik-app_postgresql_1 pg_restore \&lt;br /&gt;
  /library/backup-stack/dumps/authentik-YYYYMMDD.sql &amp;lt; &amp;lt; (gunzip -c /path/to/dump)&lt;br /&gt;
&lt;br /&gt;
# 11. Start all services&lt;br /&gt;
for dir in /library/*-app/; do&lt;br /&gt;
  cd &amp;quot;$dir&amp;quot;&lt;br /&gt;
  docker compose up -d&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# 12. Verify critical services are running&lt;br /&gt;
docker ps | grep &amp;quot;server\|worker\|postgres&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario: Restore specific service from Restic ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example:&amp;#039;&amp;#039;&amp;#039; Only Immich needs recovery, not entire system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Restore just Immich config and database dump&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/restore \&lt;br /&gt;
  --repo sftp:u540853@u540853.your-storagebox.de:./backups \&lt;br /&gt;
  --include &amp;quot;/data/immich*&amp;quot; \&lt;br /&gt;
  --include &amp;quot;/dumps/immich*&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Move to correct location&lt;br /&gt;
mv /tmp/restore/data/immich-config /library/immich-app&lt;br /&gt;
mv /tmp/restore/data/immich-photos /raid1/Consolidated/Picture/Immich-Photos&lt;br /&gt;
&lt;br /&gt;
# Restore the database&lt;br /&gt;
cd /library/immich-app&lt;br /&gt;
docker compose up -d immich_postgres&lt;br /&gt;
sleep 10&lt;br /&gt;
docker exec -i immich_postgres psql -U immich &amp;lt; &amp;lt;(gunzip -c /tmp/restore/dumps/immich-*.sql.gz)&lt;br /&gt;
&lt;br /&gt;
# Start full Immich&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Data Locations &amp;amp; Backup Coverage ==&lt;br /&gt;
&lt;br /&gt;
=== What&amp;#039;s Backed Up (via Restic) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Backed up to Hetzner:&lt;br /&gt;
├── /data/authentik/           — Authentik OAuth provider config&lt;br /&gt;
├── /data/vaultwarden/         — Password vault (SQLite + attachments)&lt;br /&gt;
├── /data/nextcloud/           — File sync config&lt;br /&gt;
├── /data/immich-config/       — Photo manager config&lt;br /&gt;
├── /data/immich-photos/       — All photos (149GB)&lt;br /&gt;
├── /data/jellyfin/            — /var/lib/jellyfin (plugins, root, subs; live DBs excluded)&lt;br /&gt;
├── /data/jellyfin-etc/        — /etc/jellyfin (system config)&lt;br /&gt;
├── /data/jellyfin-server/     — /library/jellyfin-server-app (active metadata 28G — see services/JELLYFIN.md)&lt;br /&gt;
├── /data/memos/               — Notes app&lt;br /&gt;
├── /data/linkwarden/          — Bookmark manager&lt;br /&gt;
├── /data/open-webui/          — AI chat interface&lt;br /&gt;
├── /data/matrix-chat/         — Synapse + bridge configs&lt;br /&gt;
├── /data/stalwart/            — Mail server data&lt;br /&gt;
├── /data/snappymail/          — Webmail data&lt;br /&gt;
├── /data/mailcow/             — Legacy mail (to be archived)&lt;br /&gt;
├── /data/developer/           — /home/mnw/Developer (7.9G)&lt;br /&gt;
├── /data/home/                — /home/mnw (selective: excludes .git/objects, node_modules, .venv)&lt;br /&gt;
├── /data/beets/               — Music library DB&lt;br /&gt;
├── /dumps/                    — PostgreSQL database dumps (daily)&lt;br /&gt;
│   ├── authentik-YYYYMMDD.sql.gz&lt;br /&gt;
│   ├── nextcloud-YYYYMMDD.sql.gz&lt;br /&gt;
│   ├── immich-YYYYMMDD.sql.gz&lt;br /&gt;
│   ├── linkwarden-YYYYMMDD.sql.gz&lt;br /&gt;
│   └── matrix-YYYYMMDD.sql.gz&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== What&amp;#039;s NOT Backed Up (Re-acquirable) ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;/var/lib/docker/volumes&amp;lt;/code&amp;gt; ⚠️ (Timeshift excludes this; Restic needs to be updated to include)&lt;br /&gt;
* Jellyfin/Kaleidescape media files (movies, TV)&lt;br /&gt;
* Ollama models (re-downloadable)&lt;br /&gt;
* Steam games&lt;br /&gt;
* VirtualBox VMs&lt;br /&gt;
* node_modules, .venv, .git/objects, build artifacts&lt;br /&gt;
&lt;br /&gt;
=== Timeshift Snapshots ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Stored on /raid1/timeshift/&lt;br /&gt;
├── 2025-05-03_17-15-00  (Backfill from old system)&lt;br /&gt;
├── 2026-05-03_11-52-11  (Backfill)&lt;br /&gt;
├── 2026-05-05_20-41-31  (Backfill)&lt;br /&gt;
├── 2026-05-05_21-36-57  (Backfill)&lt;br /&gt;
├── 2026-05-11_10-00-02  (Monthly snapshot)&lt;br /&gt;
├── 2026-05-12_00-00-01  (Daily snapshot)&lt;br /&gt;
├── 2026-05-12_10-00-01  (Weekly snapshot)&lt;br /&gt;
└── 2026-05-13_00-00-01  (Daily snapshot)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each snapshot contains a full filesystem image (~100-300GB depending on changes).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Critical Gotchas ==&lt;br /&gt;
&lt;br /&gt;
=== 1. ⚠️ Docker Volumes NOT in Timeshift ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Problem:&amp;#039;&amp;#039;&amp;#039; Timeshift is configured to exclude &amp;lt;code&amp;gt;/var/lib/docker/volumes&amp;lt;/code&amp;gt;, which contains:&lt;br /&gt;
* PostgreSQL databases for Authentik, Nextcloud, Immich, Linkwarden, Matrix&lt;br /&gt;
* Docker-managed volumes&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;If you restore from Timeshift, you lose all database data.&amp;#039;&amp;#039;&amp;#039; PostgreSQL will be empty.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Solution:&amp;#039;&amp;#039;&amp;#039; Always use Restic for full recovery (it includes the databases via dumps), or manually restore PostgreSQL dumps.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Status:&amp;#039;&amp;#039;&amp;#039; Known issue, needs fix in Timeshift config or backup-stack dumps.&lt;br /&gt;
&lt;br /&gt;
=== 2. Hetzner Port is 23, NOT 22 ===&lt;br /&gt;
&lt;br /&gt;
Many SSH configs default to port 22. Hetzner requires port 23.&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Wrong:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;ssh u540853@u540853.your-storagebox.de&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Right:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;ssh -p 23 u540853@u540853.your-storagebox.de&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;ssh-config&amp;lt;/code&amp;gt; file in backup-stack handles this automatically.&lt;br /&gt;
&lt;br /&gt;
=== 3. Hetzner Relative Paths Required ===&lt;br /&gt;
&lt;br /&gt;
Hetzner SFTP doesn&amp;#039;t support absolute paths for new directories.&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Wrong:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;sftp:u540853@...:/backups&amp;lt;/code&amp;gt; (fails with SSH_FX_FAILURE)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Right:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;sftp:u540853@....:./backups&amp;lt;/code&amp;gt; (relative to home directory)&lt;br /&gt;
&lt;br /&gt;
=== 4. Restic Password != Hetzner Password ===&lt;br /&gt;
&lt;br /&gt;
Two different passwords:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Hetzner account password:&amp;#039;&amp;#039;&amp;#039; Logs into Hetzner web console&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Restic password:&amp;#039;&amp;#039;&amp;#039; Encrypts your backup repository&lt;br /&gt;
&lt;br /&gt;
Save both securely. If you lose the Restic password, your backups are unrecoverable.&lt;br /&gt;
&lt;br /&gt;
=== 5. SSH Key Permissions in Docker ===&lt;br /&gt;
&lt;br /&gt;
When mounting SSH keys into the Restic container, SSH is strict about ownership (must be 600 or 400).&lt;br /&gt;
&lt;br /&gt;
The docker-compose entrypoint handles this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot;&amp;gt;&lt;br /&gt;
entrypoint: [&amp;quot;/bin/sh&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;mkdir -p /root/.ssh &amp;amp;&amp;amp; cp /tmp/ssh/&amp;#039;&amp;#039; /root/.ssh/ &amp;amp;&amp;amp; chmod 600 /root/.ssh/&amp;#039;&amp;#039; &amp;amp;&amp;amp; sleep infinity&amp;quot;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&amp;#039;t modify without understanding SSH permission requirements.&lt;br /&gt;
&lt;br /&gt;
=== 6. PostgreSQL Dumps Staged Before Backup ===&lt;br /&gt;
&lt;br /&gt;
Database dumps are created at backup time by &amp;lt;code&amp;gt;run-backup.sh&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec &amp;lt;container&amp;gt; pg_dump -U &amp;lt;user&amp;gt; &amp;lt;db&amp;gt; | gzip &amp;gt; /library/backup-stack/dumps/&amp;lt;service&amp;gt;.sql.gz&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a database is corrupted, the dump will fail, and that database won&amp;#039;t be backed up. Check backup logs:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cat /library/backup-stack/dumps/backup-*.log | grep -i &amp;quot;error\|dump&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7. Backup Window (03:00 CST) Must Not Overlap with Other Heavy Operations ===&lt;br /&gt;
&lt;br /&gt;
Backup runs daily at 03:00 CST. If you have other cron jobs or maintenance windows:&lt;br /&gt;
* Stagger them by at least 30 minutes&lt;br /&gt;
* Monitor systemd timers: &amp;lt;code&amp;gt;systemctl list-timers&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Concurrent heavy I/O can cause slowdowns or backup timeouts.&lt;br /&gt;
&lt;br /&gt;
=== 8. Restic Prune Deletes Old Snapshots Automatically ===&lt;br /&gt;
&lt;br /&gt;
Restic &amp;lt;code&amp;gt;prune&amp;lt;/code&amp;gt; runs after backup to clean up old snapshots per retention policy:&lt;br /&gt;
* Keep 7 daily, 4 weekly, 6 monthly&lt;br /&gt;
&lt;br /&gt;
If you manually delete recent snapshots (for testing), they may be pruned. Don&amp;#039;t delete recent snapshots without understanding the implications.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Verification &amp;amp; Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Daily Check: Backup Health ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Check if backup completed successfully&lt;br /&gt;
docker logs restic-backup | tail -20 | grep -i &amp;quot;success\|error&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Check ntfy for backup alerts&lt;br /&gt;
curl https://ntfy.wilsoz.com/backup-alerts&lt;br /&gt;
&lt;br /&gt;
# List recent snapshots&lt;br /&gt;
docker exec restic-backup restic snapshots --limit 5&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Weekly Test: Restore a File ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Restore a small non-critical file to /tmp&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/test-restore \&lt;br /&gt;
  --include &amp;quot;/data/memos/*&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Verify files exist&lt;br /&gt;
ls -la /tmp/test-restore/data/memos/&lt;br /&gt;
&lt;br /&gt;
# Clean up&lt;br /&gt;
rm -rf /tmp/test-restore&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Monthly Test: Restore a Service Database ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Restore Linkwarden dump (small DB, ~50MB)&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/test-restore \&lt;br /&gt;
  --include &amp;quot;/dumps/linkwarden*&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Verify dump is valid&lt;br /&gt;
gunzip -t /tmp/test-restore/dumps/linkwarden-*.sql.gz&lt;br /&gt;
&lt;br /&gt;
# Should report: OK (no errors)&lt;br /&gt;
&lt;br /&gt;
# Clean up&lt;br /&gt;
rm -rf /tmp/test-restore&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Quarterly: Full System Restore Dry-Run ===&lt;br /&gt;
&lt;br /&gt;
Once per quarter, do a mock recovery to verify procedures:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Don&amp;#039;t actually restore (too time-consuming), but verify:&lt;br /&gt;
# 1. Can list snapshots?&lt;br /&gt;
docker exec restic-backup restic snapshots --limit 1&lt;br /&gt;
&lt;br /&gt;
# 2. Can stat the repository (verify password works)?&lt;br /&gt;
docker exec restic-backup restic stats&lt;br /&gt;
&lt;br /&gt;
# 3. Can list files in a snapshot?&lt;br /&gt;
docker exec restic-backup restic ls latest | head -20&lt;br /&gt;
&lt;br /&gt;
# If all three pass, recovery is ready.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Backup Failed ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Check logs:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Container logs&lt;br /&gt;
docker logs restic-backup | tail -50&lt;br /&gt;
&lt;br /&gt;
# Systemd log&lt;br /&gt;
sudo journalctl -u backup.service -n 50&lt;br /&gt;
&lt;br /&gt;
# Backup script log&lt;br /&gt;
ls -lht /library/backup-stack/dumps/backup-*.log&lt;br /&gt;
tail -100 /library/backup-stack/dumps/backup-*.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Common causes:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* SSH key permission error → Run entrypoint again: &amp;lt;code&amp;gt;docker compose up -d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Hetzner unreachable → Test SSH manually: &amp;lt;code&amp;gt;ssh -p 23 u540853@u540853.your-storagebox.de&amp;lt;/code&amp;gt;&lt;br /&gt;
* Database dump failed → Check Docker container: &amp;lt;code&amp;gt;docker ps | grep postgres&amp;lt;/code&amp;gt;&lt;br /&gt;
* Out of disk space → Check /library and /raid1: &amp;lt;code&amp;gt;df -h /library /raid1&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restore Is Slow ===&lt;br /&gt;
&lt;br /&gt;
Restic restores are network-bound from Hetzner. Typical speeds:&lt;br /&gt;
* Small file (&amp;lt; 100MB): 30 seconds&lt;br /&gt;
* Medium service (1-5GB): 5-15 minutes&lt;br /&gt;
* Large dataset (100+ GB): 1-3 hours&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;If slower than expected:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Check network: &amp;lt;code&amp;gt;ping u540853.your-storagebox.de&amp;lt;/code&amp;gt; (port 23 doesn&amp;#039;t reply to ping)&lt;br /&gt;
* Try SSH: &amp;lt;code&amp;gt;ssh -p 23 u540853@u540853.your-storagebox.de echo ok&amp;lt;/code&amp;gt;&lt;br /&gt;
* Check Restic cache: &amp;lt;code&amp;gt;du -sh /library/backup-stack/cache&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Can&amp;#039;t Connect to Hetzner ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Test SSH connectivity&lt;br /&gt;
ssh -p 23 u540853@u540853.your-storagebox.de echo &amp;quot;Connected&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# If failed:&lt;br /&gt;
# 1. Check SSH key exists: ls -la ~/.ssh/id_rsa&lt;br /&gt;
# 2. Check Hetzner console: &amp;quot;External reachability&amp;quot; enabled?&lt;br /&gt;
# 3. Check your public key uploaded to Hetzner&lt;br /&gt;
# 4. Try with password instead: ssh -p 23 -o PubkeyAuthentication=no u540853@u540853.your-storagebox.de&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restic Unlock Stuck ===&lt;br /&gt;
&lt;br /&gt;
If Restic process was killed, lock file may remain:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Remove lock&lt;br /&gt;
docker exec restic-backup restic unlock --repo sftp:u540853@...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Maintenance ==&lt;br /&gt;
&lt;br /&gt;
=== Monthly: Review Snapshot Count ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo timeshift --list&lt;br /&gt;
&lt;br /&gt;
# Should show 5-8 snapshots. If too many:&lt;br /&gt;
sudo timeshift --delete-snapshot &amp;lt;snapshot_name&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# If too few, create one manually:&lt;br /&gt;
sudo timeshift --create --comments &amp;quot;Manual snapshot for recovery testing&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Quarterly: Clean Up Restic Cache ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Cache can grow to 1-2GB over time&lt;br /&gt;
du -sh /library/backup-stack/cache&lt;br /&gt;
&lt;br /&gt;
# Prune cache (safe, will be rebuilt)&lt;br /&gt;
docker exec restic-backup restic cache-dir&lt;br /&gt;
docker exec restic-backup rm -rf /root/.cache/restic/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Annually: Update Restic Image ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
&lt;br /&gt;
# Update to latest Restic&lt;br /&gt;
docker compose pull&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Verify after update&lt;br /&gt;
docker exec restic-backup restic version&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Links &amp;amp; References ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Timeshift Docs:&amp;#039;&amp;#039;&amp;#039; https://github.com/teejee2008/timeshift&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Restic Docs:&amp;#039;&amp;#039;&amp;#039; https://restic.readthedocs.io/&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Hetzner Storage Box:&amp;#039;&amp;#039;&amp;#039; https://www.hetzner.com/storage/storage-box&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Backup Strategy Overview:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;/home/mnw/Developer/SystemResiliency/DISASTER_RECOVERY.md&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Backup Stack Code:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;/home/mnw/Developer/SystemResiliency/backup-stack/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Related Services ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;All services&amp;#039;&amp;#039;&amp;#039; — depend on this backup system for disaster recovery&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;OpenBao&amp;#039;&amp;#039;&amp;#039; — stores Hetzner SSH key and Restic password&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;ntfy&amp;#039;&amp;#039;&amp;#039; — receives backup alerts (success/failure notifications)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;PostgreSQL&amp;#039;&amp;#039;&amp;#039; (in each service) — database dumps backed up daily&lt;/div&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
</feed>