Installing Devuan GNU+Linux or Debian GNU/Linux on ARM64-based SbCs that are not officially supported


If you want to self-host stuff at home using an ARM64-based board running Linux, both Debian GNU/Linux and Devuan GNU+Linux are excellent distribution choices. Unfortunately, they officially support only very few ARM64-based boards. Yet, you can run them unmodified on a lot more boards - if you manage to install them. After discussing some basics and showing how to check the support status for a particular board, this article describes my recommended way for performing the actual installation by a concrete example.

Why use official distributions?

Before we dwelve into details, let's discuss some basic things first. I often hear the argument that people want to solve basic stuff like installing the OS as quickly as possible and focus on the more interesting stuff in the layers above. Usually, these people end up downloading and flashing one of the countless unofficial OS images from the Internet. What's wrong with that?

Choosing a good base OS for your setup is the foundation for running a trustworthy and secure system. Sure, this might be irrelevant for a quick and dirty test setup. However, if you want to run your system for years and put personal data on it, this aspect is critical.

Personally, I discourage using unofficial images for a number of reasons:

  • Many unofficial images bundle additional software that is not packaged in the official repositores. Therefore, you never get updates for them. This is bad, especially for critical components such as the kernel.
  • In addition to trusting the upstream project (such as Debian), you also have to trust the projects and individuals that built the images.
  • Apart from trusting these third parties, you also have to trust their build infrastructure.
  • Setting up the system yourself, you can optimize the setup to your needs from the very beginning.

On the other side, running an official distribution, you can be sure to get the best “vanilla” experience. In addition, all the base software for your system comes from official sources and was build on their official infrastructure.

Unofficially supported boards

The Linux kernel supports a variety of ARM64-based boards. Unlike x86 machines, ARM64-based boards require a board-specific device tree binary (dtb) that is built from source. As Debian aims to be an universal OS, it ships a generic ARM64 kernel that enables support for as many of these boards as possible by shipping dozens of dtbs. This is also true for Devuan since it uses Debian's unmodified kernel.

The current OS versions (Debian 10 and Devuan 3) ship version 4.19 of the Linux kernel. To look them up the boards they theoretically support, do the following:

  • visit to packages.debian.org.
  • use “search package directories” with “only show exact matches” and search for the package linux-image-arm64 in the stable distribution.
  • hit the search button and click on the result.
  • since linux-image-arm64 is a metapackage that depends on the current kernel, check which version it is (as of now, it is linux-image-4.19.0-9-arm64). Click on this package to show more details.
  • click on “list of files” to see all files contained in this actual kernel package and check the dtb files listed there.

If your board is listed here, chances are good you will be able to run the official distribution of Debian or Devuan on it.

If your board is not listed, don't give up yet - it might still be supported by a newer Linux kernel. Later in this tutorial, I will show how you can install a newer kernel in the current stable release and get regular updates for it. However, this only works if your board is supported at least in the “unstable” release.

Don't worry too much about the name “unstable” - the kernels in Debian unstable are based on well-tested kernels from upstream and usually work well. They are updated frequently and, thus, receive security updates. While I consider them okay as a daily driver, they are not as stable as the kernels from the stable distribution.

To check if your board has support in the unstable kernel, repeat the previous steps but choose “unstable” instead of “stable” for the distribution. If your board is still not listed, then you are out of luck for now as your board is currently not supported (but it might get official support in the future).

Here is a small list of boards I tried this approach on using the current 5.7 kernel and U-Boot 2020.07:

Manufacturer Make/model Remarks
Olimex A64-OLinuXino works (only kernel 4.9 works reliably, needs dtb from 5.4, see ยน)
Pine64 Rock64 works mostly (issues with USB 3)
Pine64 RockPro64 works partially, usable only from eMMC (issues with sdcard and USB 3)
Pine64 Pinebook Pro works very degraded (internal display does not work, issues with sdcard and USB 3)
Xunlong OrangePi Zero Plus works

