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 : ? } "
2023-11-05 04:49:32 +01:00
declare zpool_name zfs_arch_dataset_name settings_file
2023-02-19 20:21:20 +01:00
zpool_name = 'zpool'
zfs_arch_dataset_name = 'archlinux'
2023-11-05 04:49:32 +01:00
settings_file = 'archzbm_settings.env'
2023-02-19 20:21:20 +01:00
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-11-05 04:51:32 +01:00
function setup_env_vars ( ) {
printf -- '%s\n' \
'We will go over a series of questions to create an answer file with' \
'options you want the script to use.' \
'' \
'Current working directory is:' \
" $( pwd ) " \
'' \
'After we' "'" 're done answer file will be written to current working dir:' \
'./' " ${ settings_file } " \
'' \
'Press <Ctrl>+C to abort this process. No answer file will' \
'be written to ./' " ${ settings_file } " ' if you abort the script.' \
'' \
'When done rerun the same command you just did without ' "'" 'setup' "'" ' argument.' \
''
read -u3 -n 1 -s -r -p "Press any key to begin questionnaire"
echo
echo '----------------------------------------'
echo
echo "Do you want compressed datasets?"
select arg_compressed in "Compressed" "Uncompressed" ; do
case " ${ arg_compressed } " in
Compressed)
break
; ;
Uncompressed)
ARCHZBM_ZFSPROPS_NO_COMPRESSION = 'true'
break
; ;
esac
done <& 3 && echo
echo "Do you want encrypted datasets?"
select arg_encrypted in "Encrypted" "Unencrypted" ; do
case " ${ arg_encrypted } " in
Encrypted)
break
; ;
Unencrypted)
ARCHZBM_ZFSPROPS_NO_ENCRYPTION = 'true'
break
; ;
esac
done <& 3 && echo
if [ [ " ${ arg_encrypted } " = 'Encrypted' ] ] ; then
echo "Do you want a custom dataset decryption password?"
select arg_custom_dataset_pw in "Yes" "No" ; do
case " ${ arg_custom_dataset_pw } " in
Yes)
want_custom_dataset_pw = 'true'
break
; ;
No)
break
; ;
esac
done <& 3 && echo
if [ [ " ${ want_custom_dataset_pw } " ] ] ; then
read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ZPOOL_PASSWORD
echo
echo
fi
fi
echo "Do you want a custom 'root' user password?"
select arg_custom_root_pw in "Yes" "No" ; do
case " ${ arg_custom_root_pw } " in
Yes)
want_custom_root_pw = 'true'
break
; ;
No)
break
; ;
esac
done <& 3 && echo
if [ [ " ${ want_custom_root_pw } " ] ] ; then
read -u3 -p 'Please type password, confirm with <Enter>: ' -s ARCHZBM_ROOT_PASSWORD
echo
echo
fi
echo "Do you want an SSH daemon in ZFSBootMenu?"
select arg_ssh_in_zbm in "Yes" "No" ; do
case " ${ arg_ssh_in_zbm } " in
Yes)
want_ssh_in_zbm = 'true'
break
; ;
No)
break
; ;
esac
done <& 3 && echo
if [ [ " ${ want_ssh_in_zbm } " ] ] ; then
echo "How do you want to assign an IP address in ZFSBootMenu?"
select arg_ip_autoconf_method in "Statically" "Dynamically, DHCP" "Dynamically, BOOTP" "Dynamically, RARP" ; do
case " ${ arg_ip_autoconf_method } " in
'Statically' )
ARCHZBM_NET_AUTOCONF = 'none'
break
; ;
'Dynamically, DHCP' )
ARCHZBM_NET_AUTOCONF = 'dhcp'
break
; ;
'Dynamically, BOOTP' )
ARCHZBM_NET_AUTOCONF = 'bootp'
break
; ;
'Dynamically, RARP' )
ARCHZBM_NET_AUTOCONF = 'rarp'
break
; ;
esac
done <& 3 && echo
read -u3 -p 'Which device to configure (eth0 is a good guess): ' ARCHZBM_NET_DEVICE
echo
if [ [ " ${ arg_ip_autoconf_method } " = 'Statically' ] ] ; then
read -u3 -p 'Interface IP address: ' ARCHZBM_NET_CLIENT_IP
echo
read -u3 -p 'Netmask: ' ARCHZBM_NET_NETMASK
echo
read -u3 -p 'Gateway IP address: ' ARCHZBM_NET_GATEWAY_IP
echo
fi
echo "Do you want a custom SSH listening port?"
select arg_custom_ssh_port in "Yes (let me specify)" "No (keep port 22)" ; do
case " ${ arg_custom_ssh_port } " in
'Yes (let me specify)' )
want_custom_ssh_port = 'true'
break
; ;
'No (keep port 22)' )
break
; ;
esac
done <& 3 && echo
if [ [ " ${ want_custom_ssh_port } " ] ] ; then
read -u3 -p 'Please type SSH port, confirm with <Enter>: ' ARCHZBM_SSH_PORT
echo
fi
echo "Do you want the SSH daemon to use a custom keepalive send interval?"
select arg_custom_ssh_keepalive_intvl in "Yes (let me specify)" "No (keep 1)" ; do
case " ${ arg_custom_ssh_keepalive_intvl } " in
'Yes (let me specify)' )
want_custom_keepalive_intvl = 'true'
break
; ;
'No (keep 1)' )
break
; ;
esac
done <& 3 && echo
if [ [ " ${ want_custom_keepalive_intvl } " ] ] ; then
read -u3 -p 'Please type server keepalive send interval, confirm with <Enter>: ' ARCHZBM_SSH_KEEPALIVE_INTVL
echo
fi
if [ [ " ${ want_custom_keepalive_intvl } " ] ] ; then
read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with <Enter>: ' ARCHZBM_SSH_AUTH_KEYS
echo
fi
fi
for env_var in 'ARCHZBM_ZFSPROPS_NO_COMPRESSION' 'ARCHZBM_ZFSPROPS_NO_ENCRYPTION' 'ARCHZBM_ZPOOL_PASSWORD' 'ARCHZBM_ROOT_PASSWORD' 'ARCHZBM_NET_AUTOCONF' 'ARCHZBM_NET_DEVICE' 'ARCHZBM_NET_CLIENT_IP' 'ARCHZBM_NET_NETMASK' 'ARCHZBM_NET_GATEWAY_IP' 'ARCHZBM_SSH_PORT' 'ARCHZBM_SSH_KEEPALIVE_INTVL' 'ARCHZBM_SSH_AUTH_KEYS' ; do
if [ [ " ${ !env_var } " ] ] ; then
printf -- '%s=' "'" '%s' "'" '\n' \
" ${ env_var } " " ${ !env_var } " \
>> " ${ settings_file } "
fi
done
}
2023-11-05 04:50:31 +01:00
function arg_parse ( ) {
[ [ " ${ 1 } " ] ] && while :; do
case " ${ 1 } " in
-[ [ :alnum:] ] *)
>& 3 printf -- '%s\n' \
'Short options ' " ' ${ 1 } ' " ' detected. Only known option is' \
'literal string ' "'" 'setup' "'" '. No idea what ' " ' ${ 1 } ' " ' is.' \
'Exiting ...'
exit 77
; ;
--*)
>& 3 printf -- '%s\n' \
'Long-form option ' " ' ${ 1 } ' " ' detected. Only known option is' \
'literal string ' "'" 'setup' "'" '. No idea what ' " ' ${ 1 } ' " ' is.' \
'Exiting ...'
exit 77
; ;
setup)
setup_env_vars
return
; ;
*)
>& 3 printf -- '%s\n' \
'Argument ' " ' ${ 1 } ' " ' detected. Only known option is literal' \
'string ' "'" 'setup' "'" '. No idea what ' " ' ${ 1 } ' " ' is.' \
'Exiting ...'
exit 77
; ;
esac
done
}
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-11-03 03:24:03 +01:00
pacman_dl_parallel
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)
2023-10-27 02:14:36 +02:00
part_type_human_readable = 'EFI System Partition (ESP) with partition type code EF00'
2023-10-21 00:19:30 +02:00
; ;
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-10-27 04:23:11 +02:00
local zpool_password
if [ [ " ${ ARCHZBM_ZPOOL_PASSWORD } " ] ] ; then
zpool_password = " ${ ARCHZBM_ZPOOL_PASSWORD } "
else
zpool_password = 'password'
fi
2023-02-21 00:10:03 +01:00
# May or may not have a newline at the end, ZFS doesn't care
2023-10-27 04:23:11 +02:00
printf -- '%s' " ${ zpool_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 } "
}
2023-10-27 04:23:11 +02:00
function load_settings_file ( ) {
2023-10-27 04:24:40 +02:00
#1.8
2023-10-27 04:23:11 +02:00
local working_dir settings_file settings_abs
working_dir = " $( pwd ) "
settings_abs = " ${ working_dir } " '/' " ${ settings_file } "
if [ [ -r " ${ settings_abs } " ] ] ; then
set -a
source " ${ settings_abs } "
set +a
fi
}
2023-02-19 20:21:20 +01:00
function setup_zpool ( ) {
2023-10-27 04:24:40 +02:00
#1.9
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-27 04:24:40 +02:00
#1.10
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-27 04:24:40 +02:00
#1.11
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-27 04:24:40 +02:00
#1.12
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-27 04:24:40 +02:00
#1.13
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-27 04:24:40 +02:00
#1.14
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-27 04:24:40 +02:00
#1.15
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'
}
2023-11-01 03:47:34 +01:00
function in_file_in_array_insert_n_before_m ( ) {
local arg_file arg_array arg_string arg_precede
arg_file = " ${ 1 : ? } "
arg_array = " ${ 2 : ? } "
arg_string = " ${ 3 : ? } "
arg_precede = " ${ 4 : ? } "
2023-11-04 00:20:39 +01:00
# Look for a line that contains in this order
# - String "${arg_array}" and a equals sign (=), assign capture group \1
# - Followed by as few characters as possible followed by either an
# opening parenthesis or a space character, assign capture group \2
# - String "${arg_precede}", capture as capture group \3
# - Followed by either a closing parenthesis or a space which are then
# followed by as many non-line break characters as possible, capture
# as capture group \2
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_precede}" equals 'filesystems'
# - "${arg_string}" equals 'zfs'
#
# This finds a 'HOOKS=' array definition that contains the string
# 'filesystems' either at the beginning of the 'HOOKS=(...)' opening
# parenthesis, at the very end or somewhere in the middle where it may
# be preceded or followed by one or more space characters. It saves
# 'HOOKS=', it saves whatever precedes 'filesystems' and 'filesystems'
# itself plus whatever comes after 'filesystems' until end of line. It
# lastly inserts 'zfs' and a space character right in front of
# 'filesystems'.
2023-11-01 03:47:34 +01:00
sed -ri \
-e 's' $'\x1' '(' " ${ arg_array } " '=)(.*?[( ])(' " ${ arg_precede } " ')([) ][^\r\n\f]*)' $'\x1' '\1\2' " ${ arg_string } " ' \3\4' $'\x1' 'g' \
" ${ arg_file } "
}
function in_file_in_array_insert_n_at_the_end ( ) {
local arg_file arg_array arg_string
arg_file = " ${ 1 : ? } "
arg_array = " ${ 2 : ? } "
arg_string = " ${ 3 : ? } "
2023-11-04 00:32:01 +01:00
# Look for end of array, insert "${arg_string}" right before
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_string}" equals 'net'
#
# This:
# - Finds a 'HOOKS=' array definition, saves it as \1
# - Finds as many non-closing parenthesis characters as possible, so all
# characters until just before the final ')' character of the line and
# saves this as \2
# - Finds the closing parenthesis character plus all non-line break
# characters until end of line that come after it and saves this as \3
# - Adds a space character and 'net' after \2
2023-11-01 03:47:34 +01:00
sed -ri \
-e 's' $'\x1' '(' " ${ arg_array } " '=)([^)]*)(\)[^\r\n\f]*)' $'\x1' '\1\2 ' " ${ arg_string } " '\3' $'\x1' 'g' \
" ${ arg_file } "
}
function in_file_in_array_remove_n ( ) {
local arg_file arg_array arg_string
arg_file = " ${ 1 : ? } "
arg_array = " ${ 2 : ? } "
arg_string = " ${ 3 : ? } "
2023-11-04 00:32:16 +01:00
# Look for any line that contains "${arg_string}", delete that string
#
# For following example text we're assuming that:
# - "${arg_array}" equals 'HOOKS'
# - "${arg_string}" equals 'fsck'
#
# First -e expression removes 'fsck' wherever it is defined as the one
# and only element of any HOOKS=(fsck) should such a line exist.
#
# Second -e expression finds string 'fsck' where it's preceded by space
# character(s) and followed by either space character(s) or a closing
# parenthesis.
#
# Third -e expression finds string 'fsck' where it's preceded by space
# character(s) or an opening parenthesis and followed space
# character(s).
2023-11-01 03:47:34 +01:00
sed -ri \
-e 's' $'\x1' '((\()(' " ${ arg_string } " ')(\)))' $'\x1' '\2\4' $'\x1' 'g' \
-e 's' $'\x1' '(' " ${ arg_array } " '=.*?)([[:space:]]+' " ${ arg_string } " ')([[:space:]]+|\))' $'\x1' '\1\3' $'\x1' 'g' \
-e 's' $'\x1' '(' " ${ arg_array } " '=.*?)([[:space:]]+|\()(' " ${ arg_string } " '[[:space:]]+)' $'\x1' '\1\2' $'\x1' 'g' \
" ${ arg_file } "
}
2023-02-19 20:21:20 +01:00
function add_zfs_hook_to_initramfs ( ) {
2023-10-27 04:24:40 +02:00
#1.16
2023-03-27 00:46:16 +02:00
# Add zfs hook, remove fsck hook from initramfs.
2023-11-01 03:49:05 +01:00
in_file_in_array_insert_n_before_m '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'zfs' 'filesystems'
in_file_in_array_remove_n '/mnt/etc/mkinitcpio.conf' 'HOOKS' 'fsck'
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-27 04:24:40 +02:00
#1.17
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-27 04:24:40 +02:00
#1.18
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-11-03 03:24:33 +01:00
in_file_in_array_remove_n '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' '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
2023-11-04 00:37:30 +01:00
# command line. In MBR/Syslinux/extlinux mode /it/ must pass arguments to
# ZFSBootMenu's kernel so check /boot/syslinux/syslinux.cfg for how we start
# ZFSBootMenu in this mode. The .Kernel.CommandLine in
# /etc/zfsbootmenu/config.yaml is irrelevant for us in MBR/Syslinux/extlinux
# mode.
2023-10-23 00:46:15 +02:00
# 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-11-03 01:37:15 +01:00
function get_dropbear_hooks ( ) {
mkdir -p '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main'
git -C '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main' clone 'https://quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook.git' .
2023-11-04 00:41:37 +01:00
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-hook'
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.sh' '/usr/local/bin/pacman-mkinitcpio-dropbear-install'
2023-11-03 01:37:15 +01:00
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-hook.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-hook.hook'
ln -s '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/pacman-mkinitcpio-dropbear-install.hook' '/usr/share/libalpm/hooks/pacman-mkinitcpio-dropbear-install.hook'
}
2023-11-03 01:37:38 +01:00
function customize_dropbear_hooks ( ) {
local env_archzbm_ssh_port env_archzbm_ssh_keepalive_intvl
env_archzbm_ssh_port = " ${ ARCHZBM_SSH_PORT :- 22 } "
env_archzbm_ssh_keepalive_intvl = " ${ ARCHZBM_SSH_KEEPALIVE_INTVL :- 1 } "
if [ [ " ${ env_archzbm_ssh_port } " -ne '22' ] ] || [ [ " ${ env_archzbm_ssh_keepalive_intvl } " -ne '1' ] ] ; then
paru_install 'rsync'
rsync -av '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook' { ,'.override' } '.patch'
fi
if [ [ " ${ env_archzbm_ssh_port } " -ne '22' ] ] ; then
sed -ri -e 's' $'\x1' '-p [[:digit:]]+' $'\x1' '-p ' " ${ env_archzbm_ssh_port } " '' $'\x1' 'g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
fi
if [ [ " ${ env_archzbm_ssh_keepalive_intvl } " -ne '1' ] ] ; then
sed -ri -e 's' $'\x1' '-K [[:digit:]]+' $'\x1' '-K ' " ${ env_archzbm_ssh_keepalive_intvl } " '' $'\x1' 'g' '/opt/git/quico.space/quico-os-setup/mkinitcpio-dropbear-pacman-hook/branches/main/dropbear_hook.override.patch'
fi
}
2023-11-04 01:08:39 +01:00
function set_unique_ip_in_syslinux_kcl ( ) {
local zbm_config default_ip
zbm_config = " ${ 1 : ? } "
default_ip = " ${ 2 : ? } "
# First -e expression removes first looks for lines that contain
# 'APPEND' plus a space character and only in those lines removes all
# occurrences of ' ip=' followed by as many non-space characters as
# possible. This removes whatever 'ip=' definition already was present.
#
# Second -e expression similarly looks for lines that contain 'APPEND'
# plus a space character then at their end inserts a space plus our
# desired new 'ip=' definition. This puts in place the 'ip=' we want.
sed -ri \
-e \\ $'\x1' 'APPEND ' $'\x1 s' $'\x1' ' ip=([^[:space:]]*)' $'\x1' '' $'\x1' 'gi' \
-e \\ $'\x1' 'APPEND ' $'\x1 s' $'\x1' '$' $'\x1' ' ' " ${ default_ip } " '' $'\x1' 'gi' \
" ${ zbm_config } "
}
2023-11-03 01:38:11 +01:00
function ensure_ip_in_kcl ( ) {
2023-11-04 01:08:39 +01:00
local default_ip
default_ip = 'ip=' " ${ ARCHZBM_NET_CLIENT_IP } " ':' " ${ ARCHZBM_NET_SERVER_IP } " ':' " ${ ARCHZBM_NET_GATEWAY_IP } " ':' " ${ ARCHZBM_NET_NETMASK } " ':' " ${ ARCHZBM_NET_HOSTNAME } " ':' " ${ ARCHZBM_NET_DEVICE } " ':' " ${ ARCHZBM_NET_AUTOCONF } "
2023-11-03 01:38:11 +01:00
2023-11-04 01:08:39 +01:00
if [ [ " ${ part_schema } " = 'gpt' ] ] ; then
local zbm_config kcl_length kcl_string ip_addr_found new_kcl first_kcl_elem
local -a kcl
paru_install 'go-yq'
zbm_config = '/etc/zfsbootmenu/config.yaml'
kcl_length = " $( yq '.Kernel.CommandLine | length' " ${ zbm_config } " ) "
if [ [ " ${ kcl_length } " -eq '0' ] ] ; then
>& 3 printf -- '%s\n' \
'No .Kernel.CommandLine YAML element with content found in ' " ${ zbm_config } " '. Exiting ...'
exit 77
else
kcl_string = " $( yq '.Kernel.CommandLine' " ${ zbm_config } " ) "
fi
2023-11-03 01:38:11 +01:00
2023-11-04 01:08:39 +01:00
mapfile -t kcl < <( <<< " ${ kcl_string } " tr ' ' '\n' | sed '/^$/d' )
for kcl_elem in " ${ !kcl[@] } " ; do
if grep -Piq -- 'ip=' <<< " ${ kcl [ $kcl_elem ] } " ; then
ip_addr_found = 'true'
kcl[ " ${ kcl_elem } " ] = " ${ default_ip } "
fi
done
if [ [ ! " ${ ip_addr_found } " ] ] ; then
kcl += ( " ${ default_ip } " )
2023-11-03 01:38:11 +01:00
fi
2023-11-04 01:08:39 +01:00
new_kcl = ''
first_kcl_elem = 'true'
for kcl_elem in " ${ kcl [@] } " ; do
if [ [ ! " ${ first_kcl_elem } " ] ] ; then
new_kcl += ' ' " ${ kcl_elem } "
else
new_kcl += " ${ kcl_elem } "
unset -v first_kcl_elem
fi
done
yq -i '.Kernel.CommandLine = "' " ${ new_kcl } " '"' " ${ zbm_config } "
else
local zbm_config
zbm_config = '/boot/syslinux/syslinux.cfg'
set_unique_ip_in_syslinux_kcl " ${ zbm_config } " " ${ default_ip } "
2023-11-03 01:38:11 +01:00
fi
}
2023-11-03 01:38:28 +01:00
function set_pub_keys ( ) {
local authorized_keys_file raw_pub_keys
authorized_keys_file = " ${ 1 : ? } "
raw_pub_keys = " ${ 2 : ? } "
:> " ${ authorized_keys_file } "
while IFS = read -r pub_key_line; do
printf -- '%s\n' " ${ pub_key_line } " >> " ${ authorized_keys_file } "
done < <( <<< " ${ raw_pub_keys } " sed -r -e 's/,,/\n/g' )
sed -i '/^$/d' " ${ authorized_keys_file } "
}
2023-11-01 03:49:46 +01:00
function we_want_ssh ( ) {
#2.10
if [ [ " ${ ARCHZBM_NET_CLIENT_IP } " ] ] || \
[ [ " ${ ARCHZBM_NET_SERVER_IP } " ] ] || \
[ [ " ${ ARCHZBM_NET_GATEWAY_IP } " ] ] || \
[ [ " ${ ARCHZBM_NET_NETMASK } " ] ] || \
[ [ " ${ ARCHZBM_NET_HOSTNAME } " ] ] || \
[ [ " ${ ARCHZBM_NET_DEVICE } " ] ] || \
[ [ " ${ ARCHZBM_NET_AUTOCONF } " ] ] || \
[ [ " ${ ARCHZBM_SSH_PORT } " ] ] || \
[ [ " ${ ARCHZBM_SSH_KEEPALIVE_INTVL } " ] ] || \
[ [ " ${ ARCHZBM_SSH_AUTH_KEYS } " ] ] ; then
>& 3 printf -- '%s\n' 'Installing SSH in ZFSBootMenu'
return 0
fi
>& 3 printf -- '%s\n' 'Not installing SSH in ZFSBootMenu'
return 1
}
function configure_ssh_in_zbm ( ) {
#2.11
2023-11-03 01:38:56 +01:00
get_dropbear_hooks
customize_dropbear_hooks
2023-11-01 03:49:46 +01:00
paru_install 'mkinitcpio-nfs-utils' 'dropbear' 'mkinitcpio-dropbear'
in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'net'
in_file_in_array_insert_n_at_the_end '/etc/zfsbootmenu/mkinitcpio.conf' 'HOOKS' 'dropbear'
2023-11-03 01:38:56 +01:00
for key_type in 'dss' 'ecdsa' 'ed25519' 'rsa' ; do
dropbearkey -t " ${ key_type } " -f '/etc/dropbear/dropbear_' " ${ key_type } " '_host_key'
done
set_pub_keys '/etc/dropbear/root_key' " ${ ARCHZBM_SSH_AUTH_KEYS } "
ensure_ip_in_kcl
2023-11-01 03:49:46 +01: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-11-01 03:49:46 +01:00
if we_want_ssh; then #2.10
configure_ssh_in_zbm #2.11
fi
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-27 04:23:11 +02:00
local root_password
if [ [ " ${ ARCHZBM_ROOT_PASSWORD } " ] ] ; then
root_password = " ${ ARCHZBM_ROOT_PASSWORD } "
else
root_password = 'password'
fi
printf -- '%s\n' 'root:' " ${ 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 ( ) {
2023-11-05 04:50:31 +01:00
if [ [ " ${# @ } " -gt '0' ] ] ; then
arg_parse " ${ @ } "
fi
2023-02-19 20:21:20 +01:00
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-27 04:24:40 +02:00
load_settings_file #1.8
setup_zpool #1.9
mount_system #1.10
copy_zpool_cache #1.11
install_archlinux #1.12
gen_fstab #1.13
set_hostname #1.14
set_locale #1.15
add_zfs_hook_to_initramfs #1.16
set_initramfs_build_list #1.17
add_zfs_files_to_new_os #1.18
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
}
2023-11-05 04:50:31 +01:00
main " ${ @ } "