After having a couple Raspberry Pi’s doing miscellaneous things, and working my way up to a Plex server where I needed real storage and video transcoding crunching power, it was time to do something real. But I didn’t want to spend a lot of money. Here’s what I did.
Hardware
I wanted a decent machine that had more horsepower than a Pi, a 64-bit CPU, space for multiple mass storage 3.5″ internal drives, was relatively quiet, and relatively inexpensive. A used PC tower would fit this bill nicely. After some hunting around on Newegg for refurbished systems, I settled on a Dell Optiplex 790 mini tower. For $168 I got a machine with space for 4 hard drives (2×3.5 + 2×5.25), a 2nd-gen Core i5 at 3.10GHz, 8GB RAM, 4 internal SATA ports, and gigabit ethernet. It did come with a single 500GB drive with a fresh copy of Windows 10 Pro preinstalled, but I replaced that with a used 2TB disk I had laying around the house upon which I installed Ubuntu 20.04 LTS to make this a headless server (no monitor/keyboard attached, just sshd).
For mass data storage I bought two Seagate Ironwolf 8TB NAS drives at $200 each. The NAS drives are designed to be on consistently and accumulate a lot of spin time, as opposed to a generic PC disk that are expected to be powered off when not in use.
Then I did some re-arranging of the hard drives.
First, take the OS disk that is in the bracket at the bottom of the case, and mount it in the 5.25 bay right below the DVD drive using a 5.25-to-3.25 adapter like this one. Now the two drive brackets at the bottom of the case are empty, which I’ll get to in a minute.
Second, pull out the DVD drive that came with that refurbished PC. Replace it with a bracket for $20 that will allow a random 3.5″ internal drive to slide in to a hot-swap SATA slot. That way I can use other various internal-style drives on-demand without having to open the case. Useful for wiping that stack of hard disks from previous PCs that I’ve been meaning to do. Ubuntu includes support for NTFS filesystems, so a simple mount command can make Windows data available here to migrate in to my mass storage disks. To bring the inserted drive online without a reboot you can use the command “/usr/bin/rescan-scsi-bus.sh” as root from the sg3-utils package via “apt install”. But to do so, I believe this bracket needs to have been powered on at boot time.
Third, now that the two hard disk brackets are open at the bottom of the case, that is where I want my two mass storage drives to go. I plan to run them in a mirrored configuration, to protect against drive failure. So I aim to put the two Ironwolf drives there. But Dell uses a blue plastic caddy tray to hold the disks here instead of old-school screws, and only one caddy is present. No worries, I can get another one just like it from Newegg marketplace for $12.
For the SATA cables, I attach the OS disk to SATA port 1 on the motherboard, the two mass storage Ironwolf drives to SATA port 2 and 3 on the motherboard, and the hot-swap bracket to SATA port 4. That way when I use the hot-swap drive and power it on and off using the built-in switch on the front of it, it will get assigned the /dev/sdd device name at the end of the list, and not impact the sda assignment for the OS disk or the sdb/sdc assignment for the mass storage disks.
After I unboxed the refurbished Dell upon arrival, it wouldn’t boot up to the POST / BIOS menu. I was worried I was going to have to return it as being DOA. As a bit of a wild guess I started re-seating the internal components, and after re-seating the RAM DIMMs, it finally booted. Easy enough for a $168 server.
Operating System
It’s got to be Linux. It’s what I’m familiar with, is highly configurable, and has lots of network application software. For the sake of ease, I chose Ubuntu 64-bit server 20.04 LTS so that I can have some stability without having to do major upgrades. Running “apt update + apt upgrade” is pretty darn easy. Download the ISO from the web site (“Manual server installation”), write it to a USB thumb drive with Etcher, boot the PC from the thumb drive via the BIOS menu, install to the OS hard disk, and in short order I’ve got Ubuntu up and running. During installation, select the “Minimal installation” because you don’t need a lot of user apps and graphics for a headless server. Create a login id and don’t forget the password.
Now for the mass storage drives. I’d like them to be mirrored, so in case one drive has a catastrophic failure, my data is still intact. I’ve heard lots of good things about ZFS, so let’s give that a try. It’s powerful but can be simple. And it helps that Ubuntu has support for ZFS and instructions for setting it up. I create a mirrored pool with sdb and sdc, use the “lsblk” command to verify the device names, looking for the ~8TB devices:
$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop1 7:1 0 55.4M 1 loop /snap/core18/1944 loop2 7:2 0 55.4M 1 loop /snap/core18/1932 loop3 7:3 0 69.8M 1 loop /snap/lxd/19032 loop4 7:4 0 31.1M 1 loop /snap/snapd/10707 loop5 7:5 0 31.1M 1 loop /snap/snapd/10492 loop6 7:6 0 69.9M 1 loop /snap/lxd/19188 sda 8:0 0 1.8T 0 disk ├─sda1 8:1 0 500M 0 part ├─sda2 8:2 0 465.3G 0 part └─sda3 8:3 0 1.4T 0 part / sdb 8:16 0 7.3T 0 disk ├─sdb1 8:17 0 7.3T 0 part └─sdb9 8:25 0 8M 0 part sdc 8:32 0 7.3T 0 disk ├─sdc1 8:33 0 7.3T 0 part └─sdc9 8:41 0 8M 0 part
$ apt install zfsutils-linux $ [...commands I forgot to write down to create the pool and mount points] $ zpool status pool: pool0 state: ONLINE scan: scrub repaired 0B in 0 days 02:05:31 with 0 errors on Sun Jan 10 02:29:33 2021 config: NAME STATE READ WRITE CKSUM pool0 ONLINE 0 0 0 mirror-0 ONLINE 0 0 0 sdb ONLINE 0 0 0 sdc ONLINE 0 0 0
errors: No known data errors $
Now I create the ZFS datasets, all under the /data mount point. Here is how it looks when done:
$ zfs list NAME USED AVAIL REFER MOUNTPOINT pool0 1.73T 5.31T 104K /data pool0/logs 2.08M 5.31T 2.08M /data/logs pool0/plex 1.73T 5.31T 1.73T /data/plex pool0/preserve 376M 5.31T 376M /data/preserve pool0/smbshare 36.7M 5.31T 36.7M /data/smbshare marcelk@filesvr:~$
Applications
Now what server applications to install and use? This is where it gets to be fun.
Plex
First, the main driver for this server is for Plex. I started off playing with Plex server on the family Windows PC for music with their free license to try it out. But I waited until Plex had a sale on their Lifetime Plex Pass, had a good experience and am ready to move up. So, start with installing Plex from the official repository via apt, and I can automatically get updates too, which seem to come out every couple of weeks. Then I shutdown the trial one on the Windows PC, and move all my content over into the usual Plex naming scheme for directories and files and for extras.
Why Plex? For starters, for music management. Instead of using a subscription service where I can pick any song and stream it, I have pretty specific tastes. I like to buy MP3s online and stick with those. I buy only a couple new songs a month that I like, which is much less money than a subscription service. And I’ve ripped my old collection of audio CDs. My main use case is that I don’t stream music to my phone to play, but I’d rather download the whole library and play locally, so that I don’t incur cell data usage when in my car or otherwise out-and-about. (I get by on 1GB of cell data a month, pre-pandemic.) Rip your CDs into the lossless FLAC format, and Plex will automatically compress it down to the desired bit rate in an MP3 file for local synchronization to your portable device. And with Google discontinuing Google Play Music, where I had uploaded my MP3 library for syncing back to my phone, and liked it, I wanted to exit cloud-based providers and just manage it myself. Plex offers a pretty good system for managing your music on a local server, and syncing to devices. I hadn’t really thought much about ripping movies to host in Plex, but after a trial that looks to work pretty good too. So might as well get a Blu-ray drive for the family PC where I can run MakeMKV in Windows to rip DVDs and Blu-ray discs and host those in Plex too. A ripped DVD is about 5GB, and a ripped Blu-ray is about 30GB. So in 1TB I can fit about 166 DVDs or 33 Blu-rays. I can run the Plex app on my Roku to watch these movies on my big screen TV in HD, or on any other local device using the Plex app, or on a PC using the browser interface. Because my TV’s old-school physical Blu-ray disc player does such a good job automatically up-sampling a DVD (480p) to HD (1080p), it’s still best to use the DVD discs on that big screen. But on a phone or tablet, the lack of upsampling is not very noticeable.
And I should mention since I don’t have cable TV, I’d like to be able to record over-the-air TV with an antenna and have it appear in Plex as a recorded TV show. That is pretty easy to do with a device like the HDHomeRun Connect Duo for $100. In retrospect, since I have stations in my area broadcasting in ATSC 3.0, I should have upgraded to the HDHomeRun Connect 4K for another $100 more. Once I have the Plex server up and running, it is really easy to get the Plex server to send commands to the Connect Duo to have it record shows based on the schedule I set up in Plex, copy the show from the Connect Duo stream and store it on Plex, and play it back on-demand in Plex. It’s a DVR!
And as I digitize some old family photo albums with a flatbed scanner, I can host those in Plex also. (My phone camera photos have been going into Google Photos, but maybe I’ll export them down to my Plex server too.) So I’m using Plex for music, playlists, movies and other shows from discs, recorded TV shows, live TV, and some photos.
Plex does have the capability to enable remote access to your server, to allow your phone or other devices to reach your Plex server on your home network from the outside. I did not enable that, because I don’t want to have any possibility of a connection being initiated from the outside to be able to reach inside my home network. That is what my firewall is for (accept established+related, otherwise drop). Yes, Plex will say that their solution is good, but all software has bugs. Here’s a recent one. You may be willing to take the risk to get the benefit, but I’m not, and instead will use Plex’s sync capability so I can play music and pre-selected video content while away from home, instead of making it reachable from the Internet for streaming.
httpd: nginx
Next, of course I’ll need a general purpose web server for whatever documents I want to have available on my home network. Nothing fancy, perhaps a home page with pointers to other places, such as the webgui admin consoles for my devices. For that I’ll choose nginx. And I’ll use the default index.html in /var/www/html for now, and let it appear on port 80. I don’t really care about SSL inside my home network, so I won’t bother to set up port 443 service and certificates. Since the Plex server runs its web interface on port 32400, might as well as add that as a first link in the index.html file on my new home page.
I do have a collection of product manual PDFs that I’ve collected, so I don’t need to keep the paper copies around. Let’s upload those to the /data/preserve/Manuals directory, and add a location stanza to the /etc/nginx/sites-available/default
location /manuals/ {
alias /data/preserve/Manuals/;
autoindex on;
autoindex_exact_size off;
}
WiFi AP Manager
I use WiFi access points in Ubiquity’s Unifi product line. They require a manager application (controller) for configuration, reports, and software updates. It is not small. I had been running that controller on a Pi 3, would be nice to consolidate it on this server. I install that application from their repository so I continue to easily get updates via apt. They seem to update every couple of months. I export the controller’s config to a file, copy that file from the Pi 3 to my new server, import it into the new controller, and shutdown the old controller on the Pi 3. This webgui runs over https on port 8443, might as well add that as another link in the index.html on my nginx home page.
Unfortunately, the Unifi manager requires Java 8. Yes, version 8. It’s a good app with a bad pre-req. So amend the instructions to include this:
# apt install openjdk-8-jre-headless # apt-mark hold openjdk-11-* # apt install unifi # service unifi start
mDNS / Avahi
This is something you need to install, but it will “configure” itself via the applications that use it. Avahi provides an implementation of multicast DNS (mDNS) and DNS Service Discovery (DNS-SD), perhaps better known as Bonjour or Rendezvous or zeroconf. The avahi-daemon is probably already installed, and there really isn’t any config you need to do. Want to use the avahi client to see what services are on your LAN? (Use ctl-C to stop after a while, it will keep listening forever)
$ avahi-browse --all --no-db-lookup $ avahi-browse --all --resolve
- _googlecast._tcp: devices that can you can Google-cast to
- _ipp._tcp: CUPS printer (Internet Printing Protocol)
- _afpovertcp._tcp: Apple File Sharing / AFP server
- _smb._tcp: Samba server or Windows file sharing
Windows File Sharing: Samba
Yes, there is a family / gaming PC running Windows 10, so some kind of Windows file sharing (SMB) is needed. And honestly, MacOS seems to play decently with SMB, so it is a good lowest-common-denominator. And heck, the Linux SMB client works too. Follow these instructions to get Samba server set up. I use pretty much the default config, and edit /etc/samba/smb.conf
to add the following shares. The first is just a general scratch space that is readable and writable by everyone (easier than a USB stick), and the other two are simply read-only raw access to the ripped movies (mkv) and music (mp3) files.
[share]
comment = Shareable Space
path = /data/smbshare
browseable = yes
read only = no
guest ok = yes
create mask = 0666
directory mask = 0777
[music]
comment = Music MP3s
path = /data/plex/Media/Music
browseable = yes
read only = yes
guest ok = yes
[movies]
comment = Movie Discs
path = /data/plex/Media/Movies
browseable = yes
read only = yes
guest ok = yes
Apple file sharing / AFP / netatalk
My spouse is an Apple fan: a big iMac desktop, iPhone, a couple iPads, AppleTV, you get the idea. Would be nice to provide Apple file shares for her, especially when backed by the same common storage as Samba for cross-platform sharing. I could set my server up to also act as a Time Machine backup server for her too, but she has enough local USB drives to handle that. Netatalk will give me the Apple File Protocol (AFP) server on Linux.
$ sudo apt install netatalk
Then edit /etc/netatalk/afp.conf. Here is what mine looks like, I didn’t bother to enable Time Machine. And note that the “mimic model” value will make my server appear on my wife’s client as an icon of a rack-mounted Mac, using the name in the “zeroconf name” setting. (Hmm, this feels similar to the Samba configuration.)
[Global]
; Global server settings
uam list = uams_guest.so, uams_dhx.so, uams_dhx2.so
guest account = nobody
mimic model = RackMac
zeroconf name = file server
; log level = default:debug
[Share]
path = /data/smbshare
case sensitive = yes
read only = no
file perm = 0666
directory perm = 777
[Plex Music files]
path = /data/plex/Media/Music
case sensitive = yes
read only = yes
[Plex Movie files]
path = /data/plex/Media/Movies
case sensitive = yes
read only = yes
Logging
And since we have plenty of storage here, might as well set up a logging server so all my logs are centrally located. So rsyslogd will take care of that for me.
UPS / Battery Backup
I have a UPS (battery backup) connected to my broadband modem, firewall, and one WiFi AP. So in the event of a power loss, I’ll still have internet+WiFi for a bit, at least for about 6 hours until the battery runs out. Since this server is physically located right there, it would be nice to integrate my server to the UPS, so I can automatically gracefully shutdown my server when the power is out for more than 60 seconds. I have an APC BackUps 1500, and attached the cable that came with it to a USB port on my server. Here is the config I use for that:
# apt install apcupsd
# vi /etc/apcupsd/apcupsd.conf
UPSNAME 1500
UPSCABLE usb
UPSTYPE usb
DEVICE
TIMEOUT 60
# apcaccess status
Printer Server
Because my family laser printer is located elsewhere, out of reach for a USB cable, I have a Raspberry Pi with Ubuntu acting as a print server for all my machines. CUPS is the way to do it. I can print from Windows, MacOS, Linux, iOS, and Android. I still find it pretty cool to print to paper from my phone, I’m old school.
DHCP with static assignments
This part may be a case of me being a neat freak. I currently have 41 devices on my home network, all of them set up for getting an IP address dynamically instead of statically. So if something goes wonky and I see an IP address behaving weird, I’d like to know what device it maps to. For the router I use (a Mikrotik hEX, which is awesomely configurable and performant for my 1GB fiber ISP) I can show the list of active DHCP leases, and convert each one to a static IP address over DHCP based on its MAC address. And I can put each one within a chosen range. The current ranges I have are for local network infrastructure (i.e., router, smart switches, WiFi access points), server / PCs / printers (including Chromebooks and laptops), media devices (Chromecasts, Rokus, etc), and smartphones / tablets. Every once in a while I’ll look to make sure the list of dynamically assigned IP addresses outside my static ranges is empty.
Cleanup
With Ubuntu installed to bare metal instead of as a virtual image, I don’t need cloud-init running, and the messages get a little bit annoying. So remove it.
Assuming you’d like to get access to a shell prompt on this server remotely within your LAN without a physical console, apt install sshd
. I also like to add to /etc/ssh/sshd_config
the line PermitRootLogin no
just for safety, which means you’ll need to ssh in as a regular user before doing an su or sudo, no direct login as root.
If you are like me and running it as a headless server without a monitor/keyboard/mouse attached, you can disable the graphical UI (X windows) and get the resource savings for that. But since you’d still like to login on the rare occasion with a monitor/keyboard in case the network is inoperative, you can enable a getty (text-only) login prompt on /dev/console. Me being old school, I also like to the see the boot progress on the console, in case anything goes wrong.
# systemctl get-default
graphical.target
# systemctl set-default multi-user
Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target.
# systemctl get-default
multi-user.target
Then edit /etc/default/grub
and make the following changes, then run update-grub
and reboot:
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_TERMINAL=console
GRUB_GFXMODE=1920x1080