Wednesday February 23 2022

Setting up Alpine Linux as a Home Router

I actually tried OPNsense before writing this article, I’ve also heavily used pfSense in the past professionally as well as personally. Though I haven’t installed pfSense recently, I don’t really like all of the added complexity and surface area for attack that they provide. Plus, they monopolize the underlying hardware, which while it isn’t so much of a concern for my immediate purposes is something worth noting.

So, with that out of the way, I’m going for a bare-bones system, that will run in RAM and have a couple of minimal shell scripts that drive configuration.

Hardware

Any machine with two network cards should work. These days most machines have USB 3 ports, and using a USB 3 ethernet adapter is fine too. Though you could technically do this on a Raspberry Pi and probably get away with it the installation is going to be quite different. I’m simply using an old laptop personally, it’s nice to have a screen, keyboard, and battery backup all in a single package. I’ve noticed that the fans in laptops tend to last longer than the ones in many mini computers.

Beyond that, you’ll need a flash drive for the installation medium and something to install to, be it another flash drive or the system’s hard drive/ssd.

Setup

Head on over to the Alpine Linux website and pick up the Extended ISO, x86_64. You can use the other images too, just note that it doesn’t come with the same packages I’ll be using to partition the disk.

Burn the image to a flash drive

On Linux:

# dd if=alpine-extended-3.15.0-x86_64.iso of=/dev/sdX bs=4M

On Windows I recommend using Rufus. On MacOS you’ll also use dd, look up how to find the correct of path.

Partitioning and installation

Boot to the flash drive. Login as root, there should be no password.

# apk add util-linux

The version of fdisk in util-linux supports GPT partition tables, which we will be using here for UEFI boot.

Replace sdX with the local hard drive, lsblk may give you some hints as to which one that is.

# fdisk /dev/sdX

Let’s create the GPT partition table:

Command (m for help): g

Now a new partition:

Command (m for help): n
Partition number (1-128, default 1): <Enter>
First sector (2048-30965726, default 2048): <Enter>
Last sector, <...trimmed...>: +2G<Enter>
# It may complain about signatures of old filesystems, answer `y` to wipe them.

Now lets set the type to UEFI:

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1<Enter>

One more partition for the rest of the disk: ( This will be used for /var )

Command (m for help): n
Partition number (1-128, default 2): <Enter>
First sector (4196352-30965726, default 4196352): <Enter>
Last sector, <...trimmed...>: <Enter>
# It may complain about signatures of old filesystems, answer `y` to wipe them.

Lets save the new partition table and exit fdisk:

Command (m for help): w

Create the filesystems:

# mkfs.vfat -F32 -n Alpine /dev/sdX1
# mkfs.xfs /dev/sdX2

Now lets mount the new UEFI partition:

# mount /dev/sdX1 /mnt

Now for a bit trickier part, we need to locate where the installation medium is, on my system it’s /media/sda. You may wish to check lsblk or mount for a bit more information on where the installation media is mounted.

Now, to copy over the files:

# setup-bootable /media/sda /mnt

Now reboot, and remove the installation medium, you should have a bone stock Alpine Linux system.

Setup

Now that we have Alpine installed to the disk, we can use their convenient setup script to further configure the system. Right now it operates in much the same way that the installation medium did, it boots and loads everything into memory. This is great, but we need a way to save our changes, this is where lbu will come in, but first, let’s get logged in as root:

Since this is going to be a router, we’re going to take a different approach to setting up the network interfaces, nftables and such, for now let’s just set up the link and grab an address via DHCP:

# ip link set up eth0
# udhcpc -i eth0

From there, let’s run the alpine setup script:

# setup-alpine

It will ask for network configuration, just type in done straight away, followed by n for the “would you like to do any manual network configuration” question. We’re going to cover this in a little bit.

The next few questions will be pretty straightforward. I used chrony for NTP and openssh for my SSH daemon.

The default alpine-cdn for my mirror.

When you get to:

No disks available. Try boot media /media/sda1? (y/n) [n]

I just hit n

Enter where to store configs ('floppy', 'sda1', 'usb' or 'none') [sda1]

sda1 is fine here.

