Alpine on a router

In this article I’ll show simple steps to install Alpine in a chroot environment on a SOHO router.

Motivation

Nowadays many SOHO routers are quite powerful devices. Probably, not as powerful in some aspects as latest Raspberry Pi but powerful enough to run several additional services besides basic routing functionality. In fact, many routers provide Samba and other file servers for years. Some powerful models contain even BitTorrent clients, DLNA servers, VPN clients and servers in the stock firmware.

On the other side, router vendors firmware is rarely ready for extension. Some vendors even lock their firmware so enthusiasts need to hack it first if they want to add anything to their devices. Luckily, there are many vendors which do provide telnet or ssh access to the device console.

Still, even if one gets to the console, those devices don’t contain any development tools and there are no vendor repositories for third party software. So enthusiasts need to create their own repositories. One of well known examples is Entware which can be used on many device brands, models and flavours.

It worth noting that router firmwares are usually quite different from a typical multipurpose Linux distribution. As a result, Entware adopts the practice of installing everything under /opt filesystem hierarchy. Installing software under a prefix like /usr/local is not something unknown in a *NIX world, however, the weird thing is that Entware can’t rely on “host” layout and libraries at all, so it needs even its own /opt/etc and its own C library, as host can use anything of uClibc, musl or glibc.

Does it remind you of anything? When one wants to run applications independently of the host system, they could run them in a virtual machine. Or in the modern Linux world, in a container.

Well, I’m not crazy enough to run Docker on a router (yet). But what about chroot? Some linux distributions have support for running in chroot. The distribution used for lightweight containers most often, Alpine, also has instructions how to setup a chroot.

But why, actually?

  • There’s enough spare power on the router to run some services 24x7 and I don’t need an additional box for that.
  • Current Entware repository for ARM x64 architecture contains about 2800 packages. Alpine contains more than 5000 packages in its “stable” repo and 14000 in “community”.
  • Entware is maintained by a few enthusiasts. Alpine has thousands of users in production including big enterprises so it gets updates faster.
  • Entware adopts some compromises to run on a low-resource hardware in a non-standard system layout and without conflicts with the firmware. Alpine is purposely built for low resource usage but follows standard layout.
  • Alpine uses a lightweight musl C library (which has its own downsides).
  • Alpine is used by many Docker images so if I need to run some application which is not packaged for Alpine, it’s likely it has an Alpine-based Docker image so the recipe can be extracted from it.

Okay, let’s give it a try

My router runs a so called Asuswrt-merlin firmware. I already have an attached USB drive formatted to ext4 and Entware installed on it.

Probably, it should be possible to follow instructions from the Alpine wiki step by step without installed Entware. However, someone already scripted that. Unfortunately, the script uses some utilities not provided by the limited router firmware so I need Entware.

I’ve made a couple of modifications so I don’t need additional configuration of the script for my router, they are in in the aarch64 branch of my fork. See the README there for more details.

Disclaimer: I’m writing these instructions from memory. I did this a couple of times but something can be missing. I’ll fix that when I’ll use my own recipe next time.

So, first I need to install some Entware packages for that script to work:

opkg install coreutils-id coreutils-mktemp bash coreutils-sha256sum

You may probably need to install curl also because router wget may lack HTTPS support.

Download the script with wget or curl.

wget https://github.com/pelepelin/alpine-chroot-install/raw/aarch64/alpine-chroot-install
chmod +x alpine-chroot-install

Find the path for the installation. In the example below the storage is mounted as /mnt/disk so the command will install Alpine under /mnt/disk/alpine which should not exist before installation.

./alpine-chroot-install -d /mnt/disk/alpine

It should download and install a minimal alpine environment. I can chroot into it with an installed script:

/mnt/disk/alpine/enter-chroot

Exit chroot by exiting the shell.

Further steps

Now I have a way to enter the chroot environment, play with Alpine packages there and do all the things.

Unfortunately, once the router is rebooted, mount points created by install script will disappear. So I need to put their creation into the enter-chroot script (and ultimately, fix it in the install script). Unmount script (called destroy) is not yet robust too.

Chroot environment provides only limited isolation. First problem I’ve found is that ID mappings used by the router in its /etc/passwd and /etc/group don’t match Alpine base ID mappings. I had to find some compromise. In theory, it should be possible to use cgroups for ID mapping and probably more isolation, up to the level which is provided by Linux containers but I’m not sure if I really need it. Anyway, Entware provides unshare utility to play with that.

On the other side, it would be good to make installation script independent of Entware utilities.

If I want any Alpine service to auto-run when the router boots up, I need to hook it into router scripts. AsusWRT-Merlin firmware provides a lot of hooks for that. At least, I’ll use post-mount, unmount and services-stop.

For starting and stopping services Alpine uses OpenRC init system. OpenRC provides some support for chroot environment but that requires additional configuration.

Written on October 19, 2023