Setting Up a Home Server

Recently, I came into possession of an old computer. Instead of letting it gather dust in the corner, I thought it would be a good idea to repurpose it as a home server. Primarily, I wanted a Samba Server, but I may go further to run other self-hosted services like NextCloud.

In this article I’ll walk through my home server setup process.

As a long time user and fan of Arch Linux for my desktop operating system (and you should also try it), Though it’s not often to hear people using Arch Linux on a server, I’d like to take the chance and give it a try. There are several features of Arch Linux that I found particularly compelling:

  • Minimal out-of-the box system. By default, Arch Linux comes with almost no preinstalled software, giving you the freedom to choose what to install.
  • Rolling release. Unlike other distributions such as Debian or CentOS, Arch Linux employs a rolling release model, which means my server will always be running the most recent version of all software. However, this could introduce unstable software updates, so it’s worth noting that this approach might be too aggressive for many use cases. For my home server, the risk is acceptable.
  • Outstanding Wiki. Almost every system operation you might want to perform with your system has a detailed guide, which is a testament to the dedication of the community.

/img/linux-rolling-release-model.png.webp

Here’s the rundown of my hardware components.

  • Motherboard: Gigabyte B360M AORUS Gaming 3
  • CPU: Intel Core i5-8400 (6 cores, 12 threads)
  • RAM: Vengeance LPX 8GB DDR4 DRAM 2400MHz x 4 (32GB in total)
  • SSD: Samsung 980 PRO 1TB PCIe NVMe Gen4 SSD M.2
  • HDD: Seagate IronWolf 4TB x 2
  • GPU: None, unnecessary for this server.

I’ve set some specific goals for this server build.

  • No proprietary software. The only exception is the boot firmware. I’ll give Libreboot a try later some time.
  • Full disk encryption. The only exception is the boot partition.
  • Disk decryption via a remove SSH session. I want to avoid the hassle of having to plug in a keyboard and a screen every time I need to restart the server.
  • RAID 1 on the two HDDs. It will be used as the Samba storage partition.

I’m still deliberating on whether to set up the SSD as a cache for the RAID. The gain for my use case will be negligible. For now, it’s not in my immediate plans, but it may be a fascinating subject for future investigation.

https://gitlab.com/cryptsetup/cryptsetup/wikis/luks-logo.png

Disk partitioning and encryption need to be handled before installing the operating system.

The 1TB SSD are split into two partitions: a 1GB EFI system partition (mounted at /boot), and the root partition taking up the rest of the SSD.

Device           Start        End    Sectors   Size Type
/dev/nvme0n1p1    2048    2099199    2097152     1G EFI System
/dev/nvme0n1p2 2099200 1953525134 1951425935 930.5G Linux filesystem

The root partition is encrypted by dm-crypt.

# Encrypt the root partition.
$ cryptsetup luksFormat /dev/nvme0n1p2

# Open the encrypted root partition and mount it to /dev/mapper/root.
$ cryptsetup open /dev/nvme0n1p2 root

The two HDDs are set up as software RAID (level 1), managed using mdadm.

$ mdadm --create --verbose --level=1 --metadata=1.2 --raid-devices=2 /dev/md0 /dev/sda1 /dev/sdb1

Here, /dev/md0 is the logical RAID block device, which is also encrypted by dm-crypt.

# Encrypt the RAID block device.
$ cryptsetup luksFormat /dev/md0

# Open the RAID block device and mount it to /dev/mapper/nas.
$ cryptsetup open /dev/md0 nas

Below is the final layout of my disk drivers:

$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
sda           8:0    0   3.6T  0 disk
└─sda1        8:1    0   3.6T  0 part
  └─md0       9:0    0   3.6T  0 raid1
    └─nas   254:1    0   3.6T  0 crypt /nas
sdb           8:16   0   3.6T  0 disk
└─sdb1        8:17   0   3.6T  0 part
  └─md0       9:0    0   3.6T  0 raid1
    └─nas   254:1    0   3.6T  0 crypt /nas
