Skip to content

Commit

Permalink
Add support for UEFI
Browse files Browse the repository at this point in the history
  • Loading branch information
jirutka committed Sep 9, 2023
1 parent 1eb7515 commit 1e68b50
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 20 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ jobs:
--fs-skel-chown root:root \
--script-chroot \
alpine2-$(date +%Y-%m-%d).qcow2 -- ./example/configure.sh
- name: Build image with UEFI boot mode
run: |
sudo ./alpine-make-vm-image \
--image-format qcow2 \
--image-size 2G \
--boot-mode UEFI \
--repositories-file example/repositories \
--packages "$(cat example/packages)" \
--fs-skel-dir example/rootfs \
--fs-skel-chown root:root \
--script-chroot \
alpine2-$(date +%Y-%m-%d).qcow2 -- ./example/configure.sh
8 changes: 5 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ image:https://github.com/{gh-name}/workflows/CI/badge.svg["Build Status", link="
endif::env-github[]

This project provides a script for making customized https://alpinelinux.org/[Alpine Linux] disk images for virtual machines.
It’s quite simple (300 LoC of shell), fast (~32 seconds on GitHub Actions) and requires minimum dependencies (QEMU and filesystem tools).
You can choose between BIOS mode (using https://syslinux.org/[Syslinux]) and UEFI mode (using Linux https://docs.kernel.org/admin-guide/efi-stub.html[EFI stub]).
It’s quite simple (400 LoC of shell), fast (~32 seconds on GitHub Actions), requires minimum dependencies (QEMU and filesystem tools).

TIP: Don’t need VM, just wanna chroot into Alpine Linux?
Try https://github.com/alpinelinux/alpine-chroot-install[alpine-chroot-install]!
Expand All @@ -23,9 +24,10 @@ TIP: Don’t need VM, just wanna chroot into Alpine Linux?
* POSIX-sh compatible shell (e.g. Busybox ash, dash, Bash, ZSH)
* qemu-img and qemu-nbd
* rsync (needed only for `--fs-skel-dir`)
* sfdisk (needed only for `--partition`)
* mdev or udevadm (needed only for `--partition` when device hotplug doesn’t work)
* sfdisk (needed only for `--partition` and `--boot-mode UEFI`)
* mdev or udevadm (needed only for `--partition` and `--boot-mode UEFI` if device hotplug doesn’t work)
* e2fsprogs (for ext4), btrfs-progs (for Btrfs), or xfsprogs (for XFS)
* dosfstools (needed only for `--boot-mode UEFI`)

All dependencies except the first two are automatically installed by the script when running on Alpine.

Expand Down
100 changes: 83 additions & 17 deletions alpine-make-vm-image
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
# <script-opts> Arguments to pass to the script.
#
# Options and Environment Variables:
# -B --boot-mode BOOT_MODE Either "BIOS" (default) or "UEFI".
#
# -b --branch ALPINE_BRANCH Alpine branch to install; used only when
# --repositories-file is not specified. Default is
# latest-stable.
Expand All @@ -36,6 +38,7 @@
# specified USER:GROUP.
#
# -P --partition (PARTITION) Create GUID Partition Table (GPT) with a single partition.
# GPT is always created for --boot-mode UEFI.
#
# -f --image-format IMAGE_FORMAT Format of the disk image (see qemu-img --help).
#
Expand Down Expand Up @@ -189,6 +192,27 @@ blk_uuid() {
blkid "$dev" | sed -En 's/.*\bUUID="([^"]+)".*/\1/p'
}

# Creates GPT parition table on the device $1 for boot mode $2 (BIOS or UEFI).
create_gpt() {
local dev="$1"
local mode="$2"

case "$mode" in
BIOS) printf '%s\n' \
'label: gpt' \
'name=system,type=L,bootable,attrs=LegacyBIOSBootable' \
| sfdisk "$dev"
;;
UEFI) printf '%s\n' \
'label: gpt' \
'name=efi,type=U,size=128M,bootable' \
'name=system,type=L' \
| sfdisk "$nbd_dev"
;;
*) die "Invalid mode: $mode";;
esac
}

# Writes Alpine APK keys embedded in this script into directory $1.
dump_alpine_keys() {
local dest_dir="$1"
Expand Down Expand Up @@ -221,6 +245,7 @@ fs_progs_pkg() {
case "$fs" in
ext4) echo 'e2fsprogs';;
btrfs) echo 'btrfs-progs';;
vfat) echo 'dosfstools';;
xfs) echo 'xfsprogs';;
esac
}
Expand Down Expand Up @@ -350,14 +375,15 @@ wgets() (

#============================= M a i n ==============================#

opts=$(getopt -n $PROGNAME -o b:cCf:hi:k:m:p:Pr:s:S:tv \
-l branch:,fs-skel-chown:,fs-skel-dir:,image-format:,image-size:,initfs-features:,kernel-flavor:,keys-dir:,mirror-uri:,no-cleanup,packages:,partition,repositories-file:,rootfs:,script-chroot,serial-console,help,version \
opts=$(getopt -n $PROGNAME -o b:B:cCf:hi:k:m:p:Pr:s:S:tv \
-l boot-mode:,branch:,fs-skel-chown:,fs-skel-dir:,image-format:,image-size:,initfs-features:,kernel-flavor:,keys-dir:,mirror-uri:,no-cleanup,packages:,partition,repositories-file:,rootfs:,script-chroot,serial-console,help,version \
-- "$@") || help 1 >&2

eval set -- "$opts"
while [ $# -gt 0 ]; do
n=2
case "$1" in
-B | --boot-mode) BOOT_MODE="$2";;
-b | --branch) ALPINE_BRANCH="$2";;
-S | --fs-skel-dir) FS_SKEL_DIR="$(realpath "$2")";;
--fs-skel-chown) FS_SKEL_CHOWN="$2";;
Expand All @@ -383,6 +409,7 @@ done

