diff --git a/README.md b/README.md index a3ddb85..0118ace 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/setup.sh b/setup.sh index 5da1519..a023530 100644 --- a/setup.sh +++ b/setup.sh @@ -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 < '/etc/zfsbootmenu/config.yaml' < '/etc/zfsbootmenu/config.yaml' < '/etc/zfsbootmenu/config.yaml' <