From 0c21e961b1eb46b2788a40eb516ba53b40105e1e Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 20:21:20 +0100 Subject: [PATCH 01/91] feat(os): Get initial script set up (#1) --- setup.sh | 496 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 setup.sh diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..a4ffd20 --- /dev/null +++ b/setup.sh @@ -0,0 +1,496 @@ +#!/bin/bash + +declare this_script_remote_repo_raw_url zpool_name zfs_arch_dataset_name +this_script_remote_repo_raw_url='https://quico.space/...' +zpool_name='zpool' +zfs_arch_dataset_name='archlinux' + +function set_ntp () { + timedatectl set-ntp true +} + +function we_are_changerooted () { + if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then + return 0 + else + return 1 + fi +} + +function install_pkgs () { + printf -- '%s\n' 'Installing packages ...' + pacman -S --needed --noconfirm "${@}" +} + +function install_zfs () { + curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash +} + +function get_partitions () { + declare partitions_json + partitions_json="$(lsblk --output PATH,PARTTYPE --json)" || return 1 + printf -- '%s' "${partitions_json}" + return 0 +} + +function get_parts () { + declare parttype parts + parttype="${1:?}" + case "${parttype}" in + zfs) + parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1 + ;; + efi) + parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 + ;; + *) + printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting 1 ...' + exit 1 + ;; + esac + printf -- '%s' "${parts}" + return 0 +} + +function we_have_exactly_one_part () { + declare parttype parts_list parts_count + parttype="${1:?}" + parts_list="${2:?}" + parts_count="$(wc -l <<<"${parts_list}")" + if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then + case "${parts_count}" in + 0) + printf -- '%s\n' 'No '"${parttype}"' partition found. Exiting 1 ...' + exit 1 + ;; + 1) + return 0 + ;; + *) + printf -- '%s\n' 'Multiple '"${parttype}"' partitions found. We expect exactly one. Cowardly exiting 1 ...' + exit 1 + ;; + esac + printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting 1 ...' + exit 1 + fi +} + +function no_zpool_exists () { + declare zpool_list + zpool_list="$(zpool list -H)" + [[ "$(wc -l <<<"${zpool_list}")" -eq '0' ]] && return 0 + return 1 +} + +function zpool_drive_id () { + declare drive_id + drive_id="$(find -L /dev/disk/by-id -samefile "${1:?}")" + if [[ "$(wc -l <<<"${drive_id}")" -eq '1' ]]; then + printf -- '%s' "${drive_id}" + return 0 + fi + printf -- '%s\n' 'More than zpool partition entry in /dev/disk/by-id, exiting 1 ...' + exit 1 +} + +function set_zpool_password () { + printf -- '%s\n' 'password' > '/etc/zfs/'"${zpool_name}"'.key' + chmod '000' '/etc/zfs/'"${zpool_name}"'.key' +} + +function import_pool () { + zpool import -d '/dev/disk/by-id' -R '/mnt' "${zpool_name}" -N -f + zfs load-key "${zpool_name}" +} + +function create_pool () { + # Create a temporary pool that is not cached + zpool create -f \ + -o 'ashift=12' \ + -o 'autotrim=on' \ + -O 'acltype=posix' \ + -O 'compression=on' \ + -O 'relatime=on' \ + -O 'xattr=sa' \ + -O 'encryption=on' \ + -O 'keyformat=passphrase' \ + -O 'keylocation=file:///etc/zfs/'"${zpool_name}"'.key' \ + -O 'normalization=formD' \ + -O 'mountpoint=none' \ + -O 'canmount=off' \ + -O 'devices=off' \ + -R '/mnt' \ + "${zpool_name}" "${1:?}" +} + +function create_root_dataset () { + zfs create -o mountpoint=none "${zpool_name}"'/root' + # zfs set org.zfsbootmenu:commandline="ro quiet" "${zpool_name}"'/root' + zfs set org.zfsbootmenu:commandline="ro" "${zpool_name}"'/root' +} + +function create_system_dataset () { + zfs create -o 'mountpoint=/' -o 'canmount=noauto' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" + zgenhostid + zpool set bootfs="${zpool_name}"'/root/'"${zfs_arch_dataset_name}" "${zpool_name}" + zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" +} + +function create_home_dataset () { + zfs create -o 'mountpoint=/' -o 'canmount=off' "${zpool_name}"'/data' + zfs create "${zpool_name}"'/data/home' +} + +function export_pool () { + zpool export "${zpool_name}" +} + +function setup_zpool () { + declare zpool_drive drive_by_id + zpool_drive="${1:?}" + drive_by_id="$(zpool_drive_id "${zpool_drive}")" + + set_zpool_password + if no_zpool_exists; then + create_pool "${drive_by_id}" + create_root_dataset + create_system_dataset + create_home_dataset + export_pool + import_pool + fi +} + +function mount_system () { + zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" + zfs mount -a + + declare efi_part + efi_part="$(get_parts 'efi')" + if we_have_exactly_one_part 'efi' "${efi_part}"; then + mkdir -p '/mnt/efi' + mount "${efi_part}" '/mnt/efi' + fi +} + +function copy_zpool_cache () { + mkdir -p '/mnt/etc/zfs' + zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}" +} + +function install_archlinux () { + sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 5'$'\x1''g' '/etc/pacman.conf' + pacstrap /mnt \ + base \ + base-devel \ + linux \ + linux-headers \ + linux-firmware \ + amd-ucode \ + efibootmgr \ + vim \ + git \ + iwd \ + networkmanager \ + network-manager-applet \ + dialog \ + os-prober \ + reflector \ + bluez \ + bluez-utils \ + man-db \ + xdg-utils \ + xdg-user-dirs +} + +function gen_fstab () { + genfstab -U /mnt | grep -v "${zpool_name}" | tr -s '\n' | sed -r -e 's/\/mnt//' -e '/./,$!d' > '/mnt/etc/fstab' +} + +function configure_hosts_file () { + cat > '/mnt/etc/hosts' <<EOF +#<ip-address> <hostname.domain.org> <hostname> +127.0.0.1 localhost ${1} +::1 localhost ${1} +EOF +} + +function set_hostname () { + declare new_hostname + install_pkgs 'pwgen' + new_hostname="$(pwgen --no-numerals --no-capitalize --ambiguous 8)" + printf -- '%s\n' "${new_hostname}" > '/mnt/etc/hostname' + configure_hosts_file "${new_hostname}" +} + +function set_locale () { + printf -- '%s\n' 'KEYMAP=de-latin1' > '/mnt/etc/vconsole.conf' + sed -ri -e 's'$'\x1''^(#)(en_US.UTF-8[^\r\n\f]*)'$'\x1''\2'$'\x1''g' '/mnt/etc/locale.gen' + printf -- '%s\n' 'LANG=en_US.UTF-8' > '/mnt/etc/locale.conf' +} + +function add_zfs_hook_to_initramfs () { + sed -ri -e 's'$'\x1''(HOOKS=)(.*?[\(| ])(filesystems)([\)| ][^\r\n\f]*)'$'\x1''\1\2zfs \3\4'$'\x1''g' '/mnt/etc/mkinitcpio.conf' +} + +function add_zfs_files_to_new_os () { + for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' '/etc/zfs/zpool.key'; do + rsync -av --itemize-changes {'','/mnt'}"${zfs_file}" + done +} + +function enter_chroot () { + arch-chroot /mnt /bin/bash -xe <<EOF +curl --silent '${this_script_remote_repo_raw_url}' | bash +EOF +} + +function get_pkg_info () { + declare from_where local_pkg_info local_pkg_version version_search + from_where="${1}" + version_search='/Version/{print $3}' + pkg_info="$(paru -$([[ "${from_where}" == 'local' ]] && printf -- '%s' 'Q' || printf -- '%s' 'S')i "${2}" 2>&1)" + if [[ "${from_where}" == 'local' ]] && grep -Piq -- '^error: package .*? was not found' <<<"${pkg_info}"; then + return 1 + else + local_pkg_version="$(awk "${version_search}" <<<"${pkg_info}")" + fi + printf -- '%s' "${local_pkg_version}" + return 0 +} + +function paru_with_zfs_first () { + if [[ "${#}" -eq '0' ]]; then + declare -A local_pkg_info + /usr/bin/paru -Sy + if local_pkg_info['zfs-dkms']="$(get_pkg_info 'local' 'zfs-dkms')" && local_pkg_info['zfs-utils']="$(get_pkg_info 'local' 'zfs-utils')"; then + local_pkg_info['zfs-dkms']="$(get_pkg_info 'local' 'zfs-dkms')" + local_pkg_info['zfs-utils']="$(get_pkg_info 'local' 'zfs-utils')" + + declare -A remote_pkg_info + remote_pkg_info['zfs-dkms']="$(get_pkg_info 'remote' 'zfs-dkms')" + remote_pkg_info['zfs-utils']="$(get_pkg_info 'remote' 'zfs-utils')" + + /usr/bin/paru -S --needed archlinux-keyring + + if [[ "${local_pkg_info['zfs-dkms']}" == "${remote_pkg_info['zfs-dkms']}" ]] && \ + [[ "${local_pkg_info['zfs-utils']}" == "${remote_pkg_info['zfs-utils']}" ]]; then + /usr/bin/paru -Su + else + /usr/bin/paru -Sy 'zfs-dkms' 'zfs-utils' \ + --assume-installed zfs-dkms="${local_pkg_info['zfs-dkms']}" \ + --assume-installed zfs-dkms="${remote_pkg_info['zfs-dkms']}" \ + --assume-installed zfs-utils="${local_pkg_info['zfs-utils']}" \ + --assume-installed zfs-utils="${remote_pkg_info['zfs-utils']}" + /usr/bin/paru -Su + fi + else + /usr/bin/paru -S --needed archlinux-keyring + /usr/bin/paru -Su + fi + else + /usr/bin/paru "${@}" + fi +} + +function create_unpriv_user () { + account_name="${1:?}" + full_name="${2:-${account_name}}" + authorized_keys_abs_path='/home/'"${account_name}"'/.ssh/authorized_keys' + useradd --create-home --shell '/bin/bash' --comment 'Personal account of '"${full_name}" --user-group "${account_name}" + chfn --full-name "${full_name}" "${account_name}" + mkdir -p "$(dirname "${authorized_keys_abs_path}")" + touch "${authorized_keys_abs_path}" + chown -R "${account_name}"':' '/home/'"${account_name}"; chmod -R 'u=rwX,go=' "$(dirname "${authorized_keys_abs_path}")" +} + +function get_aur_helper () { + create_unpriv_user 'build' + usermod --append --groups 'wheel' 'build' + printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt' + pushd /tmp + git clone 'https://aur.archlinux.org/paru.git' + chmod -R 'build:' 'paru' + pushd 'paru' + sudo --user 'build' makepkg -si --noconfirm + popd + rm -rf 'paru' + popd + alias paru='paru_with_zfs_first' +} + +function paru_install () { + sudo --user build paru -S --noconfirm "${@}" +} + +function install_os_in_chroot () { + ### Reinit keyring + # As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running + # Time changed after keyring initialization, it leads to malfunction + # Keyring needs to be reinitialised properly to be able to sign keys. + rm -rf '/etc/pacman.d/gnupg' + pacman-key --init + pacman-key --populate archlinux + pacman -S archlinux-keyring --noconfirm + + get_aur_helper + paru_install 'zfs-dkms' 'zfs-utils' + hwclock --systohc + locale-gen + source /etc/locale.conf + mkinitcpio -P + + # Install ZFSBootMenu and deps + git clone --depth=1 https://github.com/zbm-dev/zfsbootmenu/ '/tmp/zfsbootmenu' + paru_install 'cpanminus' 'kexec-tools' 'fzf' 'util-linux' + pushd '/tmp/zfsbootmenu' + make + make install + cpanm --notest --installdeps . + popd + rm -rf '/tmp/zfsbootmenu' +} + +function set_root_pw () { + printf -- '%s\n' 'root:password' | chpasswd --root '/mnt' +} + +function configure_networking () { + cat > '/mnt/etc/systemd/network/50-wired.network' <<"EOF" +[Match] +Name=en* + +[Network] +DHCP=ipv4 +IPForward=yes + +[DHCPV4] +UseDNS=no +RouteMetric=10 +EOF + systemctl enable 'systemd-networkd' --root='/mnt' + systemctl disable 'systemd-networkd-wait-online' --root='/mnt' +} + +function configure_dns () { + rm '/mnt/etc/resolv.conf' + ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf' + # sed -i 's/^#DNS=.*/DNS=1.1.1.1/' /mnt/etc/systemd/resolved.conf + systemctl enable 'systemd-resolved' --root='/mnt' +} + +function configure_zfs () { + systemctl enable 'zfs-import-cache' 'zfs-mount' 'zfs-import.target' 'zfs.target' --root='/mnt' +} + +function configure_zfs_mount_gen () { + mkdir -p '/mnt/etc/zfs/zfs-list.cache' + touch '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}" + zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand | sed 's/\/mnt//' > '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}" + systemctl enable 'zfs-zed.service' --root='/mnt' +} + +function configure_zfsbootmenu () { + mkdir -p '/mnt/efi/EFI/ZBM' + curl -s 'https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master/etc/zfsbootmenu/mkinitcpio.conf' | sed -r -e '/^#/d' -e '/^$/d' > '/mnt/etc/zfsbootmenu/mkinitcpio.conf' + cat > '/mnt/etc/zfsbootmenu/config.yaml' <<EOF +Global: + ManageImages: true + BootMountPoint: /efi + InitCPIO: true + +Components: + Enabled: false +EFI: + ImageDir: /efi/EFI/ZBM + Versions: false + Enabled: true +Kernel: + CommandLine: ro loglevel=0 zbm.import_policy=hostid + Prefix: vmlinuz +EOF +# Up here maybe 'ro quiet' instead of 'ro' + zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" +} + +function gen_zfsbootmenu () { + arch-chroot /mnt /bin/bash -xe <<"EOF" +source /etc/locale.conf +find /efi/EFI/ZBM -type f -delete +generate-zbm +EOF +} + +function get_disk_with_efipart () { + declare disks_with_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_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=="ebd0a0a2-b9e5-4433-87c0-68b6b72699c7")] | length == 1) ) | .path')" + if [[ "$(wc -l <<<"${disks_with_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_efipart}")" -gt '1' ]]; then + printf -- '%s' "${disks_with_efipart}" + return 0 + fi + return 1 +} + +function add_zbm_to_efi () { + if ! efibootmgr | grep -Pi -- 'ZFSBootMenu'; then + declare efi_disk + efi_disk="$(get_disk_with_efipart)" + efibootmgr --disk "${efi_disk}" \ + --part 1 \ + --create \ + --label "ZFSBootMenu" \ + --loader "\EFI\ZBM\vmlinuz.efi" \ + --verbose + fi +} + +function umount_all () { + umount '/mnt/efi' + zfs umount -a + zpool export "${zpool_name}" +} + +function finalize_os_setup () { + set_root_pw + configure_networking + configure_dns + configure_zfs + configure_zfs_mount_gen + configure_zfsbootmenu + gen_zfsbootmenu + add_zbm_to_efi + umount_all +} + +function main () { + if we_are_changerooted; then + install_os_in_chroot + else + set_ntp + install_pkgs 'jq' + declare zfs_part + zfs_part="$(get_parts 'zfs')" + if we_have_exactly_one_part 'zfs' "${zfs_part}"; then + printf -- 'Creating zpool on partition '"'"'%s'"'"' ...\n' "${zfs_part}" + install_zfs + setup_zpool "${zfs_part}" + mount_system + copy_zpool_cache + systemctl start reflector + install_archlinux + gen_fstab + set_hostname + set_locale + add_zfs_hook_to_initramfs + enter_chroot + # We're done in chroot + finalize_os_setup + fi + fi +} + +main -- 2.47.2 From 8bc0c2e8c32f7f0f3b4689c9623cd8df9cda6116 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 20:22:01 +0100 Subject: [PATCH 02/91] docs(os): Set up README.md markdown outline (#1) --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e33c66..913ab6f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ # arch-zbm -Helper script to install Arch Linux with ZFSBootMenu \ No newline at end of file +Helper script to install Arch Linux with ZFSBootMenu from within a running Arch live CD ISO image + +# Prep + +The script expects minimal prep on your end. Please make sure that before execution at least one of the following conditions are met. + +- Your machine has exactly one partition with partition type code `BF00` ("Solaris root") + +# How to run this? + +- Boot an Arch Linux live CD ISO image +- Run: + ``` + curl -s https://quico.space/hygienic-books/config-jetbrains-ides/raw/branch/master/colors.scheme.xml | bash + ``` + +# Steps + +The scripts takes the following installation steps. + +1. Install ZFS with [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) + +# Assumptions + +# Development + +## Conventional commits + +This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages. + +### Commit types + +Commit _types_ besides `fix` and `feat` are: + +* `build`: Project structure, directory layout, build instructions for roll-out +* `refactor`: Keeping functionality while streamlining or otherwise improving function flow +* `test`: Working on test coverage +* `docs`: Documentation for project or components + +### Commit scopes + +The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none: + +* `zbm`: Adjusting ZFSBootMenu's behavior +* `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset +* `os`: Getting an perating system set up to correctly work in a ZFS boot environment +* `meta`: Affects the project's repo layout, readme content, file names etc. -- 2.47.2 From 9f9a1b86ebf5487ee51b8b16346ed1ce0dfd3aa4 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 20:38:42 +0100 Subject: [PATCH 03/91] fix(iso): Do an initial pacman database update (#1) --- README.md | 1 + setup.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 913ab6f..3be7ecb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Commit _types_ besides `fix` and `feat` are: The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none: +* `iso`: Changing Arch Linux ISO CD * `zbm`: Adjusting ZFSBootMenu's behavior * `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset * `os`: Getting an perating system set up to correctly work in a ZFS boot environment diff --git a/setup.sh b/setup.sh index a4ffd20..b70839c 100644 --- a/setup.sh +++ b/setup.sh @@ -17,6 +17,10 @@ function we_are_changerooted () { fi } +function update_pacman_db () { + pacman -Sy +} + function install_pkgs () { printf -- '%s\n' 'Installing packages ...' pacman -S --needed --noconfirm "${@}" @@ -471,6 +475,7 @@ function main () { install_os_in_chroot else set_ntp + update_pacman_db install_pkgs 'jq' declare zfs_part zfs_part="$(get_parts 'zfs')" -- 2.47.2 From b463cd95aa53da2a2deaa173fdefb8dab81b1702 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 20:46:51 +0100 Subject: [PATCH 04/91] docs(iso): We don't care about partially upgrading our ISO (#1) --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index b70839c..c77cce6 100644 --- a/setup.sh +++ b/setup.sh @@ -18,6 +18,8 @@ function we_are_changerooted () { } function update_pacman_db () { + # In an ISO and for the minimal number of packages we need we do not + # care about partial upgrades pacman -Sy } -- 2.47.2 From 2aaa2b67294ced3ebfdf9966fce32af84b607fed Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 20:52:20 +0100 Subject: [PATCH 05/91] fix(zfs): After running github.com/eoli3n/archiso-zfs init script we need to reset shell colors (#1) --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index c77cce6..46f1cee 100644 --- a/setup.sh +++ b/setup.sh @@ -29,7 +29,9 @@ function install_pkgs () { } function install_zfs () { + declare reset_colors='\033[0m' curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash + printf -- '%s' "${reset_colors}" } function get_partitions () { -- 2.47.2 From e21e7381f925d97889790bce725424513871d584 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:00:03 +0100 Subject: [PATCH 06/91] fix(zfs): Handle zpool list output (#1) --- setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 46f1cee..10da6f3 100644 --- a/setup.sh +++ b/setup.sh @@ -87,7 +87,7 @@ function we_have_exactly_one_part () { function no_zpool_exists () { declare zpool_list zpool_list="$(zpool list -H)" - [[ "$(wc -l <<<"${zpool_list}")" -eq '0' ]] && return 0 + [[ "$(wc -l <<<"${zpool_list}")" -le '1' ]] && [[ "$(wc -c <<<"${zpool_list}")" -le '1' ]] && return 0 return 1 } @@ -167,6 +167,9 @@ function setup_zpool () { create_home_dataset export_pool import_pool + else + printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...' + exit 1 fi } -- 2.47.2 From 61897e0364331425193a914d413f69d3f3287174 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:01:36 +0100 Subject: [PATCH 07/91] fix(zfs): Fix color reset in bash (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 10da6f3..292651f 100644 --- a/setup.sh +++ b/setup.sh @@ -31,7 +31,7 @@ function install_pkgs () { function install_zfs () { declare reset_colors='\033[0m' curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash - printf -- '%s' "${reset_colors}" + printf -- "${reset_colors}" } function get_partitions () { -- 2.47.2 From a5e240f35068b5fdcf325104c5ec214bde5f8ebe Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:13:11 +0100 Subject: [PATCH 08/91] fix(iso): Start reflector once before first package install (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 292651f..1ba15fc 100644 --- a/setup.sh +++ b/setup.sh @@ -18,6 +18,7 @@ function we_are_changerooted () { } function update_pacman_db () { + systemctl start reflector # In an ISO and for the minimal number of packages we need we do not # care about partial upgrades pacman -Sy @@ -492,7 +493,6 @@ function main () { setup_zpool "${zfs_part}" mount_system copy_zpool_cache - systemctl start reflector install_archlinux gen_fstab set_hostname -- 2.47.2 From 4dcacebcddaa9bfa24a537efdb99c0701c26c754 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:22:07 +0100 Subject: [PATCH 09/91] docs(os): Remind user to customize their OS (#1) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 3be7ecb..993f695 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,17 @@ The scripts takes the following installation steps. 1. Install ZFS with [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) +# Post-run manual steps + +When all is said and done you're goig to want to at least touch these points in your new Arch Linux install: + +* Hostname: We chose a pseudo-randomly generated 8-character string with `pwgen` +* Unprivileged user accounts: The OS was installed only with a `root` account +* Passwords + + * ZFS: The password for all datasets underneath `zpool` is `password`. + * Local `root` account: The local `root` account's password is `password`. + # Assumptions # Development -- 2.47.2 From ec315459e86a4ea807d6f728c87032909a5ce09f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:28:42 +0100 Subject: [PATCH 10/91] fix(os): chown instead of chmod works wonders (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 1ba15fc..a182442 100644 --- a/setup.sh +++ b/setup.sh @@ -323,7 +323,7 @@ function get_aur_helper () { printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt' pushd /tmp git clone 'https://aur.archlinux.org/paru.git' - chmod -R 'build:' 'paru' + chown -R 'build:' 'paru' pushd 'paru' sudo --user 'build' makepkg -si --noconfirm popd -- 2.47.2 From d78e537f613331e071e839d99a624f82be502e25 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:37:51 +0100 Subject: [PATCH 11/91] docs(os): Remind user about our AUR helper choice paru (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 993f695..f9c9f8d 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ When all is said and done you're goig to want to at least touch these points in * Hostname: We chose a pseudo-randomly generated 8-character string with `pwgen` * Unprivileged user accounts: The OS was installed only with a `root` account * Passwords - * ZFS: The password for all datasets underneath `zpool` is `password`. * Local `root` account: The local `root` account's password is `password`. +* Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. You may want to replace that by an AUR native installation e.g. by doing `paru paru`. # Assumptions -- 2.47.2 From 082973129411406d3ba1b29845f940ab1dc7d8fd Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 21:55:47 +0100 Subject: [PATCH 12/91] fix(iso): Try running script (#1) --- setup.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index a182442..13affdc 100644 --- a/setup.sh +++ b/setup.sh @@ -1,10 +1,15 @@ #!/bin/bash -declare this_script_remote_repo_raw_url zpool_name zfs_arch_dataset_name -this_script_remote_repo_raw_url='https://quico.space/...' +declare this_script_url +this_script_url="${SCRIPT_URL:?}" + +declare zpool_name zfs_arch_dataset_name zpool_name='zpool' zfs_arch_dataset_name='archlinux' +echo "${this_script_url}" +exit 0 + function set_ntp () { timedatectl set-ntp true } -- 2.47.2 From 393b0a73c3f607ebe64f3ba46bec61cb112f2c12 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 22:01:00 +0100 Subject: [PATCH 13/91] fix(iso): Update invocation command (#1) --- README.md | 5 +++-- setup.sh | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f9c9f8d..6a3d969 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ The script expects minimal prep on your end. Please make sure that before execut - Boot an Arch Linux live CD ISO image - Run: ``` - curl -s https://quico.space/hygienic-books/config-jetbrains-ides/raw/branch/master/colors.scheme.xml | bash + export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh' + curl -s "${SCRIPT_URL}" | bash ``` - + The script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. # Steps The scripts takes the following installation steps. diff --git a/setup.sh b/setup.sh index a182442..6d09ec6 100644 --- a/setup.sh +++ b/setup.sh @@ -1,7 +1,9 @@ #!/bin/bash -declare this_script_remote_repo_raw_url zpool_name zfs_arch_dataset_name -this_script_remote_repo_raw_url='https://quico.space/...' +declare this_script_url +this_script_url="${SCRIPT_URL:?}" + +declare zpool_name zfs_arch_dataset_name zpool_name='zpool' zfs_arch_dataset_name='archlinux' -- 2.47.2 From f2572c333cfbd01224dd43a456127d05d57626b7 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 22:17:38 +0100 Subject: [PATCH 14/91] docs(zbm): Explain prep steps (#1) --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a3d969..2ac54fc 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,15 @@ Helper script to install Arch Linux with ZFSBootMenu from within a running Arch # Prep -The script expects minimal prep on your end. Please make sure that before execution at least one of the following conditions are met. +The script expects minimal prep on your end. Please make sure that before execution the following conditions are met. -- Your machine has exactly one partition with partition type code `BF00` ("Solaris root") +- Arch live CD ISO image sees exactly one partition with partition type code `BF00` ("Solaris root") +- Arch live CD ISO image sees exactly one partition with partition type code `EF00` ("EFI system partition") +- No ZFS zpool exists + +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`. + +The script will use the `EF00` partition to install a ZFSBootMenu EFI executable if - and only if - `efibootmgr` says that no such `ZFSBootMenu` entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option. # How to run this? @@ -17,6 +23,7 @@ The script expects minimal prep on your end. Please make sure that before execut curl -s "${SCRIPT_URL}" | bash ``` The script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. + # Steps The scripts takes the following installation steps. -- 2.47.2 From 5fa9e07bac363f71b0e1a56d2305739e42a61f16 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 22:35:19 +0100 Subject: [PATCH 15/91] docs(zbm): Explain install and post-install steps (#1) --- README.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2ac54fc..b62cfed 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # arch-zbm -Helper script to install Arch Linux with ZFSBootMenu from within a running Arch live CD ISO image +Helper script to install Arch Linux with ZFSBootMenu from within a running Arch Linux live CD ISO image # Prep -The script expects minimal prep on your end. Please make sure that before execution the following conditions are met. +We expect minimal prep on your end. Please make sure that before execution the following conditions are met. -- Arch live CD ISO image sees exactly one partition with partition type code `BF00` ("Solaris root") -- Arch live CD ISO image sees exactly one partition with partition type code `EF00` ("EFI system partition") +- 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") - No ZFS zpool exists 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`. -The script will use the `EF00` partition to install a ZFSBootMenu EFI executable if - and only if - `efibootmgr` says that no such `ZFSBootMenu` entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option. +The script will use the `EF00` partition to install a ZFSBootMenu EFI executable if `efibootmgr` says that no such `ZFSBootMenu` entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option. # How to run this? @@ -22,27 +22,40 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh' curl -s "${SCRIPT_URL}" | bash ``` - The script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. + During execution the script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. # Steps The scripts takes the following installation steps. -1. Install ZFS with [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) +1. Install ZFS tools and kernel module with [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) +1. Create one encrypted ZFS zpool on top of `BF00` partition, password `password` +1. Create dataset for Arch Linux and `/home` +1. Install Arch Linux into pool +1. Add ZFSBootMenu to `EF00` partition if it doesn't exist already +1. Exit into Arch Linux live CD ISO image shell for you to `reboot` and frolick + +# Flavor choices + +We make the following opinionated flavor choices. Feel free to change them to your liking. + +* Arch Linux locale is set to `en_US.UTF-8` +* Keymap is set `de-latin1` + * Consult `/etc/vconsole.conf` + * Change `zfs set org.zfsbootmenu:commandline=...` +* No X.Org Server, Wayland compositors or other GUI elements get installed # Post-run manual steps -When all is said and done you're goig to want to at least touch these points in your new Arch Linux install: +After installation you're goig to want to at least touch these points in your new Arch Linux install: -* Hostname: We chose a pseudo-randomly generated 8-character string with `pwgen` +* Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` * Unprivileged user accounts: The OS was installed only with a `root` account * Passwords * ZFS: The password for all datasets underneath `zpool` is `password`. * Local `root` account: The local `root` account's password is `password`. * Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. You may want to replace that by an AUR native installation e.g. by doing `paru paru`. -# Assumptions - # Development ## Conventional commits -- 2.47.2 From 48ead422ec8cca4c8ccbcf95423a0817e4264d9f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 22:50:10 +0100 Subject: [PATCH 16/91] fix(os): systemd.network unit does not recognize [DHCPV4] (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 13affdc..b28e6b9 100644 --- a/setup.sh +++ b/setup.sh @@ -382,7 +382,7 @@ Name=en* DHCP=ipv4 IPForward=yes -[DHCPV4] +[DHCP] UseDNS=no RouteMetric=10 EOF -- 2.47.2 From 99a8eeeedea016d7cc10eecec7a6fffe61013441 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 19 Feb 2023 23:59:04 +0100 Subject: [PATCH 17/91] docs(os): Default timezone can be changed (#1) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b62cfed..ccdbad1 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ We make the following opinionated flavor choices. Feel free to change them to yo * Consult `/etc/vconsole.conf` * Change `zfs set org.zfsbootmenu:commandline=...` * No X.Org Server, Wayland compositors or other GUI elements get installed +* Timezone is `Etc/UTC` + * Check `timedatectl set-timezone <tzdata-zone>` # Post-run manual steps -- 2.47.2 From 9b9c8840636d82eaa966728bad52f6eeec28dfe9 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:22:21 +0100 Subject: [PATCH 18/91] docs(zbm): Add credits (#1) --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index ccdbad1..bebb66a 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,20 @@ The following _scopes_ are known for this project. A Conventional Commits commit * `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset * `os`: Getting an perating system set up to correctly work in a ZFS boot environment * `meta`: Affects the project's repo layout, readme content, file names etc. + +# Credits + +Most of what's here was shamelessly copied and slightly adapted for personal use from Jonathan Kirszling at GitHub. + +Thanks to: + +* Jonathan Kirszling: + * [github.com/eoli3n/arch-config/tree/master/scripts/zfs/install](https://github.com/eoli3n/arch-config/tree/master/scripts/zfs/install) + * [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) +* Maurizio Oliveri: + * [github.com/Soulsuke/arch-zfs-tools](https://github.com/Soulsuke/arch-zfs-tools) + * [gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c) +* Zach Dykstra, Andrew J. Hesford and all other [ZFSBootMenu contributors](https://github.com/zbm-dev/zfsbootmenu/graphs/contributors): + * Their [ZFSBootMenu testing helper scripts](https://github.com/zbm-dev/zfsbootmenu/tree/master/testing/helpers) ([chroot-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/chroot-arch.sh), [install-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/install-arch.sh)) +* [github.com/kongkrit](https://github.com/kongkrit): + * [gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d](https://gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d) -- 2.47.2 From 554b806f1ab15f36bfb7584957fc67213f838d7b Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:22:53 +0100 Subject: [PATCH 19/91] docs(os): Clarify post-install steps (#1) --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bebb66a..4c16d2e 100644 --- a/README.md +++ b/README.md @@ -49,14 +49,16 @@ We make the following opinionated flavor choices. Feel free to change them to yo # Post-run manual steps -After installation you're goig to want to at least touch these points in your new Arch Linux install: +After installation you're going to want to at least touch these points in your new Arch Linux install: +* Package manager hook: `pacman` does not have a hook to do ZFS snapshots + * See [this GitHub gist](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c#file-arch_on_zfs-txt-L238) and [zfs-snapshotter.bash](https://github.com/Soulsuke/arch-zfs-tools/blob/master/zfs-snapshotter.bash) for inspiration * Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` -* Unprivileged user accounts: The OS was installed only with a `root` account +* Unprivileged user accounts: The OS was installed `root` and unprivileged `build` * Passwords * ZFS: The password for all datasets underneath `zpool` is `password`. * Local `root` account: The local `root` account's password is `password`. -* Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. You may want to replace that by an AUR native installation e.g. by doing `paru paru`. +* Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. # Development -- 2.47.2 From c7dbcc4e1383801d50a683e97be47a55583cc513 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:24:38 +0100 Subject: [PATCH 20/91] docs(os): Shorten docs (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c16d2e..e7e5fbe 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The scripts takes the following installation steps. We make the following opinionated flavor choices. Feel free to change them to your liking. * Arch Linux locale is set to `en_US.UTF-8` -* Keymap is set `de-latin1` +* Keymap is `de-latin1` * Consult `/etc/vconsole.conf` * Change `zfs set org.zfsbootmenu:commandline=...` * No X.Org Server, Wayland compositors or other GUI elements get installed -- 2.47.2 From d33206866ac31ef3a96435d9f28fd232715a5034 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:29:05 +0100 Subject: [PATCH 21/91] docs(os): Clarify username setup (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e7e5fbe..4e18ce2 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ After installation you're going to want to at least touch these points in your n * Package manager hook: `pacman` does not have a hook to do ZFS snapshots * See [this GitHub gist](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c#file-arch_on_zfs-txt-L238) and [zfs-snapshotter.bash](https://github.com/Soulsuke/arch-zfs-tools/blob/master/zfs-snapshotter.bash) for inspiration * Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` -* Unprivileged user accounts: The OS was installed `root` and unprivileged `build` +* Unprivileged user accounts: The OS was installed with `root` and unprivileged `build` users * Passwords * ZFS: The password for all datasets underneath `zpool` is `password`. * Local `root` account: The local `root` account's password is `password`. -- 2.47.2 From f1deb9de7c6c353cc376e500c1bfb6e57b57460d Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:30:04 +0100 Subject: [PATCH 22/91] docs(os): Clarify hostname choice (#1) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4e18ce2..731b745 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ After installation you're going to want to at least touch these points in your n * Package manager hook: `pacman` does not have a hook to do ZFS snapshots * See [this GitHub gist](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c#file-arch_on_zfs-txt-L238) and [zfs-snapshotter.bash](https://github.com/Soulsuke/arch-zfs-tools/blob/master/zfs-snapshotter.bash) for inspiration * Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` + * Check `hostnamectl set-hostname <hostname>` * Unprivileged user accounts: The OS was installed with `root` and unprivileged `build` users * Passwords * ZFS: The password for all datasets underneath `zpool` is `password`. -- 2.47.2 From a798481e49c1ab3f7f2aa96ea89491a7d0fcbf0c Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:36:52 +0100 Subject: [PATCH 23/91] fix(iso): Remove test exit (#1) --- setup.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.sh b/setup.sh index b28e6b9..bfd67cc 100644 --- a/setup.sh +++ b/setup.sh @@ -7,9 +7,6 @@ declare zpool_name zfs_arch_dataset_name zpool_name='zpool' zfs_arch_dataset_name='archlinux' -echo "${this_script_url}" -exit 0 - function set_ntp () { timedatectl set-ntp true } -- 2.47.2 From d03821e46ac0da6a457dbb343a71313f8fada278 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:39:31 +0100 Subject: [PATCH 24/91] fix(iso): Notify that we're running reflector otherwise it looks like script's hanging (#1) --- setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.sh b/setup.sh index bfd67cc..3c22514 100644 --- a/setup.sh +++ b/setup.sh @@ -20,6 +20,7 @@ function we_are_changerooted () { } function update_pacman_db () { + printf -- '%s\n' 'Refreshing mirror list ...' systemctl start reflector # In an ISO and for the minimal number of packages we need we do not # care about partial upgrades -- 2.47.2 From 0e9314ef6f184cc07a75e29efde34ace394b3c63 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:47:53 +0100 Subject: [PATCH 25/91] fix(iso): Call correct path inside chroot (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 3c22514..df5d0a6 100644 --- a/setup.sh +++ b/setup.sh @@ -257,7 +257,7 @@ function add_zfs_files_to_new_os () { function enter_chroot () { arch-chroot /mnt /bin/bash -xe <<EOF -curl --silent '${this_script_remote_repo_raw_url}' | bash +curl --silent '${this_script_url}' | bash EOF } -- 2.47.2 From db0e0f0b7cc399f50ec9643a856f42788ed3d31a Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:57:54 +0100 Subject: [PATCH 26/91] docs(zbm): Harmonize unordered list bullet points (#1) --- README.md | 71 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 731b745..ad9aecc 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable ``` During execution the script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. + # Steps The scripts takes the following installation steps. @@ -39,27 +40,27 @@ The scripts takes the following installation steps. We make the following opinionated flavor choices. Feel free to change them to your liking. -* Arch Linux locale is set to `en_US.UTF-8` -* Keymap is `de-latin1` - * Consult `/etc/vconsole.conf` - * Change `zfs set org.zfsbootmenu:commandline=...` -* No X.Org Server, Wayland compositors or other GUI elements get installed -* Timezone is `Etc/UTC` - * Check `timedatectl set-timezone <tzdata-zone>` +- Arch Linux locale is set to `en_US.UTF-8` +- Keymap is `de-latin1` + - Consult `/etc/vconsole.conf` + - Change `zfs set org.zfsbootmenu:commandline=...` +- No X.Org Server, Wayland compositors or other GUI elements get installed +- Timezone is `Etc/UTC` + - Check `timedatectl set-timezone <tzdata-zone>` # Post-run manual steps After installation you're going to want to at least touch these points in your new Arch Linux install: -* Package manager hook: `pacman` does not have a hook to do ZFS snapshots - * See [this GitHub gist](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c#file-arch_on_zfs-txt-L238) and [zfs-snapshotter.bash](https://github.com/Soulsuke/arch-zfs-tools/blob/master/zfs-snapshotter.bash) for inspiration -* Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` - * Check `hostnamectl set-hostname <hostname>` -* Unprivileged user accounts: The OS was installed with `root` and unprivileged `build` users -* Passwords - * ZFS: The password for all datasets underneath `zpool` is `password`. - * Local `root` account: The local `root` account's password is `password`. -* Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. +- Package manager hook: `pacman` does not have a hook to do ZFS snapshots + - See [this GitHub gist](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c#file-arch_on_zfs-txt-L238) and [zfs-snapshotter.bash](https://github.com/Soulsuke/arch-zfs-tools/blob/master/zfs-snapshotter.bash) for inspiration +- Hostname: Installation chose a pseudo-randomly generated 8-character string with `pwgen` + - Check `hostnamectl set-hostname <hostname>` +- Unprivileged user accounts: The OS was installed with `root` and unprivileged `build` users +- Passwords + - ZFS: The password for all datasets underneath `zpool` is `password`. + - Local `root` account: The local `root` account's password is `password`. +- Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. # Development @@ -71,20 +72,20 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f Commit _types_ besides `fix` and `feat` are: -* `build`: Project structure, directory layout, build instructions for roll-out -* `refactor`: Keeping functionality while streamlining or otherwise improving function flow -* `test`: Working on test coverage -* `docs`: Documentation for project or components +- `build`: Project structure, directory layout, build instructions for roll-out +- `refactor`: Keeping functionality while streamlining or otherwise improving function flow +- `test`: Working on test coverage +- `docs`: Documentation for project or components ### Commit scopes The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none: -* `iso`: Changing Arch Linux ISO CD -* `zbm`: Adjusting ZFSBootMenu's behavior -* `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset -* `os`: Getting an perating system set up to correctly work in a ZFS boot environment -* `meta`: Affects the project's repo layout, readme content, file names etc. +- `iso`: Changing Arch Linux ISO CD +- `zbm`: Adjusting ZFSBootMenu's behavior +- `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset +- `os`: Getting an perating system set up to correctly work in a ZFS boot environment +- `meta`: Affects the project's repo layout, readme content, file names etc. # Credits @@ -92,13 +93,13 @@ Most of what's here was shamelessly copied and slightly adapted for personal use Thanks to: -* Jonathan Kirszling: - * [github.com/eoli3n/arch-config/tree/master/scripts/zfs/install](https://github.com/eoli3n/arch-config/tree/master/scripts/zfs/install) - * [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) -* Maurizio Oliveri: - * [github.com/Soulsuke/arch-zfs-tools](https://github.com/Soulsuke/arch-zfs-tools) - * [gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c) -* Zach Dykstra, Andrew J. Hesford and all other [ZFSBootMenu contributors](https://github.com/zbm-dev/zfsbootmenu/graphs/contributors): - * Their [ZFSBootMenu testing helper scripts](https://github.com/zbm-dev/zfsbootmenu/tree/master/testing/helpers) ([chroot-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/chroot-arch.sh), [install-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/install-arch.sh)) -* [github.com/kongkrit](https://github.com/kongkrit): - * [gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d](https://gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d) +- Jonathan Kirszling: + - [github.com/eoli3n/arch-config/tree/master/scripts/zfs/install](https://github.com/eoli3n/arch-config/tree/master/scripts/zfs/install) + - [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs) +- Maurizio Oliveri: + - [github.com/Soulsuke/arch-zfs-tools](https://github.com/Soulsuke/arch-zfs-tools) + - [gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c](https://gist.github.com/Soulsuke/6a7d1f09f7fef968a2f32e0ff32a5c4c) +- Zach Dykstra, Andrew J. Hesford and all other [ZFSBootMenu contributors](https://github.com/zbm-dev/zfsbootmenu/graphs/contributors): + - Their [ZFSBootMenu testing helper scripts](https://github.com/zbm-dev/zfsbootmenu/tree/master/testing/helpers) ([chroot-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/chroot-arch.sh), [install-arch.sh](https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/install-arch.sh)) +- [github.com/kongkrit](https://github.com/kongkrit): + - [gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d](https://gist.github.com/kongkrit/a0585e179e33c2adf92db4050ec5171d) -- 2.47.2 From a5733bdb56c4e1cd332768aee0805750c22a4ff7 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 00:59:36 +0100 Subject: [PATCH 27/91] docs(zbm): Trim unnecessary newline (#1) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ad9aecc..fb1b28d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable ``` During execution the script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. - # Steps The scripts takes the following installation steps. -- 2.47.2 From 0ea47e25bf695e84b9ffff7e50c83693f66358b6 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 01:19:13 +0100 Subject: [PATCH 28/91] fix(iso): You know we should really add ZFS files to chroot (#1) --- setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.sh b/setup.sh index df5d0a6..6effed1 100644 --- a/setup.sh +++ b/setup.sh @@ -501,6 +501,7 @@ function main () { set_hostname set_locale add_zfs_hook_to_initramfs + add_zfs_files_to_new_os enter_chroot # We're done in chroot finalize_os_setup -- 2.47.2 From 11b43e6fd06c0258accef97a4e7623eeaa0b75d8 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 02:49:21 +0100 Subject: [PATCH 29/91] refactor(os): Clean up initramfs generation (#1) --- setup.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index 6effed1..bfddaf2 100644 --- a/setup.sh +++ b/setup.sh @@ -240,13 +240,22 @@ function set_hostname () { } function set_locale () { - printf -- '%s\n' 'KEYMAP=de-latin1' > '/mnt/etc/vconsole.conf' + printf -- '%s\n' \ + 'KEYMAP=de-latin1' \ + 'FONT=Lat2-Terminus16' \ + 'FONT_MAP=8859-1' \ + > '/mnt/etc/vconsole.conf' sed -ri -e 's'$'\x1''^(#)(en_US.UTF-8[^\r\n\f]*)'$'\x1''\2'$'\x1''g' '/mnt/etc/locale.gen' printf -- '%s\n' 'LANG=en_US.UTF-8' > '/mnt/etc/locale.conf' } function add_zfs_hook_to_initramfs () { - sed -ri -e 's'$'\x1''(HOOKS=)(.*?[\(| ])(filesystems)([\)| ][^\r\n\f]*)'$'\x1''\1\2zfs \3\4'$'\x1''g' '/mnt/etc/mkinitcpio.conf' + # 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' \ + -e 's'$'\x1''((\()(fsck)(\)))'$'\x1''\2\4'$'\x1''g' \ + -e 's'$'\x1''(([[:space:]]+)(fsck)|(fsck)([[:space:]]+))'$'\x1'''$'\x1''g' \ + '/mnt/etc/mkinitcpio.conf' } function add_zfs_files_to_new_os () { -- 2.47.2 From cddcce4ff37941cd27c69e24d5fcd211e6829bd2 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 04:14:44 +0100 Subject: [PATCH 30/91] fix(os): Shorten initramfs build list (#1) --- setup.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setup.sh b/setup.sh index bfddaf2..ce8a107 100644 --- a/setup.sh +++ b/setup.sh @@ -258,6 +258,16 @@ function add_zfs_hook_to_initramfs () { '/mnt/etc/mkinitcpio.conf' } +function set_initramfs_build_list () { + # No need to build fallback initramfs, our new fallback is ZFS snapshots + sed -ri \ + -e '/^#/d' \ + -e '/^$/d' \ + -e '/^fallback/d' \ + -e 's'$'\x1''^(PRESETS=)[^\r\n\f]*'$'\x1''\1('"'"'default'"'"')'$'\x1''g' \ + '/mnt/etc/mkinitcpio.d/linux.preset' +} + function add_zfs_files_to_new_os () { for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' '/etc/zfs/zpool.key'; do rsync -av --itemize-changes {'','/mnt'}"${zfs_file}" @@ -510,6 +520,7 @@ function main () { set_hostname set_locale add_zfs_hook_to_initramfs + set_initramfs_build_list add_zfs_files_to_new_os enter_chroot # We're done in chroot -- 2.47.2 From fbb19eb710d1c39b21a50dffe9cd9497f0318e33 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 21:26:59 +0100 Subject: [PATCH 31/91] fix(zfs): keylocation file must not have newline (#1) --- setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index ce8a107..cf195c4 100644 --- a/setup.sh +++ b/setup.sh @@ -107,7 +107,8 @@ function zpool_drive_id () { } function set_zpool_password () { - printf -- '%s\n' 'password' > '/etc/zfs/'"${zpool_name}"'.key' + # No newline at the end + printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' chmod '000' '/etc/zfs/'"${zpool_name}"'.key' } -- 2.47.2 From f4c4504e1d9ca7c898d5da18b510d8ea9d7fd463 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 21:27:27 +0100 Subject: [PATCH 32/91] fix(zfs): Having keylocation readble for owner is fine (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index cf195c4..0d0ba5a 100644 --- a/setup.sh +++ b/setup.sh @@ -109,7 +109,7 @@ function zpool_drive_id () { function set_zpool_password () { # No newline at the end printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' - chmod '000' '/etc/zfs/'"${zpool_name}"'.key' + chmod '600' '/etc/zfs/'"${zpool_name}"'.key' } function import_pool () { -- 2.47.2 From 75757ee1085b6f536a8263450be4e2b443d97101 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 23:58:23 +0100 Subject: [PATCH 33/91] fix(os): Add plain text ZFS key file into initramfs, it lives inside an encrypted pool anyway (#1) --- setup.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 0d0ba5a..802f5a6 100644 --- a/setup.sh +++ b/setup.sh @@ -251,8 +251,10 @@ function set_locale () { } function add_zfs_hook_to_initramfs () { - # Add zfs hook, remove fsck hook from initramfs + # Add zfs hook, remove fsck hook from initramfs. Also add plain text key + # file into initramfs since it's living inside an encrypted pool anyway. sed -ri \ + -e 's'$'\x1''^(FILES=)[^\r\n\f]*'$'\x1''\1(/etc/zfs/'"${zpool_name}"'.key)'$'\x1''g' \ -e 's'$'\x1''(HOOKS=)(.*?[\(| ])(filesystems)([\)| ][^\r\n\f]*)'$'\x1''\1\2zfs \3\4'$'\x1''g' \ -e 's'$'\x1''((\()(fsck)(\)))'$'\x1''\2\4'$'\x1''g' \ -e 's'$'\x1''(([[:space:]]+)(fsck)|(fsck)([[:space:]]+))'$'\x1'''$'\x1''g' \ -- 2.47.2 From 6820fdfbb01f127ca5df86a11b55b14040344aea Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Mon, 20 Feb 2023 23:59:13 +0100 Subject: [PATCH 34/91] fix(os): Replace hard-coded ZFS key file name with variable (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 802f5a6..b151aec 100644 --- a/setup.sh +++ b/setup.sh @@ -272,7 +272,7 @@ function set_initramfs_build_list () { } function add_zfs_files_to_new_os () { - for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' '/etc/zfs/zpool.key'; do + for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' '/etc/zfs/'"${zpool_name}"'.key'; do rsync -av --itemize-changes {'','/mnt'}"${zfs_file}" done } -- 2.47.2 From 0f099b1d45889f4af3b50c9a927390605747d477 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 00:10:03 +0100 Subject: [PATCH 35/91] docs(zfs): Key file may have newline at the end, ZFS doesn't care (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index b151aec..bb38554 100644 --- a/setup.sh +++ b/setup.sh @@ -107,7 +107,7 @@ function zpool_drive_id () { } function set_zpool_password () { - # No newline at the end + # May or may not have a newline at the end, ZFS doesn't care printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' chmod '600' '/etc/zfs/'"${zpool_name}"'.key' } -- 2.47.2 From 6cee2ab403c1858fba0b2166e40fe2eeba19008d Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 00:36:06 +0100 Subject: [PATCH 36/91] feat(os): chmod 0600 new initramfs files, they contain our pool password (#1) --- setup.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/setup.sh b/setup.sh index bb38554..d582b60 100644 --- a/setup.sh +++ b/setup.sh @@ -361,6 +361,37 @@ function paru_install () { sudo --user build paru -S --noconfirm "${@}" } +function keep_initiramfs_root_only_rw () { + declare systemd_local_admin_override_path unit_name + systemd_local_admin_override_path='/etc/systemd/system' + unit_name='chmod-initramfs' + path_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.path' + service_unit="${systemd_local_admin_override_path%/}"'/'"${unit_name}"'.service' + + cat > "${path_unit}" <<"EOF" +[Unit] +Description=chmod initramfs to be root-read-writable only + +[Path] +PathChanged=/boot/initramfs-linux.img + +[Install] +WantedBy=multi-user.target +WantedBy=system-update.target +EOF + + cat > "${service_unit}" <<"EOF" +[Unit] +Description=chmod initramfs to be root-read-writable only + +[Service] +Type=oneshot +ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img +EOF + + systemctl enable --now "${path_unit}" +} + function install_os_in_chroot () { ### Reinit keyring # As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running @@ -376,6 +407,7 @@ function install_os_in_chroot () { hwclock --systohc locale-gen source /etc/locale.conf + keep_initiramfs_root_only_rw mkinitcpio -P # Install ZFSBootMenu and deps -- 2.47.2 From 23ca5f0c6522e93f1120cb6ebc77d8b68f2b3b5f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 00:38:55 +0100 Subject: [PATCH 37/91] refactor(os): Remove existing initramfs fallback files when we remove them from build instructions (#1) --- setup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.sh b/setup.sh index d582b60..b68e667 100644 --- a/setup.sh +++ b/setup.sh @@ -269,6 +269,9 @@ function set_initramfs_build_list () { -e '/^fallback/d' \ -e 's'$'\x1''^(PRESETS=)[^\r\n\f]*'$'\x1''\1('"'"'default'"'"')'$'\x1''g' \ '/mnt/etc/mkinitcpio.d/linux.preset' + + # Remove any existing fallback initramfs files + find '/mnt/boot' -type f -regextype posix-extended -iregex '^/mnt/boot/initramfs-.*?-fallback.img' -delete } function add_zfs_files_to_new_os () { -- 2.47.2 From 155e36dc602f2fbf50df95b67e1218d5a43d472d Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 00:40:16 +0100 Subject: [PATCH 38/91] refactor(os): Earlier systemd path unit setup (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index b68e667..fe0c154 100644 --- a/setup.sh +++ b/setup.sh @@ -405,12 +405,12 @@ function install_os_in_chroot () { pacman-key --populate archlinux pacman -S archlinux-keyring --noconfirm + keep_initiramfs_root_only_rw get_aur_helper paru_install 'zfs-dkms' 'zfs-utils' hwclock --systohc locale-gen source /etc/locale.conf - keep_initiramfs_root_only_rw mkinitcpio -P # Install ZFSBootMenu and deps -- 2.47.2 From 4bf236df751ccd33d437cb03ecaa6d8ad0792355 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 00:54:58 +0100 Subject: [PATCH 39/91] docs(zfs): Explain ZFS dataset structure in plain English (#1) --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fb1b28d..622f6aa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Helper script to install Arch Linux with ZFSBootMenu from within a running Arch Linux live CD ISO image -# Prep +## Prep We expect minimal prep on your end. Please make sure that before execution the following conditions are met. @@ -14,7 +14,7 @@ The script will create a single ZFS zpool `zpool` on the `BF00` partition with d The script will use the `EF00` partition to install a ZFSBootMenu EFI executable if `efibootmgr` says that no such `ZFSBootMenu` entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option. -# How to run this? +## How to run this? - Boot an Arch Linux live CD ISO image - Run: @@ -24,7 +24,7 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable ``` During execution the script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want. -# Steps +## Steps The scripts takes the following installation steps. @@ -35,7 +35,7 @@ The scripts takes the following installation steps. 1. Add ZFSBootMenu to `EF00` partition if it doesn't exist already 1. Exit into Arch Linux live CD ISO image shell for you to `reboot` and frolick -# Flavor choices +## Flavor choices We make the following opinionated flavor choices. Feel free to change them to your liking. @@ -47,7 +47,7 @@ We make the following opinionated flavor choices. Feel free to change them to yo - Timezone is `Etc/UTC` - Check `timedatectl set-timezone <tzdata-zone>` -# Post-run manual steps +## Post-run manual steps After installation you're going to want to at least touch these points in your new Arch Linux install: @@ -61,6 +61,42 @@ After installation you're going to want to at least touch these points in your n - Local `root` account: The local `root` account's password is `password`. - Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. +# ZFS setup explained + +The ZFS pool and dataset setup that makes this tick, explained in plain English. + +1. Create zpool with options: + 1. `-R /mnt` (aka `-o cachefile=none -o altroot=/mnt`). The pool is never cached, i.e. it's considered temporary. All pool and dataset mount paths have `/mnt` prepended. From `man zpoolprops`: + > This can be used when examining an unknown pool where the mount points cannot be trusted, or in an alternate boot environment, where the typical paths are not valid. `altroot` is not a persistent property. It is valid only while the system is up. + 1. `-O canmount=off`: Note the capital `-O` which makes this a file system property, not a pool property. File system cannot be mounted, and is ignored by `zfs mount -a`. This property is not inherited. + 1. `-O mountpoint=none`: What it says on the tin, the pool has no mountpoint configured. + 1. `-O encryption=on`: Makes this our `encryptionroot` and passes the `encryption` setting to all child datasets. Selecting `encryption=on` when creating a dataset indicates that the default encryption suite will be selected, which is currently `aes-256-gcm`. + 1. `-O keylocation=file://...`: This property is only set for encrypted datasets which are encryption roots. Controls where the user's encryption key will be loaded from by default for commands such as `zfs load-key`. + 1. `-O keyformat=passphrase`: Controls what format the user's encryption key will be provided as. Passphrases must be between 8 and 512 bytes long. +1. At this time the newly created zpool is not mounted anywhere. Next we create the "root" dataset, that's an arbitary term for the parent dataset of all boot environments. Boot environments in your case may be for example different operating systems all of which live on separate datasets underneath the root. + 1. `-o mountpoint=none`: Same as above, the root dataset has - just like the pool - no mountpoint configured. + 1. `zfs set org.zfsbootmenu:commandline=...`: Set a common kernel command line for all boot environment such as `"ro quiet"`. +1. Neither the root dataset nor the pool are mounted at this time. We now create one boot environment dataset where we want to install Arch Linux. + 1. `-o mountpoint=/`: Our Arch Linux dataset will be mounted at `/`. + zpool set bootfs="zroot/ROOT/$1" zroot + 1. `-o canmount=noauto`: When set to `noauto`, a dataset can only be mounted and unmounted explicitly. The dataset is not mounted automatically when the dataset is created or imported, nor is it mounted by the `zfs mount -a` command or unmounted by the `zfs unmount -a` command. + 1. We explicitly mount the boot environment. Since the entire pool is still subject to our initial `-R /mnt` during creation a `zfs mount zpool/root/archlinux` will mount the Arch Linux dataset not into `/` but instead into `/mnt`. +1. We also create a `data` dataset that - at least for now - we use to store only our `/home` data. + 1. For `zpool/data`: + 1. `-o mountpoint=/`: We use the `mountpoint` property here only for inheritance. + 1. `-o canmount=off`: The `zpool/data` dataset itself cannot actually be mounted. + 1. For a `zpool/data/home` child dataset: + 1. We do not specify any properties. Since `canmount` cannot be inherited the parent's `canmount=off` does not apply, it instead defaults to `canmount=on`. The parent's `mountpoint=/` property on the other hand is inherited so for a `home` child dataset it conveniently equals `mountpoint=/home`. + 1. In effect this `zpool/data/home` dataset is subject to `zfs mount -a` and will happily automount into `/home`. +1. We export the zpool once, we then reimport it by scanning only inside `/dev/disk/by-id`, again setting `-R /mnt` as we did during pool creation a moment ago and we do not mount any file systems. +1. We `zfs load-key <encryptionroot>` which will load the key from `keylocation` after which the `keystatus` property for `<encryptionroot>` and all child datasets will change from `unavailable` to `available`. +1. We mount our Arch Linux boot environment dataset. It automatically get prepended with `-R /mnt` since that's how we imported the pool. +1. We `zfs mount -a` which automounts `zpool/data/home` into `/home`, which again gets auto-prepended by `/mnt`. +1. We lastly mount our EFI partition into `/mnt/efi`. +1. We instruct ZFS to save its pool configuration via `zpool set cachefile=/etc/zfs/zpool.cache zpool`. + +The complete ZFS structure now exists and is mounted at `/mnt` ready for any `pacstrap`, [debootstrap](https://wiki.debian.org/Debootstrap), `dnf --installroot` or other bootstrapping action. + # Development ## Conventional commits -- 2.47.2 From c367be8845a6ac894fed7bf8ee0caa6593414989 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 01:03:29 +0100 Subject: [PATCH 40/91] docs(zfs): Expand on ZFS bootfs property (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 622f6aa..c0d7bfd 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,8 @@ The ZFS pool and dataset setup that makes this tick, explained in plain English. 1. `zfs set org.zfsbootmenu:commandline=...`: Set a common kernel command line for all boot environment such as `"ro quiet"`. 1. Neither the root dataset nor the pool are mounted at this time. We now create one boot environment dataset where we want to install Arch Linux. 1. `-o mountpoint=/`: Our Arch Linux dataset will be mounted at `/`. - zpool set bootfs="zroot/ROOT/$1" zroot 1. `-o canmount=noauto`: When set to `noauto`, a dataset can only be mounted and unmounted explicitly. The dataset is not mounted automatically when the dataset is created or imported, nor is it mounted by the `zfs mount -a` command or unmounted by the `zfs unmount -a` command. + 1. We then `zpool set bootfs="zpool/root/archlinux" zpool`: ZFSBootMenu uses the `bootfs` property to identify suitable boot environments. If only one dataset has it - as is the case here - it'll be booted by default with a 10-second countdown allowing manual interaction in ZFSBootMenu. 1. We explicitly mount the boot environment. Since the entire pool is still subject to our initial `-R /mnt` during creation a `zfs mount zpool/root/archlinux` will mount the Arch Linux dataset not into `/` but instead into `/mnt`. 1. We also create a `data` dataset that - at least for now - we use to store only our `/home` data. 1. For `zpool/data`: -- 2.47.2 From c3708a098ec8e02e2e2ab8b44025ca0e93bc874f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 02:56:44 +0100 Subject: [PATCH 41/91] refactor(iso): Keep partition count vars local (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index fe0c154..1eeeb5c 100644 --- a/setup.sh +++ b/setup.sh @@ -65,7 +65,7 @@ function get_parts () { } function we_have_exactly_one_part () { - declare parttype parts_list parts_count + local parttype parts_list parts_count parttype="${1:?}" parts_list="${2:?}" parts_count="$(wc -l <<<"${parts_list}")" -- 2.47.2 From b12c35ed07ab18b62f96ce625b344b2f57b7884c Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 02:57:13 +0100 Subject: [PATCH 42/91] refactor(iso): Uppercase partition type (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 1eeeb5c..5f9e530 100644 --- a/setup.sh +++ b/setup.sh @@ -72,7 +72,7 @@ function we_have_exactly_one_part () { if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then case "${parts_count}" in 0) - printf -- '%s\n' 'No '"${parttype}"' partition found. Exiting 1 ...' + printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting 1 ...' exit 1 ;; 1) -- 2.47.2 From 49fe3decedd1209d4fee1335fb2754f5165e10b2 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 02:58:02 +0100 Subject: [PATCH 43/91] refactor(iso): No need to hard-fail when more than one of a partition type exists, we ask for a choice instead (#1) --- setup.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index 5f9e530..d4cc67b 100644 --- a/setup.sh +++ b/setup.sh @@ -79,8 +79,7 @@ function we_have_exactly_one_part () { return 0 ;; *) - printf -- '%s\n' 'Multiple '"${parttype}"' partitions found. We expect exactly one. Cowardly exiting 1 ...' - exit 1 + return 1 ;; esac printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting 1 ...' -- 2.47.2 From 8d38827cd1160af1275b017a9533e48565b20195 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 02:58:55 +0100 Subject: [PATCH 44/91] feat(iso): Interactively ask for ZFS/EFI partition choice if needed (#1) --- setup.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/setup.sh b/setup.sh index d4cc67b..8211378 100644 --- a/setup.sh +++ b/setup.sh @@ -105,6 +105,35 @@ function zpool_drive_id () { exit 1 } +function select_part () { + local parts enriched_parts enriched_parts_count part_number part_type + declare part + part_type="${1:?}" # 'efi' or 'zfs' + parts="$(get_parts "${part_type}")" + + if we_have_exactly_one_part "${part_type}" "${parts}"; then + part="${parts}" + else + printf -- '%s\n' 'Which '"${part_type^^}"' partition do you want us to use?' + while IFS= read -r found_part; do + enriched_parts+=("'${found_part}'"' (aka ID '"'$(get_drive_id "${found_part}")'"')') + done <<<"${parts}" + enriched_parts_count="${#enriched_parts[@]}" + select part in "${enriched_parts[@]}"; do + part_number="${REPLY}" + if [[ "${part_number}" -le "${enriched_parts_count}" ]]; then + part="$(sed "${REPLY}q;d" <<<"${parts}")" + printf -- '%s\n' 'You'"'"'ve selected '"'${part}'"' ...' + break + else + printf -- '%s\n' 'Invalid option, please choose between 1 and '"${enriched_parts_count}" + fi + done + fi + printf -- '%s' "${part}" + return 0 +} + function set_zpool_password () { # May or may not have a newline at the end, ZFS doesn't care printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' -- 2.47.2 From ef03281a635c9a0a261011fc6a8bbd73f0262933 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:00:25 +0100 Subject: [PATCH 45/91] refactor(iso): Rewrite get_drive_id to be more broadly applicable (#1) --- setup.sh | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/setup.sh b/setup.sh index 8211378..37389ec 100644 --- a/setup.sh +++ b/setup.sh @@ -87,21 +87,14 @@ function we_have_exactly_one_part () { fi } -function no_zpool_exists () { - declare zpool_list - zpool_list="$(zpool list -H)" - [[ "$(wc -l <<<"${zpool_list}")" -le '1' ]] && [[ "$(wc -c <<<"${zpool_list}")" -le '1' ]] && return 0 - return 1 -} - -function zpool_drive_id () { +function get_drive_id () { declare drive_id drive_id="$(find -L /dev/disk/by-id -samefile "${1:?}")" - if [[ "$(wc -l <<<"${drive_id}")" -eq '1' ]]; then + if [[ "$(wc -l <<<"${drive_id}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id}")" -gt '1' ]]; then printf -- '%s' "${drive_id}" return 0 fi - printf -- '%s\n' 'More than zpool partition entry in /dev/disk/by-id, exiting 1 ...' + printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' exit 1 } -- 2.47.2 From ed737409e64eb4fac51e8dcf3f6a7074b75e4527 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:00:55 +0100 Subject: [PATCH 46/91] refactor(iso): Loically sort no_zpool_exists lower (#1) --- setup.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.sh b/setup.sh index 37389ec..a04fe11 100644 --- a/setup.sh +++ b/setup.sh @@ -127,6 +127,13 @@ function select_part () { return 0 } +function no_zpool_exists () { + declare zpool_list + zpool_list="$(zpool list -H)" + [[ "$(wc -l <<<"${zpool_list}")" -le '1' ]] && [[ "$(wc -c <<<"${zpool_list}")" -le '1' ]] && return 0 + return 1 +} + function set_zpool_password () { # May or may not have a newline at the end, ZFS doesn't care printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' -- 2.47.2 From 10440fc6d7d78dfb015a75d5d1b345752238db4d Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:01:21 +0100 Subject: [PATCH 47/91] refactor(iso): Use refactored get_drive_id to get drive ID (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index a04fe11..56c04be 100644 --- a/setup.sh +++ b/setup.sh @@ -190,7 +190,7 @@ function export_pool () { function setup_zpool () { declare zpool_drive drive_by_id zpool_drive="${1:?}" - drive_by_id="$(zpool_drive_id "${zpool_drive}")" + drive_by_id="$(get_drive_id "${zpool_drive}")" set_zpool_password if no_zpool_exists; then -- 2.47.2 From c27f0e9c7cca371e5a27e2c936dfdd47e014da01 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:01:57 +0100 Subject: [PATCH 48/91] refactor(iso): Rely on select_part for EFI partition identification (#1) --- setup.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/setup.sh b/setup.sh index 56c04be..2049aa2 100644 --- a/setup.sh +++ b/setup.sh @@ -209,13 +209,10 @@ function setup_zpool () { function mount_system () { zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" zfs mount -a - - declare efi_part - efi_part="$(get_parts 'efi')" - if we_have_exactly_one_part 'efi' "${efi_part}"; then - mkdir -p '/mnt/efi' - mount "${efi_part}" '/mnt/efi' - fi + + local efi_part="$(select_part 'efi')" + mkdir -p '/mnt/efi' + mount "${efi_part}" '/mnt/efi' } function copy_zpool_cache () { -- 2.47.2 From a970a3d1797017b5fbdf0830d25b7090b16e36f5 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:02:28 +0100 Subject: [PATCH 49/91] refactor(iso): Rely on select_part for ZFS partition identification (#1) --- setup.sh | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/setup.sh b/setup.sh index 2049aa2..9bc990b 100644 --- a/setup.sh +++ b/setup.sh @@ -570,25 +570,7 @@ function main () { set_ntp update_pacman_db install_pkgs 'jq' - declare zfs_part - zfs_part="$(get_parts 'zfs')" - if we_have_exactly_one_part 'zfs' "${zfs_part}"; then - printf -- 'Creating zpool on partition '"'"'%s'"'"' ...\n' "${zfs_part}" - install_zfs - setup_zpool "${zfs_part}" - mount_system - copy_zpool_cache - install_archlinux - gen_fstab - set_hostname - set_locale - add_zfs_hook_to_initramfs - set_initramfs_build_list - add_zfs_files_to_new_os - enter_chroot - # We're done in chroot - finalize_os_setup - fi + zfs_part="$(select_part 'zfs')" fi } -- 2.47.2 From 1fb3cc37eb57e969fa37c8442708f338deb5f545 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:02:57 +0100 Subject: [PATCH 50/91] refactor(iso): Trust that ZFS partition selection works, remove if-else clause (#1) --- setup.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/setup.sh b/setup.sh index 9bc990b..3fc188f 100644 --- a/setup.sh +++ b/setup.sh @@ -569,8 +569,25 @@ function main () { else set_ntp update_pacman_db + install_pkgs 'jq' + local zfs_part zfs_part="$(select_part 'zfs')" + + install_zfs + setup_zpool "${zfs_part}" + mount_system + copy_zpool_cache + install_archlinux + gen_fstab + set_hostname + set_locale + add_zfs_hook_to_initramfs + set_initramfs_build_list + add_zfs_files_to_new_os + enter_chroot + # We're done in chroot + finalize_os_setup fi } -- 2.47.2 From 97bebe9a99544ed1e6863fde74aeaa8a82de66cf Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:27:56 +0100 Subject: [PATCH 51/91] refactor(iso): ZFS partition selection happens only inside setup_zpool (#1) --- setup.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/setup.sh b/setup.sh index 3fc188f..7142e16 100644 --- a/setup.sh +++ b/setup.sh @@ -188,8 +188,8 @@ function export_pool () { } function setup_zpool () { - declare zpool_drive drive_by_id - zpool_drive="${1:?}" + local zpool_drive drive_by_id + zpool_drive="$(select_part 'zfs')" drive_by_id="$(get_drive_id "${zpool_drive}")" set_zpool_password @@ -569,13 +569,9 @@ function main () { else set_ntp update_pacman_db - install_pkgs 'jq' - local zfs_part - zfs_part="$(select_part 'zfs')" - install_zfs - setup_zpool "${zfs_part}" + setup_zpool mount_system copy_zpool_cache install_archlinux -- 2.47.2 From 8aa2f6ea44917fd0643770f383aa803801ffc198 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:39:18 +0100 Subject: [PATCH 52/91] fix(os): After adding new systemd unit files we need to daemon-reload (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 7142e16..3de0938 100644 --- a/setup.sh +++ b/setup.sh @@ -416,7 +416,7 @@ Description=chmod initramfs to be root-read-writable only Type=oneshot ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img EOF - + systemctl daemon-reload systemctl enable --now "${path_unit}" } -- 2.47.2 From 0f6cc4aa13a543800b250646e01752d7dda51163 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:41:36 +0100 Subject: [PATCH 53/91] fix(os): In chroot without active systemd 'enable --now' won't work (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 3de0938..be610c6 100644 --- a/setup.sh +++ b/setup.sh @@ -417,7 +417,7 @@ Type=oneshot ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img EOF systemctl daemon-reload - systemctl enable --now "${path_unit}" + systemctl enable "${path_unit}" } function install_os_in_chroot () { -- 2.47.2 From 35dacb977cc245b80e53539fa826c4719e408e65 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:46:27 +0100 Subject: [PATCH 54/91] fix(os): Actually scratch daemon-reload, no daemon here to reload in a chroot (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index be610c6..a3a97a8 100644 --- a/setup.sh +++ b/setup.sh @@ -416,7 +416,7 @@ Description=chmod initramfs to be root-read-writable only Type=oneshot ExecStart=/usr/bin/chmod 600 /boot/initramfs-linux.img EOF - systemctl daemon-reload + systemctl enable "${path_unit}" } -- 2.47.2 From 1290869dfe47428307de7e6e6dd219c7a47aeed8 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Tue, 21 Feb 2023 03:53:58 +0100 Subject: [PATCH 55/91] feat(os): For default AUR builds increase makepkg core use (#1) --- setup.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.sh b/setup.sh index a3a97a8..c66e480 100644 --- a/setup.sh +++ b/setup.sh @@ -370,6 +370,12 @@ function create_unpriv_user () { chown -R "${account_name}"':' '/home/'"${account_name}"; chmod -R 'u=rwX,go=' "$(dirname "${authorized_keys_abs_path}")" } +function unleash_makepkg () { + sed -ri \ + -e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \ + '/mnt/etc/makepkg.conf' +} + function get_aur_helper () { create_unpriv_user 'build' usermod --append --groups 'wheel' 'build' -- 2.47.2 From 61cffda88b405e96092df1cdd200fb0bc0834cde Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 00:48:17 +0100 Subject: [PATCH 56/91] fix(iso): Unleash makepkg core count (#1) --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index c66e480..7cd5e3e 100644 --- a/setup.sh +++ b/setup.sh @@ -437,6 +437,7 @@ function install_os_in_chroot () { pacman -S archlinux-keyring --noconfirm keep_initiramfs_root_only_rw + unleash_makepkg get_aur_helper paru_install 'zfs-dkms' 'zfs-utils' hwclock --systohc @@ -576,6 +577,7 @@ function main () { set_ntp update_pacman_db install_pkgs 'jq' + unleash_makepkg install_zfs setup_zpool mount_system -- 2.47.2 From d5f6e62b56d686a7b1af62ace871f2732f88ec18 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 00:56:03 +0100 Subject: [PATCH 57/91] fix(iso): Don't store subshell error msgs in vars, exit completely (#1) --- setup.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/setup.sh b/setup.sh index 7cd5e3e..b1a915b 100644 --- a/setup.sh +++ b/setup.sh @@ -7,6 +7,10 @@ declare zpool_name zfs_arch_dataset_name zpool_name='zpool' zfs_arch_dataset_name='archlinux' +# https://unix.stackexchange.com/a/48550 +set -E +trap '[ "$?" -ne 77 ] || exit 77' ERR + function set_ntp () { timedatectl set-ntp true } @@ -56,8 +60,8 @@ function get_parts () { parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 ;; *) - printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting 1 ...' - exit 1 + >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting 1 ...' + exit 77 ;; esac printf -- '%s' "${parts}" @@ -72,8 +76,8 @@ function we_have_exactly_one_part () { if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then case "${parts_count}" in 0) - printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting 1 ...' - exit 1 + >2 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting 1 ...' + exit 77 ;; 1) return 0 @@ -82,8 +86,8 @@ function we_have_exactly_one_part () { return 1 ;; esac - printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting 1 ...' - exit 1 + >2 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting 1 ...' + exit 77 fi } @@ -94,8 +98,8 @@ function get_drive_id () { printf -- '%s' "${drive_id}" return 0 fi - printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' - exit 1 + >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' + exit 77 } function select_part () { -- 2.47.2 From 73258c22b096b138cdb869c9d3e393a003e27521 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 00:57:06 +0100 Subject: [PATCH 58/91] refactor(iso): Keep drive_id local (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index b1a915b..51ed154 100644 --- a/setup.sh +++ b/setup.sh @@ -92,7 +92,7 @@ function we_have_exactly_one_part () { } function get_drive_id () { - declare drive_id + local drive_id drive_id="$(find -L /dev/disk/by-id -samefile "${1:?}")" if [[ "$(wc -l <<<"${drive_id}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id}")" -gt '1' ]]; then printf -- '%s' "${drive_id}" -- 2.47.2 From 5ea002201f6b7821ce5bbaf7ca820485c431f3fc Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 00:58:01 +0100 Subject: [PATCH 59/91] refactor(iso): drive_id can be a list without us knowing (#1) --- setup.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.sh b/setup.sh index 51ed154..2232306 100644 --- a/setup.sh +++ b/setup.sh @@ -92,10 +92,10 @@ function we_have_exactly_one_part () { } function get_drive_id () { - local drive_id - drive_id="$(find -L /dev/disk/by-id -samefile "${1:?}")" - if [[ "$(wc -l <<<"${drive_id}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id}")" -gt '1' ]]; then - printf -- '%s' "${drive_id}" + local drive_id_list + drive_id_list="$(find -L /dev/disk/by-id -samefile "${1:?}")" + if [[ "$(wc -l <<<"${drive_id_list}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id_list}")" -gt '1' ]]; then + printf -- '%s' "${drive_id_list}" return 0 fi >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' -- 2.47.2 From f9cb8c57a3c319c8536504c3d39ef42b4beeeb02 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:00:11 +0100 Subject: [PATCH 60/91] refactor(iso): When multiple drive IDs pick lexicographically first (#1) --- setup.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.sh b/setup.sh index 2232306..89b1ab4 100644 --- a/setup.sh +++ b/setup.sh @@ -92,10 +92,11 @@ function we_have_exactly_one_part () { } function get_drive_id () { - local drive_id_list - drive_id_list="$(find -L /dev/disk/by-id -samefile "${1:?}")" - if [[ "$(wc -l <<<"${drive_id_list}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id_list}")" -gt '1' ]]; then - printf -- '%s' "${drive_id_list}" + local drive_id_list drive_id_single + drive_id_list="$(find -L /dev/disk/by-id -samefile "${1:?}" | sort)" + drive_id_single="$(head -n1 <<<"${drive_id_list}")" + if [[ "$(wc -l <<<"${drive_id_single}")" -eq '1' ]] && [[ "$(wc -c <<<"${drive_id_single}")" -gt '1' ]]; then + printf -- '%s' "${drive_id_single}" return 0 fi >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' -- 2.47.2 From ac0e7dadf1ab7632830d4338c97aae16bf8ab7c7 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:03:29 +0100 Subject: [PATCH 61/91] docs(zbm): bootfs property indicates preferred/default boot environment (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0d7bfd..e3b4a72 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The ZFS pool and dataset setup that makes this tick, explained in plain English. 1. Neither the root dataset nor the pool are mounted at this time. We now create one boot environment dataset where we want to install Arch Linux. 1. `-o mountpoint=/`: Our Arch Linux dataset will be mounted at `/`. 1. `-o canmount=noauto`: When set to `noauto`, a dataset can only be mounted and unmounted explicitly. The dataset is not mounted automatically when the dataset is created or imported, nor is it mounted by the `zfs mount -a` command or unmounted by the `zfs unmount -a` command. - 1. We then `zpool set bootfs="zpool/root/archlinux" zpool`: ZFSBootMenu uses the `bootfs` property to identify suitable boot environments. If only one dataset has it - as is the case here - it'll be booted by default with a 10-second countdown allowing manual interaction in ZFSBootMenu. + 1. We then `zpool set bootfs="zpool/root/archlinux" zpool`: ZFSBootMenu uses the `bootfs` property to identify suitable boot environments. If only one pool has it - as is the case here - it identifies the pool's preferred boot dataset that will be booted with a 10-second countdown allowing manual interaction in ZFSBootMenu. 1. We explicitly mount the boot environment. Since the entire pool is still subject to our initial `-R /mnt` during creation a `zfs mount zpool/root/archlinux` will mount the Arch Linux dataset not into `/` but instead into `/mnt`. 1. We also create a `data` dataset that - at least for now - we use to store only our `/home` data. 1. For `zpool/data`: -- 2.47.2 From f9b2f144a6259564325cb57b5a5e0e6d32eee92e Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:13:26 +0100 Subject: [PATCH 62/91] docs(os): Add a getting started motd (#1) --- setup.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/setup.sh b/setup.sh index 89b1ab4..9ff6fe8 100644 --- a/setup.sh +++ b/setup.sh @@ -431,6 +431,21 @@ EOF systemctl enable "${path_unit}" } +function add_motd_getting_started_msg () { + cat > '/etc/motd' <<"EOF" + +#################### + +GUI basics: + + paru -S xorg plasma-meta kde-applications-meta sddm + localectl set-x11-keymap de + +#################### + +EOF +} + function install_os_in_chroot () { ### Reinit keyring # As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running @@ -443,6 +458,7 @@ function install_os_in_chroot () { keep_initiramfs_root_only_rw unleash_makepkg + add_motd_getting_started_msg get_aur_helper paru_install 'zfs-dkms' 'zfs-utils' hwclock --systohc -- 2.47.2 From c66e910146790814eba849360205beb17ebef01c Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:15:13 +0100 Subject: [PATCH 63/91] feat(os): Always do 5 parallel pacman downloads (#1) --- setup.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 9ff6fe8..77ab804 100644 --- a/setup.sh +++ b/setup.sh @@ -225,8 +225,12 @@ function copy_zpool_cache () { zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}" } -function install_archlinux () { +function pacman_dl_parallel () { sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 5'$'\x1''g' '/etc/pacman.conf' +} + +function install_archlinux () { + pacman_dl_parallel pacstrap /mnt \ base \ base-devel \ @@ -457,6 +461,7 @@ function install_os_in_chroot () { pacman -S archlinux-keyring --noconfirm keep_initiramfs_root_only_rw + pacman_dl_parallel unleash_makepkg add_motd_getting_started_msg get_aur_helper -- 2.47.2 From e443f668324ea75e8c2ff730502ec1c624c2e1dc Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:18:18 +0100 Subject: [PATCH 64/91] feat(os): Enable the reflector systemd service unit (#1) --- setup.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.sh b/setup.sh index 77ab804..f869c86 100644 --- a/setup.sh +++ b/setup.sh @@ -510,6 +510,10 @@ function configure_dns () { systemctl enable 'systemd-resolved' --root='/mnt' } +function configure_reflector () { + systemctl enable 'reflector' --root='/mnt' +} + function configure_zfs () { systemctl enable 'zfs-import-cache' 'zfs-mount' 'zfs-import.target' 'zfs.target' --root='/mnt' } @@ -588,6 +592,7 @@ function finalize_os_setup () { set_root_pw configure_networking configure_dns + configure_reflector configure_zfs configure_zfs_mount_gen configure_zfsbootmenu -- 2.47.2 From fdc9046726e8c3b6e237cdce7dc04504d678fc4f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:27:10 +0100 Subject: [PATCH 65/91] fix(zfs): If we're seeing more than one ZFS partition exit (#1) --- setup.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.sh b/setup.sh index f869c86..e996d23 100644 --- a/setup.sh +++ b/setup.sh @@ -112,6 +112,10 @@ function select_part () { if we_have_exactly_one_part "${part_type}" "${parts}"; then part="${parts}" else + if [[ "${part_type}" == 'zfs' ]]; then + 2> printf -- '%s\n' 'We have more than one ZFS (BF00) partition to pick for installation. Cowardly exiting ...' + exit 77 + fi printf -- '%s\n' 'Which '"${part_type^^}"' partition do you want us to use?' while IFS= read -r found_part; do enriched_parts+=("'${found_part}'"' (aka ID '"'$(get_drive_id "${found_part}")'"')') -- 2.47.2 From 38ca0c9cd38693f88a7dfbd7eb5b08cad1b560fa Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:51:19 +0100 Subject: [PATCH 66/91] refactor(iso): Indicate that zpool_drive will be used globally (#1) --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index e996d23..5cc791e 100644 --- a/setup.sh +++ b/setup.sh @@ -11,6 +11,8 @@ zfs_arch_dataset_name='archlinux' set -E trap '[ "$?" -ne 77 ] || exit 77' ERR +declare zpool_drive + function set_ntp () { timedatectl set-ntp true } -- 2.47.2 From d18ad238d5c9dd3ea94ab7c925b50ae795d460cd Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:52:10 +0100 Subject: [PATCH 67/91] refactor(iso): Correctly print and exit when in subshell (#1) --- setup.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.sh b/setup.sh index 5cc791e..ee3a45a 100644 --- a/setup.sh +++ b/setup.sh @@ -62,7 +62,7 @@ function get_parts () { parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 ;; *) - >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting 1 ...' + >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...' exit 77 ;; esac @@ -78,7 +78,7 @@ function we_have_exactly_one_part () { if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then case "${parts_count}" in 0) - >2 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting 1 ...' + >2 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...' exit 77 ;; 1) @@ -88,7 +88,7 @@ function we_have_exactly_one_part () { return 1 ;; esac - >2 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting 1 ...' + >2 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...' exit 77 fi } @@ -101,7 +101,7 @@ function get_drive_id () { printf -- '%s' "${drive_id_single}" return 0 fi - >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting 1 ...' + >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting ...' exit 77 } -- 2.47.2 From 05b6a1aeaa1a7828700f152daf3696a7609b0fe2 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:53:40 +0100 Subject: [PATCH 68/91] refactor(iso): Try to pick EFI partition on same drive as zpool (#1) --- setup.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/setup.sh b/setup.sh index ee3a45a..6a0fe0a 100644 --- a/setup.sh +++ b/setup.sh @@ -52,14 +52,20 @@ function get_partitions () { } function get_parts () { + local zfs_install_drive declare parttype parts parttype="${1:?}" + zfs_install_drive="${2:-}" case "${parttype}" in zfs) parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1 ;; efi) - parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 + if [[ "${zfs_install_drive}" ]]; then + parts="$(get_partitions | jq --raw-output '.[][] | select(.path=="'"${zfs_install_drive}"'") | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 + else + parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 + fi ;; *) >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...' @@ -106,10 +112,15 @@ function get_drive_id () { } function select_part () { - local parts enriched_parts enriched_parts_count part_number part_type + local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive zfs_install_parent declare part part_type="${1:?}" # 'efi' or 'zfs' - parts="$(get_parts "${part_type}")" + zfs_install_drive="${2:-}" + if [[ "${zfs_install_drive}" ]]; then + parts="$(get_parts "${part_type}" "${zfs_install_drive}")" + else + parts="$(get_parts "${part_type}")" + fi if we_have_exactly_one_part "${part_type}" "${parts}"; then part="${parts}" @@ -221,7 +232,7 @@ function mount_system () { zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" zfs mount -a - local efi_part="$(select_part 'efi')" + local efi_part="$(select_part 'efi' "${zpool_drive:?})" mkdir -p '/mnt/efi' mount "${efi_part}" '/mnt/efi' } -- 2.47.2 From e36822678c0487f0a95ce0c7d323709b5cc004b0 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:53:57 +0100 Subject: [PATCH 69/91] refactor(iso): Local var is sensible (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 6a0fe0a..8d3a125 100644 --- a/setup.sh +++ b/setup.sh @@ -210,7 +210,7 @@ function export_pool () { } function setup_zpool () { - local zpool_drive drive_by_id + local drive_by_id zpool_drive="$(select_part 'zfs')" drive_by_id="$(get_drive_id "${zpool_drive}")" -- 2.47.2 From 3df24d831150193bbbb6381ec7608891b1fa5884 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:54:22 +0100 Subject: [PATCH 70/91] refactor(iso): If we fail to pick a single EFI partition just exit (#1) --- setup.sh | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/setup.sh b/setup.sh index 8d3a125..bec3832 100644 --- a/setup.sh +++ b/setup.sh @@ -125,25 +125,8 @@ function select_part () { if we_have_exactly_one_part "${part_type}" "${parts}"; then part="${parts}" else - if [[ "${part_type}" == 'zfs' ]]; then - 2> printf -- '%s\n' 'We have more than one ZFS (BF00) partition to pick for installation. Cowardly exiting ...' - exit 77 - fi - printf -- '%s\n' 'Which '"${part_type^^}"' partition do you want us to use?' - while IFS= read -r found_part; do - enriched_parts+=("'${found_part}'"' (aka ID '"'$(get_drive_id "${found_part}")'"')') - done <<<"${parts}" - enriched_parts_count="${#enriched_parts[@]}" - select part in "${enriched_parts[@]}"; do - part_number="${REPLY}" - if [[ "${part_number}" -le "${enriched_parts_count}" ]]; then - part="$(sed "${REPLY}q;d" <<<"${parts}")" - printf -- '%s\n' 'You'"'"'ve selected '"'${part}'"' ...' - break - else - printf -- '%s\n' 'Invalid option, please choose between 1 and '"${enriched_parts_count}" - fi - done + 2> printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...' + exit 77 fi printf -- '%s' "${part}" return 0 -- 2.47.2 From c3833c397bc5f431211530f259da3e92142936e8 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 01:56:09 +0100 Subject: [PATCH 71/91] refactor(iso): Remove unused var (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index bec3832..4acd9d7 100644 --- a/setup.sh +++ b/setup.sh @@ -112,7 +112,7 @@ function get_drive_id () { } function select_part () { - local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive zfs_install_parent + local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive declare part part_type="${1:?}" # 'efi' or 'zfs' zfs_install_drive="${2:-}" -- 2.47.2 From 60b8ff36d5954c1e2dc935893f7e33073a038fa9 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 02:03:37 +0100 Subject: [PATCH 72/91] refactor(iso): Do JSON tree output (#1) --- setup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.sh b/setup.sh index 4acd9d7..193882c 100644 --- a/setup.sh +++ b/setup.sh @@ -46,7 +46,7 @@ function install_zfs () { function get_partitions () { declare partitions_json - partitions_json="$(lsblk --output PATH,PARTTYPE --json)" || return 1 + partitions_json="$(lsblk --output PATH,PARTTYPE --json --tree)" || return 1 printf -- '%s' "${partitions_json}" return 0 } @@ -58,13 +58,13 @@ function get_parts () { zfs_install_drive="${2:-}" case "${parttype}" in zfs) - parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1 + parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .children[].path')" || exit 1 ;; efi) if [[ "${zfs_install_drive}" ]]; then parts="$(get_partitions | jq --raw-output '.[][] | select(.path=="'"${zfs_install_drive}"'") | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 else - parts="$(get_partitions | jq --raw-output '.[][] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 + parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .children[].path')" || exit 1 fi ;; *) -- 2.47.2 From 91f4022ad13b2c7e9cf79ca294a298e97b230009 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 02:08:08 +0100 Subject: [PATCH 73/91] refactor(iso): Always pick EFI partition based on ZFS drive selection (#1) --- setup.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/setup.sh b/setup.sh index 193882c..dfc80c2 100644 --- a/setup.sh +++ b/setup.sh @@ -61,11 +61,7 @@ function get_parts () { parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .children[].path')" || exit 1 ;; efi) - if [[ "${zfs_install_drive}" ]]; then - parts="$(get_partitions | jq --raw-output '.[][] | select(.path=="'"${zfs_install_drive}"'") | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 - else - parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .children[].path')" || exit 1 - fi + parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${zfs_install_drive:?}"'") | select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .children[].path')" || exit 1 ;; *) >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...' @@ -117,6 +113,7 @@ function select_part () { part_type="${1:?}" # 'efi' 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}")" else parts="$(get_parts "${part_type}")" -- 2.47.2 From 5cad96b5e2a0374e9b33ed009f1d12574a6353ca Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 02:16:12 +0100 Subject: [PATCH 74/91] refactor(iso): Always pick EFI partition based on ZFS drive selection (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index dfc80c2..24bbc73 100644 --- a/setup.sh +++ b/setup.sh @@ -61,7 +61,7 @@ function get_parts () { parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .children[].path')" || exit 1 ;; efi) - parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${zfs_install_drive:?}"'") | select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .children[].path')" || exit 1 + parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 ;; *) >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...' -- 2.47.2 From 616272a06941dabb20ebbeb39530cb856b5e89bc Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 02:22:51 +0100 Subject: [PATCH 75/91] fix(iso): Close quote (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 24bbc73..e1b773a 100644 --- a/setup.sh +++ b/setup.sh @@ -212,7 +212,7 @@ function mount_system () { zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" zfs mount -a - local efi_part="$(select_part 'efi' "${zpool_drive:?})" + local efi_part="$(select_part 'efi' "${zpool_drive:?}")" mkdir -p '/mnt/efi' mount "${efi_part}" '/mnt/efi' } -- 2.47.2 From e1a6599a6e35d1d837b48c93bb7469b97c3973e6 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 02:38:22 +0100 Subject: [PATCH 76/91] fix(iso): Only pick ZFS/BF00 partition for install (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index e1b773a..10b6271 100644 --- a/setup.sh +++ b/setup.sh @@ -58,7 +58,7 @@ function get_parts () { zfs_install_drive="${2:-}" case "${parttype}" in zfs) - parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .children[].path')" || exit 1 + parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1 ;; efi) parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 -- 2.47.2 From 877e948941a91ca44c33c944848ba1c912f72a5d Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 03:21:09 +0100 Subject: [PATCH 77/91] fix(iso): Don't unleash too early when no makepkg exists (#1) --- setup.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.sh b/setup.sh index 10b6271..3767508 100644 --- a/setup.sh +++ b/setup.sh @@ -605,7 +605,6 @@ function main () { set_ntp update_pacman_db install_pkgs 'jq' - unleash_makepkg install_zfs setup_zpool mount_system -- 2.47.2 From 05f2aa8035a76683e93c912b2a02cb2993b2e974 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 03:22:00 +0100 Subject: [PATCH 78/91] fix(iso): Fix EFI part UUID (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 3767508..836c7f6 100644 --- a/setup.sh +++ b/setup.sh @@ -558,7 +558,7 @@ function get_disk_with_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_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=="ebd0a0a2-b9e5-4433-87c0-68b6b72699c7")] | length == 1) ) | .path')" + disks_with_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')" if [[ "$(wc -l <<<"${disks_with_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_efipart}")" -gt '1' ]]; then printf -- '%s' "${disks_with_efipart}" return 0 -- 2.47.2 From a5f32cfe76ae4a1bcbc7afb28c43bbc545d4f9bb Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Thu, 23 Feb 2023 03:42:30 +0100 Subject: [PATCH 79/91] fix(iso): Propagate EFI drive to efibootmgr (#1) --- setup.sh | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/setup.sh b/setup.sh index 836c7f6..970fd30 100644 --- a/setup.sh +++ b/setup.sh @@ -11,7 +11,7 @@ zfs_arch_dataset_name='archlinux' set -E trap '[ "$?" -ne 77 ] || exit 77' ERR -declare zpool_drive +declare zpool_drive efi_drive function set_ntp () { timedatectl set-ntp true @@ -51,6 +51,14 @@ function get_partitions () { return 0 } +function get_part_parent () { + local child_partition parent_partition + child_partition="${1:?}" + parent_partition="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${child_partition:?}"'") | .path')" + printf -- '%s' "${parent_partition}" + return 0 +} + function get_parts () { local zfs_install_drive declare parttype parts @@ -61,7 +69,7 @@ function get_parts () { parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1 ;; efi) - parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].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=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1 ;; *) >2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...' @@ -212,7 +220,10 @@ function mount_system () { zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}" zfs mount -a - local efi_part="$(select_part 'efi' "${zpool_drive:?}")" + local zfs_parent efi_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' } @@ -523,7 +534,6 @@ function configure_zfs_mount_gen () { } function configure_zfsbootmenu () { - mkdir -p '/mnt/efi/EFI/ZBM' curl -s 'https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master/etc/zfsbootmenu/mkinitcpio.conf' | sed -r -e '/^#/d' -e '/^$/d' > '/mnt/etc/zfsbootmenu/mkinitcpio.conf' cat > '/mnt/etc/zfsbootmenu/config.yaml' <<EOF Global: @@ -548,19 +558,20 @@ EOF function gen_zfsbootmenu () { arch-chroot /mnt /bin/bash -xe <<"EOF" source /etc/locale.conf +mkdir -p '/efi/EFI/ZBM' find /efi/EFI/ZBM -type f -delete generate-zbm EOF } -function get_disk_with_efipart () { - declare disks_with_efipart +function get_disks_with_one_efipart () { + local 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_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')" - if [[ "$(wc -l <<<"${disks_with_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_efipart}")" -gt '1' ]]; then - printf -- '%s' "${disks_with_efipart}" + 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')" + 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 fi return 1 @@ -568,14 +579,16 @@ function get_disk_with_efipart () { function add_zbm_to_efi () { if ! efibootmgr | grep -Pi -- 'ZFSBootMenu'; then - declare efi_disk - efi_disk="$(get_disk_with_efipart)" - efibootmgr --disk "${efi_disk}" \ - --part 1 \ - --create \ - --label "ZFSBootMenu" \ - --loader "\EFI\ZBM\vmlinuz.efi" \ - --verbose + local efi_disks_list + efi_disks_list="$(get_disks_with_one_efipart)" + if grep -Piq -- '^'"${efi_drive}"'$' <<<"${efi_disks_list}"; then + efibootmgr --disk "${efi_drive}" \ + --part 1 \ + --create \ + --label "ZFSBootMenu" \ + --loader "\EFI\ZBM\vmlinuz.efi" \ + --verbose + fi fi } -- 2.47.2 From 96ab417c729a7c954d0041930ab43a169869e14f Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sat, 4 Mar 2023 18:09:34 +0100 Subject: [PATCH 80/91] fix(zfs): chmod 000 ZFS encryption keyfile per ZBM's docs (#1) --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 970fd30..6e264b1 100644 --- a/setup.sh +++ b/setup.sh @@ -147,7 +147,7 @@ function no_zpool_exists () { function set_zpool_password () { # May or may not have a newline at the end, ZFS doesn't care printf -- '%s' 'password' > '/etc/zfs/'"${zpool_name}"'.key' - chmod '600' '/etc/zfs/'"${zpool_name}"'.key' + chmod '000' '/etc/zfs/'"${zpool_name}"'.key' } function import_pool () { -- 2.47.2 From dd1fcb0c725cd8dd2f46cad85a52f55c2f521374 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sat, 4 Mar 2023 23:12:49 +0100 Subject: [PATCH 81/91] feat(zfs): Explain password change and caveats (#1) --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index e3b4a72..aa36ef1 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,49 @@ After installation you're going to want to at least touch these points in your n - Local `root` account: The local `root` account's password is `password`. - Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`. +# Password change + +## Steps + +After installation you're going to want to change your ZFS encryption password. This generally means in a running OS: + +1. Change password in `keylocation` file, e.g. `/etc/zfs/zpool.key` or whatever other `"${zpool_name}"'.key'` file you used during setup +1. Set this key as the new encryption key: + ``` + zfs change-key -l zpool + ``` + Quoting `man 8 zfs-change-key` from `zfs-utils` version 2.1.9 for the `-l` argument: "Ensures the key is loaded before attempting to change the key." When successful the command will not output data, it'll just silently change your encryption key. +1. Rebuild initramfs: + ``` + mkinitcpio -P + ``` + Here for example with `-P` (`--allpresets`) which processes all presets contained in `/etc/mkinitcpio.d`. This step puts the changed key file into your initramfs. During setup we've adjusted `/etc/mkinitcpio.conf` so that it contains `FILES=(/etc/zfs/zpool.key)` which causes the file to be added to initramfs as-is. + +## Boot flow + +With your password changed in two locations (key file and initramfs) The boot process works as follows. + +At boot time ZFSBootMenu will scan all pools that it can import for a `bootfs` property. If it only finds one pool with that property the dataset given as `bootfs` will be selected for boot with a 10-second countdown allowing manual interaction. With `bootfs` set ZFSBootMenu will not actively search through datasets for valid kernel and initramfs combinations, it'll instead accept `bootfs` as the default boot entry without entering the pool decryption passphrase. + +Upon loading into a given dataset ZFSBootMenu will attempt to auto-load the matching decryption key. In our setup this will fail because we purposely stored the encryption key inside our `zpool/root/archlinux` dataset. ZFSBootMenu will prompt us to type in the decryption key. + +Lastly ZFSBootMenu loads our OS' kernel and initramfs combination via `kexec`. For this step we don't need to enter the decryption key again. Our initramfs file contains the plain-text `/etc/zfs/zpool.key` file which allows it to seamlessly import the right dataset, load its key and mount it. + +## Caveats in a password change + +ZFS differentiates between user keys - also called wrapping keys - and the master key for any given encryption root. You never interact with the master key, you only pick your personal user key. Subsequently a user key change (in our use case we perceive this simply as a password change) has zero effect on data that's already encrypted. The operation is instant and merely reencrypted the already existing master key, the so-called _wrapped_ master key. + +ZFS generates the master key exactly once when you enable encryption on a dataset. Among other inputs it uses your user key to encrypt (to _wrap_) the master key. So when you change your user key it just means that the master key stays exactly the same and only the encrypted (_wrapped_) key changes. + +`man 8 zfs-change-key` from `zfs-utils` version 2.1.9 adds: +> If the user's key is compromised, `zfs change-key` does not necessarily protect existing or newly-written data from attack. Newly-written data will continue to be encrypted with the same master key as the existing data. The master key is compromised if an attacker obtains a user key and the corresponding wrapped master key. Currently, `zfs change-key` does not overwrite the previous wrapped master key on disk, so it is accessible via forensic analysis for an indeterminate length of time. +> +> In the event of a master key compromise, ideally the drives should be securely erased to remove all the old data (which is readable using the compromised master key), a new pool created, and the data copied back. This can be approximated in place by creating new datasets, copying the data (e.g. using `zfs send | zfs recv`), and then clearing the free space with `zpool trim --secure` if supported by your hardware, otherwise `zpool initialize`. + +On one hand changing the ZFS encryption password is generally a good and useful thing to do. On the other hand changing your password does not currently overwrite previous wrapped master keys on disk. A sufficiently motivated party that gains access to a wrapped master key and the matching user key is able to decrypt the master key and use it to read all data encrypted with it. + +By extension this means after a password change your data remains at risk until you've copied it to a new dataset and erased previously used space thereby erasing any previous wrapped master keys. + # ZFS setup explained The ZFS pool and dataset setup that makes this tick, explained in plain English. -- 2.47.2 From 58919e0d7f5f4c538b84a0b886cf7d79fab75787 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sat, 4 Mar 2023 23:21:06 +0100 Subject: [PATCH 82/91] fix(os): Find /etc/makepkg.conf both with and without chroot (#1) --- setup.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 6e264b1..e47100e 100644 --- a/setup.sh +++ b/setup.sh @@ -388,9 +388,14 @@ function create_unpriv_user () { } function unleash_makepkg () { + local path_prefix + path_prefix='/mnt' + if we_are_changerooted; then + path_prefix='' + fi sed -ri \ -e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \ - '/mnt/etc/makepkg.conf' + "${path_prefix}"'/etc/makepkg.conf' } function get_aur_helper () { -- 2.47.2 From a2ca77b70aa50866d4c842e4f37d729b66f6c420 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sat, 4 Mar 2023 23:43:11 +0100 Subject: [PATCH 83/91] feat(os): Let's assume we want a DNS server pushed via DHCP (#1) --- setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index e47100e..e1e7177 100644 --- a/setup.sh +++ b/setup.sh @@ -509,7 +509,7 @@ DHCP=ipv4 IPForward=yes [DHCP] -UseDNS=no +UseDNS=yes RouteMetric=10 EOF systemctl enable 'systemd-networkd' --root='/mnt' @@ -519,6 +519,9 @@ EOF function configure_dns () { rm '/mnt/etc/resolv.conf' ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf' + + # Optionally you may want /etc/systemd/network/50-wired.network to use + # UseDNS=no and hardcode DNS server(s) here: # sed -i 's/^#DNS=.*/DNS=1.1.1.1/' /mnt/etc/systemd/resolved.conf systemctl enable 'systemd-resolved' --root='/mnt' } -- 2.47.2 From bfc6f814ef948676c2d693c3d010911c54622359 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 03:34:59 +0100 Subject: [PATCH 84/91] docs(zfs): Explain how to rekey all data with new master key after user key change (#1) --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa36ef1..aa90fcb 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Lastly ZFSBootMenu loads our OS' kernel and initramfs combination via `kexec`. F ZFS differentiates between user keys - also called wrapping keys - and the master key for any given encryption root. You never interact with the master key, you only pick your personal user key. Subsequently a user key change (in our use case we perceive this simply as a password change) has zero effect on data that's already encrypted. The operation is instant and merely reencrypted the already existing master key, the so-called _wrapped_ master key. -ZFS generates the master key exactly once when you enable encryption on a dataset. Among other inputs it uses your user key to encrypt (to _wrap_) the master key. So when you change your user key it just means that the master key stays exactly the same and only the encrypted (_wrapped_) key changes. +ZFS generates the master key exactly once when you enable encryption on a dataset - technically when it becomes an encryption root. Among other inputs it uses your user key to encrypt (to _wrap_) the master key. So when you change your user key it just means that the master key stays exactly the same and only the encrypted (_wrapped_) key changes. `man 8 zfs-change-key` from `zfs-utils` version 2.1.9 adds: > If the user's key is compromised, `zfs change-key` does not necessarily protect existing or newly-written data from attack. Newly-written data will continue to be encrypted with the same master key as the existing data. The master key is compromised if an attacker obtains a user key and the corresponding wrapped master key. Currently, `zfs change-key` does not overwrite the previous wrapped master key on disk, so it is accessible via forensic analysis for an indeterminate length of time. @@ -104,6 +104,66 @@ On one hand changing the ZFS encryption password is generally a good and useful By extension this means after a password change your data remains at risk until you've copied it to a new dataset and erased previously used space thereby erasing any previous wrapped master keys. +## Changing master key + +In order to generate a new master key after you've changed your user key as mentioned in `man 8 zfs-change-key` from `zfs-utils` version 2.1.9 one example workflow goes like this: + +1. Change user key + - Update `/etc/zfs/zpool.key` + - Generate new initramfs with `mkinitcpio -P` +1. Create a snapshot from current system dataset + ``` + # Assuming current system dataset is zpool/root/archlinux-sxu + # where '-sxu' is a random suffix to differentiate datasets + # and has no real meaning + zfs snapshot zpool/root/archlinux-sxu@rekey + ``` +1. Within same pool `send`/`receive` snapshot + ``` + zfs send \ + --large-block \ + --compressed \ + 'zpool/root/archlinux-sxu@rekey' | \ + + zfs receive \ + -Fvu \ + -o 'encryption=on' \ + -o 'keyformat=passphrase' \ + -o 'keylocation=file:///etc/zfs/zpool.key' \ + -o 'mountpoint=/' \ + -o 'canmount=noauto' \ + -o 'org.zfsbootmenu:commandline=rw nowatchdog rd.vconsole.keymap=de-latin1' \ + 'zpool/root/archlinux-frn' + ``` + Explanation: + - We specifically don't `zfs send -R` (`--replicate`). While it would normally be nice to transfer all of a dataset's children at once such as all of its snapshots the `-R` argument conflicts with the `encryption` property. See [comment by Tom Caputi on GitHub openzfs/zfs issue 10507 from June 2020](https://github.com/openzfs/zfs/issues/10507#issuecomment-651162104) for details. Basically if `encryption` is set then `-R` doesn't work. We could transfer existing encryption properties with `-w`/`--raw` but we don't actually want to transfer encryption properties at all. We want them to change during transfer, see the bullet point four points down from here talking about `encryption`. + - We `zfs receive -F` destroying any target snapshots and file systems beyond the snapshot we're transferring. In this example the target `zpool/root/archlinux-frn` doesn't even exist so `-F` isn't necessary to clean anything up. It's just good practice. + - With `-v` we get verbose progress output + - Argument `-u` makes sure the dataset does not get mounted after transfer. ZFS would mount it into `/` which wouldn't be helpful since we're currently using that filesystem ourselves. + - We set encryption properties `keyformat`, `keylocation` and most importantly `encryption`. The latter will turn our transferred dataset into its own `encryptionroot` which in turn generates a new master key. The auto-generated new master key gets wrapped with our updated passphrase in `keylocation`. This basically rekeys all data on our pool during transfer. + - We set `mountpoint` and `canmount` as well as a `org.zfsbootmenu:commandline` as we would for any new system dataset. +1. Change zpool's `bootfs` property to new system dataset + ``` + zpool set bootfs=zpool/root/archlinux-frn zpool + ``` +1. Boot into new system dataset +1. Update the new system dataset's `encryptionroot` by letting it inherit key data from its parent: + ``` + zfs change-key -i -l zpool/root/archlinux-frn + ``` + The parent `zpool/root` is inheriting this property from `zpool` which will make sure that `zpool/root/archlinux-frn` essentially gets its key now from `zpool`. Both `zpool/root/archlinux-frn` and `zpool` use the same exact `keylocation` with identical content. This operation is instant. + +Optionally you may want to clean up: + +1. In newly keyed/reencrypted system dataset destroy its snapshot + ``` + zfs destroy zpool/root/archlinux-frn@rekey + ``` +1. Recursively destroy source dataset + ``` + zfs destroy -r zpool/root/archlinux-sxu + ``` + # ZFS setup explained The ZFS pool and dataset setup that makes this tick, explained in plain English. -- 2.47.2 From 9b11a12f4cddd572ca44b8585a001865cb3e6b9e Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 03:58:15 +0100 Subject: [PATCH 85/91] docs(zfs): Reformat password change headline (#1) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa90fcb..e48c568 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,11 @@ After installation you're going to want to at least touch these points in your n # Password change +After installation you're going to want to change your ZFS encryption password. + ## Steps -After installation you're going to want to change your ZFS encryption password. This generally means in a running OS: +In a running OS: 1. Change password in `keylocation` file, e.g. `/etc/zfs/zpool.key` or whatever other `"${zpool_name}"'.key'` file you used during setup 1. Set this key as the new encryption key: -- 2.47.2 From 60136f4807c1c94fbad1b62a6089b53cd8d26a5b Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 03:59:06 +0100 Subject: [PATCH 86/91] docs(zfs): Minor style fixes (#1) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e48c568..c58c7cf 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ In a running OS: With your password changed in two locations (key file and initramfs) The boot process works as follows. -At boot time ZFSBootMenu will scan all pools that it can import for a `bootfs` property. If it only finds one pool with that property the dataset given as `bootfs` will be selected for boot with a 10-second countdown allowing manual interaction. With `bootfs` set ZFSBootMenu will not actively search through datasets for valid kernel and initramfs combinations, it'll instead accept `bootfs` as the default boot entry without entering the pool decryption passphrase. +At boot time ZFSBootMenu will scan all pools that it can import for a `bootfs` property. If it only finds one pool with that property the dataset given as `bootfs` will be selected for boot with a 10-second countdown allowing manual interaction. With `bootfs` set ZFSBootMenu will not actively search through datasets for valid kernel and initramfs combinations, it'll instead accept `bootfs` as the default boot entry without us entering the pool decryption passphrase. Upon loading into a given dataset ZFSBootMenu will attempt to auto-load the matching decryption key. In our setup this will fail because we purposely stored the encryption key inside our `zpool/root/archlinux` dataset. ZFSBootMenu will prompt us to type in the decryption key. @@ -93,9 +93,9 @@ Lastly ZFSBootMenu loads our OS' kernel and initramfs combination via `kexec`. F ## Caveats in a password change -ZFS differentiates between user keys - also called wrapping keys - and the master key for any given encryption root. You never interact with the master key, you only pick your personal user key. Subsequently a user key change (in our use case we perceive this simply as a password change) has zero effect on data that's already encrypted. The operation is instant and merely reencrypted the already existing master key, the so-called _wrapped_ master key. +ZFS differentiates between user keys - also called wrapping keys - and the master key for any given encryption root. You never interact with the master key, you only pick your personal user key. Subsequently a user key change (in our use case we perceive this simply as a password change) has zero effect on data that's already encrypted. The operation is instant and merely reencrypts the already existing master key, the so-called _wrapped_ master key. -ZFS generates the master key exactly once when you enable encryption on a dataset - technically when it becomes an encryption root. Among other inputs it uses your user key to encrypt (to _wrap_) the master key. So when you change your user key it just means that the master key stays exactly the same and only the encrypted (_wrapped_) key changes. +ZFS generates the master key exactly once when you enable encryption on a dataset - technically when it becomes an encryption root. Among other inputs it uses your user key to encrypt (to _wrap_) the master key. When you change your user key it just means that the master key stays exactly the same and only the encrypted (_wrapped_) key changes. `man 8 zfs-change-key` from `zfs-utils` version 2.1.9 adds: > If the user's key is compromised, `zfs change-key` does not necessarily protect existing or newly-written data from attack. Newly-written data will continue to be encrypted with the same master key as the existing data. The master key is compromised if an attacker obtains a user key and the corresponding wrapped master key. Currently, `zfs change-key` does not overwrite the previous wrapped master key on disk, so it is accessible via forensic analysis for an indeterminate length of time. -- 2.47.2 From 930d5f4687ed6cba53458dfc240e1f99a16aa181 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 03:59:25 +0100 Subject: [PATCH 87/91] fix(zfs): After password change always zfs change-key (#1) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c58c7cf..8efa56b 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ In order to generate a new master key after you've changed your user key as ment 1. Change user key - Update `/etc/zfs/zpool.key` + - Update zpool with new key via `zfs change-key -l zpool` - Generate new initramfs with `mkinitcpio -P` 1. Create a snapshot from current system dataset ``` -- 2.47.2 From 10c9ae589f70de0c7156c18200c31ba20efa1d41 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 04:00:00 +0100 Subject: [PATCH 88/91] docs(zfs): Clarify reencryption in flight (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8efa56b..5a79523 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ In order to generate a new master key after you've changed your user key as ment - We `zfs receive -F` destroying any target snapshots and file systems beyond the snapshot we're transferring. In this example the target `zpool/root/archlinux-frn` doesn't even exist so `-F` isn't necessary to clean anything up. It's just good practice. - With `-v` we get verbose progress output - Argument `-u` makes sure the dataset does not get mounted after transfer. ZFS would mount it into `/` which wouldn't be helpful since we're currently using that filesystem ourselves. - - We set encryption properties `keyformat`, `keylocation` and most importantly `encryption`. The latter will turn our transferred dataset into its own `encryptionroot` which in turn generates a new master key. The auto-generated new master key gets wrapped with our updated passphrase in `keylocation`. This basically rekeys all data on our pool during transfer. + - We set encryption properties `keyformat`, `keylocation` and most importantly `encryption`. The latter will turn our transferred dataset into its own `encryptionroot` which in turn generates a new master key. The auto-generated new master key gets wrapped with our updated passphrase in `keylocation`. This basically reencrypts all data in this dataset during transfer. - We set `mountpoint` and `canmount` as well as a `org.zfsbootmenu:commandline` as we would for any new system dataset. 1. Change zpool's `bootfs` property to new system dataset ``` -- 2.47.2 From a19adbfe28f7b266fcec8e08565d27af13251a82 Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 04:00:26 +0100 Subject: [PATCH 89/91] docs(zfs): Clarify reboot first, then update encryptionroot (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a79523..1cbd385 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ In order to generate a new master key after you've changed your user key as ment zpool set bootfs=zpool/root/archlinux-frn zpool ``` 1. Boot into new system dataset -1. Update the new system dataset's `encryptionroot` by letting it inherit key data from its parent: +1. After reboot and now that you're in the new system dataset change its `encryptionroot` by letting it inherit data from its parent: ``` zfs change-key -i -l zpool/root/archlinux-frn ``` -- 2.47.2 From 9943b6c61b056dedbfe2f0c965eab81591613def Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 04:00:51 +0100 Subject: [PATCH 90/91] docs(zfs): Explain how to confirm new master key (#1) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1cbd385..d016cac 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,16 @@ In order to generate a new master key after you've changed your user key as ment ``` The parent `zpool/root` is inheriting this property from `zpool` which will make sure that `zpool/root/archlinux-frn` essentially gets its key now from `zpool`. Both `zpool/root/archlinux-frn` and `zpool` use the same exact `keylocation` with identical content. This operation is instant. +## Finishing touches + +Just to confirm that the master key has changed run this commands. It takes a moment to output data: + +``` +zfs send --raw zpool/root/archlinux-frn@rekey | zstream dump | sed -n -e '/crypt_keydata/,/end crypt/p; /END/q' +``` + +Repeat for source dataset `zpool/root/archlinux-sxu@rekey`. You're particularly interested in parameters `DSL_CRYPTO_MASTER_KEY_1` and the initialization vector `DSL_CRYPTO_IV`. Notice that they differ between old and new dataset confirming that your new dataset has a new master key. + Optionally you may want to clean up: 1. In newly keyed/reencrypted system dataset destroy its snapshot -- 2.47.2 From 1c17351d86118a1484160e6cce920878fbddd58e Mon Sep 17 00:00:00 2001 From: hygienic-books <hygienic-books@tentic.net> Date: Sun, 5 Mar 2023 04:01:50 +0100 Subject: [PATCH 91/91] docs(zfs): Typo (#1) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d016cac..572502d 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ In order to generate a new master key after you've changed your user key as ment ## Finishing touches -Just to confirm that the master key has changed run this commands. It takes a moment to output data: +Just to confirm that the master key has changed run this command. It takes a moment to output data: ``` zfs send --raw zpool/root/archlinux-frn@rekey | zstream dump | sed -n -e '/crypt_keydata/,/end crypt/p; /END/q' -- 2.47.2