#!/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' < 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 <&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' <