Enter apk cache directory (or '?' or 'none') [/media/sda1/cache]

The default /media/sda1/cache Is fine here too.

Now that is all setup, type in lbu ci and reboot. If done properly the system should boot up and keep all of the configuration you just set. ( I’ll explain this more in a little bit, if you find you’re able to login without a password and the system appears unconfigured after a reboot, go back and check your work. )

Now lets setup the network again:

# ip link set up eth0
# udhcpc -i eth0

Get all of our packages upgraded:

# apk upgrade

Install packages for our basic router:

# apk add iptables ip6tables dnsmasq miniupnpd dhcpcd radvd curl

Now setup the local service for all of our scripts:

# rc-update add local boot

From here we’re going to clone down the Alpine Home Router scripts I created, they’re small enough to be easily read in 20 minutes or so, and I encourage you to do so, for now let’s just get them installed:

# curl https://git.riedstra.dev/mitch/alpine-home-router/snapshot/alpine-home-router-1.0.tar.gz | tar -C /tmp -xzvf -
# cp /tmp/alpine-home-router-1.0/etc/local.d/* /etc/local.d/

Now lets configure them:

vi /etc/local.d/vars.sh

You should be confronted with a file along the lines of:

# You can replace tty1 with ttyS0 for serial output
exec >/dev/tty1 2>&1
printf '\033[1;32m' # Green output for our scripts, comment out to disable.
echo "Starting script: $0"
set -x

wan=eth0 # WAN / ISP uplink interface, assumed ip is provided by DHCP
wan_hwaddr=EA:5D:EA:DB:EE:FF
lan=eth1 # LAN interface
# `sipcalc` is a useful program here
lan_ip=192.168.0.1
lan_net=192.168.0.0
lan_mask_bits=24
domain=router.local
dhcp_range=192.168.0.30,192.168.0.230,24h
# Specifically overrides upstream on `dnsmasq` for the DHCP clients
dns_servers="1.1.1.1 8.8.4.4"

The first line simply ensures that any output is dumped to the main boot terminal, you can comment it out if you want a more quiet boot process or to get output from the scripts when you call them directly.

The second line colors it green, entirely optional, you can comment it out.

From there the rest of the configuration should be self explanatory if you have a little bit a networking background.

If not, simply adjust wan to the interface that will have your modem plugged into it, and lan to the interface that will have your home network pugged into it. ( You can tell which one is plugged in with ip link, the ones unplugged with say “NO-CARRIER” )

Saving your changes

Because of how the system is loaded into ram on boot, you must explicitly save to disk any changes, this is done with a little program called lbu that writes out a simple tarball with your configuration files. The Alpine Linux initrd ( early boot ) will scan for an apkovl ( the aforementioned tarball ), if it finds one it will load it up into memory on top of what was there, effectively restoring your changes.

There’s a lot of documentation on the Alpine Linux wiki about lbu, and I encourage you to read it.

As quick rundown though:

# lbu st # shows the current status, no output is no changes
# lbu ci # commits current changes to disk
# lbu diff # show specifically the differences between now and the last commit

Anyway, go ahead and run lbu ci and reboot. When the system comes back up, if you have the WAN and LAN interfaces plugged in properly, you will have your very own Alpine Linux router.

Remote adminstration

Those familiar will know SSH and probably be off to the races configuring it. I’m not going to belabor and duplicate a bunch of the other high quality content out there, I’m just going to say:

Use an SSH key, do not enable password logins for root.

Anyway, the default lbu configuration does not include the /root/.ssh directory where you’d normally store the SSH keys, that’s easy enough to remedy:

# lbu include /root/.ssh

Then simply run a commit once you’ve added your SSH keys:

# lbu ci

Finishing touches and miniupnpd

From here if you want miniupnpd, simply enable the service:

# rc-update add miniupnpd default

Anything else you’d like add to the system should be straight forward enough run apk add <pkgname> configure it and run lbu ci to have your changes persist across reboots.

If you wish to configure a database, webserver, or something else with large amounts of persistent storage, simply create an entry in /etc/fstab for that XFS partition we created earlier. My advise? Copy the entire contents of /var into it, and then mount it there.