Fiddling with Ubuntu Server Images
Authored by Edwin Kofler
Published on 2020-1-0
Oddly enough, the latest version of Ubuntu Server failed to install on my MacBook Pro (2012). It’s due to some bug in efibootmgr
. Ubuntu Server Installer uses efibootmgr
to create some NVRAM variables for the UEFI boot process. Unfortunately, efibootmgr
failed right in the middle and printed an empty string to stderr, abruptly ending the installation process.
To fix this, I hypothesized that I could somehow redirect the command’s stderr to /dev/null
using a file descriptor redirect. So, no error would be printed and the installation would complete as usual. But, when further inspecting the image, I couldn’t seem to find the installation scripts to modify.
I decided to sidestep the issue by installing Debian Stretch instead. Because I used the ‘CD installer’ (rather than the DVD installer), many of the ‘popular packages’ were not pre-installed. I underestimated the limitations of this setup, and I bailed out of setting up Debian. I only bailed out after manually configuring the network, when systemd-resolved
did not seem to be reading the DNS=
entry in my systemd.network (5)
configuration file.
I then decided to retry installing Ubuntu, but by using an Ubuntu Cloud image because I knew it did not have an installation wizard.
Copying the rootfs
I installed the Ubuntu Cloud image from the official source.
wget https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img
Because the downloaded image is in qcow
format, I converted into a raw
format. (you can use qemu-img info
to query for this information).
qemu-img convert -p \
-f qcow2 bionic-server-cloudimg-amd64.img \
-O raw bionic.img \
Because we will be accessing the filesystem as a block device, we mount the file to a loop device.
sudo losetup -fP bionic.img
Now, we can finally dd
the data to an actual partition.
sudo dd if=/dev/loop7p1 of=/dev/vg/ubuntu-server \
status=progress conv=fsync bs=2048
After this process, I remounted the partition, chrooted into it, and added a password for the root user.
Making the Server Bootable
The distribution’s files for the EFI System Partition (ESP) are in dev/loop7p3
(or your equivalent). It only contains grub-related files. Rather than simply copying the grub files over and manually creating an NVRAM varaible entry for UEFI, I simply created an \EFI\ubuntu-server
folder in my ESP, and moved the bootloader and EFI stub kernel into it, from /boot/
. I then created an fstab entry to mount the ESP to /efi
, and from that, bind mount /efi/EFI/ubuntu-server
to /boot
and bind mount /efi
to /boot/efi
. At the time, I crossed my fingers that nothing related to updating the kernels and initramfs required features not supported by vfat, such as symlinks (spoiler alert: it does).
Now we have all the files needed to boot, we just have to tell our boot loader (or boot manager) to use them. Because I was already using an SSD with Refind installed, I added an entry for my Ubuntu Server. I did not configure GRUB.
menuentry ubuntu-server {
loader \EFI\ubuntu-server\vmlinuz
initrd \EFI\ubuntu-server\initrd.img
options "root=UUID=65e98e43-8591-48f8-8746-34c8b3819ee7 rw"
submenuentry "boot to tty" {
add_options "systemd.unit=multi-user.target"
}
submenuentry "boot fallback initramfs" {
initrd /boot/initrd.img.old
}
submenuentry "boot fallback kernel" {
loader /boot/vmlinuz.old
}
}
When rebooting, I was pretty certain I did not need to do any more work and that I was almost finished with the setup. The computer booted properly, getty opened some virtual terminals, and login ran without any hiccups. However, I was unable to type at all. My laptop keyboard simply did not work. However, I was able to login with a nearby bluetooth keyboard!
After poking around the system, I found the problem. ls -alF "/lib/modules/$(uname -r)/kernel/drivers/hid"
showed me that there were almost no human interface driver kernel modules that were loaded. Of course, this is an Ubuntu cloud image, so it makes sense that login can only be done through serial and ssh.
Anyways, to fix this, I simply downloaded the drivers/hid/hid-apple.c
kernel driver (along with drivers/hid/hid-ids.h
), placing them in a subdirectory.
I then ran a Makefile with the following content
obj-m += hid-apple.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
This built the proper modules. After inserting the modules into the kernel, it worked flawlessly! Hooray! 🎉 I added the modules to /lib/modules/$(uname -r)/updates
, and did some other miscellaneous cleanup. Of course, if I try to boot the system from a different computer, the keyboard will likely not work.
A few other problems later arose. Remember I added the boot files in a directory of the ESP? It turned out symlinks are used in the kernel upgrade process, so I had to recreate a /boot
partition for my Ubuntu server, copy over everything, and update my /etc/fstab
.
If I were to do this again, I would look into creating a preseed file, (which I did not know about at the beginning of this process), and optionally use packer to uniformly perform the installation process in a virtual machine.