DEV Community

Jaspal Chauhan
Jaspal Chauhan

Posted on

Building a Home Server [0]: Hardware, Linux Installation, and Initial Configuration

Intro

Hi! In this series of posts, I'll bring you along on my journey to setup a home server. I intend to use this server for file sharing/backup, hosting git repos, and various other services. The main applications I have in mind right now are Nextcloud, Gitlab, Pi-hole, and Prometheus/Grafana. Since I'm planning to run everything under Docker, it should be relatively easy to add more services on demand.

Hardware

So...I have a bad habit of rebuilding my gaming PC somewhat often. However, this means I have a lot of spare parts for the server!

Parts List:

CPU: AMD Ryzen 2700X

CPU Cooler: Noctua NH-D15 chromax.Black

Motherboard:: Asus ROG Strix X470-F

RAM: 4x8GB G.SKILL Trident RGB DDR4-3000

Boot Drive: Samsung 980 PRO 500 GB NVMe SSD

Storage Drives: | 2x Seagate BarraCuda 8TB HDD (will be run in RAID 1)

Graphics Card: EVGA GTX 1080 Reference (needed since no onboard CPU graphics)

Power Supply: EVGA SuperNOVA G1 80+ GOLD 650W

Case: NZXT H500i

Case Fans: 2x be quiet! Silent Wings 3 140mm

For my use case, consumer grade hardware should suffice. Also, I can't decide whether 8TB of storage is overkill or not enough! Guess we'll find out...

drive cage
Drive cage containing the two Seagate 8TB HDDs

Assembling the server was straightforward aside from one minor hiccup with the fans. I had to replace one of the two 140mm fans on the CPU cooler with a 120 mm one since my RAM sticks were too tall. Damn those RGB heat spreaders!

server
The server in all of its post-gaming glory

Initial Installation

Linux Distribution

I've chosen to use Ubuntu Server for the OS since it's one of the most popular server distributions. I'm more familiar with CentOS, but given the recent shift in that project, Ubuntu seems to be a more stable alternative.

Creating and Booting into the Ubuntu Install Media

  • Download the Ubuntu Server ISO from the official website.
  • Flash the ISO onto a USB drive (balenaEtcher is a good tool for this).
  • Plug the USB drive into the server and boot into the install media.
    • Ensure that the server has an Internet connection so that packages and updates can be downloaded during the install. Ethernet is preferred for ease of use and stability.
    • NOTE: You may have to change BIOS settings if the server doesn't boot into the USB automatically. Refer to your motherboard's docs for more information.

Installation Process

The official installation guide documents the process well, so I won't list all the steps here. Here are some steps I want to emphasize:

Configure storage:

  • Make sure that you select the correct disk to install the OS on. For me, the OS disk will be the Samsung 500GB NVMe SSD (this will vary depending on your specific hardware configuration).
  • I'm choosing to configure the two storage disks later manually.

Profile setup:

  • The username and password created in this step will be used to login to the server after installation, so keep note of them!
  • The newly created user will have sudo privileges.
  • I'm using positron as the hostname for my server, in case you see it later.

SSH Setup:

  • Make sure to install OpenSSH so you can login to the machine remotely.
  • You can also choose to import SSH keys from Github/Launchpad. If you do this, it's better to disable password authentication for SSH since it won't be needed. If anything goes wrong, you can always login to the server directly as long as you have physical access.

Snaps:

  • I'm not installing any extra packages during the install since I'll do that later using Ansible.

After the installer does its thing, reboot the server and login with the username and password you created earlier.

Initial Configuration

Remote Login

Get the IP address of the server so we can login remotely:

jaswraith@positron:~$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 04:92:26:c0:e1:28 brd ff:ff:ff:ff:ff:ff inet 192.168.1.140/24 brd 192.168.1.255 scope global dynamic enp7s0 valid_lft 85934sec preferred_lft 85934sec inet6 fe80::692:26ff:fec0:e128/64 scope link valid_lft forever preferred_lft forever 
Enter fullscreen mode Exit fullscreen mode

The IPv4 address for interface enp7s0 is the first part of the inet field: 192.168.1.140. Your server's interface name might be different depending on your networking hardware.

Now we can login from a different machine using ssh:

jaswraith@photon[~]=> ssh jaswraith@192.168.1.140 Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-65-generic x86_64) ... jaswraith@positron:~$ 
Enter fullscreen mode Exit fullscreen mode

Note that I didn't have to enter my password since:

  • I imported my public SSH key from Github during installation.
  • I have the corresponding private key on my laptop (photon) under /home/jaswraith/.ssh/id_ed25519

I'm adding the entry 192.168.1.140 positron to /etc/hosts on my laptop so I can login using the hostname instead of the IP address:

