Recently while setting up a new install of Linux and making my partitions I was wondering why I needed a separate boot partition for every Linux install on my system. I also didn’t want to have to use a specific boot loader to achieve this. I know it’s possible with rEFInd and GRUB, but they are over complicated for what I need, for this reason I decided to stick with the nice and simple bootloader systemd-boot, previously referred to as gummiboot. The system I figured out works quite nicely, and lets me achieve all of this.

Booting Link to heading

I’m currently quad-booting an install of Archlinux as my main setup, a secondary install of Archlinux in case I need to do some recovery, a FreeBSD install, and an old Windows install. I want to be able to boot every system on my computer from systemd-boot to avoid having to go into the UEFI menu to change boot settings.

The one partition I knew I wont be able to get rid of is FreeBSD’s boot partition. I need to be using FreeBSD’s actual bootloader because I am running root on ZFS and want support for boot environments. Since I wanted to be able to continue using systemd-boot, and have all my entries there when I boot my computer, the last option I had was to chain load FreeBSD’s bootloader. This involves adding FreeBSD as an entry to systemd-boot, and upon choosing this option systemd-boot would load FreeBSD’s bootloader, which would then boot FreeBSD.

systemd-boot Link to heading

esp is used to denote the mountpoint in this article

The bootloader systemd-boot is made up of a series of configuration files and can be easily understood, it sets up two directories inside the EFI System Partition (ESP), esp/EFI and esp/loader. esp/loader contains all the configuration files necessary to set up the bootloader, and esp/EFI contains the EFI stubs that are run upon boot. esp/loader/entries contains a configuration file for each different operating system, and a loader.conf configuration file for setting the default boot and other general settings.

“Regular” systemd-boot Structure

└── loader
    ├── entries
    │   ├── entry_1.conf
    │   ├── entry_2.conf
    │   ├── entry_3.conf
    │   └── entry_4.conf
    └── loader.conf
    ├── initramfs-linux-fallback.img
    ├── initramfs-linux.img
    ├── intel-ucode.img
    └── vmlinuz-linux
    └── EFI
        ├── Boot
        │   └── BOOTX64.EFI
        └── systemd
            └── systemd-bootx64.efi

The issue with this directory structure for multi booting with my setup is if you try to use systemd boot with more than one Linux install there’s nowhere to put the initramfs and Linux kernel vmlinuz-linux aswell as any other files the system wants in its boot directory.

Initially I tried making a directory, say boot/installs/${system-name} for each operating system, and when the kernel gets rebuilt during an update, replace the old kernel image. I added a preset file in the directory /etc/mkinitcpio.d/linux.preset which says to copy the kernel after building to a certain directory. The problem with this is it expects images to already exist in the boot directory. I kept getting errors that the specified kernel image didn’t exist.

sudo mkinitcpio -p linux
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g /boot/installs/Butters/initramfs-linux.img
==> ERROR: specified kernel image does not exist: `/boot/vmlinuz-linux'
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g /boot/installs/Butters/initramfs-linux-fallback.img -S autodetect
==> ERROR: specified kernel image does not exist: `/boot/vmlinuz-linux'

New Directory Structure Link to heading

The next thing I tried was instead of trying to tell the system to move the files after updating the kernel, I made a new directory /mnt/efi, mounted my boot partition there, and told systemd-boot to install there.

bootctl --path=/mnt/efi install

Inside /mnt/efi I made a directory boot/installs/${system-name} for each operating system and moved the necessary files into subdirectories.

├── EFI
│   ├── Boot
│   │   └── BOOTX64.EFI
│   └── systemd
│       └── systemd-bootx64.efi
├── installs
│   ├── Beastie
│   │   └── boot1.efi
│   ├── Butters
│   │   ├── initramfs-linux-fallback.img
│   │   ├── initramfs-linux.img
│   │   ├── intel-ucode.img
│   │   └── vmlinuz-linux
│   ├── Hector
│   │   └── Microsoft
│   │       └── Boot
│   │           ├── BCD
│   │           ├── BCD.LOG
│   │           ├── bootmgfw.efi
│   │           ├── bootmgr.efi
│   │           ├── BOOTSTAT.DAT
│   │           ├── boot.stl
│   │           └── Fonts
│   └── Tiny
│       ├── initramfs-linux-fallback.img
│       ├── initramfs-linux.img
│       └── vmlinuz-linux
└── loader
    ├── entries
    │   ├── Beastie.conf
    │   ├── Butters.conf
    │   ├── Hector.conf
    │   └── Tiny.conf
    └── loader.conf

Next I made an entry for each operating system noting the new location of the kernel and initramfs.

Here is the example of my main Arch install entry.

/mnt/efi » cat /mnt/efi/loader/entries/Butters.conf
title		Butters (Arch Linux)
linux		/installs/Butters/vmlinuz-linux
initrd		/installs/Butters/intel-ucode.img
initrd		/installs/Butters/initramfs-linux.img
options		root=PARTUUID=68e329cd-01b9-45e7-b516-ba62c516b4e5 rw rootflags=subvol=ROOT

After adding a configuration file for each entry, the bootloader should be functional.

Boot Directory Link to heading

The remaining issue is that upon updating the kernel, the updated files will not be in the right location. In order to fix this I bind mounted the directory holding the kernel to my boot directory in my fstab.

# Boot
UUID=82fd3efc-dbb3-42df-bb42-89c71675a5dc	/mnt/Butters	btrfs	rw,noatime,discard,ssd,compress=lzo,space_cache 	0 0

# Butters boot bind mount

/mnt/efi/installs/Butters /boot none defaults,bind 0 0

This effectively put the /mnt/efi/installs/Butters directory under /boot.

├── initramfs-linux-fallback.img
├── initramfs-linux.img
├── intel-ucode.img
└── vmlinuz-linux

Now upon update the old files will be replaced by the system without it needing to know that the actual partition is located somewhere else.

For this to work with the other Linux install on my system, I repeated the same process of bind mounting its same directory /mnt/efi/installs/Tiny to it’s /boot directory.

Now I am able to boot Windows, FreeBSD, and multiple installs of Linux all from one bootloader, and one partition. Pentadruple-boot here I come!