nvme0n1     259:0    0 931.5G  0 disk
├─nvme0n1p1 259:1    0     1G  0 part  /boot
└─nvme0n1p2 259:2    0 930.5G  0 part
  └─root    254:0    0 930.5G  0 crypt /

mdadm requires the mdadm_udev hook, so we need to add it to the HOOKS section in /etc/mkinitcpio.conf:

$ cat /etc/mkinitcpio.conf | grep '^HOOKS'
HOOKS=(base udev autodetect modconf block mdadm_udev filesystems keyboard fsck systemd)

Follow this great Arch wiki to install the system.

I’m using systemd-boot as the UEFI boot manager. Ensure the CFG Lock BIOS switch is disabled. The installation process is quite straightforward:

  1. Mount the EFI system partition at /boot.
  2. Execute bootctl install to install systemd-boot. This will copy systemd-boot to the EFI partition and set systemd-boot as the default EFI boot entry loaded by the EFI Boot manager.
  3. Generate the bootloader entry. Two files are required: /boot/loader/loader.conf and /boot/loader/entries/arch.conf.
$ cat <<EOF > /boot/loader/loader.conf
default arch.conf
timeout 3
console-mode max
editor no
EOF

$ cat <<EOF > /boot/loader/entries/arch.conf
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options root=/dev/mapper/root
EOF

One of my objectives is to remote unlock the LUKS-encrypted root partition. For this, I use a mkinitcpio hook called systemd-tool, which provides early remote SSH access before the root partition gets mounted.

First, install systemd-tool and its dependencies.

$ pacman -S mkinitcpio-systemd-tool busybox cryptsetup openssh tinyssh tinyssh-convert mc

Add the systemd-tool hook in /etc/mkinitcpio.conf. The final HOOKS section in /etc/mkinitcpio.conf looks like below.

$ cat /etc/mkinitcpio.conf | grep '^HOOKS'
HOOKS=(base udev autodetect modconf block mdadm_udev filesystems keyboard fsck systemd systemd-tool)

Following this, I need to configure /etc/crypttab and /etc/mkinitcpio-systemd-tool/config/crypttab:

$ echo "crypt UUID=$(blkid -s UUID -o value /dev/sdX2) none luks" > /etc/crypttab
$ cat /etc/crypttab > /etc/mkinitcpio-systemd-tool/config/crypttab

As well as /etc/fstab and /etc/mkinitcpio-systemd-tool/config/fstab:

$ echo "UUID=$(blkid -s UUID -o value /dev/mapper/root) /   ext4    rw,relatime 0 1" > /etc/fstab
$ echo "/dev/mapper/root    /sysroot    auto    x-systemd.device-timeout=9999h  0 1" > /etc/mkinitcpio-systemd-tool/config/fstab

Then, enable the required services and build initramfs.

$ systemctl enable initrd-cryptsetup.path
$ systemctl enable initrd-tinysshd
$ systemctl enable initrd-debug-progs
$ systemctl enable initrd-sysroot-mount

$ mkinitcpio -P

After rebooting, the server now has a fancy remote shell environment running before the root partition gets mounted. This allows me to connect to the server remotely. How are a few things to note about this remote shell environment:

  • It’s a tinyssh service.
  • Tinyssh only recognizes Ed25519 SSH key. Therefore, I have to generate an Ed25519 key pair on my local machine and paste the public key to /root/.ssh/authorized_keys.
  • At this early stage, we can only connect as root user, because other users are not yet available.

After entering the decryption key at the prompt, the root partition gets decrypted and mounted automatically.

I have not yet discussed the decryption and mounting of the RAID device. It is actually simpler. We can create a key file under /etc/cryptsetup-keys.d/ (let’s call it nas.key) and use this key file to decrypt the RAID device. All I need to do is modify /etc/crypttab and /etc/fstab as described below and we are good to go.

$ echo "nas UUID=$(blkid -s UUID -o value /dev/md0) /etc/cryptsetup-keys.d/nas.key" >> /etc/crypttab
$ echo "UUID=$(blkid -s UUID -o value /dev/mapper/nas) /nas   ext4    rw,relatime 0 1" >> /etc/fstab