Side reading

Run this on a normal day, twice a year and after adding a stateful service. Note the start time. The result is a measured recovery time, not an assumption.

This annex uses chapter 11's root wrapper, exact Compose path and repository credentials. Do not substitute bare restic unless you have deliberately configured an equivalent recovery client.

1. Reach and check the repository #

$ sudo /usr/local/sbin/homeserver-restic snapshots
$ sudo /usr/local/sbin/homeserver-restic check
$ sudo /usr/local/sbin/homeserver-restic check --read-data-subset=1/12

Rotate the deterministic fraction from 1/12 through 12/12 across checks, or run a complete annual --read-data check. Stop if the repository or structural check fails.

2. Restore into a unique directory #

$ drill=$(mktemp -d /var/tmp/homeserver-drill.XXXXXX)
$ sudo /usr/local/sbin/homeserver-restic restore latest --target "$drill"
$ sudo find "$drill/srv/homeserver/data/photos" -type f | sort | head
$ sudo du -sh "$drill/srv/homeserver/data" "$drill/srv/homeserver/appdata/dumps"

Open several restored photographs and a video. Check every irreplaceable directory that your installation uses: upload/, library/ when Storage Template is enabled, and profile/. Confirm that deliberately excluded generated directories such as thumbs/ and encoded-video/ are absent.

3. Restore the Immich database through stdin #

The dump exists on the host, not in the database container. Feed it through stdin and use a scratch database:

$ sudo docker compose -f /home/admin/homeserver/immich/compose.yaml \
    exec -T database createdb -U postgres restore_test
$ sudo bash -c 'docker compose -f /home/admin/homeserver/immich/compose.yaml \
    exec -T database pg_restore -U postgres -d restore_test < "$1"' _ \
    "$drill/srv/homeserver/appdata/dumps/immich.dump"
$ sudo docker compose -f /home/admin/homeserver/immich/compose.yaml \
    exec -T database psql -U postgres -d restore_test -c '\dt'

Choose one meaningful table that exists in your installed Immich release and compare its row count with the live database. A table list alone proves only that some schema restored.

$ sudo docker compose -f /home/admin/homeserver/immich/compose.yaml \
    exec -T database dropdb -U postgres restore_test

4. Restore every later database #

After chapter 13, repeat the same process for authentik.dump in the authentik-db container. Create authentik_restore_test, restore through stdin as user authentik, inspect a meaningful row count, then drop the scratch database. If you add another stateful application, add its tested dump here at the same time.

5. Locate both recovery secrets #

Find the off-server copy of the age private key and the separate restic repository password. The first decrypts committed configuration; the second decrypts backup data. Check that each copy is readable without depending on the home server. Do not print either secret during the drill.

6. Clean up only the directory you created #

$ case "$drill" in /var/tmp/homeserver-drill.*) sudo rm -rf -- "$drill";; *) false;; esac

The guard refuses an empty, broad or unexpected target. Record:

  • the date and total time;
  • the snapshot restored and data fraction checked;
  • each file category and database verified;
  • anything missing, surprising or looked up;
  • the corrective change and the successful repeat run.

The drill fails if the repository is unreachable, expected content is absent, a database does not restore, a recovery secret cannot be found, or the procedure depends on undocumented knowledge. Fix the cause and repeat from step 1; a run repaired halfway through is useful diagnosis, not a passing drill.

My restore drill failed

Identify the failed recovery layer without risking live data

Paste this into a new agent session. It carries everything the agent needs to know about where you are, and asks it to walk you through the problem rather than fix it for you.

I restored into a unique /var/tmp/homeserver-drill.* directory and one check failed. I have not changed live data. Help me distinguish repository access, snapshot selection, exclusions, dump creation and database restore before I clean up.

Settings

Your values

The book is written with placeholder names so it makes sense to everybody. Put your own in and every chapter, every command and every copy-paste prompt updates to match.

Nothing here is sent anywhere. It is saved in this browser, so it comes back next time. A different browser or a private window gets the placeholders again.

Live preview

$ ssh admin@192.168.1.20
$ sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
$ sudo hostnamectl set-hostname homeserver
$ sudo timedatectl set-timezone Europe/Paris

Real commands from chapters 2, 3 and 4. They change as you type.

The account you log in as. Not root, and not necessarily the same name you use on your laptop.

Introduced in Chapter 2, Meet your server

The book's placeholder is admin

What the machine calls itself. You choose it, and it shows up in your shell prompt and your logs.

Introduced in Chapter 3, A safe front door

The book's placeholder is homeserver

The IP address your server has on your home network, from ip -brief addr.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.20

The address range and prefix shown by ip route or ip -brief addr, written in CIDR form. Copy the real prefix; do not guess /24.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.0/24

The address traffic goes to on its way out of your house, from ip route.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.1

In Region/City form, or Etc/UTC if you would rather read logs in UTC.

Introduced in Chapter 3, A safe front door

The book's placeholder is Europe/Paris

A registered name you control. Chapter 8 uses it for the LAN route; chapter 12 uses a separate private Tailscale name remotely.

Introduced in Chapter 8, One door, many rooms

The book's placeholder is example.com

The email identity allowed to administer the tagged server in your Tailscale policy.

Introduced in Chapter 10, Your own private network

The book's placeholder is you@example.com

The mailbox that should receive actionable home-server alerts.

Introduced in Chapter 14, Knowing it is alive

The book's placeholder is alerts@example.com

Once you save, the prose and the commands read with your names, the copy buttons copy your values, and the copy-paste prompts describe your machine accurately. That last one matters: an assistant told your network is 192.168.1.0/24 when it is not will send you chasing the wrong thing.

Anything you leave empty keeps the book's placeholder.