jaswraith@photon[~]=> cat /etc/hosts # Static table lookup for hostnames. # See hosts(5) for details. 127.0.0.1 localhost ::1 localhost 127.0.1.1 photon.localdomain photon 192.168.1.140 positron 
Enter fullscreen mode Exit fullscreen mode

The network router on the local network needs to be configured to reserve the server's IP address so that it doesn't change after a reboot. My router is running dd-wrt, so I'll use that as an example (refer to your own router's documentation on how to set a non-expiring DHCP lease).

dd-wrt specific steps:

  • Navigate to 192.168.1.1 in a browser
  • Click on the "Services" tab
  • Under the "DHCP Server" section, click on "Add" under "Static Leases"
  • Enter the server's MAC address, IP address, and hostname (you can get this info from the router's "Status" page or by running ip addr on the server). Leave the "Client Lease Expiration" field blank so that the DHCP lease never expires.

dd-wrt
Adding a non-expiring DHCP lease for the server on dd-wrt

System Upgrade

Upgrade all installed packages on the server using apt :

jaswraith@positron:~$ sudo apt update Hit:1 http://us.archive.ubuntu.com/ubuntu focal InRelease ... Reading package lists... Done Building dependency tree Reading state information... Done 13 packages can be upgraded. Run 'apt list --upgradable' to see them. jaswraith@positron:~$ sudo apt upgrade Reading package lists... Done Building dependency tree Reading state information... Done Calculating upgrade... Done The following packages will be upgraded: dirmngr friendly-recovery gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm gpgv open-vm-tools 13 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. Need to get 3,133 kB of archives. After this operation, 114 kB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://us.archive.ubuntu.com/ubuntu focal-updates/main amd64 gpg-wks-client amd64 2.2.19-3ubuntu2.1 [97.6 kB] ... 
Enter fullscreen mode Exit fullscreen mode
  • apt update downloads the latest package information from all configured sources on the server. It also informs you how many packages can be upgraded.
  • apt upgrade actually upgrades the packages.

Storage Drive Configuration

Time for the fun stuff, let's configure those storage drives!

Check if the drives are recognized by the system with lsblk:

jaswraith@positron:~$ sudo lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop0 7:0 0 55.4M 1 loop /snap/core18/1944 loop1 7:1 0 31.1M 1 loop /snap/snapd/10707 loop2 7:2 0 69.9M 1 loop /snap/lxd/19188 sda 8:0 0 7.3T 0 disk sdb 8:16 0 7.3T 0 disk nvme0n1 259:0 0 465.8G 0 disk ├─nvme0n1p1 259:1 0 512M 0 part /boot/efi ├─nvme0n1p2 259:2 0 1G 0 part /boot └─nvme0n1p3 259:3 0 464.3G 0 part └─ubuntu--vg-ubuntu--lv 253:0 0 200G 0 lvm / 
Enter fullscreen mode Exit fullscreen mode

The two storage drives are listed as sda and sdb. NOTE: Drive names can be different depending on the type and amount of drives in your system.

Creating a RAID Array

I'm going to run the two storage drives in a RAID 1 array. Essentially, the two drives will be exact copies of one another and can act as a single device on the system. If one drive happens to fail, the data is still accessible from the good drive. The array can be repaired by replacing the bad drive and running a few commands. The downside is that the array can only be as large as the smallest member disk (8TB in my case).

On Linux, mdadm is the utility used to manage software RAID arrays. Use the following command to create the array md0 from the two disks:

jaswraith@positron:~$ sudo mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/sda /dev/sdb mdadm: Note: this array has metadata at the start and may not be suitable as a boot device. If you plan to store '/boot' on this device please ensure that your boot-loader understands md/v1.x metadata, or use --metadata=0.90 mdadm: size set to 7813894464K mdadm: automatically enabling write-intent bitmap on large array Continue creating array? yes mdadm: Defaulting to version 1.2 metadata mdadm: array /dev/md0 started. 
Enter fullscreen mode Exit fullscreen mode

NOTE: Make sure you use the correct drive names and DON'T use the OS drive!

Check that the array was successfully created:

jaswraith@positron:~$ sudo mdadm -D /dev/md0 /dev/md0: Version : 1.2 Creation Time : Tue Feb 16 02:51:43 2021 Raid Level : raid1 Array Size : 7813894464 (7451.91 GiB 8001.43 GB) Used Dev Size : 7813894464 (7451.91 GiB 8001.43 GB) Raid Devices : 2 Total Devices : 2 Persistence : Superblock is persistent Intent Bitmap : Internal Update Time : Tue Feb 16 02:56:33 2021 State : clean, resyncing Active Devices : 2 Working Devices : 2 Failed Devices : 0 Spare Devices : 0 Consistency Policy : bitmap Resync Status : 0% complete Name : positron:0 (local to host positron) UUID : dfe41661:16eb9d7f:ed891288:904746b0 Events : 58 Number Major Minor RaidDevice State 0 8 0 0 active sync /dev/sda 1 8 16 1 active sync /dev/sdb 
Enter fullscreen mode Exit fullscreen mode

