Creating a custom Archlinux ISO

Using Archiso to create a custom interactive installer USB drive.

Apr 18, 2024    m. Apr 18, 2024    #linux   #github  

Background

At my job, I was faced with the task of creating a way to provision small-form-factor PCs with a custom Linux installation. We had a Ubuntu VM image that we had been using with customers that had virtualisation infrastructure, and now we wanted an easy way to install it on bare metal in the field. We couldn’t rely on PXE booting, as the network infrastructure was not under our control. The technicians in the field were not very tech-savvy, so we needed a solution that was as simple as possible. Every PC needed a slightly different configuration, so we needed something interactive.

After some research, I found tools like goldboot that looked interesting, but at the time this was far from being production-ready. Being an Archlinux user myself, I remembered that Archlinux has a tool called archiso that is used to create the official Archlinux ISOs. It can be used to spin a custom ISO with custom packages and configurations. It’s also possible to add a startup script that automatically runs when the ISO boots, so I could make it interactive. The only purpose of this script would be to ask the user for some information and then run a script that would configure the system according to the user’s input. It would dd the included Ubuntu VM image to the disk, resize the partition, and install the bootloader. Then some configuration files would be overwritten with the user’s custom configuration.

Archiso

Configuration

Using Archiso is quite straightforward if you’re using ArchLinux. We were not, so I had to come up with a way to build it on Github Actions. We ended up using a Docker container to build the ISO.

It’s best to start from the baseline example. As of writing, the link in the documentation page is dead. You can find it here .

Copy this folder to another location and start customizing it.

Files you might want to customize:

To include extra files in the ISO, you can add them to the airootfs folder. This is where the root filesystem of the ISO is built.

This is how our profiledef.sh looked like:

#!/usr/bin/env bash
# shellcheck disable=SC2034

iso_name="bootstrap"
iso_label="ARCH_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)"
iso_publisher="REDACTED"
iso_application="Custom PC Bootstrap"
iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)"
install_dir="arch"
buildmodes=('iso')
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito'
           'uefi-ia32.grub.esp' 'uefi-x64.grub.esp'
           'uefi-ia32.grub.eltorito' 'uefi-x64.grub.eltorito')
arch="x86_64"
pacman_conf="pacman.conf"
airootfs_image_type="squashfs"
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-Xdict-size' '1M' '-b' '1M')
file_permissions=(
  ["/etc/shadow"]="0:0:400"
  ["/root"]="0:0:750"
  ["/root/.automated_script.sh"]="0:0:755"
  ["/root/.gnupg"]="0:0:700"
  ["/usr/local/bin/choose-mirror"]="0:0:755"
  ["/usr/local/bin/Installation_guide"]="0:0:755"
  ["/usr/local/bin/livecd-sound"]="0:0:755"
)

One notable change is the compression type of the airootfs_image_tool_options. We changed it to xz as a compromise between compression ratio and speed. You can read about all the options in the mksquashfs manpage .

Archiso produces an iso that can boot in both BIOS and UEFI systems. You can customize the boot menu by editing the syslinux and grub configuration files.

This is how I customized the grub configuration file to hide the startup messages and immediately go into the installation script:

menuentry "Custom PC Installer (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' {
    set gfxpayload=keep
    linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisodevice=UUID=${ARCHISO_UUID} script=/opt/bootstrap.sh quiet
    initrd /%INSTALL_DIR%/boot/intel-ucode.img /%INSTALL_DIR%/boot/amd-ucode.img /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
}

Syslinux was a bit more of a mess to edit, it has many files that link to each other in a strange way if you’ve never seen it before. You can modify the colors and background image easily, however. This is how I modified the archiso_sys-linux.cfg file to achieve the same effect as with the grub modifications.

LABEL arch64
TEXT HELP
Boot the Custom PC Installer on BIOS.
ENDTEXT
MENU LABEL Custom PC Installer (x86_64, BIOS)
LINUX /%INSTALL_DIR%/boot/x86_64/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/intel-ucode.img,/%INSTALL_DIR%/boot/amd-ucode.img,/%INSTALL_DIR%/boot/x86_64/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisodevice=UUID=%ARCHISO_UUID% script=/opt/bootstrap.sh quiet

The bootstrap.sh script is the script that will be run when the ISO boots. It’s outside the scope of this post to show the contents of this script (and it’s too tailored to my employer’s need), but it’s a simple bash script that uses dialog to ask the user for some information and then runs the installation script. I might write a post about it in the future.

Building the ISO

As we needed to build this ISO using our CI/CD pipeline, we used a Docker container to build it. The public Github Actions runners provided run Ubuntu.

This workflow file is a bit of a hack, but it works. It uses a Docker container to build the ISO and then copies the resulting ISO as an artifact. This last part is pretty slow, it would be better to upload the ISO to a cloud storage service This workflow example shows how we included the VM images in the ISO, which you of course can completely ignore.

At the time of writing, the touch command is necessary to create a file that is missing in the Archiso build process. This is a bug in Archiso that has been probably been fixed in the latest version by now.

.github/workflows/ci.yml

name: CI

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v4
    - name: archiso
      run: |
        docker run --rm -v ${{ github.workspace }}:/workspace --privileged archlinux:base-devel \
          bash -c "\
            pacman -Syyuu --noconfirm && \
            pacman -Sy archiso qemu-img wget --noconfirm && \
            mkdir -p /vmdk && cd /vmdk && \
            wget -qO disk-1.vmdk '${{ secrets.URL_1 }}' && \
            qemu-img convert -p -f vmdk -O raw /vmdk/disk-1.vmdk /workspace/bootstrap/airootfs/images/disk-image-1.raw && \
            pacman -Rs wget qemu-img --noconfirm && \
            cd / && rm -rf /vmdk && \
            mkdir -p /tmp/x86_64/airootfs/usr/share/licenses/common/GPL2/ && touch /tmp/x86_64/airootfs/usr/share/licenses/common/GPL2/license.txt && \
            find /workspace -type f -name '.gitkeep' -delete 2> /dev/null && \
            mkarchiso -v -w /tmp -o /workspace /workspace/bootstrap && \
            find /workspace -type f ! -name '*.iso' -exec rm {} \; "        
    - name: 'Archive ISO'
      uses: actions/upload-artifact@v3
      with:
        name: iso
        path: ${{ github.workspace }}/*.iso

Conclusion

Archiso is a nice building block to create custom Linux bootable media. I haven’t quite seen it used in production in the way I used it, but it worked well for us.

It’s probably handy to create both unattended installations and custom rescue media in many situations. I hope this post has been helpful to you.



Next: Hello World!