diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af65835 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +archzbm_settings.env diff --git a/README.md b/README.md index 4a1e4ca..c992f5c 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,9 @@ The script will create a single ZFS zpool `zpool` on the zpool partition with da ## Options -The following options can be given either by exporting them as shell variables prior to script execution or in a file named `archzbm_settings.env` that lives in your current working directory where you're about to execute the script. File format is identical to shell variable assignments of the form `VAR=value` or `VAR='value'`. +The following options can be given either by exporting them as shell variables prior to script execution or in a file named `archzbm_settings.env` that lives in your current working directory where you're about to execute the script. You can walk yourself through an interactive questionnaire that helps create a valid `archzbm_settings.env` file. Check out [Command line setup help](#command-line-setup-help) for details on the questionnaire. + +If you instead want to define settings yourself with an `archzbm_settings.env` file its file format is identical to shell variable assignments of the form `VAR=value` or `VAR='value'`. If `./archzbm_settings.env` exists the script will `source` its content and `export` all variables for use in future steps. @@ -149,6 +151,8 @@ By default the script configures plain ZFSBootMenu without networking nor an SSH #### IP address +> IPv6 addresses are untested. Script has been confirmed working with IPv4 addresses. + ``` ARCHZBM_NET_CLIENT_IP='' ARCHZBM_NET_SERVER_IP='' @@ -203,6 +207,18 @@ ARCHZBM_NET_DEVICE='eth0' ARCHZBM_NET_AUTOCONF='dhcp' ``` +> In ZFSBootMenu the device names that go into `ARCHZBM_NET_DEVICE` are raw unchanged kernel device names such as `eth0`. If you're unsure which device name to use in your Arch Linux live CD ISO image check `dmesg` output. During boot typically a kernel module will first assign the raw kernel device name then later `systemd` will enforce [Predictable Network Interface Names](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/). +> +> In `dmesg | grep` on a physical PC with an MSI B550-A Pro mainboard from 2020 that comes with one onboard Realtek RTL8111H network adapter governed by the Realtek RTL-8169 Gigabit Ethernet driver from the `r8169` kernel module you will for example see: +> ``` +> # dmesg -T | grep eth +> [time] r8169 0000:2a:00.0 eth0: RTL8168h/8111h, 04:7c:16:00:01:02, XID 541, IRQ 95 +> [time] r8169 0000:2a:00.0 eth0: jumbo features [frames: 9194 bytes, tx checksumming: ko] +> [time] r8169 0000:2a:00.0 enp42s0: renamed from eth0 +> ``` +> +> Notice how a Predictable Network Interface Name comes in on line 3. What *__you__* need here is the `eth0` part. + #### SSH If you want networking indicated by the fact that at least one of the `ARCHZBM_NET_*` variables is set or one of the `ARCHZBM_SSH_*` vars we assume that you want an SSH daemon as well. This comes in the form of a `dropbear` daemon with minimal configurability. Use the following variables to define Dropbear's behavior. @@ -223,6 +239,24 @@ ssh-rsa Eahajei8,,ssh-ed25519 kaeD0mas ... This syntax crutch allows you to use the full range of Dropbear-supported `authorized_keys` stanzas, see [man 8 dropbear](https://man.archlinux.org/man/extra/dropbear/dropbear.8.en) for what's available. Whether or not this is useful to you is another topic :) At least the functionality for stanzas is there by separating values in `ARCHZBM_SSH_AUTH_KEYS` with double-commas. +## Command line setup help + +An interactive questionnaire can guide you through settings and goes like this: + +![Command line setup questionnaire](https://i.imgur.com/RhCStdu.gif) + +To do the questionnaire yourself start this script with the `setup` argument: + +``` +export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh' && curl -s "${SCRIPT_URL}" | bash -s -- setup +``` + +When done rerun it without that argument: + +``` +export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh' && curl -s "${SCRIPT_URL}" | bash +``` + # Steps The script takes the following installation steps. @@ -286,7 +320,7 @@ After installation you're going to want to at least touch these points in your n - Unless you had a settings file or exported shell env vars per [Passwords](#passwords) you're going to want to change passwords now: - ZFS: The password for all datasets underneath `zpool` is `password`. - Local `root` account: The local `root` account's password is `password`. -- Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si` then replaced it with its [paru-bin](https://aur.archlinux.org/packages/paru-bin) version from AUR. +- Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from AUR as [paru-bin](https://aur.archlinux.org/packages/paru-bin). - In `/etc/systemd/network/50-wired.network` instead of a DHCP-based network config you can get a static one. The DHCP-based one for reference looks like: ``` ... diff --git a/setup.sh b/setup.sh index 5cccee2..c6404b7 100644 --- a/setup.sh +++ b/setup.sh @@ -19,9 +19,10 @@ exec 3>&1 declare this_script_url this_script_url="${SCRIPT_URL:?}" -declare zpool_name zfs_arch_dataset_name +declare zpool_name zfs_arch_dataset_name settings_file zpool_name='zpool' zfs_arch_dataset_name='archlinux' +settings_file='archzbm_settings.env' declare -A partition_types partition_types[gpt_zfs]='6a85cf4d-1dd2-11b2-99a6-080020736631' @@ -35,6 +36,230 @@ trap '[ "$?" -ne 77 ] || exit 77' ERR declare zpool_drive efi_drive boot_drive part_schema +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 +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 : ' -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 : ' -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 : ' 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 : ' ARCHZBM_SSH_KEEPALIVE_INTVL + echo + fi + + read -u3 -p 'Please type SSH pub keys on one line separated by double-commas (,,) and confirm with : ' ARCHZBM_SSH_AUTH_KEYS + echo + 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 + + printf -- '%s\n' \ + 'Done, please rerun script now with just' \ + '... | bash' \ + 'so without the '"'"'setup'"'"' argument's + exit 77 +} + +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 +} + function we_are_changerooted () { if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then return 0 @@ -328,9 +553,8 @@ function export_pool () { function load_settings_file () { #1.8 - local working_dir settings_file settings_abs + local working_dir settings_abs working_dir="$(pwd)" - settings_file='archzbm_settings.env' settings_abs="${working_dir}"'/'"${settings_file}" if [[ -r "${settings_abs}" ]]; then set -a @@ -677,56 +901,17 @@ function get_aur_helper () { 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' - chown -R 'build:' 'paru' - pushd 'paru' + git clone https://aur.archlinux.org/paru-bin.git + chown -R 'build:' 'paru-bin' + pushd 'paru-bin' sudo --user 'build' makepkg -si --noconfirm popd - rm -rf 'paru' + rm -rf 'paru-bin' popd } function paru_install () { - declare -a paru_install_packages - [[ "${1}" ]] && while :; do - case "${1}" in - -[[:alnum:]]*) - >&3 printf -- '%s\n' \ - '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 - ;; - --*) - >&3 printf -- '%s\n' \ - '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 || { - >&3 printf -- '%s\n' \ - 'No argument '"'${1}'"' given for function '"'${FUNCNAME[0]}'"'. Exiting ...' - exit 77 - } - if [[ "${pacman_force_yes}" ]]; then - yes 'y' | sudo --user 'build' paru -S "${paru_install_packages[@]}" - unset -v pacman_force_yes - else - sudo --user 'build' paru -S --noconfirm "${paru_install_packages[@]}" - fi + sudo --user build paru -S --noconfirm "${@}" } function configure_syslinux () { @@ -988,7 +1173,6 @@ function install_os_in_chroot () { unleash_makepkg #2.5 add_motd_getting_started_msg #2.6 get_aur_helper #2.7 - paru_install --replace-conflicting 'paru-bin' paru_install 'zfs-dkms' 'zfs-utils' 'jq' hwclock --systohc mkinitcpio -P @@ -1137,6 +1321,9 @@ function finalize_os_setup () { } function main () { + if [[ "${#@}" -gt '0' ]]; then + arg_parse "${@}" + fi if we_are_changerooted; then install_os_in_chroot #2.2 else @@ -1164,4 +1351,4 @@ function main () { fi } -main +main "${@}"