1: Use the stable kernel (4.9) but replace the DTB file with the one from 5.4 (otherwise ethernet is broken) and add the kernel commandline parameter console=ttyS0,115200 to disable the HDMI output. With HDMI output enabled and running newer kernels, the device hangs up as soon as the HDMI output turns the display off (after aprox one minute). Also, since Kernel 5.7, LUKS password prompts go to HDMI on this board and are not displayed on the serial console.

Installation

You have a board that is at least unofficially supported by the unstable kernel (see previous section)? Great - let's get our hands dirty!

In the following, I will assume you want to install Devuan 3 on a RockPro64 by Pine64 [1]. You can also use this guide with small variations if you want to install Debian instead of Devuan or use a different hardware.

Concept

My installation concept covers the following stepts:

  • Installing a device-independent base system using the official installer
  • Customizing the base system
  • Adapting the base system for the target
  • creating tar archives for boot and root
  • building the bootloader (device-specific)
  • assembling a device-specific image
  • flashing the assembled image

This might seem a lot of work, but note that the firsttwo steps are device-independent. Thus, won't have to redo them when installing other devices in the future.

Note: This concept borrows and enhances some ideas from my previous debootstrap-based approach.

Requirements and assumptions

Apart from having an unofficially supported board, I assume you have basic knowledge about the following:

  • creating and operating virtual machines with KVM/QEMU using virt-manager
  • operating your ARM64 board using the serial console

In addition, you will need:

  • a dedicated physical x86 machine with a fresh installation of Debian GNU/Linux 10 or Devuan GNU+Linux 3 with at least 2 GiB of RAM and 20 GiB of storage
  • a user account with a user named youruser
  • an empty microSD card with a capacity at least 4 GiB
  • a working internet connection

Of course, the tutorial should also work with other systems, distributions and environments but some steps may differ. Also, note that using a VM is only possible if you enable nesting as we will exercise the ability to run ARM64-VMs ourselves. Using very old hardware (e. g. a single-core 32bit machine) is theoretically possible but practically infeasible

Disclaimer

All data and information provided in this tutorial is for informational purposes only. The author makes no representations as to accuracy, completeness, currentness, suitability, or validity of any information on this tutorial and will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use. All information is provided on an as-is basis.

In no event, the author will be liable for any loss or damage including without limitation, indirect or consequential loss or damage, or any loss or damage whatsoever arising from loss of data or profits arising out of, or in connection with, the use of this tutorial.

Step 1: Installing the base system

First of all, let's create a directory where we will store all the assets produced in this tutorial:

mkdir -p /home/youruser/assets

Then, download the mini ISO for arm64 from the following location:

https://pkgmaster.devuan.org/devuan/dists/beowulf/main/installer-arm64/current/images/netboot/mini.iso

If you want to have Debian instead of Devuan, use the following:

http://deb.debian.org/debian/dists/stable/main/installer-arm64/current/images/netboot/mini.iso

Make sure you also have qemu and virt-manager installed:

apt install qemu-efi-aarch64 qemu-system-arm virt-manager

Next, create a new virtual machine using the aarch64 architecture as shown here:

screen010

In the next step, choose the mini.iso you downloaded previously as ISO and select “Debian 10 (stretch)” as OS. Give the VM the default of 1 GiB of RAM and as many cores as you have. Next, create a disk image with at least 5 GiB (or stick with the default) and leave the default network settings (using NAT). Finally, fire up your new VM. You should get a screen like mine:

screen020

After selecting the first option, the “oldschool” text installer should boot and give you the following screen:

screen025