The resync process is going to take a loooong time. We can track the progress by running watch cat /proc/mdstat:

jaswraith@positron:~$ watch cat /proc/mdstat Every 2.0s: cat /proc/mdstat positron: Tue Feb 16 03:03:05 2021 Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] md0 : active raid1 sdb[1] sda[0] 7813894464 blocks super 1.2 [2/2] [UU] [>....................] resync = 1.4% (116907456/7813894464) finish=739.5min speed=173457K/sec bitmap: 59/59 pages [236KB], 65536KB chunk unused devices: <none> 
Enter fullscreen mode Exit fullscreen mode

740 minutes?! This is going to take over 12 hours!

We can tweak some kernel parameters with sysctl to make this go faster:

jaswraith@positron:~$ sudo sysctl -w dev.raid.speed_limit_min=100000 dev.raid.speed_limit_min = 100000 jaswraith@positron:~$ sudo sysctl -w dev.raid.speed_limit_max=500000 dev.raid.speed_limit_max = 500000 
Enter fullscreen mode Exit fullscreen mode

Or I guess not...turns out that the reported speed of ~170000K/s is right around the documented read speed for my Seagate drives (190MB/s). Fortunately, we can still write to the md0 device during this process.

Configuring the md0 Array

Use mkfs to format md0:

jaswraith@positron:~$ sudo mkfs.ext4 -F /dev/md0 mke2fs 1.45.5 (07-Jan-2020) /dev/md0 contains a ext4 file system created on Tue Feb 16 04:07:25 2021 Creating filesystem with 1953473616 4k blocks and 244187136 inodes Filesystem UUID: 3e8 b5548-14c5-4fac-8e90-98cdbd71123f Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 102400000, 214990848, 512000000, 550731776, 644972544, 1934917632 Allocating group tables: done Writing inode tables: done Creating journal (262144 blocks): done Writing superblocks and filesystem accounting information: done 
Enter fullscreen mode Exit fullscreen mode

NOTE: The -F flag is needed since md0 is not a partition on a block device.

I opted to use the classic ext4 filesystem type instead of other ones (xfs, zfs, etc.).

In order to use the newly created filesystem, we have to mount it. Create a mountpoint with mkdir and use mount to mount it:

jaswraith@positron:~$ sudo mkdir -p /data jaswraith@positron:~$ sudo mount /dev/md0 /data 
Enter fullscreen mode Exit fullscreen mode

Confirm that the filesystem has been mounted:

jaswraith@positron:~$ df -h /data Filesystem Size Used Avail Use% Mounted on /dev/md0 7.3T 93M 6.9T 1% /data 
Enter fullscreen mode Exit fullscreen mode

The /etc/mdadm/mdadm.conf file needs to be edited so that the array is automatically assembled after boot. We can redirect the output of mdadm --detail --scan --verbose using tee -a to accomplish this:

jaswraith@positron:~$ sudo mdadm --detail --scan --verbose | sudo tee -a /etc/mdadm/mdadm.conf ARRAY /dev/md0 metadata=1.2 name=positron:0 UUID=dfe41661:16eb9d7f:ed891288:904746b0 
Enter fullscreen mode Exit fullscreen mode

Confirm that the md0 array configuration has been added to the end of /etc/mdadm/mdadm.conf:

jaswraith@positron:~$ cat /etc/mdadm/mdadm.conf # mdadm.conf # # !NB! Run update-initramfs -u after updating this file. # !NB! This will ensure that initramfs has an uptodate copy. # # Please refer to mdadm.conf(5) for information about this file. ... ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 name=positron:0 UUID=dfe41661:16eb9d7f:ed891288:904746b0 devices=/dev/sda,/dev/sdb 
Enter fullscreen mode Exit fullscreen mode

We also need to regenerate the initramfs image with our RAID configuration:

jaswraith@positron:~$ sudo update-initramfs -u update-initramfs: Generating /boot/initrd.img-5.4.0-65-generic 
Enter fullscreen mode Exit fullscreen mode

The last step is to add an entry for md0 in /etc/fstab so that the array is automatically mounted after boot:

jaswraith@positron:~$ echo '/dev/md0 /data ext4 defaults,nofail,discard 0 0' | sudo tee -a /etc/fstab /dev/md0 /data ext4 defaults,nofail,discard 0 0 jaswraith@positron:~$ cat /etc/fstab # /etc/fstab: static file system information. ... /dev/md0 /data ext4 defaults,nofail,discard 0 0 
Enter fullscreen mode Exit fullscreen mode

Note that I used the same mountpoint for md0 that was created eariler (/data).

Conclusion

So we've completed the intial setup of the server and created a RAID 1 array for storage. This server looks ready to run some services! Stay tuned for the next post where we'll define and run those services using Docker and Ansible.

Top comments (0)