Cross-distro and cross-arch bootstrapping of ArchLinux (ARM)
Background
After several months of maintaining orangepi5-archlinuxarm , I grew tired of the old method which bootstraps an ALARM rootfs inside an QEMU-based ALARM build environment on the Ubuntu 22.04 Github Actions runner, which was not only in-efficient but also a mess of mixture of 3 systems’ permissions and filesystem structure.
Thus, the project switched to a nightly release model which utilizes cross_nobuild.sh to simply bootstrap on the x86-64 Ubuntu 22.04 host environment. The new builder took some effort as it is not only cross-distro but also cross-architecture, and it did not use any pre-built rootfs archive at all, just like pacstrap
– What was worked around as two seperate problems is now one single, big problem to solve. And I find that experience valuable to document.
Although the main purpose behind this was to bootstrap ArchLinux ARM, it is also useful for bootstrapping any Arch-based distros from any architrecure, even Arch itself from ALARM.
Host
The expected host environment is x86-64 Ubuntu 22.04, same as the main distro used on Github Actions. If you’re using Arch itself then some of the following parts could be skipped.
Although the distro where I figured out most of the process was also Arch itself, I don’t think the steps on Arch itself would need to be documented, as you already have some very convenient scripts to use provided by arch-install-scripts
.
Do note that the host must be either a VM or an actual box, I didn’t test these inside a systemd-nspawn container, a Docker container, nor a LXC container. You will meet some challenges if you’re running these containers due to the missing init.
Dependency
qemu-user-static
The only one hard dependency that’s needed from the Ubuntu repo is qemu-user-static
, which is needed to run necessary programs like those needed during Pacman’s post-install scripts.
sudo apt update
sudo apt install qemu-user-static
pacman-static
The other dependency we’ll also need is pacman
, or more precisely pacman-static
, a build of pacman
but with every library staticly linked, so it could run distro-independent. It’s needed as we’ll bootstrap the target from ground up with everything installed by pacman
.
You can build it by yourself, but since there’re already repos providing the pre-built binary we can save the effort by just downloading the binary.
An example of the repo is archlinuxcn
, you can find the package on opentuna’s archlinuxcn mirror, the latest one at the time of writing was 6.0.1-49, although the link would probably be dead when you read this blog in the future.
Download the package file and extract it, you would get a bunch of files but you only need usr/bin/pacman
, move it to a place easier to call. You can also do all of these (downloading + extraction + mv) in one single step, e.g.
wget -O- https://opentuna.cn/archlinuxcn/x86_64/pacman-static-6.0.1-49-x86_64.pkg.tar.xz | tar -xOJ usr/bin/pacman-static | install -m 755 /dev/stdin pacman
Remember to test your pacman to confirm it works.
> ./pacman --version
.--. Pacman v6.0.1 - libalpm v13.0.1
/ _.-' .-. .-. .-. Copyright (C) 2006-2021 Pacman Development Team
\ '-. '-' '-' '-' Copyright (C) 2002-2006 Judd Vinet
'--'
This program may be freely redistributed under
the terms of the GNU General Public License.
Target root
You’ll need a dedicated mount point for the target root, it could be simply a folder bind-mounted to itself. The reason behind this is that a top-level mountpoint is always needed for many of the system utilities that’ll run during the boostrapping.
You can also create a disk image, but we’ll go the simple bind-mount route here just for demonstration:
sudo mkdir target
sudo mount -o bind target target
Note the target root must be owned by root itself, so we used sudo mkdir
And under the mount root, there’re other subfolders needed:
root=target
sudo mkdir -p "${root}"/{dev/{pts,shm},etc/pacman.d,proc,run,sys,tmp,var/{cache/pacman/pkg,lib/pacman,log}}
sudo mount proc "${root}"/proc -t proc -o nosuid,noexec,nodev
sudo mount sys "${root}"/sys -t sysfs -o nosuid,noexec,nodev,ro
sudo mount udev "${root}"/dev -t devtmpfs -o mode=0755,nosuid
sudo mount devpts "${root}"/dev/pts -t devpts -o mode=0620,gid=5,nosuid,noexec
sudo mount shm "${root}"/dev/shm -t tmpfs -o mode=1777,nosuid,nodev
sudo mount run "${root}"/run -t tmpfs -o nosuid,nodev,mode=0755
sudo mount tmp "${root}"/tmp -t tmpfs -o mode=1777,strictatime,nodev,nosuid
Remember to also create and mount target/boot if you’re bootstrapping into a disk image.
Pacman config
Pacman won’t run correctly if it misses the pacman config, found as /etc/pacman.conf
under Arch.
We’ll need two seperate configs, pacman-loose.conf
which allows packages to be installed without signkey verification, and pacman-strict.conf
which does not. The loose
one is used to install the base
package group and archlinuxarm-keyring
, and the strict
one is used to install everything else.
It is possible to omit the strict
one, if you either install all pacakges in one step or chroot into the target root to install the remaining packages.
mkdir pkg
root=target
repo_url='http://mirror.archlinuxarm.org/aarch64/$repo'
pacman_config="
RootDir = ${root}
DBPath = ${root}/var/lib/pacman/
CacheDir = pkg/
LogFile = ${root}/var/log/pacman.log
GPGDir = ${root}/etc/pacman.d/gnupg/
HookDir = ${root}/etc/pacman.d/hooks/
Architecture = aarch64"
pacman_mirrors="
[core]
Server = ${repo_url}
[extra]
Server = ${repo_url}
[alarm]
Server = ${repo_url}
[aur]
Server = ${repo_url}"
echo "[options]${pacman_config}
SigLevel = Never${pacman_mirrors}" > pacman-loose.conf
echo "[options]${pacman_config}
SigLevel = Never${pacman_mirrors}" > pacman-strict.conf
You should notice that we explicitly set the arch
as aarch64
, this is due to the fact that pacman
runs natively on x86-64
. If not set then it would consider the arch
to x86_64
.
Base packages
Install the base
group and archlinuxarm-keyring
into the target root
sudo ./pacman -Sy --config pacman-loose.conf --noconfirm base archlinuxarm-keyring
Keyring
Before installing other packages, the keyring need to be initialized and populated, so the integrity could be checked. (It’s also possible to go back and verify the packages in base
group, but I’ll omit that).
First get into the target root:
sudo chroot target
Then init and populate the keyring:
pacman-key --init
pacman-key --populate archlinuxarm
Exit from the chroot to continue
Other pacakges
Install other packages needed, but this time with the strict
config, you’ll need at least the kernel pacakge and the firmware package to ensure the target is bootable. And while we’re at it, I recommend to install everything you need. E.g.
sudo ./pacman -Syu --config pacman-strict.conf --noconfirm vim nano sudo openssh linux-aarch64 linux-firmware
Configuration
Follow the Arch Wiki to configure the system, except that you should use chroot
itself to enter the target root.
Finish
Due to pacman-key
and gpg-agent
expecting them running in a whole system, not in chroot, they would continue running, even after you existing from chroot. You’ll need to kill them before umounting the target root:
sudo chroot target killall -s KILL gpg-agent dirmngr
Then you can umount the target root
umount -R