#!/usr/bin/env bash revision=1.0l set -a set -E # ZFS key # zfsgpgkey=DDF7DB817396A49B2A2723F7403BD972F75D9D76 # archzfs zfsgpgkey=D0F2AE55C1BF11A026D155813A658A95B8CFCC51 # myvezfs # Exit function trap '[ "${?}" -ne 77 ] || exit 77' ERR function die { local reset="$(tput sgr0)" local red="${reset}$(tput setaf 1)" local yellow="${reset}$(tput setaf 3)" cat <<- abort ${red} Error encountered for the following reason: ${yellow} ${@} ${red} Script aborted... ${reset} abort exit 77 } # EFI system check if [ ! -d /sys/firmware/efi/efivars ] then die 'This script only works with UEFI systems' fi # Prompt function function reformat { case "${1}" in # Formatting bold) format+=($(tput bold)) ;; italic) format+=($(tput sitm)) ;; uline) format+=($(tput smul)) ;; blink) format+=($(tput blink)) ;; dim) format+=($(tput dim)) ;; # Colours black) format+=($(tput setaf 0)) ;; red) format+=($(tput setaf 1)) ;; green) format+=($(tput setaf 2)) ;; yellow) format+=($(tput setaf 3)) ;; blue) format+=($(tput setaf 4)) ;; magenta) format+=($(tput setaf 5)) ;; cyan) format+=($(tput setaf 6)) ;; white) format+=($(tput setaf 7)) ;; # Read-specific array) array="-a" ;; secret) secret="-s" ;; press) press="-n 1" ;; # Everything else goes into line *) line="${@}" ;; esac } # Syntax: ask for var [in format] "statement" function ask { local format variable line secret press array case "${1}" in for) case "${2}" in in) die "Syntax error detected (missing variable before $(tput sitm)$(tput smso)in$(tput rmso)$(tput ritm)) Usage: ask for \${variable} in \${format[@]} \"\${line}\"" ;; *) variable="${2}" shift 2 ;; esac ;; *) die "Syntax error detected (misuse of $(tput sitm)$(tput smso)for$(tput rmso)$(tput ritm)) Usage: ask for \${variable} in \${format[@]} \"\${line}\"" ;; esac case "${1}" in in) shift for arg in "${@}" do reformat ${arg} done ;; *) line="${@}" ;; esac read ${press} -r -p "$(printf "%s" "${format[@]}" "${line}: " "$(tput sgr0)")" ${secret} ${array} ${variable} echo export ${variable} } # Syntax: say [as template] [in format] "statement" function say { local format line heading case "${1}" in as) # Templates case ${2} in title) format+=($(tput setaf 6)) # cyan ;; heading) format+=($(tput setaf 3) $(tput bold)) # bold yellow heading=":: " ;; success) format+=($(tput setaf 2)) # green ;; warning) format+=($(tput setaf 1)) # red ;; *) die "Syntax error detected (misuse of $(tput sitm)$(tput smso)as$(tput rmso)$(tput ritm)) Usage: say as \${template} in \${format[@]} \"\${line}\"" ;; esac shift 2 ;; esac case "${1}" in in) shift for arg in "${@}" do reformat ${arg} done ;; *) line="${@}" ;; esac printf "%s" "${format[@]}" "${heading}" "${line}" "$(tput sgr0)" echo } function presskeytoresume { read -n 1 -s -p "Press any key when ready..." echo } # Internet connection check if nc -z -w 1 archlinux.org 443 >/dev/null 2>&1 || nc -z -w 1 google.com 443 >/dev/null 2>&1 then timedatectl set-ntp true else die "No internet connectivity detected, plug in an ethernet cable or run ${reset}${green}iwd-connect${yellow} if using wifi and try again" fi ## ## functions start ## # Default systemd services systemd_services+=(systemd-networkd.service systemd-resolved.service sshd.service iptables.service) # Repeat a command until it exits with 0 function repeat { until ${@} do sleep 3 done } # Pacstrap new root and generate fs tables function pacstrapGenfstab { local archpkgs="sudo openssh efibootmgr pacman-contrib \ vim rsync pv git less openbsd-netcat \ man-db bash-completion reflector \ wireguard-tools systemd-resolvconf \ ${linux_firmware[@]} \ ${btrfs[@]} ${zfs[@]} \ ${ucode} \ ${grub[@]} \ ${iwd} \ ${yubikey[@]} \ ${nvidia[@]} \ ${bluetooth[@]}" # Delete unneccessary EFI files rm -f /sys/firmware/efi/efivars/dump-* for entry in $(efibootmgr | grep "Arch" | grep -o 'Boot....' | sed 's/Boot//') do efibootmgr --quiet -Bb ${entry} done # Ensure pacman.conf settings are properly set sed -e '/Color/c Color' \ -e '/ParallelDownloads/c ParallelDownloads = 10' \ -i /etc/pacman.conf # Add CacheServer if [ ${cacheserver} ] then sed -e "/^\[core\]$\|^\[extra\]$/a CacheServer = ${cacheserver}" \ -i /etc/pacman.conf fi # Temporarily disable mkinitcpio install ln -s -f /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook echo say as heading "Installing Arch Linux base packages" case ${filesystem} in zfs) # Add zfs repo grep -q '\[myvezfs\]' /etc/pacman.conf || sed -i '/\[core\]/i [myvezfs]\ Server = https://mirror.myvelabs.com/repo/$repo\ Server = https://repo.myvelabs.com/$repo\n' /etc/pacman.conf # Add CacheServer if [ ${cacheserver} ] then sed -e "/^\[myvezfs\]$/a CacheServer = ${cacheserver}" \ -i /etc/pacman.conf fi local zfslinux=$(pacman -Syi zfs-${linux_kernel} | grep "Depends On" | sed "s|.*${linux_kernel}=||" | awk '{print $1}') # Install zfs-compatible linux kernel if [ ${zfslinux} = $(pacman -Si myvezfs/${linux_kernel} | grep "Version" | awk '{print $3}') ] then repeat pacstrap -K /mnt --ask 4 \ base mkinitcpio iptables-nft ${linux_kernel} ${archpkgs} else repeat pacstrap -K /mnt base mkinitcpio iptables-nft if [ ${cacheserver} ] then pacstrap -K -U /mnt ${cacheserver}/${linux_kernel}-${zfslinux}-x86_64.pkg.tar.zst ||\ repeat pacstrap -K -U /mnt https://archive.archlinux.org/packages/l/${linux_kernel}/${linux_kernel}-${zfslinux}-x86_64.pkg.tar.zst else repeat pacstrap -K -U /mnt https://archive.archlinux.org/packages/l/${linux_kernel}/${linux_kernel}-${zfslinux}-x86_64.pkg.tar.zst fi repeat pacstrap -K /mnt --ask 4 ${archpkgs} fi # Add archzfs repo sed -e '/\[core\]/i [myvezfs]\ Server = https://mirror.myvelabs.com/repo/$repo\ Server = https://repo.myvelabs.com/$repo\n' \ -i /mnt/etc/pacman.conf # Add archzfs repo keys arch-chroot /mnt pacman-key -r ${zfsgpgkey} arch-chroot /mnt pacman-key --lsign-key ${zfsgpgkey} # ZFS properties zpool set bootfs=zroot/ROOT zroot zpool set cachefile=/etc/zfs/zpool.cache zroot rsync -a /etc/zfs/zpool.cache /mnt/etc/zfs/ # Create zfs directory mkdir -p /mnt/zfs/bin/ # Configure zfs for mkinitcpio echo 'BINARIES+=(/usr/bin/zfs)' >/mnt/etc/mkinitcpio.conf.d/zz-binaries.conf sed -e '/^HOOKS/ s/filesystems/zfs &/' \ -e '/^HOOKS/ s/ fsck//' \ /mnt/etc/mkinitcpio.conf >/mnt/etc/mkinitcpio.conf.d/zz-hooks.conf # Generate fstab case ${arch} in 1|2) genfstab -U -p /mnt/boot | sed "s|vfat.*rw|vfat rw,x-systemd.idle-timeout=1min,x-systemd.automount,noauto,nofail|" >>/mnt/etc/fstab sed -i '/UUID.*vfat/ s|/|/boot|' /mnt/etc/fstab ;; *) genfstab -U -p /mnt | grep -v zroot >>/mnt/etc/fstab ;; esac # Add systemd services systemd_services+=(zfs.target zfs-import-cache.service zfs-mount.service zfs-import.target zfs-trim@zroot.timer zfs-scrub@zroot.timer) ;; btrfs|ext4) repeat pacstrap -K /mnt --ask 4 \ base mkinitcpio iptables-nft ${linux_kernel} ${archpkgs} # Generate fstab genfstab -U -p /mnt >>/mnt/etc/fstab case ${filesystem} in btrfs) echo 'BINARIES+=(/usr/bin/btrfs)' >/mnt/etc/mkinitcpio.conf.d/zz-binaries.conf ;; esac ;; esac # Ensure pacman.conf settings are properly set sed -e '/Color/c Color' \ -e '/ParallelDownloads/c ParallelDownloads = 10' \ -i /mnt/etc/pacman.conf # Custom mkinitcpio.conf echo 'MODULES_DECOMPRESS="yes"' >/mnt/etc/mkinitcpio.conf.d/zz-modules_decompress.conf # Add CacheServer if [ ${cacheserver} ] then grep -q "^CacheServer" /etc/pacman.conf ||\ sed -e "/^\[core\]$\|^\[extra\]$\|^\[myvezfs\]$/a CacheServer = ${cacheserver}" \ -i /mnt/etc/pacman.conf fi # Make custom directories mkdir -p /mnt/etc/pacman.d/hooks/ /mnt/opt/local/{bin,hooks}/ # Manually configure mkinitcpio ln -s -f /dev/null /mnt/etc/pacman.d/hooks/90-mkinitcpio-install.hook # Add hooks hooks } # Update mkinitcpio with encrypt hooks function encryptHook { case ${filesystem} in zfs) sed "s/block/& ${1}/" -i /mnt/etc/mkinitcpio.conf.d/zz-hooks.conf # Pacman hook install /dev/stdin /mnt/opt/local/hooks/mkinitcpio.conf <<- hook #!/usr/bin/env bash grep '^HOOKS' /etc/mkinitcpio.conf | sed -e "s/filesystems/zfs &/" -e "s/ fsck//" -e "s/block/& ${1}/" >/etc/mkinitcpio.conf.d/zz-hooks.conf sed -e "s|%PKGBASE%|${linux_kernel}|g" \\ -e "s/^fallback/#&/g" \\ -e "s/ 'fallback'//" \\ /usr/share/mkinitcpio/hook.preset >/etc/mkinitcpio.d/${linux_kernel}.preset hook ;; *) grep '^HOOKS' /mnt/etc/mkinitcpio.conf | sed "s/block/& ${1}/" >/mnt/etc/mkinitcpio.conf.d/zz-hooks.conf # Pacman hook install /dev/stdin /mnt/opt/local/hooks/mkinitcpio.conf <<- hook #!/usr/bin/env bash grep '^HOOKS' /etc/mkinitcpio.conf | sed "s/block/& ${1}/" >/etc/mkinitcpio.conf.d/zz-hooks.conf sed -e "s|%PKGBASE%|${linux_kernel}|g" \\ -e "s/^fallback/#&/g" \\ -e "s/ 'fallback'//" \\ /usr/share/mkinitcpio/hook.preset >/etc/mkinitcpio.d/${linux_kernel}.preset hook ;; esac cat >/mnt/etc/pacman.d/hooks/85-mkinitcpio.hook <<- mkinitcpio [Trigger] Operation = Install Operation = Upgrade Type = Package Target = mkinitcpio [Action] Description = Fixing mkinitcpio.conf When = PostTransaction Exec = /opt/local/hooks/mkinitcpio.conf mkinitcpio } # Compile yubikey if arch package fails function compileYubikey { cd yubikey-full-disk-encryption make install || die 'Yubikey full disk encryption (github) package installation failed' } # Install yubikey inside new root function chrootYubikey { if [ -d ~/yubikey-full-disk-encryption ] then rsync -a ~/yubikey-full-disk-encryption /mnt/ arch-chroot /mnt /usr/bin/bash <<- YUBIKEY compileYubikey rm -r /yubikey-full-disk-encryption YUBIKEY fi rsync -a /etc/ykfde.conf /mnt/etc/ chmod 600 /mnt/etc/ykfde.conf encryptHook ykfde echo say in blue 'Copied yubikey configuration files' } # Yubikey root encryption function encryptRootYubikey { yubikey=(yubikey-personalization) for package in yubikey-personalization yubikey-full-disk-encryption do if ! pacman -Q | grep -q -w ${package} then missing_yubikey_requirements+=(${package}) fi done if [ ${missing_yubikey_requirements} ] then yes | pacman -Sy ${missing_yubikey_requirements[@]} echo fi function ykfdeFormat { # Custom challenge slot # sed -i '/YKFDE_CHALLENGE_SLOT/c YKFDE_CHALLENGE_SLOT="1"' /etc/ykfde.conf case ${decryptroot} in [yY]) sed '/YKFDE_CHALLENGE=/c YKFDE_CHALLENGE="'"$(printf '%q' "${lukspass}")"'"' -i /etc/ykfde.conf ;; [nN]) sed '/YKFDE_CHALLENGE_PASSWORD_NEEDED/c YKFDE_CHALLENGE_PASSWORD_NEEDED="1"' -i /etc/ykfde.conf ;; esac say as heading "Encrypting ${rootpart} with yubikey HMAC encryption" until printf '%s\n' "${lukspass}" "${lukspass}" | ykfde-format ${encryptopts} ${luks_header} ${rootpart} do sleep 3 done printf '%s\n' "${lukspass}" |\ ykfde-open \ -d ${rootpart} \ -n cryptroot \ -- ${luks_header} } while true do # Try with yubikey-full-disk-encryption pacman package ykfdeFormat if dmsetup ls | grep -q 'cryptroot' then yubikey+=(yubikey-full-disk-encryption) echo break fi for package in git make do if ! pacman -Q | grep -q -w ${package} then make_yubikey_requirements+=(${package}) fi done echo say in blue 'Setting up yubikey' if [ ${make_yubikey_requirements} ] then yes | pacman -Sy ${make_yubikey_requirements[@]} echo fi # Try yubikey full disk encryption again using latest git cd ~/ git clone https://github.com/agherzan/yubikey-full-disk-encryption.git compileYubikey ykfdeFormat if dmsetup ls | grep -q 'cryptroot' then echo break fi die 'Unable to encrypt root partition' done } # Root encryption function encryptRootLuks { say as heading "Encrypting ${rootpart}" until printf '%s' "${lukspass}" | cryptsetup luksFormat -v ${encryptopts} ${luks_header} ${rootpart} do sleep 3 done printf '%s' "${lukspass}" | cryptsetup open -v ${rootpart} cryptroot ${luks_header} if dmsetup ls | grep -q 'cryptroot' then echo else die 'Unable to encrypt root partition' fi } # External boot encryption function encryptBootLuks { say as heading "Encrypting ${bootpart}" until printf '%s' "${grubpass}" | cryptsetup luksFormat -v --type luks1 ${bootpart} do sleep 3 done printf '%s' "${grubpass}" | cryptsetup open -v ${bootpart} cryptboot yes | mkfs.ext2 -q /dev/mapper/cryptboot if dmsetup ls | grep -q cryptboot then echo else die 'Unable to encrypt boot partition' fi } # Default cryptsetup options encryptopts="--cipher aes-xts-plain64 \ --key-size 256 \ --iter-time 2000 \ --hash sha512" # Generate keyfile for external boot function generateKeyfile { echo say as heading 'Generating keyfile' dd bs=1 count=256 if=/dev/random of=/mnt/crypto_keyfile.bin status=progress chmod 000 /mnt/crypto_keyfile.bin printf '%s' "${grubpass}" | cryptsetup luksAddKey ${bootpart} /mnt/crypto_keyfile.bin if [[ ${decryptroot} = [yY] ]] then case ${arch} in 3) printf '%s' "${lukspass}" | cryptsetup luksAddKey ${rootpart} /mnt/crypto_keyfile.bin ;; 6) printf '%s' "${lukspass}" | cryptsetup luksAddKey /mnt/boot/header.img /mnt/crypto_keyfile.bin ;; esac fi } # Format partitions function configureVolumes { say as heading 'Configuring volumes' # Root case ${filesystem} in ext4) yes | mkfs.ext4 ${rootvol} mount -o noatime ${rootvol} /mnt ;; btrfs) mkfs.btrfs -f ${rootvol} btrfsopts="compress=zstd:2,noatime,space_cache=v2" # Create btrfs subvolumes mount ${rootvol} /mnt for subvol in @{,home,var{,/pkg,/log,/tmp,/qemu},opt} # ,snapshots{,/root,/home} do btrfs subvolume create /mnt/${subvol} done chattr +C /mnt/@var/qemu chmod 1777 /mnt/@var/tmp umount /mnt # Mount @root subvolume mount --mkdir -o ${btrfsopts},subvol=@ ${rootvol} /mnt # Mount home and home snapshots subvolumes mount --mkdir -o ${btrfsopts},subvol=@home ${rootvol} /mnt/home # mount --mkdir -o ${btrfsopts},subvol=@snapshots/home ${rootvol} /mnt/home/.snapshots # mount --mkdir -o ${btrfsopts},subvol=@snapshots/root ${rootvol} /mnt/.snapshots # Mount remaining subvolumes mount --mkdir -o ${btrfsopts},subvol=@var/pkg ${rootvol} /mnt/var/cache/pacman/pkg mount --mkdir -o ${btrfsopts},subvol=@var/log ${rootvol} /mnt/var/log mount --mkdir -o ${btrfsopts},subvol=@var/tmp ${rootvol} /mnt/var/tmp mount --mkdir -o ${btrfsopts},subvol=@var/qemu ${rootvol} /mnt/var/lib/libvirt/images mount --mkdir -o ${btrfsopts},subvol=@opt ${rootvol} /mnt/opt mount --mkdir -o ${btrfsopts},subvol=/ ${rootvol} /mnt/btrfs echo ;; zfs) until printf '%s' "${lukspass}" |\ zpool create -f zroot \ -o ashift=12 \ -o autotrim=on \ -R /mnt \ -O acltype=posixacl \ -O canmount=off \ -O compression=zstd \ -O dnodesize=auto \ -O normalization=formD \ -O atime=off \ -O xattr=sa \ -O mountpoint=none \ ${zpoolencryption} ${rootvol} do sleep 3 done # Mount datasets zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT zfs create -o mountpoint=/.boot zroot/BOOT zfs create -o mountpoint=/home zroot/HOME zfs create -o mountpoint=/var/log zroot/LOG zfs create -o mountpoint=/var/tmp zroot/TMP zfs create -o mountpoint=/var/cache/pacman/pkg zroot/PKG zfs create -o mountpoint=/var/lib/libvirt/images zroot/QEMU # Verify zpool zpool export zroot zpool import -R /mnt zroot -N -d ${rootvol} # Native encryption case ${arch} in 2) printf '%s' "${lukspass}" | zfs load-key zroot ;; esac zfs mount zroot/ROOT zfs mount -a ;; esac # Boot volume if [[ ${arch} = [3567] ]] then yes | mkfs.fat -F 32 ${efivol} else yes | mkfs.fat -F 32 ${bootvol} fi mount --mkdir ${bootvol} /mnt/boot # EFI volume if [[ ${arch} = [3567] ]] then mount --mkdir ${efivol} /mnt/boot/efi grub+=(grub) fi } # Configure grub function configureGrub { sed '/GRUB_TIMEOUT=/c GRUB_TIMEOUT=3' -i /etc/default/grub sed '/GRUB_ENABLE_CRYPTODISK/c GRUB_ENABLE_CRYPTODISK=y' -i /etc/default/grub echo 'cryptboot '${bootpart}' /crypto_keyfile.bin luks' >>/etc/crypttab } # Ask for system settings function initialSetup { echo # Username say in blue 'Create a new user' until [ ${username} ] do ask for username 'Username' if [ -z ${username} ] then say as warning 'Username cannot be empty, try again' fi done # Hostname say in blue 'Create a name for your computer' until [ ${hostname} ] do ask for hostname 'Hostname' if [ -z ${hostname} ] then say as warning 'Hostname cannot be empty, try again' fi done # User password say in blue "Set a password for ${username}" until [ "${userpass}" = "${userpass2}" -a "${userpass}" ] do ask for userpass in secret 'User password' ask for userpass2 in secret 'Verify user password' echo if [ -z "${userpass}" ] then say as warning 'Password field cannot be empty, try again' elif [ "${userpass}" != "${userpass2}" ] then say as warning 'Passwords did not match, try again' fi done say as success "${username}@${hostname}'s password has been saved" echo # Encryption key if [[ ${arch} = [2-7] ]] then say in blue 'Create an encryption passphrase for the system drive' until [ "${lukspass}" = "${lukspass2}" -a "${lukspass}" -a "${#lukspass}" -ge 8 ] do ask for lukspass in secret 'Encryption password' ask for lukspass2 in secret 'Verify encryption password' echo if [ -z "${lukspass}" ] then say as warning 'Passphrase field cannot be empty, try again' elif [ "${lukspass}" != "${lukspass2}" ] then say as warning 'Passphrases did not match, try again' elif [ "${#lukspass}" -lt 8 ] then say as warning 'Passphrase needs to be at least 8 characters' fi done say as success 'Encryption passphrase has been saved' echo fi # GRUB password if [[ ${arch} = [3567] ]] then say in blue 'Create an encryption passphrase for the bootloader' until [ "${grubpass}" = "${grubpass2}" -a "${grubpass}" -a "${#grubpass}" -ge 6 ] do ask for grubpass in secret 'Bootloader password' ask for grubpass2 in secret 'Verify bootloader password' echo if [ -z "${grubpass}" ] then say as warning 'Password field cannot be empty, try again' elif [ "${grubpass}" != "${grubpass2}" ] then say as warning 'Passwords did not match, try again' elif [ "${#grubpass}" -lt 6 ] then say as warning 'Passphrase needs to be at least 6 characters' fi done say as success 'Bootloader password has been saved' echo fi # Assign system drive say in blue 'Identify the system drive from the list of available devices below' lsblk -d -o name,model,size,mountpoint | grep -v 'archiso' say in uline white dim '## SSD and HDD device format begins with "sd" or "hd" (sda, sdb, sd[*])' say in uline white dim '## NVME and PCI device format is "nvme[*]n1" (nvme0n1, nvme1n1, nvme[*]n1)' while true do ask for drive 'Installation device' if [ ${drive} ] && lsblk -o name | grep -q -w ${drive} then break else lsblk -d -o name,model,size,mountpoint | grep -v 'archiso' if [ -z ${drive} ] then say as warning 'Field cannot be empty, try again' else say as warning 'Invalid selection or drive not available, try again' fi fi done say as success "Installation drive set as $(tput smso)/dev/${drive}$(tput rmso)" # Assign detached boot drive (if applicable) if [[ ${arch} = [67] ]] then echo say in blue 'Identify the removable boot drive from the list of available devices below' lsblk -d -o name,model,size,mountpoint | grep -v "archiso\|$drive" say in uline white dim '## Removable drives usually begin with "sd" (sda, sdb, sd[*])' while true do ask for external_drive 'Removable boot device' if [ ${external_drive} ] && lsblk -o name | grep -v ${drive} | grep -q -w ${external_drive} then break else lsblk -d -o name,model,size,mountpoint | grep -v "archiso\|$drive" if [ -z ${external_drive} ] then say as warning 'Field cannot be empty, try again' elif [ ${external_drive} = ${drive} ] then say as warning 'Boot drive cannot be the same as the system drive, try again' else say as warning 'Invalid selection or drive not available, try again' fi fi done say as success "Detached boot drive set as $(tput smso)/dev/${external_drive}$(tput rmso)" fi } # Virtual machine defaults function virtualMachineSetup { if ls -l /dev/disk/* | grep -q VBOX then hostname=vbox drive=sda external_drive=sdb else hostname=qemu if ls -l /dev/disk/* | grep -q virtio then drive=vda elif ls -l /dev/disk/* | grep -q QEMU then drive=sda fi fi username=user userpass=123 lukspass=12345678 grubpass=123456 echo say in uline white dim 'Troubleshooting defaults loaded' } function hooks { # Common pacman.conf install /dev/stdin /mnt/opt/local/hooks/pacman.conf <<- 'hook' #!/usr/bin/env bash if [ -f /etc/pacman.conf.pacnew ] then sed -e '/ParallelDownloads/c ParallelDownloads = 10' \ -e '/Color/c Color' /etc/pacman.conf.pacnew >/etc/pacman.conf rm /etc/pacman.conf.pacnew fi hook case ${filesystem} in zfs) # pacman.conf sed -e "/Color\/c Color/a\\ -e '/^\\\[core\\\]$/i [myvezfs]\\\\\\ Server = https://mirror.myvelabs.com/repo/\$repo\\\\\\ Server = https://repo.myvelabs.com/\$repo\\\n' \\\\" \ -i /mnt/opt/local/hooks/pacman.conf cat >/mnt/etc/pacman.d/hooks/100-pacman.conf.hook <<- pacman [Trigger] Operation = Install Operation = Upgrade Type = Package Target = pacman [Action] Description = Fixing pacman.conf When = PostTransaction Exec = /opt/local/hooks/pacman.conf pacman # mkinitcpio.conf install /dev/stdin /mnt/opt/local/hooks/mkinitcpio.conf <<- 'hook' #!/usr/bin/env bash grep '^HOOKS' /etc/mkinitcpio.conf | sed 's/filesystems fsck/zfs filesystems/' >/etc/mkinitcpio.conf.d/zz-hooks.conf sed -e "s|%PKGBASE%|${linux_kernel}|g" \\ -e "s/^fallback/#&/g" \\ -e "s/ 'fallback'//" \\ /usr/share/mkinitcpio/hook.preset >/etc/mkinitcpio.d/${linux_kernel}.preset hook cat >/mnt/etc/pacman.d/hooks/85-mkinitcpio.conf.hook <<- mkinitcpio [Trigger] Operation = Install Operation = Upgrade Type = Package Target = mkinitcpio [Action] Description = Fixing mkinitcpio.conf When = PostTransaction Exec = /opt/local/hooks/mkinitcpio.conf mkinitcpio ;; *) # pacman.conf hook cat >/mnt/etc/pacman.d/hooks/100-pacman.conf.hook <<- pacman [Trigger] Operation = Install Operation = Upgrade Type = Package Target = pacman [Action] Description = Fixing pacman.conf When = PostTransaction Exec = /opt/local/hooks/pacman.conf pacman ;; esac } ## ## functions end ## ## ## Static variables ## # Determine network controller if lspci | grep -q 'Network controller' then if ! lspci | grep -q 'Ethernet controller' then iwd=iwd systemd_services+=(iwd.service) fi fi # Determine ucode if lscpu | grep 'Model name:' | grep -q AMD then ucode=amd-ucode elif lscpu | grep 'Model name:' | grep -q Intel then ucode=intel-ucode else die 'Unable to determine CPU type' fi # Detect bluetooth if lsmod | grep -q bluetooth || [ -d /sys/class/bluetooth ] then bluetooth=(bluez bluez-utils) fi # Read flags rm -f sshkeys while [ ${1} ] do case ${1} in -s | --ssh-key ) if [ "${2}" ] then echo "${2}" >>sshkeys if ! ssh-keygen -l -f sshkeys >/dev/null then die 'Invalid SSH public key detected' fi shift fi ;; -o | --option ) if [ "${2}" ] then arch=${2} shift fi ;; -c | --cache ) if [ "${2}" ] then cacheserver=${2} shift fi ;; -k | --kernel ) case "${2}" in linux) linux_kernel=linux shift ;; linux-lts) linux_kernel=linux-lts shift ;; linux-hardened) linux_kernel=linux-hardened shift ;; linux-zen) linux_kernel=linux-zen shift ;; *) die "Invalid option for flag \"${1}\"" ;; esac ;; -f | --filesystem ) case "${2}" in ext4) filesystem_choice=1 shift ;; btrfs) filesystem_choice=2 shift ;; zfs) filesystem_choice=3 shift ;; *) die "Invalid option for flag \"${1}\"" ;; esac ;; -y | --noconfirm ) noconfirm=yes load_defaults=y shred_installation_disk=n shred_boot=n begin_install=Y ;; -? | -h | --help ) cat <<- help Parameters: -s, --ssh-key Add SSH public key (enclosed in quotes) -o, --option Arch installation setup -c, --cache Pacman cache server -k, --kernel Linux kernel -f, --filesystem Filesystem choice -y, --noconfirm Skip choices -?, -h, --help This help screen help exit 0 ;; * ) die "Unknown flag: ${1}" ;; esac shift done clear say in uline white dim "## rev ${revision}" echo say in bold blue "$(tput smso)Arch Linux Installer" echo say in yellow "Select a system configuration from the options below" cat <<- menu 1) Basic 2) Full Disk Encryption with EFISTUB 3) Full Disk Encryption with Secured GRUB 4) Yubikey Full Disk Encryption with EFISTUB 5) Yubikey Full Disk Encryption with Secured GRUB menu if ls -l /dev/disk/* | grep -q 'virtio\|QEMU' then echo until [[ ${arch} = [1-5] ]] do ask for arch in press ':' echo [[ ${arch} = [1-5] ]] || say as warning 'Invalid selection, type an option from 1 to 5' done else cat <<- menu 6) Full Disk Encryption with Detached LUKS Header and Secured GRUB 7) Yubikey Encrypted Detached LUKS Header with Secured GRUB menu echo until [[ ${arch} = [1-7] ]] do ask for arch in press ':' echo [[ ${arch} = [1-7] ]] || say as warning 'Invalid selection, type an option from 1 to 7' done fi # Prevent continuing unless yubikey is detected, only applies to yubikey setups if [[ ${arch} = [457] ]] && ! dmesg | grep -q -i YubiKey then say as warning 'Yubikey not detected' say as warning 'Insert one to continue...' until dmesg | grep -q -i YubiKey do sleep 1 done fi clear say in uline white dim "## rev ${revision}" echo -e -n "\n$(tput setab 7)$(tput setaf 4)$(tput bold)\t" case ${arch} in 1) echo 'Basic Installation' ;; 2) echo 'Full Disk Encryption with EFISTUB' ;; 3) echo 'Full Disk Encryption with Secured GRUB' ;; 4) echo 'Yubikey Full Disk Encryption with EFISTUB' ;; 5) echo 'Yubikey Full Disk Encryption with Secured GRUB' ;; 6) echo 'Full Disk Encryption with Detached LUKS Header and Secured GRUB' ;; 7) echo 'Yubikey Encrypted Detached LUKS Header with Secured GRUB' ;; esac echo "$(tput sgr0)" presskeytoresume # Initial setup if ls -l /dev/disk/* | grep -q 'VBOX\|virtio\|QEMU' then cacheserver=http://192.168.122.1:9090 case ${noconfirm} in yes) virtualMachineSetup ;; *) echo while true do ask for load_defaults in press yellow 'Would you like to load virtual machine troubleshooting defaults? (y/n)' case ${load_defaults} in [yY]) virtualMachineSetup break ;; [nN]) initialSetup break ;; *) echo say as warning 'Not a valid answer, type "y" or "n"' ;; esac done ;; esac else initialSetup fi # Linux firmware if ls -l /dev/disk/* | grep -q VBOX then linux_firmware=(virtualbox-guest-utils) systemd_services+=(vboxservice.service) elif ls -l /dev/disk/* | grep -q 'virtio\|QEMU' then linux_firmware=(qemu-guest-agent spice-vdagent) systemd_services+=(qemu-guest-agent.service) else # Detect needed firmwares linux_firmware=(linux-firmware) for firmware in $(pacman -Ssq linux-firmware- | sed 's/linux-firmware-//') do if lspci | grep -q -i ${firmware} then linux_firmware+=(linux-firmware-${firmware}) fi done fi # Choose linux kernel echo say in blue "Select a linux kernel:" cat <<- menu 1) linux 2) linux-lts 3) linux-hardened 4) linux-zen menu until [ ${linux_kernel} ] do ask for linux_kernel_choice in press ':' case ${linux_kernel_choice} in 1) linux_kernel=linux ;; 2) linux_kernel=linux-lts ;; 3) linux_kernel=linux-hardened ;; 4) linux_kernel=linux-zen ;; *) echo say as warning 'Invalid selection, type an option from 1 to 4' ;; esac done echo say as success "You have chosen $(tput smso)${linux_kernel}$(tput rmso) for a kernel" # GPU firmware if lspci | grep VGA | grep -q QXL then linux_firmware+=(xf86-video-qxl) fi if lspci | grep VGA | grep -q NVIDIA then case ${linux_kernel} in linux) nvidia=(nvidia-open nvidia-settings) ;; linux-lts) nvidia=(nvidia-lts nvidia-settings) ;; *) nvidia=(nvidia-dkms nvidia-settings) ;; esac fi # Choose filesystem echo say in blue "Select a filesystem:" cat <<- menu 1) ext4 2) btrfs menu if modprobe zfs >/dev/null 2>&1 then cat <<- MENU 3) zfs MENU fi until [[ ${filesystem_choice} = [1-3] ]] do ask for filesystem_choice in press ':' if ! [[ ${filesystem_choice} = [1-3] ]] then echo say as warning 'Invalid selection, type an option from 1 to 3' fi done case ${filesystem_choice} in 1) filesystem=ext4 rootflags='quiet rw' ;; 2) filesystem=btrfs rootflags='rootflags=subvol=@ quiet rw' btrfs=(btrfs-progs timeshift cronie) systemd_services+=(cronie.service) ;; 3) filesystem=zfs rootflags='zfs=bootfs quiet rw' zfs=(zfs-${linux_kernel} zfs-utils) ;; esac echo say as success "You have chosen $(tput smso)${filesystem}$(tput rmso) for a filesystem" # Option to automatically decrypt / using a keyfile, only applies to encrypted GRUB/yubikey 1FA setups if [[ ${arch} = [3-7] ]] then echo if [[ ${arch} = [457] ]] then say in uline white dim '## Root will be decrypted as long as yubikey is inserted when prompted' fi until [[ ${decryptroot} = [yYnN] ]] do ask for decryptroot in press yellow "Would you like to automatically decrypt the root partition upon boot? (y/n)" if ! [[ ${decryptroot} = [yYnN] ]] then echo say as warning 'Not a valid answer, type "y" or "n"' fi done fi # Skip confirmations if flag is on if [ -z ${noconfirm} ] then echo # Device shredding until [[ ${shred_installation_disk} = [yYnN] ]] do ask for shred_installation_disk in press yellow "Would you like to shred your installation drive? (y/n)" if ! [[ ${shred_installation_disk} = [yYnN] ]] then echo say as warning 'Not a valid answer, type "y" or "n"' fi done if [[ ${arch} = [67] ]] then echo until [[ ${shred_boot} = [yYnN] ]] do if [[ ${shred_installation_disk} = [yY] ]] then ask for shred_boot in press yellow "Would you also like to shred your removable drive? (y/n)" else ask for shred_boot in press yellow "Would you like to shred your removable drive? (y/n)" fi if ! [[ ${shred_boot} = [yYnN] ]] then echo say as warning 'Not a valid answer, type "y" or "n"' fi done fi echo if [[ ${shred_installation_disk} = [nN] && ${shred_boot} = [nN] ]] || [[ ${arch} != [67] && ${shred_installation_disk} = [nN] ]] then say in yellow 'Disk shredding skipped' echo elif [[ ${shred_installation_disk} = [yY] || ${shred_boot} = [yY] ]] then say in blue "Select an overwrite source:" cat <<- menu 1) zero 2) urandom menu until [[ ${disk_destroyer} = [12] ]] do ask for disk_destroyer in press ':' if ! [[ ${disk_destroyer} = [12] ]] then echo say as warning 'Invalid selection, type an option from 1 to 2' fi done echo case ${disk_destroyer} in 1) dd=zero ;; 2) dd=urandom ;; esac fi say as heading "Shredding drives" cat <<- warning $(tput setab 1)#############################################################$(tput sgr0) $(tput setab 1)#############################################################$(tput sgr0) $(tput setab 1) $(tput sgr0) $(tput setab 1) $(tput blink)!!!WARNING!!! $(tput sgr0) $(tput setab 1) !!!ALL DATA WILL BE WIPED FROM THE DRIVE!!! $(tput sgr0) $(tput setab 1) $(tput sgr0) $(tput setab 1)#############################################################$(tput sgr0) $(tput setab 1)#############################################################$(tput sgr0) ## This will overwrite the drive by one pass ## Confirm by typing "Y" to proceed ## Anything other than capital Y will abort the installation warning ask for begin_install in press ':' if ! [[ ${begin_install} = Y ]] then echo say as warning 'Aborting installation' say as warning 'Installer script stopped' echo exit 1 fi fi # Skip disk shredding # Unmount if mounted for mounted in /mnt/boot /mnt do if mount | grep -q -w ${mounted} then umount -R ${mounted} || die 'A drive or partition is already mounted on /mnt, unable to proceed' fi done # Export existing zpools zpool export -a >/dev/null 2>&1 # Close opened luks containers for mounted in cryptroot cryptboot do if dmsetup ls | grep -q -w ${mounted} then cryptsetup close "/dev/mapper/${mounted}" || die 'LUKS container already exists, unable to proceed' fi done # Grab disk-id of ${drive} installation_disk=$(ls -l /dev/disk/by-id/* | grep "${drive}$" | grep 'wwn\|nvme-uuid\|nvme-nvme\|nvme-eui\|QEMU\|virtio\|VBOX' | awk '{print $9}' | head -1) external_boot=$(ls -l /dev/disk/by-id/* | grep "${external_drive}$" | grep 'wwn\|nvme-uuid\|nvme-nvme\|nvme-eui\|QEMU\|virtio\|VBOX\|usb' | awk '{print $9}' | head -1) # Exit if disk IDs are null if [ -z ${installation_disk} ] then die 'Ensure installation disk has a valid ID' fi if [ ${external_drive} ] && [ -z ${external_boot} ] then die 'Ensure external boot disk has a valid ID' fi echo # Shred installation drive blkdiscard --quiet --force --secure ${installation_disk} >/dev/null 2>&1 wipefs --quiet --force --all ${installation_disk} if [[ ${shred_installation_disk} = [yY] ]] then say as heading "Shredding ${installation_disk}" dd if=/dev/${dd} of=${installation_disk} bs=4M status=progress echo 3 >/proc/sys/vm/drop_caches echo fi # Shred external boot drive if [[ ${arch} = [67] ]] then wipefs --quiet --force --all ${external_boot} fi if [[ ${shred_boot} = [yY] ]] then say as heading "Shredding ${external_boot}" dd if=/dev/${dd} of=${external_boot} bs=4M status=progress echo 3 >/proc/sys/vm/drop_caches say as success 'Device shredded' echo fi case ${arch} in 1) # Basic setup parted --script --align=optimal ${installation_disk} \ mklabel gpt \ mkpart boot 1MiB 300MiB \ mkpart root 300MiB 100% \ set 1 esp on partprobe ${installation_disk} export {efivol,bootvol}=${installation_disk}-part1 export {rootpart,rootvol}=${installation_disk}-part2 configureVolumes pacstrapGenfstab echo say as heading 'Configuring efistub' efibootmgr --disk ${installation_disk} --part 1 --create --label "Arch Linux (${hostname})" --loader "/vmlinuz-${linux_kernel}" --unicode "initrd=\initramfs-${linux_kernel}.img root=${rootpart} ${rootflags}" ;; 2|4) # FDE with EFISTUB parted --script --align=optimal ${installation_disk} \ mklabel gpt \ mkpart boot 1MiB 300MiB \ mkpart root 300MiB 100% \ set 1 esp on partprobe ${installation_disk} export {efivol,bootvol}=${installation_disk}-part1 rootpart=${installation_disk}-part2 rootvol=/dev/mapper/cryptroot case ${arch} in 2) case ${filesystem} in zfs) zpoolencryption="-O encryption=aes-256-gcm \ -O keyformat=passphrase \ -O keylocation=prompt" rootvol=${installation_disk}-part2 ;; *) encryptRootLuks ;; esac ;; 4) encryptRootYubikey ;; esac configureVolumes pacstrapGenfstab case ${arch} in 2) echo say as heading 'Configuring efistub' case ${filesystem} in zfs) efibootmgr --disk ${installation_disk} --part 1 --create --label "Arch Linux (${hostname})" --loader "/vmlinuz-${linux_kernel}" --unicode "initrd=\initramfs-${linux_kernel}.img ${rootflags}" ;; btrfs|ext4) encryptHook encrypt efibootmgr --disk ${installation_disk} --part 1 --create --label "Arch Linux (${hostname})" --loader "/vmlinuz-${linux_kernel}" --unicode "initrd=\initramfs-${linux_kernel}.img cryptdevice=${rootpart}:cryptroot root=/dev/mapper/cryptroot ${rootflags}" ;; esac ;; 4) chrootYubikey echo say as heading 'Configuring efistub' efibootmgr --disk ${installation_disk} --part 1 --create --label "Arch Linux (${hostname})" --loader "/vmlinuz-${linux_kernel}" --unicode "initrd=\initramfs-${linux_kernel}.img cryptdevice=${rootpart}:cryptroot root=/dev/mapper/cryptroot ${rootflags}" ;; esac ;; 3|5) # FDE with Secured GRUB parted --script --align=optimal ${installation_disk} \ mklabel gpt \ mkpart efi 1MiB 40MiB \ mkpart boot 40MiB 300MiB \ mkpart root 300MiB 100% \ set 1 esp on partprobe ${installation_disk} efivol=${installation_disk}-part1 bootpart=${installation_disk}-part2 rootpart=${installation_disk}-part3 rootvol=/dev/mapper/cryptroot bootvol=/dev/mapper/cryptboot case ${arch} in 3) encryptRootLuks ;; 5) encryptRootYubikey ;; esac encryptBootLuks configureVolumes generateKeyfile pacstrapGenfstab case ${arch} in 3) encryptHook encrypt if [[ ${decryptroot} = [yY] ]] then echo "FILES=(/crypto_keyfile.bin)" >/mnt/etc/mkinitcpio.conf.d/zz-files.conf fi ;; 5) chrootYubikey echo "MODULES=(loop)" >/mnt/etc/mkinitcpio.conf.d/zz-modules.conf ;; esac sed "/^GRUB_CMDLINE_LINUX_DEFAULT/c GRUB_CMDLINE_LINUX_DEFAULT=\"cryptdevice=${rootpart}:cryptroot root=/dev/mapper/cryptroot ${rootflags}\"" \ -i /mnt/etc/default/grub grep -q "${rootpart}" /mnt/etc/default/grub || die 'Grub cfg generation failed' arch-chroot /mnt /usr/bin/bash <<- grub configureGrub grub ;; 6|7) # FDE with detached luks header parted --script --align=optimal ${external_boot} \ mklabel gpt \ mkpart efi 1MiB 40MiB \ mkpart boot 40MiB 300MiB \ mkpart none 300MiB 100% \ set 1 esp on partprobe ${external_boot} rootpart=${installation_disk} efivol=${external_boot}-part1 bootpart=${external_boot}-part2 rootvol=/dev/mapper/cryptroot bootvol=/dev/mapper/cryptboot luks_header=--header=header.img case ${arch} in 6) encryptRootLuks ;; 7) encryptRootYubikey ;; esac encryptBootLuks configureVolumes mv header.img /mnt/boot generateKeyfile pacstrapGenfstab # Mkinitcpio files cpiofiles=(/boot/header.img) case ${arch} in 6) sed -e '/for cryptopt in/i\\n local headerFlag=false' \ -e '/case ${cryptopt} in/a\ header)\ cryptargs="${cryptargs} --header /boot/header.img"\ headerFlag=true\ ;;' \ -e 's/cryptsetup isLuks/$headerFlag || &/' \ -i /mnt/usr/lib/initcpio/hooks/encrypt encryptHook encrypt # encrypt hook install /dev/stdin /mnt/opt/local/hooks/encrypt-initcpio <<- 'hook' #!/usr/bin/env bash if [ -f /usr/lib/initcpio/hooks/encrypt.pacnew ] then sed -e '/for cryptopt in/i\\n local headerFlag=false' \ -e '/case ${cryptopt} in/a\ header)\ cryptargs="${cryptargs} --header /boot/header.img"\ headerFlag=true\ ;;' \ -e 's/cryptsetup isLuks/$headerFlag || &/' \ -i /usr/lib/initcpio/hooks/encrypt.pacnew mv /usr/lib/initcpio/hooks/encrypt.pacnew /usr/lib/initcpio/hooks/encrypt fi hook cat >/mnt/etc/pacman.d/hooks/85-encrypt.hook <<- encrypt [Trigger] Operation = Install Operation = Upgrade Type = Package Target = cryptsetup [Action] Description = Fixing encrypt hook for detached headers When = PostTransaction Exec = /opt/local/hooks/encrypt-initcpio encrypt if [[ ${decryptroot} = [yY] ]] then cpiofiles+=(/crypto_keyfile.bin) fi ;; 7) chrootYubikey sed '/YKFDE_LUKS_OPTIONS/c YKFDE_LUKS_OPTIONS="--header=/boot/header.img"' -i /mnt/etc/ykfde.conf ;; esac # mkinitcpio.conf echo "FILES=(${cpiofiles[@]})" >/mnt/etc/mkinitcpio.conf.d/zz-files.conf echo "MODULES=(ext2 loop)" >/mnt/etc/mkinitcpio.conf.d/zz-modules.conf sed "/^GRUB_CMDLINE_LINUX_DEFAULT/c GRUB_CMDLINE_LINUX_DEFAULT=\"cryptdevice=${rootpart}:cryptroot:header root=/dev/mapper/cryptroot ${rootflags}\"" \ -i /mnt/etc/default/grub grep -q "${rootpart}" /mnt/etc/default/grub || die 'Grub cfg generation failed' arch-chroot /mnt /usr/bin/bash <<- grub configureGrub grub ;; esac unset lukspass grubpass # Re-activate mkinitcpio rm -f /mnt/etc/pacman.d/hooks/90-mkinitcpio-install.hook sed -e "s|%PKGBASE%|${linux_kernel}|g" \ -e "s/^fallback/#&/g" \ -e "s/ 'fallback'//" \ /mnt/usr/share/mkinitcpio/hook.preset >/mnt/etc/mkinitcpio.d/${linux_kernel}.preset rsync -a /mnt/usr/lib/modules/*/vmlinuz /mnt/boot/vmlinuz-${linux_kernel} # Copy some networking files ln -s -f /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf rsync -a /etc/systemd/network/20-ethernet.network /mnt/etc/systemd/network/ if [ ${iwd} ] then rsync -a /usr/local/bin/iwd-connect /mnt/opt/local/bin/ chmod +x /mnt/opt/local/bin/iwd-connect mkdir -p /mnt/var/lib/iwd /mnt/etc/iwd rsync -a /var/lib/iwd/*.psk /mnt/var/lib/iwd/ # sed -i '/^Passphrase=/d' /mnt/var/lib/iwd/*.psk cat >/mnt/etc/iwd/main.conf <<- iwd [General] EnableNetworkConfiguration=true iwd fi # chroot setup arch-chroot /mnt /usr/bin/bash <<- chroot || die "chroot setup failed" curl --fail --silent https://git.myvelabs.com/lab/archlinux/raw/branch/master/functions/chroot -o /tmp/chroot /usr/bin/bash /tmp/chroot chroot # System services arch-chroot /mnt systemctl enable ${systemd_services[@]} || die "Unable to start systemd services" # SSH Authentication if [ -f sshkeys ] then cat sshkeys >/mnt/home/${username}/.ssh/authorized_keys fi # DE installer install /dev/stdin /mnt/opt/local/bin/desktop <<- desktop #!/usr/bin/env bash set -e if curl --fail --silent https://git.myvelabs.com/lab/archlinux/raw/branch/master/functions/desktop -o /tmp/desktop then /usr/bin/bash /tmp/desktop \${@} sudo rm -f \${0} else exit 1 fi desktop # BTRFS fresh snapshot case ${filesystem} in btrfs) arch-chroot /mnt /usr/bin/bash <<- "timeshift" # Take fresh snapshots timeshift --btrfs >/dev/null timeshift --create --comments "Fresh install" >/dev/null sed \ -e '/stop_cron_emails/ s/false/true/' \ -e '/schedule_monthly/ s/false/true/' \ -e '/schedule_weekly/ s/false/true/' \ -e '/schedule_daily/ s/false/true/' \ -e '/schedule_hourly/ s/false/true/' \ -e '/schedule_boot/ s/true/false/' \ -e '/count_monthly/ s/: ".*",/: "3",/' \ -e '/count_weekly/ s/: ".*",/: "4",/' \ -e '/count_daily/ s/: ".*",/: "5",/' \ -e '/count_hourly/ s/: ".*",/: "6",/' \ -i /etc/timeshift/timeshift.json echo -e '\e[1;33mSnapshots taken\e[0m\n' timeshift ;; esac # Reboot only if script succeeded if arch-chroot /mnt uname -a | grep -q Linux then for mounted in /mnt/boot /mnt do umount -R ${mounted} done case ${filesystem} in zfs) zfs snapshot zroot/ROOT@fresh-installation zpool export -a ;; esac say as success in bold "Installer has completed and system drive has been unmounted" say as success in bold "Boot into the new system, connect to a network and install a DE by running $(tput smso)desktop$(tput rmso) in the terminal" say as success in bold "Rebooting..." echo reboot else die 'Something does not feel right' fi