<?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=Identity%2FAuthentikMatrix</id>
	<title>Identity/AuthentikMatrix - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Identity%2FAuthentikMatrix"/>
	<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Identity/AuthentikMatrix&amp;action=history"/>
	<updated>2026-07-06T07:05:22Z</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=Identity/AuthentikMatrix&amp;diff=26&amp;oldid=prev</id>
		<title>Wikibot: Migrated from INFRASTRUCTURE_INVENTORY.md auth matrix</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Identity/AuthentikMatrix&amp;diff=26&amp;oldid=prev"/>
		<updated>2026-07-05T18:19:18Z</updated>

		<summary type="html">&lt;p&gt;Migrated from INFRASTRUCTURE_INVENTORY.md auth matrix&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 INFRASTRUCTURE_INVENTORY.md&amp;#039;s Authentication Matrix section&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
= How the Backup System Works =&lt;br /&gt;
&lt;br /&gt;
A beginner-friendly guide to understanding your new backup system.&lt;br /&gt;
&lt;br /&gt;
== The Big Picture ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Your Server (stronghold)&lt;br /&gt;
         │&lt;br /&gt;
         │ 1. Dump databases to files&lt;br /&gt;
         ▼&lt;br /&gt;
   ┌─────────────┐&lt;br /&gt;
   │ PostgreSQL  │──► .sql.gz files&lt;br /&gt;
   │ Containers  │    (in /library/backup-stack/dumps/)&lt;br /&gt;
   └─────────────┘&lt;br /&gt;
         │&lt;br /&gt;
         │ 2. Restic reads all your data&lt;br /&gt;
         ▼&lt;br /&gt;
   ┌─────────────┐&lt;br /&gt;
   │   Restic    │──► Deduplicates, compresses, encrypts&lt;br /&gt;
   │  Container  │&lt;br /&gt;
   └─────────────┘&lt;br /&gt;
         │&lt;br /&gt;
         │ 3. Uploads to cloud storage&lt;br /&gt;
         ▼&lt;br /&gt;
   ┌─────────────┐&lt;br /&gt;
   │   Hetzner   │  Your encrypted backups live here&lt;br /&gt;
   │ Storage Box │  (in Germany, €3.81/month)&lt;br /&gt;
   └─────────────┘&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== What Gets Backed Up ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! What&lt;br /&gt;
! Where It Lives&lt;br /&gt;
! Size&lt;br /&gt;
! Why It Matters&lt;br /&gt;
|-&lt;br /&gt;
| Authentik DB&lt;br /&gt;
| PostgreSQL dump&lt;br /&gt;
| 14MB&lt;br /&gt;
| Your SSO - lose this, lose all logins&lt;br /&gt;
|-&lt;br /&gt;
| Vaultwarden&lt;br /&gt;
| SQLite file&lt;br /&gt;
| 356KB&lt;br /&gt;
| Your passwords!&lt;br /&gt;
|-&lt;br /&gt;
| Immich photos&lt;br /&gt;
| /raid1/.../Immich-Photos&lt;br /&gt;
| 149GB&lt;br /&gt;
| Family photos&lt;br /&gt;
|-&lt;br /&gt;
| Immich DB&lt;br /&gt;
| PostgreSQL dump&lt;br /&gt;
| 437MB&lt;br /&gt;
| Photo metadata, faces, albums&lt;br /&gt;
|-&lt;br /&gt;
| Jellyfin&lt;br /&gt;
| /var/lib/jellyfin&lt;br /&gt;
| 92GB&lt;br /&gt;
| Watch history, metadata&lt;br /&gt;
|-&lt;br /&gt;
| Nextcloud&lt;br /&gt;
| Config + PostgreSQL&lt;br /&gt;
| ~1GB&lt;br /&gt;
| Files, calendar, contacts&lt;br /&gt;
|-&lt;br /&gt;
| Open-WebUI&lt;br /&gt;
| SQLite + uploads&lt;br /&gt;
| 1.7GB&lt;br /&gt;
| AI chat history&lt;br /&gt;
|-&lt;br /&gt;
| Memos, Linkwarden, Mailcow&lt;br /&gt;
| Various&lt;br /&gt;
| Small&lt;br /&gt;
| Notes, bookmarks, email&lt;br /&gt;
|-&lt;br /&gt;
| MediaWiki&lt;br /&gt;
| Config bind mount + MariaDB dump + images volume&lt;br /&gt;
| ~300KB&lt;br /&gt;
| The wilsoz wiki — infra docs, runbooks, ADRs&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The Components ==&lt;br /&gt;
&lt;br /&gt;
=== 1. Restic Container (&amp;lt;code&amp;gt;restic-backup&amp;lt;/code&amp;gt;) ===&lt;br /&gt;
A small container that knows how to:&lt;br /&gt;
* Read your data from mounted directories&lt;br /&gt;
* Encrypt everything with your password&lt;br /&gt;
* Upload to Hetzner via SFTP&lt;br /&gt;
* Deduplicate (only upload changed data)&lt;br /&gt;
&lt;br /&gt;
=== 2. Database Dump Script (&amp;lt;code&amp;gt;host-dump-databases.sh&amp;lt;/code&amp;gt;) ===&lt;br /&gt;
Runs on the host (not in container) because it needs Docker access.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
authentik-postgresql → authentik-20260201.sql.gz&lt;br /&gt;
nextcloud-db         → nextcloud-20260201.sql.gz&lt;br /&gt;
immich_postgres      → immich-20260201.sql.gz&lt;br /&gt;
linkwarden_postgres  → linkwarden-20260201.sql.gz&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3. Backup Script (&amp;lt;code&amp;gt;backup-to-hetzner.sh&amp;lt;/code&amp;gt;) ===&lt;br /&gt;
Runs inside the container:&lt;br /&gt;
# Connects to Hetzner via SSH (port 23, key auth)&lt;br /&gt;
# Reads all your data directories&lt;br /&gt;
# Uploads new/changed files&lt;br /&gt;
# Prunes old snapshots (keeps 7 daily, 4 weekly, 6 monthly)&lt;br /&gt;
&lt;br /&gt;
=== 4. Systemd Timers ===&lt;br /&gt;
Schedules everything to run automatically:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;3:00 AM&amp;#039;&amp;#039;&amp;#039; - Full backup&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;6:00 AM&amp;#039;&amp;#039;&amp;#039; - Integrity check&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Sunday 7:00 AM&amp;#039;&amp;#039;&amp;#039; - Restore test&lt;br /&gt;
&lt;br /&gt;
== How Restic Works (The Magic) ==&lt;br /&gt;
&lt;br /&gt;
=== Deduplication ===&lt;br /&gt;
Restic breaks files into chunks. If you have 1000 photos and add 1 new one:&lt;br /&gt;
* First backup: uploads all 1000 photos (~149GB)&lt;br /&gt;
* Second backup: uploads only the 1 new photo (~5MB)&lt;br /&gt;
&lt;br /&gt;
=== Encryption ===&lt;br /&gt;
Everything is encrypted with your password before leaving your server.&lt;br /&gt;
* Hetzner can&amp;#039;t read your data&lt;br /&gt;
* Without the password, backups are useless&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Password:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;E+mVDSjj8VsB5Feklelsq/BAJ3FmMX3E&amp;lt;/code&amp;gt; (stored in .env)&lt;br /&gt;
&lt;br /&gt;
=== Snapshots ===&lt;br /&gt;
Each backup creates a &amp;quot;snapshot&amp;quot; - a point-in-time view of your data.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
$ restic snapshots&lt;br /&gt;
ID        Time                 Host        Tags&lt;br /&gt;
─────────────────────────────────────────────────&lt;br /&gt;
a1b2c3d4  2026-02-01 23:50    stronghold  full&lt;br /&gt;
e5f6g7h8  2026-02-02 03:00    stronghold  full&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can restore any snapshot, not just the latest.&lt;br /&gt;
&lt;br /&gt;
== How to Check on Backups ==&lt;br /&gt;
&lt;br /&gt;
=== Is the backup running? ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker logs restic-backup&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== How big is my backup repo? ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup restic stats&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List all snapshots ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup restic snapshots&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Check repo integrity ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup restic check&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How to Restore ==&lt;br /&gt;
&lt;br /&gt;
=== Restore everything (disaster recovery) ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup restic restore latest --target /tmp/full-restore&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restore one file ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup restic restore latest \&lt;br /&gt;
  --target /tmp/restore \&lt;br /&gt;
  --include &amp;quot;/data/vaultwarden/data/db.sqlite3&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restore from specific date ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# List snapshots to find the ID&lt;br /&gt;
