7-add-legacy-bios-support #8

Merged
hygienic-books merged 12 commits from 7-add-legacy-bios-support into main 2023-10-24 00:25:35 +00:00
2 changed files with 221 additions and 47 deletions

View File

@ -6,14 +6,31 @@ Helper script to install Arch Linux with ZFSBootMenu from within a running Arch
We expect minimal prep on your end. Please make sure that before execution the following conditions are met.
### UEFI
On a UEFI system ensure these conditions are met. See [How to prep](#how-to-prep) for details on how to meet these conditions.
- One GPT-partitioned disk
- Arch Linux live CD ISO image sees exactly one partition with partition type code `BF00` ("Solaris root")
- Arch Linux live CD ISO image sees exactly one partition with partition type code `EF00` ("EFI system partition")
- The `EF00` EFI partition is mountable, in practical terms this usually only means it has a file system.
- No ZFS zpool exists
### How to prep
### Legacy BIOS
On a blank example disk `/dev/sda` you can fulfill the requirements (One `EF00` partition with a file system plus one `BF00` partition) for example like so:
If you are instead running a legacy BIOS machine ensure these conditions are met. See [How to prep](#how-to-prep) for details on how to meet these conditions.
- One MBR-partitioned disk
- Arch Linux live CD ISO image sees exactly one partition with partition type code `BF` ("Solaris root")
- Arch Linux live CD ISO image sees exactly one partition with partition type code `83` ("Linux")
- The `83` Linux partition is mountable, in practical terms this usually only means it has a file system.
- No ZFS zpool exists
## How to prep
### UEFI
On a blank example disk `/dev/sda` you can fulfill the UEFI requirements (One `EF00` partition with a file system plus one `BF00` partition) for example like so:
```
sgdisk --new '1::+512M' --new '2' --typecode '1:EF00' --typecode '2:BF00' /dev/sda
mkfs.vfat /dev/sda1
@ -37,6 +54,28 @@ NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
└─/dev/sda2 202:2 0 9.5G 0 part
```
### Legacy BIOS
For a legacy BIOS machine you'll be using a master boot record (MBR) on your disk.
```
printf -- '%s\n' 'label: dos' 'start=1MiB, size=512MiB, type=83, bootable' 'start=513MiB, size=+, type=bf' | sfdisk /dev/sda
mkfs.vfat /dev/sda1
```
> `label: dos`: Create the following partition layout in a master boot record.
>
> `start=1MiB, size=512MiB, type=83, bootable`: Partition 1 begins 1 Mebibyte after disk start and is 512 Mebibyte in size. We're setting its bootable flag and setting partition type code `83` ("Linux").
>
> `start=513MiB, size=+, type=bf`: Partition 2 begins right at the start of Mebibyte 513, this is the very next sector after the end of partition 1. It takes up the remaining disk space, we're assigning type code `bf` ("Solaris").
The result will be something like this at which point you can start the `setup.sh` script, see [How to run this?](#how-to-run-this) below for more details.
```
# lsblk --paths --output 'NAME,SIZE,FSTYPE,PARTTYPE,PARTTYPENAME,PTTYPE' /dev/sda
NAME SIZE FSTYPE PARTTYPE PARTTYPENAME PTTYPE
/dev/sda 10G dos
├─/dev/sda1 512M vfat 0x83 Linux dos
└─/dev/sda2 9.5G 0xbf Solaris dos
```
## ZFS dataset layout
The script will create a single ZFS zpool `zpool` on the `BF00` partition with dataset child `zpool/root` which itself has one child `zpool/root/archlinux`, that's where Arch Linux gets installed. Parallel to `zpool/root` it'll create `zpool/data` with a `zpool/data/home` child dataset that gets mounted at `/home`.

225
setup.sh
View File

@ -24,11 +24,17 @@ declare zpool_name zfs_arch_dataset_name
zpool_name='zpool'
zfs_arch_dataset_name='archlinux'
declare -A partition_types
partition_types[gpt_zfs]='6a85cf4d-1dd2-11b2-99a6-080020736631'
partition_types[gpt_efi]='c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
partition_types[mbr_zfs]='0xbf'
partition_types[mbr_boot]='0x83'
# https://unix.stackexchange.com/a/48550
set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR
declare zpool_drive efi_drive
declare zpool_drive efi_drive boot_drive part_schema
function we_are_changerooted () {
if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then
@ -56,6 +62,12 @@ function resize_cow_space () {
function update_pacman_db () {
#1.4
printf -- '%s\n' 'Refreshing mirror list ...'
printf -- '%s\n' \
'--save /etc/pacman.d/mirrorlist' \
'--protocol https' \
'--latest 5' \
'--sort age' \
> '/etc/xdg/reflector/reflector.conf'
systemctl start reflector
# In an ISO and for the minimal number of packages we need we do not
# care about partial upgrades
@ -75,6 +87,35 @@ function install_zfs () {
printf -- "${reset_colors}"
}
function uefi_or_bios () {
#1.7
local part_count_linux part_count_efi
# Select disks with at least one partition. Among them count how many
# with the given partition type code we have.
part_count_linux="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path] | length')"
part_count_efi="$(get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path] | length')"
if [[ "${part_count_linux}" -eq '1' ]] && [[ "${part_count_efi}" -eq '0' ]]; then
part_schema='mbr'
>&3 printf -- '%s\n' \
'Treating this as an MBR/legacy BIOS machine ...'
elif [[ "${part_count_linux}" -eq '0' ]] && [[ "${part_count_efi}" -eq '1' ]]; then
part_schema='gpt'
>&3 printf -- '%s\n' \
'Treating this as a GPT/UEFI machine ...'
else
>&3 printf -- '%s\n' \
'We are seeing partitions as follows:' \
'- Partition type code '"${partition_types[mbr_boot]}"': '"${part_count_linux}" \
'- Partition type code '"${partition_types[gpt_efi]}"': '"${part_count_efi}" \
'' \
'We are instead expecting either 1 and 0 indicating a legacy' \
'BIOS setup or 0 and 1 indicating UEFI. '"${part_count_linux}"' and '"${part_count_efi}"' is' \
'neither. Cowardly exiting ...'
exit 77
fi
export part_schema="${part_schema}"
}
function get_partitions () {
declare partitions_json
partitions_json="$(lsblk --output PATH,PARTTYPE --json --tree)" || return 1
@ -91,16 +132,24 @@ function get_part_parent () {
}
function get_parts () {
local zfs_install_drive
local zfs_install_drive part_type_code
declare parttype parts
parttype="${1:?}"
zfs_install_drive="${2:-}"
case "${parttype}" in
zfs)
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1
if [[ "${part_schema}" = 'mbr' ]]; then
part_type_code="${partition_types[mbr_zfs]}"
else
part_type_code="${partition_types[gpt_zfs]}"
fi
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="'"${part_type_code}"'") | .path')" || exit 1
;;
boot)
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[mbr_boot]}"'") | .path')" || exit 1
;;
efi)
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="'"${partition_types[gpt_efi]}"'") | .path')" || exit 1
;;
*)
>&3 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
@ -147,15 +196,26 @@ function get_drive_id () {
}
function select_part () {
local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive
local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive part_type_code specific_part_type
declare part
part_type="${1:?}" # 'efi' or 'zfs'
part_type="${1:?}" # 'boot' or 'zfs'
zfs_install_drive="${2:-}"
if [[ "${zfs_install_drive}" ]]; then
# This is intended to find correct EFI partition
parts="$(get_parts "${part_type}" "${zfs_install_drive}")"
if [[ "${part_type}" = 'boot' ]]; then
if [[ "${part_schema}" = 'mbr' ]]; then
specific_part_type='boot'
else
specific_part_type='efi'
fi
else
parts="$(get_parts "${part_type}")"
specific_part_type="${part_type}"
fi
if [[ "${zfs_install_drive}" ]]; then
# This is intended to find correct boot/EFI partition
parts="$(get_parts "${specific_part_type}" "${zfs_install_drive}")"
else
parts="$(get_parts "${specific_part_type}")"
fi
if [[ ! "${parts}" ]]; then
@ -164,7 +224,12 @@ function select_part () {
part_type_human_readable='EFI system partition (ESP) with partition type code EF00'
;;
zfs)
part_type_human_readable='ZFS zpool partition with partition type code BF00'
if [[ "${part_schema}" = 'mbr' ]]; then
part_type_code='bf'
else
part_type_code='BF00'
fi
part_type_human_readable='ZFS zpool partition with partition type code '"${part_type_code}"
;;
esac
>&3 printf -- '%s\n' \
@ -256,7 +321,7 @@ function export_pool () {
}
function setup_zpool () {
#1.7
#1.8
local drive_by_id
zpool_drive="$(select_part 'zfs')"
drive_by_id="$(get_drive_id "${zpool_drive}")"
@ -276,20 +341,30 @@ function setup_zpool () {
}
function mount_system () {
#1.8
#1.9
zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
zfs mount -a
local zfs_parent efi_part
local zfs_parent efi_part boot_part
zfs_parent="$(get_part_parent "${zpool_drive:?}")"
efi_drive="${zfs_parent}"
efi_part="$(select_part 'efi' "${zfs_parent:?}")"
mkdir -p '/mnt/efi'
mount "${efi_part}" '/mnt/efi'
if [[ "${part_schema}" = 'mbr' ]]; then
boot_drive="${zfs_parent}"
export boot_drive="${boot_drive}"
boot_part="$(select_part 'boot' "${zfs_parent:?}")"
mkdir -p '/mnt/boot/syslinux'
mount "${boot_part}" '/mnt/boot/syslinux'
else
efi_drive="${zfs_parent}"
export efi_drive="${efi_drive}"
efi_part="$(select_part 'boot' "${zfs_parent:?}")"
mkdir -p '/mnt/efi'
mount "${efi_part}" '/mnt/efi'
fi
}
function copy_zpool_cache () {
#1.9
#1.10
mkdir -p '/mnt/etc/zfs'
zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}"
}
@ -309,7 +384,7 @@ function pacman_dont_check_space () {
}
function install_archlinux () {
#1.10
#1.11
pacman_dl_parallel
pacman_dont_check_space
pacstrap /mnt \
@ -336,7 +411,7 @@ function install_archlinux () {
}
function gen_fstab () {
#1.11
#1.12
genfstab -U /mnt | grep -v "${zpool_name}" | tr -s '\n' | sed -r -e 's/\/mnt//' -e '/./,$!d' > '/mnt/etc/fstab'
}
@ -349,7 +424,7 @@ EOF
}
function set_hostname () {
#1.12
#1.13
declare new_hostname
install_pkgs 'pwgen'
new_hostname="$(pwgen --no-numerals --no-capitalize --ambiguous 8)"
@ -358,7 +433,7 @@ function set_hostname () {
}
function set_locale () {
#1.13
#1.14
printf -- '%s\n' \
'KEYMAP=de-latin1' \
'FONT=Lat2-Terminus16' \
@ -369,7 +444,7 @@ function set_locale () {
}
function add_zfs_hook_to_initramfs () {
#1.14
#1.15
# Add zfs hook, remove fsck hook from initramfs.
sed -ri \
-e 's'$'\x1''(HOOKS=)(.*?[\(| ])(filesystems)([\)| ][^\r\n\f]*)'$'\x1''\1\2zfs \3\4'$'\x1''g' \
@ -384,7 +459,7 @@ function add_zfs_hook_to_initramfs () {
}
function set_initramfs_build_list () {
#1.15
#1.16
# No need to build fallback initramfs, our new fallback is ZFS snapshots
sed -ri \
-e '/^#/d' \
@ -398,7 +473,7 @@ function set_initramfs_build_list () {
}
function add_zfs_files_to_new_os () {
#1.16
#1.17
for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && printf -- '%s' '/etc/zfs/'"${zpool_name}"'.key'); do
rsync -av --itemize-changes {'','/mnt'}"${zfs_file}"
done
@ -550,11 +625,41 @@ function paru_install () {
fi
}
function configure_syslinux () {
paru_install 'syslinux'
cp /usr/lib/syslinux/bios/*.c32 /boot/syslinux
extlinux --install /boot/syslinux
cat > /boot/syslinux/syslinux.cfg <<EOF
UI menu.c32
PROMPT 0
MENU TITLE ZFSBootMenu
TIMEOUT 100
DEFAULT zfsbootmenu
LABEL zfsbootmenu
MENU LABEL ZFSBootMenu
KERNEL /zfsbootmenu/vmlinuz-bootmenu
INITRD /zfsbootmenu/initramfs-bootmenu.img
APPEND zfsbootmenu quiet loglevel=0
LABEL zfsbootmenu-backup
MENU LABEL ZFSBootMenu (Backup)
KERNEL /zfsbootmenu/vmlinuz-bootmenu-backup
INITRD /zfsbootmenu/initramfs-bootmenu-backup.img
APPEND zfsbootmenu quiet loglevel=0
EOF
dd bs=440 count=1 conv=notrunc if='/usr/lib/syslinux/bios/mbr.bin' of="${boot_drive}"
}
function configure_zfsbootmenu () {
#2.9
paru_install 'zfsbootmenu'
mkdir -p '/etc/zfsbootmenu/posthooks.d'
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
if [[ "${part_schema}" = 'gpt' ]]; then
mkdir -p '/etc/zfsbootmenu/posthooks.d'
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
ManageImages: true
BootMountPoint: /efi
@ -571,7 +676,28 @@ Kernel:
CommandLine: ro loglevel=0 zbm.import_policy=hostid
Prefix: vmlinuz
EOF
# Up here maybe 'ro quiet' instead of 'ro'
get_known_good_stub_loader
else
configure_syslinux
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
ManageImages: true
BootMountPoint: /boot/syslinux
InitCPIO: true
Components:
Enabled: true
Versions: false
ImageDir: /boot/syslinux/zfsbootmenu
Kernel:
Prefix: vmlinuz
EOF
fi
# Up here maybe 'ro quiet' instead of 'ro'. This is ZFSBootMenu's kernel
# command line.
# Assign cmdline for final kernel start. This is our Arch Linx kernel
# command line.
zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
}
@ -588,7 +714,7 @@ function get_disks_with_one_efipart () {
# Find disks that have exactly one EFI partition and where that EFI
# partition is partition number 1. We expect exactly one disk to meet
# these criteria. Anything else and we bail.
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) and ([select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")] | length == 1) ) | .path')"
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="'"${partition_types[gpt_efi]}"'")) and ([select(.children[].parttype=="'"${partition_types[gpt_efi]}"'")] | length == 1) ) | .path')"
if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
printf -- '%s' "${disks_with_one_efipart}"
return 0
@ -622,7 +748,9 @@ function install_os_in_chroot () {
# Install ZFSBootMenu image
configure_zfsbootmenu #2.9
get_known_good_stub_loader #2.10
generate-zbm
# Yes, we do this twice so we immediately get a functional backup file
generate-zbm
}
@ -730,7 +858,11 @@ function insert_zbm_postconf_hook () {
function umount_all () {
#3.10
umount '/mnt/efi'
if [[ "${part_schema}" = 'mbr' ]]; then
umount '/mnt/boot/syslinux'
else
umount '/mnt/efi'
fi
zfs umount -a
zpool export "${zpool_name}"
}
@ -743,8 +875,10 @@ function finalize_os_setup () {
configure_reflector #3.5
configure_zfs #3.6
configure_zfs_mount_gen #3.7
set_new_uefi_boot_entries #3.8
insert_zbm_postconf_hook #3.9
if [[ "${part_schema}" = 'gpt' ]]; then
set_new_uefi_boot_entries #3.8
insert_zbm_postconf_hook #3.9
fi
umount_all #3.10
}
@ -756,18 +890,19 @@ function main () {
set_ntp #1.2
resize_cow_space #1.3
update_pacman_db #1.4
install_pkgs 'base-devel' 'git' 'jq' #1.5
install_pkgs 'jq' #1.5
install_zfs #1.6
setup_zpool #1.7
mount_system #1.8
copy_zpool_cache #1.9
install_archlinux #1.10
gen_fstab #1.11
set_hostname #1.12
set_locale #1.13
add_zfs_hook_to_initramfs #1.14
set_initramfs_build_list #1.15
add_zfs_files_to_new_os #1.16
uefi_or_bios #1.7
setup_zpool #1.8
mount_system #1.9
copy_zpool_cache #1.10
install_archlinux #1.11
gen_fstab #1.12
set_hostname #1.13
set_locale #1.14
add_zfs_hook_to_initramfs #1.15
set_initramfs_build_list #1.16
add_zfs_files_to_new_os #1.17
enter_chroot #2.1
# We're done in chroot
finalize_os_setup #3.1