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.