Side reading

The layout #

/srv/homeserver/
  data/                     irreplaceable, backed up always
    photos/
  appdata/                  live or generated application state
    immich-db/
    dumps/                  database dumps, written before each backup

One rule: data holds user content; appdata holds state managed by applications. That name does not decide backup policy by itself. Chapter 11 selects all of data plus validated logical dumps, rather than copying a live database directory. Authentik's named database volume follows the same dump rule. Prometheus and Loki history are disposable unless you deliberately change that decision.

The split makes later questions explicit. Back up data and appdata/dumps. Exclude only confirmed generated content. Move a category under data to another disk without moving the operating system or databases with it.

Why paths belong in variables #

Every path an application uses should come from one place: an environment file, a compose variable, a playbook variable. Never typed twice.

PHOTOS_ROOT=/srv/homeserver/data/photos
ARCHIVE_ROOT=/srv/homeserver/data/archive

Moving a category then means changing one value and redeploying, rather than finding every occurrence.

Adding a disk #

The migration, done carefully. Roughly an hour for a large library, most of it copying.

1. Attach and identify it.

$ lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,FSTYPE,UUID,MOUNTPOINTS
$ ls -l /dev/disk/by-id/

Choose the stable /dev/disk/by-id/<device>-part1 path by matching the physical disk's model, serial and size. Record it as <new-partition>. Stop if you cannot identify it unambiguously.

2. Make a filesystem, if it is new.

$ lsblk -f <new-partition>
$ sudo wipefs --no-act <new-partition>
$ findmnt --source <new-partition>

mkfs destroys existing filesystem metadata. Continue only for the deliberately empty new partition, when findmnt shows it is not mounted and you have accounted for every signature reported by wipefs --no-act:

$ sudo mkfs.ext4 -L archive <new-partition>

3. Mount it somewhere temporary, by UUID.

$ sudo mkdir -p /mnt/new
$ sudo mount UUID=<the uuid> /mnt/new
$ findmnt /mnt/new

4. Stop what writes to the directory. Not optional. Copying a live tree gives you a copy of a moving target.

$ sudo docker compose -f /home/admin/homeserver/immich/compose.yaml stop

5. Copy, preserving everything.

$ sudo rsync -aHAX --numeric-ids --info=progress2 /srv/homeserver/data/archive/ /mnt/new/

-a preserves permissions, times and symlinks. -H hard links. -A ACLs. -X extended attributes. --numeric-ids keeps the numbers rather than resolving names, which matters for exactly the reasons the permissions annex describes.

Note the trailing slash on the source. With it you copy the contents; without it you copy the directory into the target and end up one level deeper.

6. Verify before you trust it.

$ sudo rsync -aHAXnci --delete --numeric-ids \
    /srv/homeserver/data/archive/ /mnt/new/
$ sudo du -sh /srv/homeserver/data/archive /mnt/new

No rsync change lines means contents and the metadata represented by these flags match. For an irreplaceable migration, also compare a checksum manifest or let the next restic backup read and verify the destination before deleting the source.

7. Mount it in the right place, permanently. By UUID, never by device name: /dev/sdb is not stable across reboots and a drive that moves to /dev/sdc will mount the wrong thing or nothing.

$ sudo umount /mnt/new
$ sudo mv /srv/homeserver/data/archive /srv/homeserver/data/archive.old
$ sudo mkdir /srv/homeserver/data/archive
$ sudo cp -a /etc/fstab /etc/fstab.before-archive
$ sudoedit /etc/fstab

Add one reviewed line, substituting the UUID you read with blkid:

UUID=<the uuid> /srv/homeserver/data/archive ext4 defaults,noatime 0 2

Then validate and mount it:

$ sudo findmnt --verify --verbose
$ sudo mount -a
$ findmnt /srv/homeserver/data/archive
$ ls /srv/homeserver/data/archive

8. Start the affected application and check it actually sees its files.

9. Keep the old copy for a week, then remove it.

Test fstab before you reboot

A bad line in /etc/fstab can stop the machine booting, and you will be recovering it at a console.

findmnt --verify catches many mistakes before mounting. sudo mount -a applies the file without rebooting. Run both and confirm the expected source, target and filesystem. Add nofail only after deciding that booting without the data is safer than stopping for recovery; an application writing into an empty underlying directory can be worse than a failed boot.

Removable drives #

Do not put persistent data on a path that automounts, like /media/<user>/<label>. Those paths depend on desktop services, change with the label, and are not mounted when nothing is logged in.

Give the drive a permanent mount point in /srv and a UUID entry in fstab.

The one that is harder #

A database directory can be moved the same way, and there is nothing special about it as long as the database is stopped. What you must not do is copy it while it runs, for the reasons chapter 11 gives.

Stop the container, copy, remount, start. Or, more safely: dump it, move the directory, restore into the new location.

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.