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' < +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' <