2023-02-19 20:21:20 +01:00
#!/bin/bash
2023-10-21 00:56:26 +02:00
# Whatever comes in on file descriptor (FD) 3 gets redirected to where file
# descriptor 1 is pointing. File descriptor 1 points to stdout so when we
# output-redirect something into FD 3 it shows up on stdout. We can use this
# to produce arbitrary logging output inside a subshell like so:
#
# function my_func () {
# some_command "${1:?}"
# >&3 echo 'A log message'
# }
#
# var="$(my_func arg_1)"
#
# Here "${var}" will only capture the output of some_command "${1:?}". It
# will not capture 'echo' which will instead show up on our stdout/FD 1.
2023-10-21 00:34:13 +02:00
exec 3>& 1
2023-10-21 00:32:16 +02:00
2023-02-19 21:55:47 +01:00
declare this_script_url
this_script_url = " ${ SCRIPT_URL : ? } "
declare zpool_name zfs_arch_dataset_name
2023-02-19 20:21:20 +01:00
zpool_name = 'zpool'
zfs_arch_dataset_name = 'archlinux'
2023-10-23 00:46:15 +02:00
declare -A partition_types
partition_types[ gpt_zfs] = '6a85cf4d-1dd2-11b2-99a6-080020736631'
partition_types[ gpt_efi] = 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
partition_types[ mbr_zfs] = '0xbf'
partition_types[ mbr_boot] = '0x83'
2023-02-23 00:56:03 +01:00
# https://unix.stackexchange.com/a/48550
set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR
2023-10-23 01:18:38 +02:00
declare zpool_drive efi_drive boot_drive part_schema
2023-02-23 01:51:19 +01:00
2023-02-19 20:21:20 +01:00
function we_are_changerooted ( ) {
if [ " $( stat -c '%d:%i' '/' ) " != " $( stat -c '%d:%i' '/proc/1/root/.' ) " ] ; then
return 0
else
return 1
fi
}
2023-10-23 00:46:15 +02:00
function no_kernel_update_in_iso ( ) {
2023-10-23 02:16:02 +02:00
#1.1
2023-10-22 02:00:16 +02:00
sed -ri -e 's' $'\x1' '#(IgnorePkg)[^\r\n\f]+' $'\x1' '\1 = linux linux-headers' $'\x1' 'g' /etc/pacman.conf
2023-10-22 01:52:33 +02:00
}
function set_ntp ( ) {
2023-10-23 02:16:02 +02:00
#1.2
2023-10-20 15:15:39 +02:00
timedatectl set-ntp true
}
2023-10-20 19:19:35 +02:00
function resize_cow_space ( ) {
2023-10-23 02:16:02 +02:00
#1.3
2023-10-20 19:19:35 +02:00
mount -o remount,size= '50%' /run/archiso/cowspace
}
function update_pacman_db ( ) {
2023-10-23 02:16:02 +02:00
#1.4
2023-02-20 00:39:31 +01:00
printf -- '%s\n' 'Refreshing mirror list ...'
2023-10-23 02:52:10 +02:00
printf -- '%s\n' \
'--save /etc/pacman.d/mirrorlist' \
'--protocol https' \
2023-10-23 03:02:23 +02:00
'--latest 5' \
'--sort age' \
2023-10-23 02:52:10 +02:00
> '/etc/xdg/reflector/reflector.conf'
2023-02-19 21:13:11 +01:00
systemctl start reflector
2023-02-19 20:46:51 +01:00
# In an ISO and for the minimal number of packages we need we do not
# care about partial upgrades
2023-10-20 21:44:30 +02:00
pacman -Syyuu --noconfirm
2023-02-19 20:38:42 +01:00
}
2023-02-19 20:21:20 +01:00
function install_pkgs ( ) {
2023-10-23 02:16:02 +02:00
#1.5
2023-02-19 20:21:20 +01:00
printf -- '%s\n' 'Installing packages ...'
pacman -S --needed --noconfirm " ${ @ } "
}
2023-10-20 19:21:41 +02:00
function install_zfs ( ) {
2023-10-23 02:16:02 +02:00
#1.6
2023-10-20 19:21:41 +02:00
declare reset_colors = '\033[0m'
curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash
printf -- " ${ reset_colors } "
}
2023-10-23 02:16:02 +02:00
function uefi_or_bios ( ) {
#1.7
local part_count_linux part_count_efi
# Select disks with at least one partition. Among them count how many
# with the given partition type code we have.
part_count_linux = " $( get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="' " ${ partition_types [mbr_boot] } " '") | .path] | length' ) "
part_count_efi = " $( get_partitions | jq --raw-output '[.[][] | select(.children | length > 0) | .children[] | select(.parttype=="' " ${ partition_types [gpt_efi] } " '") | .path] | length' ) "
if [ [ " ${ part_count_linux } " -eq '1' ] ] && [ [ " ${ part_count_efi } " -eq '0' ] ] ; then
part_schema = 'mbr'
>& 3 printf -- '%s\n' \
'Treating this as an MBR/legacy BIOS machine ...'
elif [ [ " ${ part_count_linux } " -eq '0' ] ] && [ [ " ${ part_count_efi } " -eq '1' ] ] ; then
part_schema = 'gpt'
>& 3 printf -- '%s\n' \
'Treating this as a GPT/UEFI machine ...'
else
>& 3 printf -- '%s\n' \
'We are seeing partitions as follows:' \
'- Partition type code ' " ${ partition_types [mbr_boot] } " ': ' " ${ part_count_linux } " \
'- Partition type code ' " ${ partition_types [gpt_efi] } " ': ' " ${ part_count_efi } " \
'' \
'We are instead expecting either 1 and 0 indicating a legacy' \
'BIOS setup or 0 and 1 indicating UEFI. ' " ${ part_count_linux } " ' and ' " ${ part_count_efi } " ' is' \
'neither. Cowardly exiting ...'
exit 77
fi
2023-10-23 23:53:11 +02:00
export part_schema = " ${ part_schema } "
2023-10-23 02:16:02 +02:00
}
2023-02-19 20:21:20 +01:00
function get_partitions ( ) {
declare partitions_json
2023-02-23 02:03:37 +01:00
partitions_json = " $( lsblk --output PATH,PARTTYPE --json --tree) " || return 1
2023-02-19 20:21:20 +01:00
printf -- '%s' " ${ partitions_json } "
return 0
}
2023-02-23 03:42:30 +01:00
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
}
2023-02-19 20:21:20 +01:00
function get_parts ( ) {
2023-10-23 01:01:18 +02:00
local zfs_install_drive part_type_code
2023-02-19 20:21:20 +01:00
declare parttype parts
parttype = " ${ 1 : ? } "
2023-02-23 01:53:40 +01:00
zfs_install_drive = " ${ 2 :- } "
2023-02-19 20:21:20 +01:00
case " ${ parttype } " in
zfs)
2023-10-23 01:01:18 +02:00
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
part_type_code = " ${ partition_types [mbr_zfs] } "
else
part_type_code = " ${ partition_types [gpt_zfs] } "
fi
parts = " $( get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="' " ${ part_type_code } " '") | .path' ) " || exit 1
2023-02-19 20:21:20 +01:00
; ;
2023-10-23 01:18:38 +02:00
boot)
parts = " $( get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="' " ${ zfs_install_drive : ? } " '") | .children[] | select(.parttype=="' " ${ partition_types [mbr_boot] } " '") | .path' ) " || exit 1
; ;
2023-02-19 20:21:20 +01:00
efi)
2023-10-23 01:01:18 +02:00
parts = " $( get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="' " ${ zfs_install_drive : ? } " '") | .children[] | select(.parttype=="' " ${ partition_types [gpt_efi] } " '") | .path' ) " || exit 1
2023-02-19 20:21:20 +01:00
; ;
*)
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' 'Unknown partition type ' "'" '"${parttype}"' "'" ', exiting ...'
2023-02-23 00:56:03 +01:00
exit 77
2023-02-19 20:21:20 +01:00
; ;
esac
printf -- '%s' " ${ parts } "
return 0
}
function we_have_exactly_one_part ( ) {
2023-02-21 02:56:44 +01:00
local parttype parts_list parts_count
2023-02-19 20:21:20 +01:00
parttype = " ${ 1 : ? } "
parts_list = " ${ 2 : ? } "
parts_count = " $( wc -l <<< " ${ parts_list } " ) "
if [ [ " $( wc -c <<< " ${ parts_list } " ) " -gt '1' ] ] ; then
case " ${ parts_count } " in
0)
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' 'No ' " ${ parttype ^^ } " ' partition found. Exiting ...'
2023-02-23 00:56:03 +01:00
exit 77
2023-02-19 20:21:20 +01:00
; ;
1)
return 0
; ;
*)
2023-02-21 02:58:02 +01:00
return 1
2023-02-19 20:21:20 +01:00
; ;
esac
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
2023-02-23 00:56:03 +01:00
exit 77
2023-02-19 20:21:20 +01:00
fi
}
2023-02-21 03:00:25 +01:00
function get_drive_id ( ) {
2023-02-23 01:00:11 +01:00
local drive_id_list drive_id_single
2023-03-26 17:26:02 +02:00
drive_id_list = " $( find -L /dev/disk/by-partuuid -samefile " ${ 1 : ? } " | sort) "
2023-02-23 01:00:11 +01:00
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 } "
2023-02-19 20:21:20 +01:00
return 0
fi
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' 'No ' " ' ${ 1 : ? } ' " ' partition entry in /dev/disk/by-partuuid, exiting ...'
2023-02-23 00:56:03 +01:00
exit 77
2023-02-19 20:21:20 +01:00
}
2023-02-21 02:58:55 +01:00
function select_part ( ) {
2023-10-23 01:18:38 +02:00
local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive part_type_code specific_part_type
2023-02-21 02:58:55 +01:00
declare part
2023-10-23 01:18:38 +02:00
part_type = " ${ 1 : ? } " # 'boot' or 'zfs'
2023-02-23 01:53:40 +01:00
zfs_install_drive = " ${ 2 :- } "
2023-10-23 01:18:38 +02:00
2023-10-23 02:28:01 +02:00
if [ [ " ${ part_type } " = 'boot' ] ] ; then
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
specific_part_type = 'boot'
else
specific_part_type = 'efi'
fi
2023-10-23 01:18:38 +02:00
else
2023-10-23 02:28:01 +02:00
specific_part_type = " ${ part_type } "
2023-10-23 01:18:38 +02:00
fi
2023-02-23 01:53:40 +01:00
if [ [ " ${ zfs_install_drive } " ] ] ; then
2023-10-23 01:18:38 +02:00
# This is intended to find correct boot/EFI partition
parts = " $( get_parts " ${ specific_part_type } " " ${ zfs_install_drive } " ) "
2023-02-23 01:53:40 +01:00
else
2023-10-23 01:18:38 +02:00
parts = " $( get_parts " ${ specific_part_type } " ) "
2023-02-23 01:53:40 +01:00
fi
2023-02-21 02:58:55 +01:00
2023-10-21 00:19:30 +02:00
if [ [ ! " ${ parts } " ] ] ; then
case " ${ part_type } " in
efi)
part_type_human_readable = 'EFI system partition (ESP) with partition type code EF00'
; ;
zfs)
2023-10-23 01:01:18 +02:00
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
part_type_code = 'bf'
else
part_type_code = 'BF00'
fi
part_type_human_readable = 'ZFS zpool partition with partition type code ' " ${ part_type_code } "
2023-10-21 00:19:30 +02:00
; ;
esac
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' \
2023-10-21 00:19:30 +02:00
'It looks as if there is no ' " ${ part_type_human_readable } " \
'on any of the disks. Did you correctly partition a disk before starting?' \
'Check https://quico.space/quico-os-setup/arch-zbm#prep. Exiting ...'
exit 77
fi
2023-02-21 02:58:55 +01:00
if we_have_exactly_one_part " ${ part_type } " " ${ parts } " ; then
part = " ${ parts } "
else
2023-10-21 00:32:16 +02:00
>& 3 printf -- '%s\n' 'More than one ' " ${ part_type ^^ } " ' partition to pick for installation. Cowardly exiting ...'
2023-02-23 01:54:22 +01:00
exit 77
2023-02-21 02:58:55 +01:00
fi
printf -- '%s' " ${ part } "
return 0
}
2023-02-21 03:00:55 +01:00
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
}
2023-02-19 20:21:20 +01:00
function set_zpool_password ( ) {
2023-02-21 00:10:03 +01:00
# May or may not have a newline at the end, ZFS doesn't care
2023-02-20 21:26:59 +01:00
printf -- '%s' 'password' > '/etc/zfs/' " ${ zpool_name } " '.key'
2023-03-04 18:09:34 +01:00
chmod '000' '/etc/zfs/' " ${ zpool_name } " '.key'
2023-02-19 20:21:20 +01:00
}
function import_pool ( ) {
2023-03-26 19:03:20 +02:00
zpool import -d '/dev/disk/by-partuuid' -R '/mnt' " ${ zpool_name } " -N -f
2023-03-27 00:46:16 +02:00
[ [ ! " ${ ARCHZBM_ZFSPROPS_NO_ENCRYPTION } " ] ] && zfs load-key " ${ zpool_name } "
2023-02-19 20:21:20 +01:00
}
function create_pool ( ) {
# Create a temporary pool that is not cached
2023-10-20 15:26:22 +02:00
#
2023-03-27 00:46:16 +02:00
# Add zfsprops 'compression' unless environment variable
# ARCHZBM_ZFSPROPS_NO_COMPRESSION is set to any value.
2023-10-20 15:26:22 +02:00
#
2023-03-27 00:46:16 +02:00
# Add zfsprops 'encryption' along with 'keyformat' and a 'keylocation'
# unless environment variable ARCHZBM_ZFSPROPS_NO_ENCRYPTION is set to
# any value.
2023-02-19 20:21:20 +01:00
zpool create -f \
-o 'ashift=12' \
-o 'autotrim=on' \
-O 'acltype=posix' \
2023-03-27 00:46:16 +02:00
$( [ [ ! " ${ ARCHZBM_ZFSPROPS_NO_COMPRESSION } " ] ] && \
printf -- '%s ' \
'-O compression=on' ) \
2023-02-19 20:21:20 +01:00
-O 'relatime=on' \
-O 'xattr=sa' \
2023-03-27 00:46:16 +02:00
$( [ [ ! " ${ ARCHZBM_ZFSPROPS_NO_ENCRYPTION } " ] ] && \
printf -- '%s ' \
'-O encryption=on' \
'-O keyformat=passphrase' \
'-O keylocation=file:///etc/zfs/' " ${ zpool_name } " '.key' ) \
2023-02-19 20:21:20 +01:00
-O 'normalization=formD' \
-O 'mountpoint=none' \
-O 'canmount=off' \
-O 'devices=off' \
-R '/mnt' \
" ${ zpool_name } " " ${ 1 : ? } "
}
function create_root_dataset ( ) {
2023-10-22 01:39:57 +02:00
zfs create -o 'mountpoint=none' -o 'canmount=off' " ${ zpool_name } " '/root'
2023-02-19 20:21:20 +01:00
# 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 ( ) {
2023-10-23 00:46:15 +02:00
#1.8
2023-02-23 01:53:57 +01:00
local drive_by_id
2023-02-21 03:27:56 +01:00
zpool_drive = " $( select_part 'zfs' ) "
2023-02-21 03:01:21 +01:00
drive_by_id = " $( get_drive_id " ${ zpool_drive } " ) "
2023-02-19 20:21:20 +01:00
2023-03-27 00:46:16 +02:00
[ [ ! " ${ ARCHZBM_ZFSPROPS_NO_ENCRYPTION } " ] ] && set_zpool_password
2023-02-19 20:21:20 +01:00
if no_zpool_exists; then
create_pool " ${ drive_by_id } "
create_root_dataset
create_system_dataset
create_home_dataset
export_pool
import_pool
2023-02-19 21:00:03 +01:00
else
2023-10-21 00:56:26 +02:00
>& 3 printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...'
2023-02-19 21:00:03 +01:00
exit 1
2023-02-19 20:21:20 +01:00
fi
}
function mount_system ( ) {
2023-10-23 00:46:15 +02:00
#1.9
2023-02-19 20:21:20 +01:00
zfs mount " ${ zpool_name } " '/root/' " ${ zfs_arch_dataset_name } "
zfs mount -a
2023-02-21 03:01:57 +01:00
2023-10-23 01:18:38 +02:00
local zfs_parent efi_part boot_part
2023-02-23 03:42:30 +01:00
zfs_parent = " $( get_part_parent " ${ zpool_drive : ? } " ) "
2023-10-23 01:18:38 +02:00
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
boot_drive = " ${ zfs_parent } "
2023-10-23 23:53:11 +02:00
export boot_drive = " ${ boot_drive } "
2023-10-23 01:18:38 +02:00
boot_part = " $( select_part 'boot' " ${ zfs_parent : ? } " ) "
mkdir -p '/mnt/boot/syslinux'
mount " ${ boot_part } " '/mnt/boot/syslinux'
else
efi_drive = " ${ zfs_parent } "
2023-10-23 23:53:11 +02:00
export efi_drive = " ${ efi_drive } "
2023-10-23 01:18:38 +02:00
efi_part = " $( select_part 'boot' " ${ zfs_parent : ? } " ) "
mkdir -p '/mnt/efi'
mount " ${ efi_part } " '/mnt/efi'
fi
2023-02-19 20:21:20 +01:00
}
function copy_zpool_cache ( ) {
2023-10-23 00:46:15 +02:00
#1.10
2023-02-19 20:21:20 +01:00
mkdir -p '/mnt/etc/zfs'
zpool set 'cachefile=/etc/zfs/' " ${ zpool_name } " '.cache' " ${ zpool_name } "
}
2023-03-27 00:45:43 +02:00
function pacman_dont_check_space ( ) {
# See pacman bug comment
# https://bugs.archlinux.org/task/45070#comment142712
2023-10-20 15:26:22 +02:00
#
2023-03-27 00:45:43 +02:00
# When we pacstrap onto ZFS pacman incorrectly calculates and
# overestimates required disk space. We instead assume an installation
# gets done with at least a 10 GiB drive which is plenty. Skip pacman's
# size check.
2023-10-20 15:37:56 +02:00
#
# We're setting this in Arch Linux ISO CD while we install proper Arch.
# No need to revert this later as it is ephemeral anyway.
2023-03-27 00:45:43 +02:00
sed -ri -e 's' $'\x1' '^.*?(CheckSpace)([^\r\n\f]*)' $'\x1' '#\1\2' $'\x1' 'g' '/etc/pacman.conf'
}
2023-02-23 01:15:13 +01:00
function install_archlinux ( ) {
2023-10-23 00:46:15 +02:00
#1.11
2023-02-23 01:15:13 +01:00
pacman_dl_parallel
2023-03-27 00:45:43 +02:00
pacman_dont_check_space
2023-02-19 20:21:20 +01:00
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 ( ) {
2023-10-23 00:46:15 +02:00
#1.12
2023-02-19 20:21:20 +01:00
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 ( ) {
2023-10-23 00:46:15 +02:00
#1.13
2023-02-19 20:21:20 +01:00
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 ( ) {
2023-10-23 00:46:15 +02:00
#1.14
2023-02-20 02:49:21 +01:00
printf -- '%s\n' \
'KEYMAP=de-latin1' \
'FONT=Lat2-Terminus16' \
'FONT_MAP=8859-1' \
> '/mnt/etc/vconsole.conf'
2023-02-19 20:21:20 +01:00
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 ( ) {
2023-10-23 00:46:15 +02:00
#1.15
2023-03-27 00:46:16 +02:00
# Add zfs hook, remove fsck hook from initramfs.
2023-02-20 02:49:21 +01:00
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'
2023-03-27 00:46:16 +02:00
# Also unless encryption's unwanted add plain text key file into
# initramfs since it's living inside an encrypted pool anyway.
[ [ ! " ${ ARCHZBM_ZFSPROPS_NO_ENCRYPTION } " ] ] && sed -ri \
-e 's' $'\x1' '^(FILES=)[^\r\n\f]*' $'\x1' '\1(/etc/zfs/' " ${ zpool_name } " '.key)' $'\x1' 'g' \
'/mnt/etc/mkinitcpio.conf'
2023-02-19 20:21:20 +01:00
}
2023-02-20 04:14:44 +01:00
function set_initramfs_build_list ( ) {
2023-10-23 00:46:15 +02:00
#1.16
2023-02-20 04:14:44 +01:00
# 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'
2023-02-21 00:38:55 +01:00
# Remove any existing fallback initramfs files
find '/mnt/boot' -type f -regextype posix-extended -iregex '^/mnt/boot/initramfs-.*?-fallback.img' -delete
2023-02-20 04:14:44 +01:00
}
2023-02-19 20:21:20 +01:00
function add_zfs_files_to_new_os ( ) {
2023-10-23 00:46:15 +02:00
#1.17
2023-03-27 00:46:16 +02:00
for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' $( [ [ ! " ${ ARCHZBM_ZFSPROPS_NO_ENCRYPTION } " ] ] && printf -- '%s' '/etc/zfs/' " ${ zpool_name } " '.key' ) ; do
2023-02-19 20:21:20 +01:00
rsync -av --itemize-changes { '' ,'/mnt' } " ${ zfs_file } "
done
}
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 } " ) "
}
2023-10-21 05:09:25 +02:00
function enter_chroot ( ) {
#2.1
arch-chroot /mnt /bin/bash -xe <<EOF
curl --silent '${this_script_url}' | bash
EOF
}
function keep_initiramfs_root_only_rw ( ) {
#2.3
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 " ${ path_unit } "
}
function pacman_dl_parallel ( ) {
#2.4
# We're setting this in Arch Linux ISO CD while we install proper Arch.
# No need to revert this later as it is ephemeral anyway.
sed -ri -e 's' $'\x1' '^.*?(ParallelDownloads)[^\r\n\f]*' $'\x1' '\1 = 20' $'\x1' 'g' '/etc/pacman.conf'
}
2023-02-21 03:53:58 +01:00
function unleash_makepkg ( ) {
2023-10-20 18:58:25 +02:00
#2.5
2023-03-04 23:21:06 +01:00
local path_prefix
path_prefix = '/mnt'
if we_are_changerooted; then
path_prefix = ''
fi
2023-02-21 03:53:58 +01:00
sed -ri \
-e 's' $'\x1' '^(#?(MAKEFLAGS=))[^\r\n\f]*' $'\x1' '\2"-j$(nproc --ignore 1)"' $'\x1' 'g' \
2023-03-04 23:21:06 +01:00
" ${ path_prefix } " '/etc/makepkg.conf'
2023-02-21 03:53:58 +01:00
}
2023-10-21 05:09:25 +02:00
function add_motd_getting_started_msg ( ) {
#2.6
cat > '/etc/motd' <<"EOF"
####################
GUI basics:
paru -S xorg plasma-meta kde-applications-meta sddm
localectl set-x11-keymap de
useradd --create-home --shell /bin/bash --user-group --groups wheel <user>
passwd <user>
systemctl enable --now sddm.service
####################
EOF
}
2023-02-19 20:21:20 +01:00
function get_aur_helper ( ) {
2023-10-20 18:58:25 +02:00
#2.7
2023-02-19 20:21:20 +01:00
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'
2023-02-19 21:28:42 +01:00
chown -R 'build:' 'paru'
2023-02-19 20:21:20 +01:00
pushd 'paru'
sudo --user 'build' makepkg -si --noconfirm
popd
rm -rf 'paru'
popd
}
function paru_install ( ) {
2023-10-20 16:30:29 +02:00
declare -a paru_install_packages
[ [ " ${ 1 } " ] ] && while :; do
case " ${ 1 } " in
-[ [ :alnum:] ] *)
2023-10-21 00:56:26 +02:00
>& 3 printf -- '%s\n' \
2023-10-20 16:30:29 +02:00
'Short-form argument ' " ' ${ 1 } ' " ' not supported for function ' " ' ${ FUNCNAME [0] } ()' " '. Only known accepted argument' \
'is ' "'" '--replace-conflicting' "'" ' without a value given. Exiting ...'
exit 77
; ;
--replace-conflicting)
pacman_force_yes = 'true'
shift
continue
; ;
--*)
2023-10-21 00:56:26 +02:00
>& 3 printf -- '%s\n' \
2023-10-20 16:30:29 +02:00
'Long-form argument ' " ' ${ 1 } ' " ' not supported for function ' " ' ${ FUNCNAME [0] } ()' " '. Only known accepted argument' \
'is ' "'" '--replace-conflicting' "'" ' without a value given. Exiting ...'
exit 77
; ;
'' )
# All arguments processed
break
; ;
*)
paru_install_packages += ( " ${ 1 } " )
shift
; ;
esac
done || {
2023-10-21 00:56:26 +02:00
>& 3 printf -- '%s\n' \
2023-10-20 16:30:29 +02:00
'No argument ' " ' ${ 1 } ' " ' given for function ' " ' ${ FUNCNAME [0] } ' " '. Exiting ...'
exit 77
}
if [ [ " ${ pacman_force_yes } " ] ] ; then
2023-10-21 00:57:57 +02:00
yes 'y' | sudo --user 'build' paru -S " ${ paru_install_packages [@] } "
2023-10-20 16:30:29 +02:00
unset -v pacman_force_yes
else
2023-10-21 00:57:57 +02:00
sudo --user 'build' paru -S --noconfirm " ${ paru_install_packages [@] } "
2023-10-20 16:30:29 +02:00
fi
2023-02-19 20:21:20 +01:00
}
2023-10-23 02:01:36 +02:00
function configure_syslinux ( ) {
paru_install 'syslinux'
cp /usr/lib/syslinux/bios/*.c32 /boot/syslinux
extlinux --install /boot/syslinux
cat > /boot/syslinux/syslinux.cfg <<EOF
UI menu.c32
PROMPT 0
MENU TITLE ZFSBootMenu
TIMEOUT 100
DEFAULT zfsbootmenu
LABEL zfsbootmenu
MENU LABEL ZFSBootMenu
KERNEL /zfsbootmenu/vmlinuz-bootmenu
INITRD /zfsbootmenu/initramfs-bootmenu.img
APPEND zfsbootmenu quiet loglevel = 0
LABEL zfsbootmenu-backup
MENU LABEL ZFSBootMenu ( Backup)
KERNEL /zfsbootmenu/vmlinuz-bootmenu-backup
INITRD /zfsbootmenu/initramfs-bootmenu-backup.img
APPEND zfsbootmenu quiet loglevel = 0
EOF
dd bs = 440 count = 1 conv = notrunc if = '/usr/lib/syslinux/bios/mbr.bin' of = " ${ boot_drive } "
}
2023-10-21 05:05:17 +02:00
function configure_zfsbootmenu ( ) {
2023-10-21 05:14:38 +02:00
#2.9
2023-10-21 05:05:17 +02:00
paru_install 'zfsbootmenu'
2023-10-23 02:01:36 +02:00
if [ [ " ${ part_schema } " = 'gpt' ] ] ; then
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
2023-10-21 05:05:17 +02:00
Global:
ManageImages: true
BootMountPoint: /efi
InitCPIO: true
Components:
Enabled: false
EFI:
ImageDir: /efi/EFI/ZBM
2023-10-22 15:47:24 +02:00
Versions: false
2023-10-21 05:05:17 +02:00
Enabled: true
2023-10-27 01:43:20 +02:00
Stub: /usr/share/zfsbootmenu/stubs/linuxx64.efi.stub/linuxx64.efi.stub # workaround: https://github.com/zbm-dev/zfsbootmenu/discussions/501
2023-10-21 05:05:17 +02:00
Kernel:
CommandLine: ro loglevel = 0 zbm.import_policy= hostid
Prefix: vmlinuz
EOF
2023-10-23 02:01:36 +02:00
else
configure_syslinux
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
Global:
ManageImages: true
BootMountPoint: /boot/syslinux
InitCPIO: true
Components:
Enabled: true
Versions: false
ImageDir: /boot/syslinux/zfsbootmenu
Kernel:
Prefix: vmlinuz
EOF
fi
2023-10-23 00:46:15 +02:00
# Up here maybe 'ro quiet' instead of 'ro'. This is ZFSBootMenu's kernel
# command line.
# Assign cmdline for final kernel start. This is our Arch Linx kernel
# command line.
2023-10-21 05:05:17 +02:00
zfs set org.zfsbootmenu:commandline= 'rw nowatchdog rd.vconsole.keymap=de-latin1' " ${ zpool_name } " '/root/' " ${ zfs_arch_dataset_name } "
2023-10-20 18:48:50 +02:00
}
2023-10-26 02:32:26 +02:00
function add_syslinux_pacman_hook ( ) {
mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-syslinux-pacman-hook.git' .
chmod +x '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh'
ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.sh' '/usr/local/bin/pacman-zbm-syslinux-regen'
ln -s '/opt/git/quico.space/quico-os-setup/zbm-syslinux-pacman-hook/branches/main/pacman-zbm-syslinux-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-syslinux-regen.hook'
}
2023-10-27 01:41:19 +02:00
function add_zbm_pacman_hook ( ) {
mkdir -p '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/zbm-regen-pacman-hook.git' .
ln -s '/opt/git/quico.space/quico-os-setup/zbm-regen-pacman-hook/branches/main/pacman-zbm-image-regen.hook' '/usr/share/libalpm/hooks/pacman-zbm-image-regen.hook'
}
2023-10-21 05:09:25 +02:00
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.
2023-10-23 01:01:18 +02:00
disks_with_one_efipart = " $( lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="' " ${ partition_types [gpt_efi] } " '")) and ([select(.children[].parttype=="' " ${ partition_types [gpt_efi] } " '")] | length == 1) ) | .path' ) "
2023-10-21 05:09:25 +02:00
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
2023-02-23 01:13:26 +01:00
}
2023-02-19 20:21:20 +01:00
function install_os_in_chroot ( ) {
2023-10-20 18:58:25 +02:00
#2.2
2023-02-19 20:21:20 +01:00
### 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
2023-10-20 21:57:27 +02:00
locale-gen
source /etc/locale.conf
2023-10-20 18:58:25 +02:00
keep_initiramfs_root_only_rw #2.3
pacman_dl_parallel #2.4
unleash_makepkg #2.5
add_motd_getting_started_msg #2.6
get_aur_helper #2.7
2023-10-20 16:30:29 +02:00
paru_install --replace-conflicting 'paru-bin'
2023-10-21 05:03:09 +02:00
paru_install 'zfs-dkms' 'zfs-utils' 'jq'
2023-02-19 20:21:20 +01:00
hwclock --systohc
mkinitcpio -P
2023-10-20 18:48:50 +02:00
# Install ZFSBootMenu image
2023-10-21 05:14:38 +02:00
configure_zfsbootmenu #2.9
2023-10-23 02:01:36 +02:00
generate-zbm
# Yes, we do this twice so we immediately get a functional backup file
2023-10-21 05:05:17 +02:00
generate-zbm
2023-10-26 02:32:26 +02:00
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
2023-10-26 03:04:37 +02:00
paru_install 'rsync'
2023-10-26 02:32:26 +02:00
add_syslinux_pacman_hook
fi
2023-10-27 01:41:19 +02:00
add_zbm_pacman_hook
2023-02-19 20:21:20 +01:00
}
function set_root_pw ( ) {
2023-10-20 18:58:25 +02:00
#3.2
2023-10-20 19:01:59 +02:00
printf -- '%s\n' 'root:password' | chpasswd --crypt-method 'SHA512' --root '/mnt'
2023-02-19 20:21:20 +01:00
}
function configure_networking ( ) {
2023-10-20 18:58:25 +02:00
#3.3
2023-02-19 20:21:20 +01:00
cat > '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
[ Match]
Name = en*
[ Network]
DHCP = ipv4
IPForward = yes
2023-02-19 22:50:10 +01:00
[ DHCP]
2023-03-04 23:43:11 +01:00
UseDNS = yes
2023-02-19 20:21:20 +01:00
RouteMetric = 10
EOF
systemctl enable 'systemd-networkd' --root= '/mnt'
systemctl disable 'systemd-networkd-wait-online' --root= '/mnt'
}
function configure_dns ( ) {
2023-10-20 18:58:25 +02:00
#3.4
2023-02-19 20:21:20 +01:00
rm '/mnt/etc/resolv.conf'
ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf'
2023-03-04 23:43:11 +01:00
# Optionally you may want /etc/systemd/network/50-wired.network to use
# UseDNS=no and hardcode DNS server(s) here:
2023-02-19 20:21:20 +01:00
# sed -i 's/^#DNS=.*/DNS=1.1.1.1/' /mnt/etc/systemd/resolved.conf
systemctl enable 'systemd-resolved' --root= '/mnt'
}
2023-02-23 01:18:18 +01:00
function configure_reflector ( ) {
2023-10-20 18:58:25 +02:00
#3.5
2023-10-20 19:03:40 +02:00
systemctl enable 'reflector.service' 'reflector.timer' --root= '/mnt'
2023-02-23 01:18:18 +01:00
}
2023-02-19 20:21:20 +01:00
function configure_zfs ( ) {
2023-10-20 18:58:25 +02:00
#3.6
2023-02-19 20:21:20 +01:00
systemctl enable 'zfs-import-cache' 'zfs-mount' 'zfs-import.target' 'zfs.target' --root= '/mnt'
}
function configure_zfs_mount_gen ( ) {
2023-10-20 18:58:25 +02:00
#3.7
2023-02-19 20:21:20 +01:00
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'
}
2023-10-21 05:08:22 +02:00
function set_new_uefi_boot_entries ( ) {
2023-10-21 05:14:38 +02:00
#3.8
2023-10-21 05:08:22 +02:00
declare -a uefi_images
mapfile -t uefi_images < \
<( find '/mnt/efi/EFI/ZBM' -type f -iname '*.efi' -print0 | \
xargs -0 --no-run-if-empty --max-args '1' stat -c '%Y %n' | \
sort -V | \
awk '{print $2}' )
if efibootmgr | grep -Piq -- 'ZFSBootMenu' ; then
local -a old_uefi_entries
mapfile -t old_uefi_entries < \
<( efibootmgr | \
grep -Pio -- '(?<=^Boot)[^\*[:space:]]+(?=\*? ZFSBootMenu)' )
for old_uefi_entry in " ${ old_uefi_entries [@] } " ; do
efibootmgr --bootnum " ${ old_uefi_entry } " --delete-bootnum & >/dev/null && {
>& 3 printf -- '%s\n' \
'EFI boot entry ' " ${ old_uefi_entry } " ' deleted.'
}
done
2023-02-19 20:21:20 +01:00
fi
2023-03-27 02:25:51 +02:00
if ! efibootmgr | grep -Piq -- 'ZFSBootMenu' ; then
2023-02-23 03:42:30 +01:00
local efi_disks_list
efi_disks_list = " $( get_disks_with_one_efipart) "
if grep -Piq -- '^' " ${ efi_drive } " '$' <<< " ${ efi_disks_list } " ; then
2023-10-21 05:08:22 +02:00
for uefi_image in " ${ uefi_images [@] } " ; do
2023-10-22 15:47:24 +02:00
uefi_image_version = " $( basename " ${ uefi_image %%.EFI } " ) "
2023-10-21 05:08:22 +02:00
uefi_image_inverted = " ${ uefi_image #/mnt/efi } "
uefi_image_inverted = " ${ uefi_image_inverted // \/ / \\ } "
efibootmgr --disk " ${ efi_drive } " \
--part 1 \
--create \
--label 'ZFSBootMenu ' " ${ uefi_image_version } " \
--loader " ${ uefi_image_inverted } " & >/dev/null && {
>& 3 printf -- '%s\n' \
'EFI boot entry ZFSBootMenu ' " ${ uefi_image_version } " ' added.'
}
done
fi
fi
}
2023-02-19 20:21:20 +01:00
function umount_all ( ) {
2023-10-27 01:47:53 +02:00
#3.9
2023-10-23 02:05:25 +02:00
if [ [ " ${ part_schema } " = 'mbr' ] ] ; then
umount '/mnt/boot/syslinux'
else
umount '/mnt/efi'
fi
2023-02-19 20:21:20 +01:00
zfs umount -a
zpool export " ${ zpool_name } "
}
function finalize_os_setup ( ) {
2023-10-20 18:58:25 +02:00
#3.1
set_root_pw #3.2
configure_networking #3.3
configure_dns #3.4
configure_reflector #3.5
configure_zfs #3.6
configure_zfs_mount_gen #3.7
2023-10-23 02:05:25 +02:00
if [ [ " ${ part_schema } " = 'gpt' ] ] ; then
set_new_uefi_boot_entries #3.8
fi
2023-10-27 01:47:53 +02:00
umount_all #3.9
2023-02-19 20:21:20 +01:00
}
function main ( ) {
if we_are_changerooted; then
2023-10-20 18:58:25 +02:00
install_os_in_chroot #2.2
2023-02-19 20:21:20 +01:00
else
2023-10-23 02:16:02 +02:00
no_kernel_update_in_iso #1.1
set_ntp #1.2
resize_cow_space #1.3
update_pacman_db #1.4
install_pkgs 'jq' #1.5
install_zfs #1.6
uefi_or_bios #1.7
2023-10-23 00:46:15 +02:00
setup_zpool #1.8
mount_system #1.9
copy_zpool_cache #1.10
install_archlinux #1.11
gen_fstab #1.12
set_hostname #1.13
set_locale #1.14
add_zfs_hook_to_initramfs #1.15
set_initramfs_build_list #1.16
add_zfs_files_to_new_os #1.17
2023-10-20 18:58:25 +02:00
enter_chroot #2.1
2023-02-21 03:02:57 +01:00
# We're done in chroot
2023-10-20 18:58:25 +02:00
finalize_os_setup #3.1
2023-02-19 20:21:20 +01:00
fi
}
main