docker exec restic-backup restic snapshots&lt;br /&gt;
&lt;br /&gt;
# Restore that snapshot&lt;br /&gt;
docker exec restic-backup restic restore a1b2c3d4 --target /tmp/restore&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Important Files ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
/library/backup-stack/&lt;br /&gt;
├── docker-compose.yml    # Container definition&lt;br /&gt;
├── .env                  # Passwords and settings (SECRET!)&lt;br /&gt;
├── ssh-config            # SSH settings for Hetzner&lt;br /&gt;
├── dumps/                # Database dumps live here&lt;br /&gt;
│   ├── authentik-20260201.sql.gz&lt;br /&gt;
│   ├── immich-20260201.sql.gz&lt;br /&gt;
│   └── ...&lt;br /&gt;
├── scripts/&lt;br /&gt;
│   ├── host-dump-databases.sh  # Dumps DBs (runs on host)&lt;br /&gt;
│   ├── backup-to-hetzner.sh    # Main backup (runs in container)&lt;br /&gt;
│   └── validate-backups.sh     # Tests backups&lt;br /&gt;
└── systemd/              # Timer definitions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== What Happens If... ==&lt;br /&gt;
&lt;br /&gt;
=== Power goes out during backup? ===&lt;br /&gt;
Restic is crash-safe. Next backup continues from where it left off.&lt;br /&gt;
&lt;br /&gt;
=== Hetzner goes down? ===&lt;br /&gt;
Your local data is fine. Backups resume when Hetzner is back.&lt;br /&gt;
&lt;br /&gt;
=== You delete a file by accident? ===&lt;br /&gt;
Restore it from a previous snapshot.&lt;br /&gt;
&lt;br /&gt;
=== Your server dies completely? ===&lt;br /&gt;
# Get new server&lt;br /&gt;
# Install Docker&lt;br /&gt;
# Connect to Hetzner&lt;br /&gt;
# Run &amp;lt;code&amp;gt;restic restore latest&amp;lt;/code&amp;gt;&lt;br /&gt;
# Rebuild containers with restored data&lt;br /&gt;
&lt;br /&gt;
=== You forget the password? ===&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Game over.&amp;#039;&amp;#039;&amp;#039; The backups are encrypted. No password = no data.&lt;br /&gt;
Save it somewhere safe (Vaultwarden, password manager, printed paper in safe).&lt;br /&gt;
&lt;br /&gt;
== Costs ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Item&lt;br /&gt;
! Cost&lt;br /&gt;
|-&lt;br /&gt;
| Hetzner Storage Box 1TB&lt;br /&gt;
| €3.81/month&lt;br /&gt;
|-&lt;br /&gt;
| Everything else&lt;br /&gt;
| Free (self-hosted)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Summary ==&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Every night at 3 AM:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Databases get dumped to .sql.gz files&lt;br /&gt;
** Restic encrypts and uploads everything to Germany&lt;br /&gt;
** Old backups get cleaned up automatically&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Every morning at 6 AM:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** System checks backup integrity&lt;br /&gt;
** Alerts you via ntfy if something&amp;#039;s wrong&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Every Sunday:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
** Does a test restore to verify backups actually work&lt;br /&gt;
&lt;br /&gt;
Your data is now protected. Sleep well.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= Disaster Recovery Runbook =&lt;br /&gt;
&lt;br /&gt;
= Disaster Recovery Runbook =&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Last Updated:&amp;#039;&amp;#039;&amp;#039; 2026-06-21&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Print this and store in a safe place.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;#039;&amp;#039;&amp;#039;Keep DR current:&amp;#039;&amp;#039;&amp;#039; after adding/changing/removing any service, run&lt;br /&gt;
:&amp;lt;code&amp;gt;scripts/dr-drift-check.sh&amp;lt;/code&amp;gt; and act on whatever it flags. See&lt;br /&gt;
:&amp;lt;code&amp;gt;DR_MAINTENANCE.md&amp;lt;/code&amp;gt; for the full workflow. Don&amp;#039;t skip this — gradual drift&lt;br /&gt;
:is how DR plans silently rot until the day you actually need them.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Quick Reference ==&lt;br /&gt;
&lt;br /&gt;
=== Stronghold (primary) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Item&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Hetzner Storage Box&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;u540853.your-storagebox.de&amp;lt;/code&amp;gt; port &amp;#039;&amp;#039;&amp;#039;23&amp;#039;&amp;#039;&amp;#039;&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; (port 23 comes from SSH config, &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; the URL — see note)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;SSH Key&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;~/.ssh/id_rsa&amp;lt;/code&amp;gt; (on stronghold)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Restic Password&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| In &amp;lt;code&amp;gt;/library/backup-stack/.env&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;HETZNER_PASSWORD&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Backup Schedule&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Daily 03:00 CST&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&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== VPS (wilsoz.com) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Item&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Hetzner Storage Box&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| same box, separate repo&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-vps&amp;lt;/code&amp;gt; (port 23 comes from SSH config, &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; the URL — see note)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;SSH Key&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;~/.ssh/id_ed25519&amp;lt;/code&amp;gt; (on VPS)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Restic Password&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| same &amp;lt;code&amp;gt;HETZNER_PASSWORD&amp;lt;/code&amp;gt; — stored in &amp;lt;code&amp;gt;/etc/restic-vps.env&amp;lt;/code&amp;gt; on VPS&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Backup Schedule&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Daily 04:30 UTC (offset from stronghold)&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&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Covers&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Forgejo data + repos, Atuin (pg_dump), ntfy, nginx, Let&amp;#039;s Encrypt certs, &amp;lt;code&amp;gt;/opt/&amp;lt;/code&amp;gt; custom scripts&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
:&amp;#039;&amp;#039;&amp;#039;⚠️ SFTP repo URL gotcha (port vs. path):&amp;#039;&amp;#039;&amp;#039; In restic&amp;#039;s single-colon SFTP form&lt;br /&gt;
:(&amp;lt;code&amp;gt;sftp:user@host:path&amp;lt;/code&amp;gt;) &amp;#039;&amp;#039;&amp;#039;everything after the host colon is the repository path&amp;#039;&amp;#039;&amp;#039; —&lt;br /&gt;
:there is no port in the URL. The storage box&amp;#039;s port &amp;#039;&amp;#039;&amp;#039;23&amp;#039;&amp;#039;&amp;#039; must be set in SSH config&lt;br /&gt;
:(&amp;lt;code&amp;gt;~/.ssh/config&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;Port 23&amp;lt;/code&amp;gt; for host &amp;lt;code&amp;gt;u540853.your-storagebox.de&amp;lt;/code&amp;gt;), which both&lt;br /&gt;
:stronghold and the &amp;lt;code&amp;gt;restic-backup&amp;lt;/code&amp;gt; container already have. Writing the port into the&lt;br /&gt;
:URL as &amp;lt;code&amp;gt;:23/backups&amp;lt;/code&amp;gt; makes restic look for a repo at the literal path &amp;lt;code&amp;gt;23/backups&amp;lt;/code&amp;gt; and&lt;br /&gt;
:fail with &amp;#039;&amp;#039;&amp;quot;repository does not exist&amp;quot;&amp;#039;&amp;#039;. Always use the relative &amp;lt;code&amp;gt;:./backups&amp;lt;/code&amp;gt; /&lt;br /&gt;
:&amp;lt;code&amp;gt;:./backups-vps&amp;lt;/code&amp;gt; form. (This exact mistake caused false &amp;quot;VPS: no snapshots found&amp;quot;&lt;br /&gt;
:alerts from the n8n &amp;#039;&amp;#039;Backup Status Alert (SSH)&amp;#039;&amp;#039; workflow until 2026-06-29.)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== What&amp;#039;s Backed Up ==&lt;br /&gt;
&lt;br /&gt;
=== Service Data (files) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Path in Backup&lt;br /&gt;
! Source on Host&lt;br /&gt;
! Contents&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/authentik&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/authentik-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| SSO/identity provider config&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/vaultwarden&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/vaultwarden-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Password vault (SQLite DB + attachments)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/nextcloud&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/nextcloud-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| File sync config + data&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/immich-config&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/immich-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Photo manager config&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/immich-photos&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/raid1/Consolidated/Picture/Immich-Photos/&amp;lt;/code&amp;gt;&lt;br /&gt;
| All photos (149G)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/jellyfin&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/var/lib/jellyfin/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Media server metadata (92G)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/jellyfin-etc&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/etc/jellyfin/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Jellyfin system config&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/memos&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/memo-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Notes app&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/linkwarden&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/linkwarden-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Bookmark manager&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/open-webui&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/open-webui-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| AI chat interface&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/matrix-chat&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/matrix-chat/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Synapse + bridge configs + signing key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/stalwart&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/stalwart-app/data/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mail server data&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/snappymail&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/snappymail-app/data/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Webmail data&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/mailcow&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/opt/mailcow-dockerized/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Legacy mail (to be archived)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/home&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/home/mnw/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Home dir: dotfiles, scripts, Documents, source, SSH/GPG keys (selective excludes)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/developer&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/home/mnw/Developer/&amp;lt;/code&amp;gt;&lt;br /&gt;
| All dev projects (7.9G) — also covered by &amp;lt;code&amp;gt;/data/home&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/beets&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/raid1/my-tunes/.beets/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Music library DB&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/openbao&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao secret store — &amp;#039;&amp;#039;&amp;#039;incl. &amp;lt;code&amp;gt;.init-output.json&amp;lt;/code&amp;gt; (root token + unseal key) and &amp;lt;code&amp;gt;.scripts-token&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;.claude-token&amp;lt;/code&amp;gt;.&amp;#039;&amp;#039;&amp;#039; Bootstrap-critical: must be restored and unsealed &amp;#039;&amp;#039;before&amp;#039;&amp;#039; services that depend on secrets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/ezbookkeeping&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/ezbookkeeping-app/data/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Personal finance tracker (SQLite + uploads)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/data/omada&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/omada-app/&amp;lt;/code&amp;gt;&lt;br /&gt;
| Omada SDN controller — switch/AP config &amp;amp; topology&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Database Dumps (in &amp;lt;code&amp;gt;/dumps/&amp;lt;/code&amp;gt;) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File Pattern&lt;br /&gt;
! Source Container&lt;br /&gt;
! Database&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;authentik-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;authentik-app_postgresql_1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Authentik identity DB&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;nextcloud-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;nextcloud-db&amp;lt;/code&amp;gt;&lt;br /&gt;
| Nextcloud DB&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;immich-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;immich_postgres&amp;lt;/code&amp;gt;&lt;br /&gt;
| Immich photo DB (~460MB compressed)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;linkwarden-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;linkwarden_postgres&amp;lt;/code&amp;gt;&lt;br /&gt;
| Linkwarden bookmark DB&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-YYYYMMDD.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-postgres&amp;lt;/code&amp;gt;&lt;br /&gt;
| Synapse + bridge DBs (~1MB compressed)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== NOT Backed Up (re-acquirable) ===&lt;br /&gt;
&lt;br /&gt;
* Jellyfin media files (movies, TV)&lt;br /&gt;
* Ollama models (~20G, re-downloadable)&lt;br /&gt;
* Steam games&lt;br /&gt;
* VirtualBox VMs&lt;br /&gt;
* node_modules, .venv, .git/objects&lt;br /&gt;
* Wyoming voice models (&amp;lt;code&amp;gt;piper-data-app&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;whisper-data-app&amp;lt;/code&amp;gt;) — redownloadable&lt;br /&gt;
* Prometheus metric history, Grafana dashboards data, uptime-kuma — re-buildable from compose&lt;br /&gt;
&lt;br /&gt;
=== ⚠️ Known Gaps — Data Not Currently Backed Up ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Severity&lt;br /&gt;
! What&lt;br /&gt;
! Where&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;HIGH&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| AudioBookshelf config + metadata&lt;br /&gt;
| &amp;lt;code&amp;gt;/usr/share/audiobookshelf/config/&amp;lt;/code&amp;gt; (38M), &amp;lt;code&amp;gt;/usr/share/audiobookshelf/metadata/&amp;lt;/code&amp;gt; (936M)&lt;br /&gt;
| systemd service, not Docker — not yet bind-mounted into restic container. Lose library DB, progress, and cover art on rebuild.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Previously flagged gaps (all closed as of 2026-06-21): Nextcloud user files, Matrix media, Mealie, n8n named volumes, uptime-kuma, Grafana, AdGuard — all now covered by bind mounts in the restic container compose.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Recovery Scenarios ==&lt;br /&gt;
&lt;br /&gt;
=== Scenario 1: Single Service Failure ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Example:&amp;#039;&amp;#039;&amp;#039; Immich database corrupted, need to restore just Immich.&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 &amp;amp;&amp;amp; docker compose down&lt;br /&gt;
&lt;br /&gt;
# 2. List available snapshots&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; restic snapshots&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# 3. Restore service data from latest snapshot&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; \&lt;br /&gt;
   restic restore latest --target /tmp/restore --include /data/immich-config&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# 4. Copy restored data to host&lt;br /&gt;
docker cp restic-backup:/tmp/restore/data/immich-config /tmp/immich-restore&lt;br /&gt;
&lt;br /&gt;
# 5. Replace the broken data&lt;br /&gt;
mv /library/immich-app /library/immich-app.broken&lt;br /&gt;
mv /tmp/immich-restore /library/immich-app&lt;br /&gt;
&lt;br /&gt;
# 6. Restore the database dump&lt;br /&gt;
docker cp restic-backup:/tmp/restore/dumps/immich-YYYYMMDD.sql.gz /tmp/&lt;br /&gt;
gunzip /tmp/immich-YYYYMMDD.sql.gz&lt;br /&gt;
# Start just the database container&lt;br /&gt;
docker compose up -d immich_postgres&lt;br /&gt;
# Wait for it to be ready, then restore&lt;br /&gt;
docker exec -i immich_postgres psql -U postgres immich &amp;lt; /tmp/immich-YYYYMMDD.sql&lt;br /&gt;
&lt;br /&gt;
# 7. Start the full service&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# 8. Clean up&lt;br /&gt;
docker exec restic-backup rm -rf /tmp/restore&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario 2: Full Server Rebuild ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Starting from:&amp;#039;&amp;#039;&amp;#039; Fresh Linux install on new hardware.&lt;br /&gt;
&lt;br /&gt;
==== Bootstrap Order (DO NOT SHUFFLE) ====&lt;br /&gt;
&lt;br /&gt;
Many services depend on OpenBao (secret store) and Authentik (SSO). Bring them&lt;br /&gt;
up in this order so each layer has its dependencies in place:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
1. Host: Docker, restic, Tailscale, SSH key&lt;br /&gt;
2. Restore everything from Hetzner via restic (raw files)&lt;br /&gt;
3. OpenBao  ← unseal it first; it holds tokens other services need&lt;br /&gt;
4. Authentik (DB dump → start) ← SSO for everything&lt;br /&gt;
5. Vaultwarden ← human-facing creds; needed for me-the-operator&lt;br /&gt;
6. Restore Claude Code access (bw CLI, MCP wrappers) ← so future-you can drive recovery&lt;br /&gt;
7. Postgres-backed services (Immich, Nextcloud, Matrix, Linkwarden): DB dump → start&lt;br /&gt;
8. Stalwart + SnappyMail (mail)&lt;br /&gt;
9. Everything else (n8n, Grafana, Mealie, etc.)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Install Prerequisites ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Install Docker&lt;br /&gt;
curl -fsSL https://get.docker.com | sh&lt;br /&gt;
sudo usermod -aG docker mnw&lt;br /&gt;
&lt;br /&gt;
# Install restic&lt;br /&gt;
sudo apt install restic&lt;br /&gt;
&lt;br /&gt;
# Set Docker MTU for Tailscale compatibility&lt;br /&gt;
# Add to /etc/docker/daemon.json:&lt;br /&gt;
# {&amp;quot;mtu&amp;quot;: 1280}&lt;br /&gt;
sudo systemctl restart docker&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Restore SSH Keys ====&lt;br /&gt;
&lt;br /&gt;
You need the SSH key to access Hetzner. If you have a copy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mkdir -p ~/.ssh&lt;br /&gt;
# Copy id_rsa from your backup/safe location&lt;br /&gt;
chmod 600 ~/.ssh/id_rsa&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t have the key, generate a new one and add it to Hetzner:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssh-keygen -t rsa -b 4096&lt;br /&gt;
# Add public key via Hetzner Robot panel → Storage Box → SSH Keys&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Bootstrap Restic ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Create the backup stack directory&lt;br /&gt;
sudo mkdir -p /library/backup-stack&lt;br /&gt;
cd /library/backup-stack&lt;br /&gt;
&lt;br /&gt;
# Create .env with Hetzner credentials:&lt;br /&gt;
cat &amp;gt; .env &amp;lt;&amp;lt; &amp;#039;EOF&amp;#039;&lt;br /&gt;
HETZNER_REPO=sftp:u540853@u540853.your-storagebox.de:23/backups&lt;br /&gt;
HETZNER_PASSWORD=&amp;lt;your-restic-password&amp;gt;&lt;br /&gt;
NTFY_TOKEN=tk_0fhigfiglbjw9r8sk9jv4hpr7q67f&lt;br /&gt;
EOF&lt;br /&gt;
&lt;br /&gt;
# Test connection&lt;br /&gt;
export $(grep -v &amp;#039;^#&amp;#039; .env | xargs)&lt;br /&gt;
restic -r &amp;quot;$HETZNER_REPO&amp;quot; snapshots&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4: Restore Everything ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Create target directories&lt;br /&gt;
sudo mkdir -p /library /raid1/Consolidated/Picture/Immich-Photos&lt;br /&gt;
&lt;br /&gt;
# Restore all service data&lt;br /&gt;
restic -r &amp;quot;$HETZNER_REPO&amp;quot; restore latest --target /&lt;br /&gt;
&lt;br /&gt;
# This puts files at their original paths:&lt;br /&gt;
#   /data/authentik → needs moving to /library/authentik-app/&lt;br /&gt;
#   /data/developer → needs moving to /home/mnw/Developer/&lt;br /&gt;
#   etc.&lt;br /&gt;
&lt;br /&gt;
# Move restored data to correct locations&lt;br /&gt;
sudo mv /data/authentik /library/authentik-app&lt;br /&gt;
sudo mv /data/vaultwarden /library/vaultwarden-app&lt;br /&gt;
sudo mv /data/nextcloud /library/nextcloud-app&lt;br /&gt;
sudo mv /data/immich-config /library/immich-app&lt;br /&gt;
sudo mv /data/immich-photos/* /raid1/Consolidated/Picture/Immich-Photos/&lt;br /&gt;
sudo mv /data/jellyfin /var/lib/jellyfin&lt;br /&gt;
sudo mv /data/jellyfin-etc /etc/jellyfin&lt;br /&gt;
sudo mv /data/memos /library/memo-app&lt;br /&gt;
sudo mv /data/linkwarden /library/linkwarden-app&lt;br /&gt;
sudo mv /data/open-webui /library/open-webui-app&lt;br /&gt;
sudo mv /data/matrix-chat /library/matrix-chat&lt;br /&gt;
sudo mv /data/stalwart /library/stalwart-app/data&lt;br /&gt;
sudo mv /data/snappymail /library/snappymail-app/data&lt;br /&gt;
sudo mv /data/developer /home/mnw/Developer&lt;br /&gt;
sudo mv /data/beets /raid1/my-tunes/.beets&lt;br /&gt;
sudo chown -R mnw:mnw /home/mnw/Developer&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 4b: Bootstrap OpenBao (Secret Store) — REQUIRED before DBs ====&lt;br /&gt;
&lt;br /&gt;
OpenBao holds tokens and secrets that downstream services + scripts read at startup.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Move restored data into place&lt;br /&gt;
sudo mv /data/openbao /library/openbao-app&lt;br /&gt;
sudo chown -R 1000:1000 /library/openbao-app  # OpenBao runs as uid 1000&lt;br /&gt;
&lt;br /&gt;
# 2. Start the container&lt;br /&gt;
cd /library/openbao-app &amp;amp;&amp;amp; docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# 3. Unseal — uses the unseal key from .init-output.json (restored from backup)&lt;br /&gt;
sudo /usr/local/bin/openbao-unseal.sh&lt;br /&gt;
# If that script isn&amp;#039;t restored yet, manually:&lt;br /&gt;
#   ROOT_TOKEN=$(jq -r .root_token /library/openbao-app/.init-output.json)&lt;br /&gt;
#   UNSEAL_KEY=$(jq -r .unseal_keys_b64[0] /library/openbao-app/.init-output.json)&lt;br /&gt;
#   docker exec openbao bao operator unseal &amp;quot;$UNSEAL_KEY&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# 4. Install the auto-unseal systemd unit so it survives reboots&lt;br /&gt;
sudo install -m 0755 scripts/openbao/openbao-unseal.sh /usr/local/bin/&lt;br /&gt;
sudo install -m 0644 scripts/openbao/openbao-unseal.service /etc/systemd/system/&lt;br /&gt;
sudo systemctl daemon-reload &amp;amp;&amp;amp; sudo systemctl enable --now openbao-unseal.service&lt;br /&gt;
&lt;br /&gt;
# 5. Verify by reading a known secret&lt;br /&gt;
TOKEN=$(cat /library/openbao-app/.scripts-token)&lt;br /&gt;
curl -sf http://127.0.0.1:8200/v1/secret/data/ntfy -H &amp;quot;X-Vault-Token: $TOKEN&amp;quot; | jq .data.data&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;If &amp;lt;code&amp;gt;.init-output.json&amp;lt;/code&amp;gt; is lost:&amp;#039;&amp;#039;&amp;#039; OpenBao is unrecoverable without it. Print&lt;br /&gt;
it now (or keep it in a sealed envelope) — it contains the root token AND the&lt;br /&gt;
unseal key. Without these, you cannot decrypt anything in the Raft store.&lt;br /&gt;
&lt;br /&gt;
==== Step 5: Restore Databases ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# For each service, start just the DB container, restore the dump, then start the rest.&lt;br /&gt;
# Example for Authentik:&lt;br /&gt;
cd /library/authentik-app&lt;br /&gt;
docker compose up -d postgresql&lt;br /&gt;
sleep 10&lt;br /&gt;
gunzip -c /dumps/authentik-YYYYMMDD.sql.gz | docker exec -i authentik-app_postgresql_1 psql -U authentik authentik&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&lt;br /&gt;
# Repeat for: nextcloud, immich, linkwarden, matrix&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 6: Restore System Services ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Jellyfin (systemd, not Docker)&lt;br /&gt;
sudo apt install jellyfin&lt;br /&gt;
# Data already restored to /var/lib/jellyfin and /etc/jellyfin&lt;br /&gt;
&lt;br /&gt;
# AudioBookshelf&lt;br /&gt;
# Install from package, data at /usr/share/audiobookshelf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 6b: Restore Claude Code Access (Vaultwarden MCP) ====&lt;br /&gt;
&lt;br /&gt;
Once Vaultwarden is up (Step 5 + nginx routed), restore the bw CLI and MCP&lt;br /&gt;
wrappers so a Claude session can help you finish recovery from this point on.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Install bw CLI (snap is the official Bitwarden source)&lt;br /&gt;
sudo snap install bw&lt;br /&gt;
&lt;br /&gt;
# 2. Install Bitwarden MCP server (npm, globally)&lt;br /&gt;
npm install -g @bitwarden/mcp-server&lt;br /&gt;
&lt;br /&gt;
# 3. Restore wrappers — these are in /data/home/.config/claude/ from backup&lt;br /&gt;
mkdir -p ~/.config/claude&lt;br /&gt;
sudo cp /data/home/.config/claude/{vaultwarden.env,bw-mcp-start.sh,bw-get.py} ~/.config/claude/&lt;br /&gt;
chmod 600 ~/.config/claude/vaultwarden.env&lt;br /&gt;
chmod 700 ~/.config/claude/bw-mcp-start.sh ~/.config/claude/bw-get.py&lt;br /&gt;
&lt;br /&gt;
# 4. Verify bw can talk to Vaultwarden (not the public Bitwarden cloud!)&lt;br /&gt;
bw config server  # MUST print https://vault.wilsoz.com — if not, run: bw config server https://vault.wilsoz.com&lt;br /&gt;
bw login --apikey  # uses BW_CLIENTID + BW_CLIENTSECRET from env&lt;br /&gt;
&lt;br /&gt;
# 5. Wire MCPs into ~/.claude.json (vault + bitwarden entries)&lt;br /&gt;
# See services/VAULTWARDEN.md for the exact JSON structure if it needs to be rebuilt.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See &amp;lt;code&amp;gt;services/VAULTWARDEN.md&amp;lt;/code&amp;gt; for full setup including the &amp;lt;code&amp;gt;mcp-server-bitwarden&amp;lt;/code&amp;gt;&lt;br /&gt;
symlink bug workaround.&lt;br /&gt;
&lt;br /&gt;
==== Step 7: Re-establish Networking ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Install Tailscale&lt;br /&gt;
curl -fsSL https://tailscale.com/install.sh | sh&lt;br /&gt;
sudo tailscale up&lt;br /&gt;
&lt;br /&gt;
# VPS nginx configs point to Tailscale IPs — update if IP changes&lt;br /&gt;
# VPS SSH: ssh -p 1981 mnw@wilsoz.com&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario 3: Restore Specific Files ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Restore a single file from a specific snapshot&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; \&lt;br /&gt;
   restic restore SNAPSHOT_ID --target /tmp/restore --include /data/developer/SystemResiliency/STATUS.md&amp;#039;&lt;br /&gt;
&lt;br /&gt;
docker cp restic-backup:/tmp/restore/data/developer/SystemResiliency/STATUS.md /tmp/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scenario 4: Matrix Chat Recovery ===&lt;br /&gt;
&lt;br /&gt;
Matrix requires special care — the signing key is critical.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# 1. Restore Matrix data&lt;br /&gt;
restic restore latest --target /tmp/restore --include /data/matrix-chat&lt;br /&gt;
&lt;br /&gt;
# 2. Critical files:&lt;br /&gt;
#    - synapse/signing.key — IRREPLACEABLE, needed for federation&lt;br /&gt;
#    - synapse/homeserver.yaml — server config&lt;br /&gt;
#    - bridges/*/config.yaml — bridge configs&lt;br /&gt;
#    - bridges/*/registration.yaml — bridge registrations&lt;br /&gt;
&lt;br /&gt;
# 3. Restore database&lt;br /&gt;
gunzip -c /dumps/matrix-YYYYMMDD.sql.gz | docker exec -i matrix-postgres psql -U synapse&lt;br /&gt;
&lt;br /&gt;
# 4. Restart stack&lt;br /&gt;
cd /library/matrix-chat &amp;amp;&amp;amp; docker compose up -d&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Verification Commands ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# List all snapshots&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; restic snapshots&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# Check repository integrity&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; restic check&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# Check repo size on Hetzner&lt;br /&gt;
ssh -p 23 u540853@u540853.your-storagebox.de &amp;quot;du -sh backups/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# List files in a snapshot&lt;br /&gt;
docker exec restic-backup sh -c \&lt;br /&gt;
  &amp;#039;export RESTIC_REPOSITORY=$HETZNER_REPO; export RESTIC_PASSWORD=$HETZNER_PASSWORD; \&lt;br /&gt;
   restic ls latest /data/matrix-chat&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# Verify a DB dump&lt;br /&gt;
gunzip -t /library/backup-stack/dumps/immich-YYYYMMDD.sql.gz &amp;amp;&amp;amp; echo &amp;quot;OK&amp;quot; || echo &amp;quot;CORRUPT&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Manual backup trigger&lt;br /&gt;
sudo /library/backup-stack/scripts/host-dump-databases.sh&lt;br /&gt;
docker exec restic-backup /scripts/backup-to-hetzner.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Automated Monitoring ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Check&lt;br /&gt;
! Frequency&lt;br /&gt;
! Alert Channel&lt;br /&gt;
|-&lt;br /&gt;
| Backup age (n8n)&lt;br /&gt;
| Every 6h&lt;br /&gt;
| ntfy (nc700x-alerts) + Matrix&lt;br /&gt;
|-&lt;br /&gt;
| Integrity check (restic)&lt;br /&gt;
| Daily 06:00&lt;br /&gt;
| ntfy if FAIL&lt;br /&gt;
|-&lt;br /&gt;
| Restore test (restic)&lt;br /&gt;
| Weekly Sunday 07:00&lt;br /&gt;
| ntfy if FAIL&lt;br /&gt;
|-&lt;br /&gt;
| Disk space (n8n)&lt;br /&gt;
| Every 4h&lt;br /&gt;
| ntfy (nc700x-alerts) + Matrix&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Key Credentials ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Keep a copy of these offline:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Credential&lt;br /&gt;
! Location&lt;br /&gt;
|-&lt;br /&gt;
| Hetzner SSH key&lt;br /&gt;
| &amp;lt;code&amp;gt;~/.ssh/id_rsa&amp;lt;/code&amp;gt; on stronghold&lt;br /&gt;
|-&lt;br /&gt;
| Restic password&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/backup-stack/.env&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Hetzner account&lt;br /&gt;
| Robot panel, u540853&lt;br /&gt;
|-&lt;br /&gt;
| ntfy token&lt;br /&gt;
| &amp;lt;code&amp;gt;tk_0fhigfiglbjw9r8sk9jv4hpr7q67f&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Authentik admin&lt;br /&gt;
| &amp;lt;code&amp;gt;https://key.wilsoz.com/if/admin/&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| VPS SSH&lt;br /&gt;
| &amp;lt;code&amp;gt;ssh -p 1981 mnw@wilsoz.com&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Synapse shared secret&lt;br /&gt;
| In &amp;lt;code&amp;gt;homeserver.yaml&amp;lt;/code&amp;gt; line 50&lt;br /&gt;
|-&lt;br /&gt;
| Matrix signing key&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/matrix-chat/synapse/signing.key&amp;lt;/code&amp;gt; — &amp;#039;&amp;#039;&amp;#039;IRREPLACEABLE&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Restore Test Log ==&lt;br /&gt;
&lt;br /&gt;
=== 2026-02-23 — False Failure Alerts + Upload Anomaly Detection ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Problem 1 — False failure alert after meta-array removal:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;code&amp;gt;validate-backups.sh&amp;lt;/code&amp;gt; still contained a &amp;lt;code&amp;gt;restic check&amp;lt;/code&amp;gt; block against &amp;lt;code&amp;gt;$METAARRAY_REPO&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;$METAARRAY_PASSWORD&amp;lt;/code&amp;gt;. Both vars were removed from &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; when meta-array was dropped on 2026-02-22. Empty repo var → restic failed → &amp;lt;code&amp;gt;ERRORS++&amp;lt;/code&amp;gt; → ntfy &amp;lt;code&amp;gt;nc700x-alerts&amp;lt;/code&amp;gt; fired every day.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fix:&amp;#039;&amp;#039;&amp;#039; Removed the dead meta-array block from &amp;lt;code&amp;gt;validate-backups.sh&amp;lt;/code&amp;gt; (lines 44–52).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Problem 2 — Large upload on first run after container recreation:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
Restic container was recreated during meta-array removal, clearing the local cache. Without cache, restic reported &amp;quot;no parent snapshot found&amp;quot; and re-scanned all 441,884 files (all showed as &amp;quot;new&amp;quot;). Chunk dedup still worked correctly — only 11.5 GiB of 314 GiB was actually uploaded. Confirmed by upload anomaly report: Stalwart ~8k new email files (ongoing Fastmail getmail import) + &amp;lt;code&amp;gt;/data/home&amp;lt;/code&amp;gt; (all new to the repo without a parent reference). Normal next run.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Enhancement — Upload anomaly detection:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
Added &amp;lt;code&amp;gt;detect_upload_anomaly()&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;run-backup.sh&amp;lt;/code&amp;gt;:&lt;br /&gt;
* Tracks upload size per run in &amp;lt;code&amp;gt;/dumps/upload-history.txt&amp;lt;/code&amp;gt; (rolling 30 entries)&lt;br /&gt;
* If upload &amp;gt;2.5× rolling average AND &amp;gt;500 MiB, runs &amp;lt;code&amp;gt;restic diff &amp;lt;prev&amp;gt; &amp;lt;cur&amp;gt;&amp;lt;/code&amp;gt; and summarises changes by service path&lt;br /&gt;
* POSTs JSON payload to n8n webhook (&amp;lt;code&amp;gt;whO5LBZHQHCXXG9I&amp;lt;/code&amp;gt;) which formats and sends to Matrix&lt;br /&gt;
* ntfy not used — this is informational, not an emergency&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== 2026-02-22 — Recurring SSH Timeout Incident ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Problem:&amp;#039;&amp;#039;&amp;#039; Backups failing with &amp;lt;code&amp;gt;subprocess ssh: Timeout&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;Fatal: unable to save snapshot: context canceled&amp;lt;/code&amp;gt;. Hetzner SFTP server drops idle connections (~5 min) during restic&amp;#039;s local scan phase (22+ min). Compounded by 96 accumulated index files (documented threshold: 49) because prune was also failing.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Secondary cause removed:&amp;#039;&amp;#039;&amp;#039; meta-array (ma.sdf.org) secondary backup target was unreliable. Since &amp;lt;code&amp;gt;run-backup.sh&amp;lt;/code&amp;gt; used &amp;lt;code&amp;gt;set -e&amp;lt;/code&amp;gt;, any metaarray failure marked the entire job as failed even when Hetzner backup succeeded. Removed entirely.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fixes applied:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# &amp;lt;code&amp;gt;restic repair index&amp;lt;/code&amp;gt; — consolidated 96 → 3 index files&lt;br /&gt;
# &amp;lt;code&amp;gt;restic forget --prune&amp;lt;/code&amp;gt; — cleaned up retention&lt;br /&gt;
# Added &amp;lt;code&amp;gt;-o sftp.args=&amp;quot;-oServerAliveInterval=10 -oServerAliveCountMax=36&amp;quot;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;backup-to-hetzner.sh&amp;lt;/code&amp;gt; — 10s keepalives, 6-min tolerance&lt;br /&gt;
# Added &amp;lt;code&amp;gt;--retry-lock 10m&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;backup-to-hetzner.sh&amp;lt;/code&amp;gt;&lt;br /&gt;
# Removed &amp;lt;code&amp;gt;backup-to-metaarray.sh&amp;lt;/code&amp;gt; and all meta-array config&lt;br /&gt;
# Added &amp;lt;code&amp;gt;/home/mnw&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;/data/home&amp;lt;/code&amp;gt; to backup scope (dotfiles, Documents, source, SSH keys)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;If SSH timeouts recur:&amp;#039;&amp;#039;&amp;#039; It means index files are accumulating again (prune is failing silently). Run:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec restic-backup sh -c &amp;#039;RESTIC_REPOSITORY=&amp;quot;$HETZNER_REPO&amp;quot; RESTIC_PASSWORD=&amp;quot;$HETZNER_PASSWORD&amp;quot; restic repair index&amp;#039;&lt;br /&gt;
docker exec restic-backup sh -c &amp;#039;RESTIC_REPOSITORY=&amp;quot;$HETZNER_REPO&amp;quot; RESTIC_PASSWORD=&amp;quot;$HETZNER_PASSWORD&amp;quot; restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== 2026-02-16 — First Manual Test ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Test:&amp;#039;&amp;#039;&amp;#039; Restore files from latest snapshot (ae89d11d), compare against live originals.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File&lt;br /&gt;
! Restored&lt;br /&gt;
! Compared&lt;br /&gt;
! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-chat/synapse/homeserver.yaml&amp;lt;/code&amp;gt;&lt;br /&gt;
| 3,022 bytes&lt;br /&gt;
| vs live&lt;br /&gt;
| IDENTICAL&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-chat/synapse/signing.key&amp;lt;/code&amp;gt;&lt;br /&gt;
| 59 bytes&lt;br /&gt;
| vs live&lt;br /&gt;
| IDENTICAL&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-chat/docker-compose.yml&amp;lt;/code&amp;gt;&lt;br /&gt;
| 3,232 bytes&lt;br /&gt;
| vs live&lt;br /&gt;
| IDENTICAL&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;matrix-chat/bridges/whatsapp/config.yaml&amp;lt;/code&amp;gt;&lt;br /&gt;
| 33,990 bytes&lt;br /&gt;
| vs live&lt;br /&gt;
| IDENTICAL&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;developer/SystemResiliency/STATUS.md&amp;lt;/code&amp;gt;&lt;br /&gt;
| Restored&lt;br /&gt;
| pre-edit version&lt;br /&gt;
| Expected diff (edited today)&lt;br /&gt;
|-&lt;br /&gt;
| DB dump: &amp;lt;code&amp;gt;immich-20260213.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| 463 MB&lt;br /&gt;
| gunzip -t&lt;br /&gt;
| VALID&lt;br /&gt;
|-&lt;br /&gt;
| DB dump: &amp;lt;code&amp;gt;matrix-20260215.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| 1 MB&lt;br /&gt;
| gunzip -t&lt;br /&gt;
| VALID&lt;br /&gt;
|-&lt;br /&gt;
| DB dump: &amp;lt;code&amp;gt;authentik-20260213.sql.gz&amp;lt;/code&amp;gt;&lt;br /&gt;
| 16 MB&lt;br /&gt;
| gunzip -t&lt;br /&gt;
| VALID&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Issues Found:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* DB dumps from Feb 14-15 were 20 bytes (empty) — container&amp;#039;s &amp;lt;code&amp;gt;run-backup.sh&amp;lt;/code&amp;gt; was calling &amp;lt;code&amp;gt;dump-databases.sh&amp;lt;/code&amp;gt; inside the container where &amp;lt;code&amp;gt;docker&amp;lt;/code&amp;gt; CLI doesn&amp;#039;t exist, overwriting the good host-side dumps.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Fixed:&amp;#039;&amp;#039;&amp;#039; Deployed updated &amp;lt;code&amp;gt;run-backup.sh&amp;lt;/code&amp;gt; that verifies dumps instead of re-dumping. Fresh dumps created manually and confirmed valid.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Verdict:&amp;#039;&amp;#039;&amp;#039; PASS — restore process works. Files are intact and match live originals.&lt;/div&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
</feed>