Now run through the installer but follow these guidelines:

  • choose a generic hostname and domain name (we want to build a generic, reuseable image)
  • choose simple passwords for root and the user you create (again, we want to build a generic image)
  • use deb.devuan.org as pkg mirror
  • partition the disk to have the following layout:
    • Partition #1: Size 500M, type ESP (EFI Firmware), no name, no mountpoint, bootable, name “efi”
    • Partition #2: Size 500M, type ext4, mountpoint /boot, name “boot”, not bootable
    • Partition #3: Size maximum, Type physical volume for encryption, name “rootencrypted” no mountpoint, not bootable
    • Unside the encrypted volume, create a single ext4 partition spanning the whole outside partition with type ext4 and mountpoint /, name root, passphrase abcd (this will ease further setup)
    • no swap
  • when installing packages, unselect all packages for installation in tasksel (our aim is to get a minimal image, we will install specific packages later as we need them).
  • regarding init, the approach should work with both sysvinit and openrc (choose what you prefer).

After installing, the system should boot up as shown here:

header

Step 2: Customizing the base system

Fix slow shutdowns

(only needed in Devuan)

Due to a bug in Devuan 3 you need to replace the cryptsetup-functions script as follows:

apt install --no-install-recommends wget ca-certificates
cd /lib/cryptsetup
mv cryptdisks-functions cryptdisks-functions.ORIG
wget https://git.devuan.org/devuan/cryptsetup-modified-functions/raw/branch/master/cryptdisks-functions

f2fs

While the installer does not support installing to f2fs filesystems, f2fs is the filesystem I recommend for flash media. Add the module to /etc/initramfs-tools/modules:

echo "f2fs" >> /etc/initramfs-tools/modules
echo "crc32" >> /etc/initramfs-tools/modules

Install this package to get support for it:

apt install f2fs-tools

Time synchronization

Most SbCs don't have a backup battery for their realtime clock (RTC). This results in the date and time being wrong each time you disconnect power. In general, even if your board has a backup battery, it is a good idea to setup time synchronization.

Synchronizing date and time can be done using the NTP protocol. Debian ships several NTP clients you can choose from. Personally, I recommend OpenBSD's openntpd as it is very lightweight and has been developed with security in mind. To install it:

apt install openntpd

It should start and also run on the next boot. In case you want to test it or sync manually, you can invoke it as follows:

ntpd -s -d

DTB file handling

Since we want to use a separate /boot partition later, we need to make sure the dtb files for the current kernel are there. Therefore, we create a dtb directory there first:

mkdir -p /boot/dtbs

And add the following script that copies the dtb files automatically each time a new kernel is installed or upgraded:

cat <<'EOF' >> /etc/kernel/postinst.d/copy-dtbs
#!/bin/sh

set -e
version="$1"

echo Copying current dtb files to /boot/dtbs....
cp -a /usr/lib/linux-image-${version}/. /boot/dtbs/
EOF

Let's make this script executable:

chmod +x /etc/kernel/postinst.d/copy-dtbs

And run it once manually to copy the dtb files for the currently installed kernel:

/etc/kernel/postinst.d/copy-dtbs `uname -r`

Bootloader configuration

Create a directory to hold the bootloader configuration:

mkdir -p /boot/extlinux

And create an extlinux boot configuration file (the bootloader u-boot looks them up):

cat <<'EOF' >> /boot/extlinux/extlinux.conf
TIMEOUT 2
PROMPT 1
DEFAULT devuan

LABEL devuan
MENU LABEL Linux devuan
KERNEL /vmlinuz
INITRD /initrd.img
DEVICETREEDIR /dtbs
APPEND root=LABEL=root cryptopts=source=LABEL=rootencrypted,target=root_crypt,luks
EOF

Kernel from unstable (optional)

As discussed previously, it might be necessary to run the kernel from the unstable distribution in order for your board to work. In addition, some board features might work better with kernels from unstable.

Note: Some people recommend installing kernels from backports instead of from unstable. However, I discourage taking this approach as the kernels in backports often receive security updates quite late.

So, which kernel should you choose if your board is supported in stable as well? It depends. The only advice I can give is: If all features of your board that are relevant to you work with the stable kernel, stick with it. If not, try the unstable kernel.

To install the unstable kernel, we need to first add the unstable distribution to apt:

cat <<'EOF'>>/etc/apt/sources.list.d/unstable.list
deb http://deb.devuan.org/merged unstable main
EOF

And specify that we only want to install the kernel from unstable (and no other packages):

cat <<'EOF'>>/etc/apt/preferences.d/99unstable
# Never prefer packages from unstable
Package: *
Pin: release a=unstable
Pin-Priority: 100

# Allow upgrading kernel from unstable
Package: linux-image-arm64
Pin: release a=unstable
Pin-Priority: 500
EOF

Finally, we can safely upgrade the kernel:

apt update
apt install -t unstable linux-image-arm64

Reboot

Before we continue to the next step, let's reboot the system:

reboot

Step 3: Adapting the base system for the target

The following changes are “devastating” and will make the VM unbootable or “less unique”. Therefore, I recommend backing up its disk image before applying them (a snapshot would be even better, but KVM on Devuan3 does not seem to support them on aarch64).

Remove GRUB

Remove the grub bootloader (we will not use it):

apt autoremove grub-efi-arm64

SSH servers (openssh and dropbear)

Install the regular OpenSSH server together with dropbear for unlocking the machine remotely during boot:

apt install --no-install-recommends openssh-server dropbear-initramfs

Change the dropbear port so we don't run into conflicts with the main server (I recommend using different SSH keys for the main and auxiliary SSH server) and add a timeout:

cat <<'EOF' >> /etc/dropbear-initramfs/config
DROPBEAR_OPTIONS="-p 4748 -I 60"
EOF

Remove the insecure dss certificate key automatically created by dropbear:

rm /etc/dropbear-initramfs/dropbear_dss_host_key

Create an authorized-keys file that limits access to running the cryptsetup binary in dropbear (put your actual SSH public key here):

cat <<'EOF' > /etc/dropbear-initramfs/authorized_keys
no-port-forwarding,no-agent-forwarding,no-x11-forwarding,command="/usr/bin/cryptroot-unlock" ssh- ...
EOF

For a reason I yet have to investigate, the cryptroot-unlock scripts does not find the crypttab file in the initramfs and bails out with the message “Try again later”. As an ugly hotfix, edit the file /usr/share/cryptsetup/initramfs/bin/cryptroot-unlock and comment out the line that does the exit 1 command so it looks as follows:

# Too early, init-top/cryptroot hasn't finished yet
echo "Try again later" >&2
#exit 1

To set a static IP address that usually works more reliable than DHCP (replace the placeholders):

cat <<'EOF' > /etc/initramfs-tools/conf.d/staticip
IP="<client-ip>::<gw-ip>:<netmask>::eth0:off"
EOF

Finally, renegerate the initramfs:

update-initramfs -u

Configure the network

If you don't want to use DHCP for the regular system either (recommended), make sure you edit /etc/network/interfaces accordingly.

Restrict access to the system

Delete the root password:

passwd -d root

Delete the default user:

userdel -r user

Use the keys authorized for dropbear for system access as well:

mkdir /root/.ssh
chmod 700 /root/.ssh
cp /etc/dropbear-initramfs/authorized_keys /root/.ssh/
chmod 600 /root/.ssh/authorized_keys