: ${ALPINE_BRANCH:="latest-stable"}
: ${ALPINE_MIRROR:="http://dl-cdn.alpinelinux.org/alpine"}
: ${BOOT_MODE:="BIOS"}
: ${CLEANUP:="yes"}
: ${FS_SKEL_CHOWN:=}
: ${FS_SKEL_DIR:=}
Expand All @@ -402,6 +429,12 @@ case "$ALPINE_BRANCH" in
[0-9]*) ALPINE_BRANCH="v$ALPINE_BRANCH";;
esac

case "$BOOT_MODE" in
BIOS | bios) BOOT_MODE='BIOS';;
UEFI | uefi) BOOT_MODE='UEFI';;
*) die "Invalid boot-mode: $BOOT_MODE";;
esac

if [ -f /etc/alpine-release ]; then
: ${INSTALL_HOST_PKGS:="yes"}
else
Expand All @@ -428,7 +461,9 @@ if [ "$INSTALL_HOST_PKGS" = yes ]; then
if ! grep -q -w "$ROOTFS" /proc/filesystems; then
modprobe $ROOTFS
fi
_apk add -t $VIRTUAL_PKG qemu-img $(fs_progs_pkg "$ROOTFS") rsync sfdisk

[ "$BOOT_MODE" = 'UEFI' ] && vfatpkg="$(fs_progs_pkg 'vfat')" || vfatpkg=
_apk add -t $VIRTUAL_PKG qemu-img $(fs_progs_pkg "$ROOTFS") $vfatpkg rsync sfdisk
fi

#-----------------------------------------------------------------------
Expand All @@ -455,20 +490,30 @@ nbd_dev=$(attach_image "$IMAGE_FILE" "$IMAGE_FORMAT")


#-----------------------------------------------------------------------
if [ "$PARTITION" = yes ]; then
if [ "$PARTITION" = yes ] || [ "$BOOT_MODE" = 'UEFI' ]; then
einfo 'Creating GPT partition table'

printf '%s\n' \
'label: gpt' \
'name=system,type=L,bootable,attrs=LegacyBIOSBootable' \
| sfdisk "$nbd_dev"
root_dev="${nbd_dev}p1"
create_gpt "$nbd_dev" "$BOOT_MODE"

if [ "$BOOT_MODE" = 'BIOS' ]; then
root_dev="${nbd_dev}p1"
else
esp_dev="${nbd_dev}p1"
root_dev="${nbd_dev}p2"
fi
# This is needed when running in a container.
settle_dev_node "$root_dev" || die "system didn't create $root_dev node"
else
root_dev="$nbd_dev"
fi

#-----------------------------------------------------------------------
if [ "$BOOT_MODE" = 'UEFI' ]; then
einfo 'Formatting EFI system partition'

mkfs.fat -F32 -n EFI "$esp_dev"
fi

#-----------------------------------------------------------------------
einfo "Formatting image to $ROOTFS"

Expand All @@ -487,6 +532,11 @@ einfo "Mounting image at $mount_dir"

mount "$root_dev" "$mount_dir"

if [ "$BOOT_MODE" = 'UEFI' ]; then
install -d -m 000 "$mount_dir"/boot
mount -t vfat "$esp_dev" "$mount_dir"/boot
fi

#-----------------------------------------------------------------------
einfo 'Installing base system'

Expand Down Expand Up @@ -532,23 +582,39 @@ else
fi

#-----------------------------------------------------------------------
einfo 'Setting up extlinux bootloader'
if [ "$BOOT_MODE" = 'BIOS' ]; then
einfo 'Setting up extlinux bootloader'

_apk add --root . --no-scripts syslinux
setup_extlinux . "UUID=$root_uuid" "$ROOTFS" "$KERNEL_FLAVOR" "$SERIAL_PORT"
_apk add --root . --no-scripts syslinux
setup_extlinux . "UUID=$root_uuid" "$ROOTFS" "$KERNEL_FLAVOR" "$SERIAL_PORT"

if [ "$PARTITION" = yes ]; then
dd bs=440 count=1 conv=notrunc if=usr/share/syslinux/gptmbr.bin of="$nbd_dev"
sync
if [ "$PARTITION" = yes ]; then
dd bs=440 count=1 conv=notrunc if=usr/share/syslinux/gptmbr.bin of="$nbd_dev"
sync
fi
fi

#-----------------------------------------------------------------------
einfo 'Configuring system'

cat > etc/fstab <<-EOF
# <fs> <mountpoint> <type> <opts> <dump/pass>
UUID=$root_uuid / $ROOTFS relatime 0 1
EOF

#-----------------------------------------------------------------------
einfo 'Configuring system'
if [ "$BOOT_MODE" = 'UEFI' ]; then
echo "LABEL=EFI /boot vfat\
rw,relatime,fmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2" \
>> etc/fstab

# This script is interpreted by UEFI.
echo "vmlinuz-$KERNEL_FLAVOR" \
"initrd=initramfs-$KERNEL_FLAVOR" \
"root=UUID=$root_uuid" \
"rootfstype=$ROOTFS" \
${SERIAL_PORT:+"console=ttyS0"} \
> boot/startup.nsh
fi

# We just prepare this config, but don't start the networking service.
cat > etc/network/interfaces <<-EOF
Expand Down

0 comments on commit 1e68b50

Please sign in to comment.