Afterwards make sure you edit the /root/.ssh/authorized_keys file to remove the restrictions (each line should start with ssh-.

Replace fstab and crypttab

Replacing crypttab and fstab should be done as the last steps, otherwise regenerating initramfs might cause troubles. Replace the fstab as follows:

cat <<'EOF' > /etc/fstab
# <file system>        <mount point>  <type>  <options>                   <dump>  <pass>
/dev/mapper/root_crypt /              f2fs    noatime,background_gc=off   0       1
LABEL=boot             /boot          ext4    errors=remount-ro           0       2
EOF

To replace the crypttab file:

cat <<'EOF' > /etc/crypttab
root_crypt LABEL="rootencrypted" none luks,initramfs
EOF

Step 4: Creating tar archives

After tweaking the system to our needs, we want to create tar archives that will contain the contents that will be used for the boot and root partitions later. To do this, we will mount the disk images of the VM directly on the host and extract the files from there.

First, power down your VM:

poweroff

To extract the files from the VM, load the nbd module on your host.

modprobe nbd max_part=8

Mount the VM's disk image (replace the path according to where you stored your virtual disk):

qemu-nbd --connect=/dev/nbd0 /var/lib/libvirt/images/devuan-aarch64.qcow2

Mount the boot partition (the guided installation installs to the second partition by default) and create a tar archive with our bootfs and store it in our assets:

mount /dev/nbd0p2 /mnt
cd /mnt
tar cfvzp /home/youruser/assets/devuan-aarch64-bootfs.tar.gz .
cd
umount /mnt

Do the same for the rootfs as well:

cryptsetup luksOpen /dev/nbd0p3 someroot
mount /dev/mapper/someroot /mnt
cd /mnt
tar cfvzp /home/youruser/assets/devuan-aarch64-rootfs.tar.gz .
cd
umount /mnt
cryptsetup luksClose /dev/mapper/someroot

Finally, clean up:

qemu-nbd -d /dev/nbd0

Step 5: Building the bootloader

We build u-boot on the host machine.

Install dependencies:

apt install device-tree-compiler build-essential gcc make git libssl-dev python3-dev bison flex bc libssl-dev make gcc swig gcc-aarch64-linux-gnu gcc-arm-none-eabi

Clone ARM's Trusted-Firmware source repository:

git clone https://github.com/ARM-software/arm-trusted-firmware
cd arm-trusted-firmware

Checkout the latest stable tag (check with git tag, it was v2.3 at the time of writing):

git checkout v2.3

Compile the firmware (for other platforms, replace rk3399 - e. g. by rk3288 if you build for a Rock64 board):

make CROSS_COMPILE=aarch64-linux-gnu- PLAT=rk3399 bl31

Leave the build directory:

cd ..

Next, clone the u-boot repository:

git clone git://git.denx.de/u-boot.git 

Checkout the latest stable tag (again, check with git tag):

git checkout v2020.07

Create a symlink to the previously produced arm trusted firmware build (if you built for a different board, adapt the link):

ln -s ../arm-trusted-firmware/build/rk3399/release/bl31/bl31.elf bl31.elf

Create the configuration from the default config (again, adapt it if you have a different board):

make CROSS_COMPILE=aarch64-linux-gnu- BL31=bl31.elf rockpro64-rk3399_defconfig

Start the build (change -j16 to match the number of your actual cpu threads):

make -j16 CROSS_COMPILE=aarch64-linux-gnu- BL31=bl31.elf all u-boot.itb

Save the assets:

cp u-boot/idbloader.img /home/youruser/assets/
cp u-boot/u-boot.itb /home/youruser/assets/

Step 6: Assembling a device-specific image

With all goodies in place, we can now create a flashable image for your board. Unless you need more than four partitions I recommend sticking with a dead-simple MBR partition scheme instead of using GPT.

Alternatively, you can also write the installation to your flash media directly instead of creating an image as described here.

Before we start, let's install kpartx:

apt install kpartx

First, change to our assets directory:

cd /home/youruser/assets

Create an image file for your device:

dd if=/dev/zero of=devuan3-rockpro64.img bs=1M count=4096

Then, create a template to hold our partition table (adjust the size of the boot partition if you have a bigger microSD card, but don't make it too big):

cat <<'EOF'>>sfdisk.template
label: mbr
unit: sectors
first-lba: 64

start=        2048, size=       16384
start=       18432, size=      614400, bootable
start=      632832
EOF

Now, apply the template to the disk image:

/sbin/sfdisk devuan3-rockpro64.img < sfdisk.template

Refresh the partition information:

kpartx -v -a devuan3-rockpro64.img

This should result in three devices (loop0p1 to loop0p3) showing up.

WARNING: Double-check this! If you have other active loop devices already, the following command might destroy your data!

Now, create an ext4 filesystem to hold our boot partition (flash wear should not be an issue as normally no writes occur on /boot during normal operation):

mkfs.ext4 -m0 -L boot /dev/mapper/loop0p2

For the root partition, we create an encrypted partition, unlock it, and format it using the f2fs filesystem:

cryptsetup luksFormat /dev/mapper/loop0p3 --label=rootencrypted
cryptsetup luksOpen /dev/mapper/loop0p3 somename
mkfs.f2fs -l root /dev/mapper/somename

Mount the boot partition, extract the tar archive :

mount /dev/mapper/loop0p2 /mnt
cd /mnt
tar xzvpf /home/youruser/assets/devuan-aarch64-bootfs.tar.gz .

Some devices (e. g. the A64-OLinuXino) require a different DTB file than the one included in the Debian kernel package. In this case, overwrite the existing dtb file in the bootfs partition (replace <platform> by your actual platform):

cp /path/to/your/special/dtb-file /mnt/dtbs/<platform>/

Make sure you prevent your dtb file from being overwritten by kernel upgrades. Thus, once you booted the final system, adapt the file /etc/kernel/postinst.d/copy-dtbs accordingly.

Unmount the bootfs:

sync
cd
umount /mnt

Do the same for the rootfs:

mount /dev/mapper/somename /mnt
cd /mnt
tar xzvpf /home/youruser/assets/devuan-aarch64-rootfs.tar.gz .

Before we unmount it, take the chance to adjust the settings for the serial console (only needed for Devuan, in Debian systemd automatically figures out the serial device). To do that, edit the file /mnt/etc/inittab and adjust the following line:

T0:23:respawn:/sbin/getty -L ttyAMA0 115200 screen

For the RockPro64, the serial device is ttyS2 (not ttyAMA0) and it uses a baud rate of 15000000 (not 115200). Again, other boards may need different settings.

In our case, change the line as follows:

T0:23:respawn:/sbin/getty -L ttyS2 1500000 screen

With that last change done, unmount the new rootfs

sync
cd /home/youruser/assets
umount /mnt
cryptsetup luksClose /dev/mapper/somename

As the last step, we need to write the bootloader to the device.

Warning: This procedure is SoC-specific! Other SoCs require different commands!

For our RockPro64. do the following:

dd if=idbloader.img of=devuan3-rockpro64.img seek=64 conv=notrunc
dd if=u-boot.itb of=devuan3-rockpro64.img seek=16384 conv=notrunc

Your image is now ready to be flashed.

Step 7: Flashing and final modifications

Flashing

To flash it to an microSD card (replace /dev/sdX by the actual device):

dd if=devuan3-rockpro64.img of=/dev/sdX bs=1M
sync

Bootup and first changes

That's it! Hook up the serial console to your board, put in the microSD card and enjoy.

WARNING: the next part was not tested!

After bootup, I recommend to renegerate the SSH keys as follows:

rm -v /etc/ssh/ssh_host_*
dpkg-reconfigure openssh-server

And to regenerate the keys used by dropbear-initramfs (deleting the dss key again):

rm /etc/dropbear-initramfs/*_key
dpkg-reconfigure dropbear-initramfs
rm /etc/dropbear-initramfs/dropbear_dss_host_key
update-initramfs -u

External References

(The providers of these resources are solely responsible for them - see legal notice).

Last but not least, I would like to thank all the helpful minds in the official Devuan forums who helped me resolve various small issues I encountered when building up this concept.

History / Changelog

  • [2020-07-18] Rework step 3 with several details, update OLinuxino-A64 status and tweaks
  • [2020-07-15] Major rewrite, now uses FDE with unencrypted boot
  • [2020-06-21] Initial writeup

Comments

(Comment features are provided by external parties and are not monitored by me.)

Join the discussion on Mastadon (external resource)