#!/usr/bin/bash # vim:ft=sh:fdm=marker:fmr={,} # This program is free software, provided under the GNU GPL # Written by Nathaniel Maia for use in Archlabs # Some ideas and code were taken from other installers # AIF, ABIF, Calamares, Arch Wiki.. Credit where credit is due # set -n # set -u # set -v # immutable variables { readonly DIST="Archlabs" # Linux distributor readonly VER="1.6.33" # Installer version readonly LIVE="liveuser" # Live session user readonly TRN="/usr/share/archlabs-installer" # Translation path readonly MNT="/mnt/install" # Install mountpoint readonly ERR="/tmp/errlog" # Built-in error log # create a regex string of all usb devices on the system for dev in $(lsblk -lno NAME,TRAN | awk '/usb/ {print $1}'); do USB_DEVS="${dev}$([[ $USB_DEVS ]] && echo -n "|$USB_DEVS")"; done # determine which device was used for booting to ignore later during partition select readonly IGNORE_DEV="$(lsblk -lno NAME,TYPE,TRAN,MOUNTPOINT | awk "/$USB_DEVS/"' && /\/run\/archiso\/bootmnt/ {sub(/[1-9]/, ""); print $1}')" readonly SYS_DEVS="$(lsblk -lno NAME,SIZE,TYPE,TRAN | awk '/disk/ && !'"/$IGNORE_DEV/"' {print "/dev/" $1 " " $2}')" readonly LOCALES="$(awk '/\.UTF-8/ {gsub(/# .*|#/, ""); if($1) print $1 " -"}' /etc/locale.gen)" readonly SYS_MEM=$(grep 'MemTotal' /proc/meminfo | awk '{print int($2 / 1024)}') readonly DEV_COUNT="$(wc -l <<< "$SYS_DEVS")" readonly KBD="$(find /usr/share/kbd/keymaps -name '*.map.gz')" readonly CONSOLE_MAPS="$(awk '{gsub(/\.map\.gz|.*\//, ""); print $1 " -"}' <<< "$KBD" | sort -r)" # create associative array for SUBZONES[zone], value is: 'sub-zone country_code' declare -Ag SUBZONES for zone in America Australia Asia Atlantic Africa Europe Indian Pacific Arctic Antarctica; do SUBZONES[$zone]="$(awk "/$zone\// {gsub(/$zone\//, \"\"); print \$3 \" \"\$1}" /usr/share/zoneinfo/zone.tab)" done readonly SUBZONES # make it read only for t in st termite xterm; do hash $t >/dev/null 2>&1 && { readonly TERM_CMD="$t"; break; } done # static string of keymap codes and respective language readonly KEYMAPS="us English cm English gb English au English gh English za English ng English ca French cd French gn French tg French fr French de German at German ch German es Spanish latam Spanish br Portuguese pt Portuguese ma Arabic sy Arabic ara Arabic ua Ukrainian cz Czech ru Russian sk Slovak nl Dutch it Italian hu Hungarian cn Chinese tw Taiwanese vn Vietnamese kr Korean jp Japanese th Thai la Lao pl Polish se Swedish is Icelandic fi Finnish dk Danish be Belgian in Indian al Albanian am Armenian bd Bangla ba Bosnian bg Bulgarian dz Berber mm Burmese hr Croatian gr Greek il Hebrew ir Persian iq Iraqi af Afghani fo Faroese ge Georgian ee Estonian kg Kyrgyz kz Kazakh lt Lithuanian mt Maltese mn Mongolian no Norwegian ro Romanian rs Serbian si Slovenian tj Tajik lk Sinhala tr Turkish uz Uzbek ie Irish pk Urdu mv Dhivehi epo Esperanto np Nepali et Amharic sn Wolof ml Bambara tz Swahili ke Swahili bw Tswana ph Filipino id Indonesian my Malay tm Turkmen bt Dzongkha lv Latvian md Moldavian mao Maori by Belarusian me Montenegrin mk Macedonian kh Khmer az Azerbaijani" declare -Agr BOOT_MNTS=( [UEFI-grub]="/boot/efi" [UEFI-systemd-boot]="/boot" [BIOS-grub]="/boot" [BIOS-syslinux]="/boot" ) # static list of bootloaders & boot partition mountpoints stored as the system type (BIOS or UEFI) declare -Agr BOOTLOADERS=( [UEFI]="grub ${BOOT_MNTS[UEFI-grub]} systemd-boot ${BOOT_MNTS[UEFI-systemd-boot]}" [BIOS]="grub ${BOOT_MNTS[BIOS-grub]} syslinux ${BOOT_MNTS[BIOS-syslinux]}" ) # static mkfs commands for each filesystem offered declare -Agr FS_CMDS=( [ext2]="mkfs.ext2 -q" [ext3]="mkfs.ext3 -q" [ext4]="mkfs.ext4 -q" [f2fs]="mkfs.f2fs" [jfs]="mkfs.jfs -q" [xfs]="mkfs.xfs -f" [nilfs2]="mkfs.nilfs2 -q" [ntfs]="mkfs.ntfs -q" [reiserfs]="mkfs.reiserfs -q" [vfat]="mkfs.vfat -F32" ) # static filesystem mount options declare -Agr FS_OPTS=( [vfat]="" [ntfs]="" [ext2]="" [ext3]="" [ext4]="dealloc - off discard - off nofail - off noacl - off relatime - off noatime - off nobarrier - off nodelalloc - off" [jfs]="discard - off errors=continue - off errors=panic - off nointegrity - off" [reiserfs]="acl - off nolog - off notail - off replayonly - off user_xattr - off" [xfs]="discard - off filestreams - off ikeep - off largeio - off noalign - off nobarrier - off norecovery - off noquota - off wsync - off" [nilfs2]="discard - off nobarrier - off errors=continue - off errors=panic - off order=relaxed - off order=strict - off norecovery - off" [f2fs]="data_flush - off disable_roll_forward - off disable_ext_identify - off discard - off fastboot - off flush_merge - off inline_xattr - off inline_data - off inline_dentry - off no_heap - off noacl - off nobarrier - off noextent_cache - off noinline_data - off norecovery - off" ) # } luks_variable_init() { declare -g LUKS=0 declare -g LVM=0 declare -g VOL_GROUP_MB=0 declare -g LUKS_NAME="cryptroot" declare -g LUKS_PART="" declare -g LUKS_PASS="" declare -g LUKS_UUID="" declare -g LUKS_DEV="" declare -g MKINIT_HOOKS="shutdown" declare -g SEPERATE_BOOT=0 } initialize_variables() { # Modified during runtime and are all globally accessible # This is called once when the script is started, and again if/when an error occurs # Some of may never be used, depending on the system and choices made declare -g ROOT_PART="" declare -g BOOT_DEVICE="" declare -g BOOT_PART="" declare -g BOOTLOADER="" declare -g EXTRA_MNT="" declare -g SWAP_FILE="none" declare -g SWAP_SIZE="${SYS_MEM}M" declare -g NEWUSER="" declare -g USER_PASS="" declare -g ROOT_PASS="" declare -g LOGIN_WM="" declare -g LOGIN_TYPE="" declare -g INSTALL_WMS="" declare -g WM_PACKAGES="" declare -g EXTRA_PACKAGES="" declare -g CURRENT_MENU="main" declare -g MENU_HIGHLIGHT=0 declare -g EDITOR_CHOICE="" declare -g MIRROR_CMD="reflector --score 100 -l 50 -f 10 --sort rate" # boolean checks declare -g AUTOLOGIN=false declare -g FIRST_PREP=false declare -g FIRST_CONFIG=false declare -g UNPACKED_BASE=false declare -g SET_ROOT_PASSWD=false declare -g TIMEZONE_SET=false declare -g DONE_UPDATE=false declare -g BOOT_DONE=false declare -g FULL_DONE=false declare -g CONFIRM_DONE=false declare -g GRUB_UEFI_FALLBACK=false # Commands used to install each bootloader. # NOTE: syslinux and grub in particular can/will change during runtime declare -Ag BOOT_CMDS=( [syslinux]="syslinux-install_update -iam" [grub]="grub-install --bootloader-id=$DIST --recheck --force" [systemd-boot]="bootctl --path=/boot install" ) # files able to be reviewed when finishing install # item index [9] can change depending on which bootloader is selected declare -Ag EDIT_FILES=( [2]="/etc/X11/xorg.conf.d/00-keyboard.conf /etc/vconsole.conf /etc/default/keyboard" [3]="/etc/locale.conf /etc/default/locale" [4]="/etc/hostname /etc/hosts" [5]="/etc/sudoers" [6]="/etc/mkinitcpio.conf" [7]="/etc/fstab" [8]="/etc/crypttab" [9]="/etc/default/grub" [10]="/etc/pacman.conf" ) } ###################################################################### ## Utility and Check Functions ## ###################################################################### chroot_cmd() { arch-chroot $MNT /bin/bash -c "$1" } show_devices() { tput civis if [[ $IGNORE_DEV != "" ]]; then lsblk -o NAME,MODEL,TYPE,FSTYPE,SIZE,MOUNTPOINT | awk "!/$IGNORE_DEV/"' && /disk|part|lvm|crypt|NAME/ {print $0}' > /tmp/.devlist else lsblk -o NAME,MODEL,TYPE,FSTYPE,SIZE,MOUNTPOINT | awk '/disk|part|lvm|crypt|NAME/ {print $0}' > /tmp/.devlist fi dialog --cr-wrap --backtitle "$BT" --title " $_PrepShowDev " --textbox /tmp/.devlist 0 0 } set_debug() { set -x exec 3>| /tmp/debug-log BASH_XTRACEFD=3 if [[ $DISPLAY ]]; then if [[ $TERM_CMD == 'st' ]]; then $TERM_CMD -e tail -f /tmp/debug-log & else $TERM_CMD -e "tail -f /tmp/debug-log" & fi fi } select_language() { tput civis local lang lang=$(dialog --cr-wrap --stdout --backtitle "$DIST Installer - (x86_64)" \ --title " Select Language " --menu \ "\nLanguage - sprache - taal - språk - lingua - idioma - nyelv - língua\n" 0 0 0 \ "1" "English (en_**)" "2" "Español (es_ES)" \ "3" "Português [Brasil] (pt_BR)" "4" "Português (pt_PT)" \ "5" "Français (fr_FR)" "6" "Russkiy (ru_RU)" \ "7" "Italiano (it_IT)" "8" "Nederlands (nl_NL)" \ "9" "Magyar (hu_HU)" "10" "Chinese (zh_CN)") source $TRN/english.trans 2>/dev/null FONT="ter-i16n" case $lang in 1) LOC="en_US.UTF-8" ;; 2) source $TRN/spanish.trans 2>/dev/null && LOC="es_ES.UTF-8" ;; 3) source $TRN/p_brasil.trans 2>/dev/null && LOC="pt_BR.UTF-8" ;; 4) source $TRN/portuguese.trans 2>/dev/null && LOC="pt_PT.UTF-8" ;; 5) source $TRN/french.trans 2>/dev/null && LOC="fr_FR.UTF-8" ;; 6) source $TRN/russian.trans 2>/dev/null && LOC="ru_RU.UTF-8" FONT="LatKaCyrHeb-14" ;; 7) source $TRN/italian.trans 2>/dev/null && LOC="it_IT.UTF-8" ;; 8) source $TRN/dutch.trans 2>/dev/null && LOC="nl_NL.UTF-8" ;; 9) source $TRN/hungarian.trans 2>/dev/null && LOC="hu_HU.UTF-8" FONT="lat2-16" ;; 10) source $TRN/chinese.trans 2>/dev/null && LOC="zh_CN.UTF-8" ;; *) clear; tput cnorm pgrep -f "$TERM_CMD -e tail" && pkill -f "$TERM_CMD -e tail" exit 0 esac sed -i "s/#en_US.UTF-8/en_US.UTF-8/" /etc/locale.gen [[ $LOC != "en_US.UTF-8" ]] && sed -i "s/#${LOC}/${LOC}/" /etc/locale.gen locale-gen >/dev/null 2>&1 setfont $FONT >/dev/null 2>&1 export LANG="$LOC" } identify_system() { if grep -qi 'apple' /sys/class/dmi/id/sys_vendor; then modprobe -r -q efivars || true else modprobe -q efivarfs fi if [[ -d /sys/firmware/efi ]]; then SYS="UEFI" [[ $(mount) =~ /sys/firmware/efi/efivars ]] && mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1 else SYS="BIOS" fi readonly BT="$DIST Installer - $SYS (x86_64) - Version $VER" } check_requirements() { declare -g HAS_NETWORK=false if [[ $(whoami) != "root" ]]; then infobox "$_ErrTitle" "$_NotRoot\n$_Exit"; clear; tput cnorm pgrep -f "$TERM_CMD -e tail" && pkill -f "$TERM_CMD -e tail" exit 1 elif ! (ping -c 1 archlabslinux.com || ping -c 1 google.com || ping -c 1 bitbucket.org || ping -c 1 github.com || ping -c 1 sourceforge.net) >/dev/null 2>&1; then if [[ $(systemctl is-active NetworkManager) == "active" ]] && hash nmtui >/dev/null 2>&1;then tput civis; nmtui fi if (ping -c 1 archlabslinux.com || ping -c 1 google.com || ping -c 1 bitbucket.org || ping -c 1 github.com || ping -c 1 sourceforge.net) >/dev/null 2>&1; then HAS_NETWORK=true else infobox "$_ErrTitle" "$_NoNetwork\nYou may experience issues without one...\n" fi else HAS_NETWORK=true fi return 0 } check_for_errors() { # if the last process exited normally then we can bail (( $? == 0 )) && return 0 local command="$1" local msg="\nThe command exited abnormally: $command" # get error message from logfile and attempt to format slightly better for humans # strip any non-printable characters, escape sequences, and other known messy text local err="$(sed 's/[^[:print:]]//g; s/\[[0-9\;:]*\?m//g; s/==> //g; s/] ERROR:/]\nERROR:/g' "$ERR")" if [[ $err != "" ]]; then msgbox "$_ErrTitle" "$msg\n\nWith the following error message:\n\n$err" fi if yesno "$_ErrTitle" "$msg\n$_ErrChoice" "Wipe Install" "Manual Fix" "no"; then for d in $MNT/?*; do if ! grep -q "boot" <<< "$d"; then rm -rf "$d" 2>/dev/null fi done unmount_partitions [[ -e $ERR ]] && rm -rf $ERR initialize_variables luks_variable_init fi return 1 } check_parts_are_mounted() { [[ $(lsblk -o MOUNTPOINT) =~ "$MNT" ]] && return 0 msgbox "$_ErrTitle" "$_ErrNoMount" return 1 } check_base_unpacked() { [[ -e $MNT/etc ]] && return 0 msgbox "$_ErrTitle" "$_ErrNoBase" return 1 } getinput() { local answer answer="$(dialog --cr-wrap --max-input 63 --stdout --backtitle "$BT" --title " $1 " --inputbox "$2" 0 0 "$3")" [[ $? != 0 || $answer == "" ]] && return 1 echo "$answer" } msgbox() { tput civis dialog --cr-wrap --backtitle "$BT" --title " $1 " --msgbox "$2\n" 0 0 } infobox() { local bt="${BT:-$DIST Installer - (x86_64)}" tput civis dialog --cr-wrap --backtitle "$bt" --title " $1 " --infobox "$2\n" 0 0 sleep 2 } yesno() { tput civis if [[ $# -eq 5 && $5 == "no" ]]; then # option for default no using custom labels dialog --cr-wrap --backtitle "$BT" --defaultno --title " $1 " \ --yes-label "$3" --no-label "$4" --yesno "$2\n" 0 0 elif [[ $# -eq 4 ]]; then # option for custom labels with standard default yes dialog --cr-wrap --backtitle "$BT" --title " $1 " --yes-label "$3" \ --no-label "$4" --yesno "$2\n" 0 0 else # basic yes no without custom labels and default yes dialog --cr-wrap --backtitle "$BT" --title " $1 " --yesno "$2\n" 0 0 fi } wrap_up() { if yesno "$_CloseInst" "$1" "$2" "$3"; then unmount_partitions [[ -e $ERR ]] && rm -rf $ERR pgrep -f "$TERM_CMD -e tail" && pkill -f "$TERM_CMD -e tail" tput cnorm clear [[ $4 == "reboot" ]] && reboot exit 0 fi } ###################################################################### ## System Settings Functions ## ###################################################################### set_keymap() { local map tput civis map="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepLayout " \ --menu "$_XMapBody" 20 70 12 $KEYMAPS)" [[ $? != 0 || $map == "" ]] && return 1 cat >/tmp/00-keyboard.conf </tmp/keyboard </dev/null 2>&1 echo -e "KEYMAP=$map\nFONT=$FONT" >/tmp/vconsole.conf return 0 } set_locale() { local locale tput civis locale="$(dialog --cr-wrap --stdout --no-cancel --backtitle "$BT" \ --title "$_ConfLocale" --menu "$_LocaleBody" 25 70 12 $LOCALES)" [[ $? != 0 || $locale == "" ]] && return 1 infobox "$_ConfLocale" "$_GenLocale $locale\n" sed -i "s/#en_US.UTF-8/en_US.UTF-8/g; s/#${locale}/${locale}/g" $MNT/etc/locale.gen sed -i "s/en_US.UTF-8/${locale}/g" $MNT/etc/locale.conf cp -f $MNT/etc/locale.conf $MNT/etc/default/locale chroot_cmd "locale-gen" >/dev/null 2>$ERR check_for_errors 'locale-gen' || return 1 set_timezone } set_timezone() { tput civis local zone zone="$(dialog --cr-wrap --stdout --no-cancel --backtitle "$BT" \ --title " $_TimeZTitle " --menu "$_TimeZBody" 20 70 10 America - Australia - \ Asia - Atlantic - Africa - Europe - Indian - Pacific - Arctic - Antarctica -)" local subzone subzone="$(dialog --cr-wrap --stdout --no-cancel --backtitle "$BT" \ --title " $_TimeZTitle " --menu "$_TimeSubZBody" 20 70 12 ${SUBZONES[$zone]})" if yesno "$_TimeZTitle" "$_TimeZQ $zone/$subzone?\n"; then chroot_cmd "ln -sf /usr/share/zoneinfo/$zone/$subzone /etc/localtime" 2>$ERR check_for_errors "ln -sf /usr/share/zoneinfo/$zone/$subzone /etc/localtime" || return 1 else set_timezone || return 1 fi TIMEZONE_SET=true set_hwclock } set_hwclock() { chroot_cmd "hwclock --systohc --utc" if [[ $? != 0 ]]; then chroot_cmd "hwclock --systohc --utc --directisa" [[ $? != 0 ]] && infobox "$_ErrTitle" "\nHwclock setup attempts failed.\n\nContinuing anyway.\n" fi return 0 } set_hostname() { tput cnorm local hostname hostname="$(getinput "$_ConfHost" "$_HostNameBody" "${DIST,,}")" [[ $? != 0 || $hostname == "" ]] && return 1 echo "$hostname" > $MNT/etc/hostname cat > $MNT/etc/hosts << EOF 127.0.0.1 localhost 127.0.1.1 $hostname ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOF } user_input_values() { local user="$1" tput cnorm local values if [[ $SET_ROOT_PASSWD != true ]]; then values="$(dialog --stdout --no-cancel --separator '~' --ok-label "Submit" --backtitle "$BT" \ --title " $_UserTitle " --insecure --mixedform "$_UserBody" 27 75 10 \ "$_Username" 1 1 "$user" 1 $((${#_Username} + 2)) 71 0 0 \ "$_Password" 2 1 "" 2 $((${#_Password} + 2)) 71 0 1 \ "$_Password2" 3 1 "" 3 $((${#_Password2} + 2)) 71 0 1 \ "$_RootBody" 6 1 "" 6 $((${#_RootBody} + 1)) 71 0 2 \ "$_Password" 8 1 "" 8 $((${#_Password} + 2)) 71 0 1 \ "$_Password2" 9 1 "" 9 $((${#_Password2} + 2)) 71 0 1)" else values="$(dialog --stdout --no-cancel --separator '~' --ok-label "Submit" --backtitle "$BT" \ --title " $_UserTitle " --insecure --mixedform "$_UserBody" 20 75 4 \ "$_Username" 1 1 "$user" 1 $((${#_Username} + 2)) 71 0 0 \ "$_Password" 2 1 "" 2 $((${#_Password} + 2)) 71 0 1 \ "$_Password2" 3 1 "" 3 $((${#_Password2} + 2)) 71 0 1)" fi [[ $? != 0 || $values == "" ]] && return 1 user="$(awk -F'~' '{print $1}' <<< "$values")" local pass pass2 pass="$(awk -F'~' '{print $2}' <<< "$values")" pass2="$(awk -F'~' '{print $3}' <<< "$values")" # if the root password has not yet been set local rpass rpass2 if [[ $SET_ROOT_PASSWD != true ]]; then rpass="$(awk -F'~' '{print $5}' <<< "$values")" rpass2="$(awk -F'~' '{print $6}' <<< "$values")" # when both passwords match but are empty # resort to using the user passwords instead if [[ $rpass == "" && $rpass2 == "" ]]; then rpass="$pass" rpass2="$pass2" fi fi # make sure we dont have anything wrong and that the passwords match if [[ ${#user} -eq 0 || $user =~ \ |\' || $user =~ [^a-z0-9\ ] || $pass == "" || "$pass" != "$pass2" || "$rpass" != "$rpass2" ]]; then tput civis # passwords don't match if [[ $pass == "" || "$pass" != "$pass2" || "$rpass" != "$rpass2" ]]; then if [[ "$rpass" != "$rpass2" ]]; then msgbox "$_ErrTitle" "$_RootPassErr\n$_TryAgain" else msgbox "$_ErrTitle" "$_UserPassErr\n$_TryAgain" fi else # bad username msgbox "$_UserErrTitle" "$_UserErrBody" user="" fi # recursively loop back unless the user chooses cancel, then bail user_input_values "$user" || return 1 else NEWUSER="$user" USER_PASS="$pass" [[ $SET_ROOT_PASSWD != true ]] && ROOT_PASS="$rpass" fi return 0 } create_user() { if [[ $AUTOLOGIN == true && $LOGIN_TYPE == 'lightdm' ]]; then local groups="rfkill,wheel,autologin,nopasswdlogin,network,lp,storage,power,video,audio,lp" else local groups="rfkill,wheel,autologin,network,lp,storage,power,video,audio,lp" fi NEWUSER="" user_input_values "$NEWUSER" || return 1 if [[ $SET_ROOT_PASSWD != true ]]; then infobox "$_ConfRoot" "\nSetting root password\n" chroot_cmd "echo 'root:$ROOT_PASS' | chpasswd" 2>$ERR check_for_errors "chpasswd root" || return 1 SET_ROOT_PASSWD=true fi infobox "$_ConfUser" "$_UserSetBody" if [[ -e $MNT/home/$LIVE ]]; then # for first user created, swap the live user account swap_livuser chroot_cmd "mv -f /home/$LIVE /home/$NEWUSER" 2>$ERR check_for_errors "mv -f /home/$LIVE /home/$NEWUSER" || return 1 chroot_cmd "usermod -aG $groups $NEWUSER" 2>$ERR check_for_errors "usermod -aG $groups $NEWUSER" || return 1 else # create new user account chroot_cmd "useradd $NEWUSER -m -g users -G $groups -s /bin/zsh" 2>$ERR check_for_errors "useradd $NEWUSER -m -g users -G $groups -s /bin/zsh" || return 1 chroot_cmd "cp -rf /etc/skel/ /home/$NEWUSER" 2>$ERR check_for_errors "cp -rf /etc/skel/ /home/$NEWUSER" || return 1 fi chroot_cmd "echo '$NEWUSER:$USER_PASS' | chpasswd" 2>$ERR check_for_errors "chpasswd $NEWUSER" || return 1 chroot_cmd "chown -Rf $NEWUSER:users /home/$NEWUSER" 2>$ERR check_for_errors "chown -Rf $NEWUSER:users /home/$NEWUSER" || return 1 if [[ $AUTOLOGIN == true && $LOGIN_TYPE == 'lightdm' ]]; then sed -i "/#autologin-user=/ c autologin-user=${NEWUSER}" $MNT/etc/lightdm/lightdm.conf fi fix_home_files if [[ $UNPACKED_BASE == true && $TIMEZONE_SET == true ]]; then [[ $SET_ROOT_PASSWD == true && $BOOT_DONE == true ]] && FULL_DONE=true fi return 0 } swap_livuser() { # edit the required files in /etc/ to swap the liveuser account name sed -i "s/${LIVE}/${NEWUSER}/g" $MNT/etc/{group,gshadow,passwd,shadow} if [[ $AUTOLOGIN != true || $LOGIN_TYPE == 'lightdm' ]]; then rm -rf $MNT/etc/systemd/system/getty@tty1.service.d >/dev/null 2>&1 elif [[ $AUTOLOGIN == true && $LOGIN_TYPE != 'lightdm' ]]; then sed -i "s/${LIVE}/${NEWUSER}/g" $MNT/etc/systemd/system/getty@tty1.service.d/autologin.conf fi return 0 } fix_home_files() { local user_home="$MNT/home/$NEWUSER" sed -i "s/${LIVE}/${NEWUSER}/g" $user_home/.config/{openbox/autostart,gtk-3.0/bookmarks} \ $user_home/.mozilla/firefox/{archlabs.default/prefs.js,archlabs.default/sessionstore.js} if [[ $AUTOLOGIN == true ]]; then if [[ $LOGIN_TYPE == 'lightdm' ]]; then rm -rf $user_home/.{zprofile,xinitrc} else sed -i "s/:-openbox/:-${LOGIN_WM}/g" $user_home/.xinitrc sed -i '/archlabs-installer/d' $user_home/.zprofile echo '[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && exec startx -- vt1 &>/dev/null' >> $user_home/.zprofile fi else rm -rf $user_home/.zprofile sed -i "s/:-openbox/:-${LOGIN_WM}/g" $user_home/.xinitrc fi return 0 } choose_window_manager() { LOGIN_WM="" LOGIN_TYPE="" WM_PACKAGES="" INSTALL_WMS="$(dialog --cr-wrap --stdout --backtitle "$BT" --no-cancel \ --title " $_WMChoice " --checklist "$_WMChoiceBody\n" 0 0 0 \ "openbox" "A lightweight, powerful, and highly configurable stacking window manager" off \ "bspwm" "A tiling window manager that represents windows as the leaves of a binary tree" off \ "i3-gaps" "A fork of i3 window manager with more features including gaps" off \ "xfce4" "A lightweight and modular desktop environment based on GTK+ 2 and 3" off)" INSTALL_WMS="${INSTALL_WMS:-openbox}" WM_NUM=$(awk '{print NF}' <<< "$INSTALL_WMS") for wm in $INSTALL_WMS; do WM_LOGIN_CHOICES="${WM_LOGIN_CHOICES}$wm - " case $wm in openbox) WM_PACKAGES="$WM_PACKAGES $wm obconf archlabs-ob-autostart archlabs-obkey archlabs-kickshaw" ;; bspwm) WM_PACKAGES="$WM_PACKAGES $wm sxhkd lxappearance" ;; i3-gaps) WM_PACKAGES="$WM_PACKAGES $wm i3status perl-anyevent-i3 lxappearance" ;; xfce4) WM_PACKAGES="$WM_PACKAGES $wm xfce4-goodies xfce4-pulseaudio-plugin" ;; esac done if yesno "$_WMLogin" "$_LoginTypeBody\n" "xinit" "lightdm"; then LOGIN_TYPE="xinit" else LOGIN_TYPE="lightdm" fi if yesno "$_WMLogin" "$_AutoLoginBody\n"; then AUTOLOGIN=true else AUTOLOGIN=false fi if [[ $WM_NUM -eq 1 ]]; then LOGIN_WM="$INSTALL_WMS" else LOGIN_WM="$(dialog --cr-wrap --stdout --no-cancel --backtitle "$BT" \ --title " $_WMLogin " --menu "$_WMLoginBody" 0 0 0 $WM_LOGIN_CHOICES)" [[ $LOGIN_WM == "" ]] && LOGIN_WM="$(awk '{print $1}' <<< "$INSTALL_WMS")" fi [[ $LOGIN_WM == 'i3-gaps' ]] && LOGIN_WM="i3" return 0 } ###################################################################### ## System Partitioning Functions ## ###################################################################### decrease_part_count() { # remove a partition from the dialog list and decrement the number partitions left local p="$1" PARTS="$(sed "s~${p} [0-9]*[G-M]~~; s~${p} [0-9]*\.[0-9]*[G-M]~~" <<< "$PARTS")" (( COUNT > 0 )) && (( COUNT-- )) return 0 } wipe_device() { local device="$1" if yesno "$_PartWipe" "$_PartBody1 $device $_PartWipeBody2"; then tput civis wipe -Ifre "$device" | dialog --cr-wrap --backtitle "$BT" --title " $_PartWipe " \ --progressbox "\nSecure wiping $device\n" 18 70 else create_partitions "$device" fi } mount_partition() { local part="$1" local mount="$2" local fs="$(lsblk -lno FSTYPE $part)" mkdir -p "${MNT}$mount" if [[ ${FS_OPTS[$fs]} != "" && $part != "$BOOT_PART" ]] && select_mount_opts "$part" "$fs"; then mount -o $MNT_OPTS "$part" "${MNT}$mount" 2>$ERR check_for_errors "mount -o $MNT_OPTS $part ${MNT}$mount" || return 1 else mount "$part" "${MNT}$mount" 2>$ERR check_for_errors "mount $part ${MNT}$mount" || return 1 fi confirm_mount "$part" "${MNT}$mount" || return 1 check_part_is_crypt_or_lvm "$part" return 0 } unmount_partitions() { swapoff -a for i in $(mount | grep "$MNT" | awk '{print $3}' | sort -r); do umount -r "$i" >/dev/null 2>&1 done } confirm_mount() { local part="$1" local mount="$2" if [[ "$mount" == "$MNT" ]]; then local msg="Partition: $part\nMountpoint: / (root)" else local msg="Partition: $part\nMountpoint: ${mount#$MNT}" fi # partition failed to mount properly if ! grep -q "$mount" <<< "$(mount)"; then infobox "$_MntTitle" "$_MntFail\n$msg\n" return 1 fi # mount was successful infobox "$_MntTitle" "$_MntSucc\n$msg\n" decrease_part_count "$part" return 0 } find_partitions() { local str="$1" local err='NONE' # string of partitions as /TYPE/PART SIZE if [[ $IGNORE_DEV != "" ]]; then PARTS="$(lsblk -lno TYPE,NAME,SIZE | awk "/$str/"' && !'"/$IGNORE_DEV/"' {sub(/^part/, "/dev/"); sub(/^lvm|^crypt/, "/dev/mapper/"); print $1$2 " " $3}')" else PARTS="$(lsblk -lno TYPE,NAME,SIZE | awk "/$str/"' {sub(/^part/, "/dev/"); sub(/^lvm|^crypt/, "/dev/mapper/"); print $1$2 " " $3}')" fi # number of partitions total COUNT=$(wc -l <<< "$PARTS") # ensure we have enough partitions for the system and action type case $str in 'part|lvm|crypt') [[ $COUNT -eq 0 || ($SYS == 'UEFI' && $COUNT -lt 2) ]] && err="$_PartErrBody" ;; 'part|crypt') (( COUNT == 0 )) && err="$_LvmPartErrBody" ;; 'part|lvm') (( COUNT < 2 )) && err="$_LuksPartErrBody" ;; esac # if an error was found with the partition setup and there aren't enough partitions # show the appropriate error message for the given $str types were using if [[ $err != 'NONE' ]]; then msgbox "$_ErrTitle" "$err" if select_device 'root'; then create_partitions "$DEVICE" else return 1 fi fi return 0 } auto_partition() { local device="$1" # confirm or bail yesno "$_PrepParts" "$_PartBody1 $device $_PartBody2" || return 0 infobox "$_PrepParts" "\nAuto partitioning device: $device\n" swapoff -a # make sure swap is disabled # partition number of each partition on the device in reverse order local dev_info="$(parted -s $device print)" for i in $(awk '/^ [1-9][0-9]?/ {print $1}' <<< "$dev_info" | sort -r); do parted -s $device rm $i 2>$ERR check_for_errors "parted -s $device rm $i" || { break; return 1; } done # make sure we have the correct device table for the system type, gpt or msdos local table="$(awk '/Table:/ {print $3}' <<< "$dev_info")" if [[ $SYS == BIOS ]]; then local newtable="msdos" else local newtable="gpt" fi # if the current device table isn't correct run mklabel if [[ $table != "$newtable" ]]; then parted -s $device mklabel $newtable 2>$ERR check_for_errors "parted -s $device mklabel $newtable" || return 1 fi local part_num=1 BOOT_PART="${device}$part_num" if [[ $SYS == "BIOS" ]]; then parted -s $device mkpart primary ext4 1MiB 513MiB 2>$ERR check_for_errors "parted -s $device mkpart primary ext4 1MiB 513MiB" || { BOOT_PART=""; return 1; } mkfs.ext4 -q $BOOT_PART >/dev/null 2>$ERR check_for_errors "mkfs.ext4 -q $BOOT_PART" || { BOOT_PART=""; return 1; } else parted -s $device mkpart ESP fat32 1MiB 513MiB 2>$ERR check_for_errors "parted -s $device mkpart ESP fat32 1MiB 513MiB" || { BOOT_PART=""; return 1; } mkfs.vfat -F32 $BOOT_PART >/dev/null 2>$ERR check_for_errors "mkfs.vfat -F32 $BOOT_PART" || { BOOT_PART=""; return 1; } parted -s $device set 1 esp on 2>$ERR check_for_errors "parted -s $device set 1 esp on" || { BOOT_PART=""; return 1; } fi parted -s $device set 1 boot on 2>$ERR check_for_errors "parted -s $device set 1 boot on" || return 1 BOOT_DEVICE="$device" # only grub on BIOS systems uses this ROOT_PART="${device}$((part_num + 1))" parted -s $device mkpart primary ext4 514MiB 100% 2>$ERR check_for_errors "parted -s $device mkpart primary ext4 514MiB 100%" || return 1 mkfs.ext4 -q $ROOT_PART >/dev/null 2>$ERR check_for_errors "mkfs.ext4 -q $ROOT_PART" || { ROOT_PART=""; return 1; } tput civis sleep 0.5 echo -e "\nAuto partitioning complete.\n" > /tmp/.devlist lsblk $device -o NAME,MODEL,TYPE,FSTYPE,SIZE >> /tmp/.devlist dialog --cr-wrap --backtitle "$BT" --title " $_PrepParts " --textbox /tmp/.devlist 0 0 return 0 } create_partitions() { local device="$1" local choice tput civis if hash gparted >/dev/null 2>&1 && [[ $DISPLAY ]]; then choice="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PartTitle " \ --menu "$_PartBody" 0 0 0 "$_PartAuto" "BIOS & UEFI" \ "gparted" "BIOS & UEFI" "cfdisk" "BIOS & UEFI" "parted" "BIOS & UEFI" "$_PartWipe" "BIOS & UEFI")" else choice="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PartTitle " \ --menu "$_PartBody" 0 0 0 "$_PartAuto" "BIOS & UEFI" \ "cfdisk" "BIOS & UEFI" "parted" "BIOS & UEFI" "$_PartWipe" "BIOS & UEFI")" fi [[ $? != 0 || $choice == "" ]] && return 1 || clear if [[ $choice != "$_PartWipe" && $choice != "$_PartAuto" ]]; then $choice $device elif [[ $choice == "$_PartWipe" ]]; then wipe_device $device && create_partitions $device else auto_partition $device || { initialize_variables; return 1; } fi } get_swap_size() { tput cnorm SWAP_SIZE="$(getinput "$_SelSwpSetup" "$_SelSwpSize" "$SWAP_SIZE")" [[ $? != 0 || $SWAP_SIZE == "" ]] && return 1 # size is incorrect if ! [[ ${SWAP_SIZE:0:1} =~ [1-9] && ${SWAP_SIZE: -1} =~ (M|G) ]]; then msgbox "$_SelSwpSetup Error" "\n$_SelSwpErr $SWAP_SIZE\n" SWAP_SIZE="${SYS_MEM}M" get_swap_size || return 1 fi return 0 } select_swap() { # Ask user to select partition or create swapfile SWAP="$(dialog --backtitle "$BT" --cr-wrap --stdout --title " $_SelSwpSetup " \ --menu "$_SelSwpBody" 0 0 0 "$_SelSwpNone" "-" "$_SelSwpFile" "-" $PARTS)" [[ $? != 0 || $SWAP == "$_SelSwpNone" ]] && return 0 if [[ $SWAP == "$_SelSwpFile" ]]; then get_swap_size || return 1 fallocate -l $SWAP_SIZE $MNT/swapfile 2>$ERR check_for_errors "fallocate -l $size $MNT/swapfile" || return 1 chmod 600 $MNT/swapfile 2>$ERR check_for_errors "chmod 600 $MNT/swapfile" || return 1 mkswap $MNT/swapfile >/dev/null 2>$ERR check_for_errors "mkswap $MNT/swapfile" || return 1 swapon $MNT/swapfile >/dev/null 2>$ERR check_for_errors "swapon $MNT/swapfile" || return 1 SWAP_FILE="/swapfile" else # Warn user if creating a new swap on the chosen partition if ! grep -qi "swap" <<< "$(lsblk -o FSTYPE $SWAP)"; then yesno "$_PrepMount" "\nmkswap $SWAP\n" || return 0 mkswap $SWAP >/dev/null 2>$ERR check_for_errors "mkswap $SWAP" || return 1 fi swapon $SWAP >/dev/null 2>$ERR check_for_error "swapon $SWAP" || return 1 decrease_part_count "$SWAP" SWAP_FILE="$SWAP" fi return 0 } select_device() { local msg="" [[ $1 == 'boot' ]] && msg="$_DevSelTitle for bootloader\n" if (( DEV_COUNT == 1 )); then DEVICE="$(awk '{print $1}' <<< "$SYS_DEVS")" infobox "$_DevSelTitle" "\nOnly one device available$([[ $1 == 'boot' ]] && echo -n " for bootloader"): $DEVICE\n" elif (( DEV_COUNT > 1 )); then tput civis DEVICE="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_DevSelTitle " \ --menu "${msg}$_DevSelBody" 0 0 0 $SYS_DEVS)" [[ $? != 0 || $DEVICE == "" ]] && return 1 else msgbox "$_ErrTitle" "\nNo available devices for installation to use.\n\nExiting.." pgrep -f "$TERM_CMD -e tail" && pkill -f "$TERM_CMD -e tail" tput cnorm clear exit 1 fi # if the device selected was for grub, set the BOOT_DEVICE # this is needed because grub uses the base device for BIOS, not the partition [[ $msg != "" ]] && BOOT_DEVICE="$DEVICE" return 0 } select_mount_opts() { local part="$1" local fs="$2" local title="${fs^} Mount Options" tput civis MNT_OPTS="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $title " \ --checklist "$_MntBody" 0 0 0 ${FS_OPTS[$fs]} | sed 's/ /,/g; $s/,$//')" [[ $? != 0 || $MNT_OPTS == "" ]] && return 1 if ! yesno "$title" "$_MntConfBody $MNT_OPTS\n"; then select_mount_opts "$part" "$fs" || return 1 fi return 0 } select_filesystem() { local part="$1" local cur_fs cur_fs="$(lsblk -lno FSTYPE $part)" tput civis local choice choice="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_FSTitle: $part" \ --menu "\nPartition: ${part}$([[ $cur_fs != "" ]] && echo -n "\nCurrent: ${cur_fs}")\n$_FSBody" 0 0 0 \ $([[ $cur_fs != "" ]] && echo -n "$_Skip -") \ "ext4" "${FS_CMDS[ext4]}" "ext3" "${FS_CMDS[ext3]}" \ "ext2" "${FS_CMDS[ext2]}" "vfat" "${FS_CMDS[vfat]}" \ "ntfs" "${FS_CMDS[ntfs]}" "f2fs" "${FS_CMDS[f2fs]}" \ "jfs" "${FS_CMDS[jfs]}" "nilfs2" "${FS_CMDS[nilfs2]}" \ "reiserfs" "${FS_CMDS[reiserfs]}" "xfs" "${FS_CMDS[xfs]}")" [[ $choice == "$_Skip" ]] && return 0 [[ $choice == "" ]] && return 1 if yesno "$_FSTitle" "\nFormat $part as $choice?\n"; then infobox "$_FSTitle" "\nFormatting: $part\n\nCommand: ${FS_CMDS[$choice]}\n" ${FS_CMDS[$choice]} $part >/dev/null 2>$ERR check_for_errors "${FS_CMDS[$choice]} $part" || return 1 else select_filesystem "$part" || return 1 fi return 0 } select_boot_setup() { # choose bootloader and by extension mountpoint (if needed) tput civis BOOTLOADER="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_PrepMount " --menu "$_MntBootBody" 0 0 0 ${BOOTLOADERS[$SYS]})" [[ $? != 0 || $BOOTLOADER == "" ]] && return 1 if [[ $SYS == 'BIOS' ]]; then if [[ $BOOTLOADER == "grub" && $BOOT_DEVICE == "" ]]; then # grub BIOS needs an install device eg. /dev/sda select_device 'boot' || return 1 BOOT_DEVICE="$DEVICE" else local cmd cmd="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_InstSysTitle " \ --menu "$_InstSysBody\nDefault: ${BOOT_CMDS[syslinux]}" 0 0 0 \ "syslinux-install_update -iam" "[MBR]" \ "syslinux-install_update -i" "[/]")" [[ $? != 0 || $cmd == "" ]] && return 1 BOOT_CMDS[syslinux]="$cmd" EDIT_FILES[9]="/boot/syslinux/syslinux.cfg" fi else if [[ $BOOTLOADER == 'systemd-boot' ]]; then EDIT_FILES[9]="/boot/loader/entries/archlabs.conf" else EDIT_FILES[9]="/etc/default/grub" if yesno "$_PrepMount" "$_SetBootDefault"; then GRUB_UEFI_FALLBACK=true fi fi fi # if BOOT_PART was set, mount the partition to the correct mountpoint if [[ $BOOT_PART != "" ]]; then mount_partition "$BOOT_PART" "${BOOT_MNTS[$SYS-$BOOTLOADER]}" || return 1 SEPERATE_BOOT=1 fi return 0 } select_efi_partition() { format_efi_as_vfat() { infobox "$_FSTitle" "\nFormatting $1 as vfat/fat32.\n" mkfs.vfat -F32 "$1" >/dev/null 2>$ERR check_for_errors "mkfs.vfat -F32 $1" || return 1 } if (( COUNT == 1 )); then BOOT_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")" infobox "$_PrepMount" "\nOnly one partition available for EFI: $BOOT_PART\n" else tput civis BOOT_PART="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepMount " \ --menu "$_SelUefiBody" 0 0 0 $PARTS)" [[ $? != 0 || $BOOT_PART == "" ]] && return 1 fi if grep -q 'fat' <<< "$(fsck -N "$BOOT_PART")"; then local msg="$_FormUefiBody $BOOT_PART $_FormUefiBody2" if yesno "$_PrepMount" "$msg" "Format $BOOT_PART" "Skip Formatting" "no"; then format_efi_as_vfat "$BOOT_PART" || return 1 fi else format_efi_as_vfat "$BOOT_PART" || return 1 fi return 0 } select_bios_boot_partition() { format_as_ext4() { infobox "$_FSTitle" "\nFormatting $1 as ext4.\n" mkfs.ext4 -q "$1" >/dev/null 2>$ERR check_for_errors "mkfs.ext4 -q $1" || return 1 } tput civis BOOT_PART="$(dialog --cr-wrap --stdout --backtitle "$BT" --title "$_PrepMount" \ --menu "$_SelBiosBody" 0 0 0 "$_Skip" "-" $PARTS)" if [[ $? != 0 || $BOOT_PART == "$_Skip" || $BOOT_PART == "" ]]; then BOOT_PART="" else if grep -q 'ext[34]' <<< "$(fsck -N "$BOOT_PART")"; then local msg="$_FormBiosBody $BOOT_PART $_FormUefiBody2" if yesno "$_PrepMount" "$msg" "Format $BOOT_PART" "Skip Formatting" "no"; then format_as_ext4 "$BOOT_PART" || return 1 fi else format_as_ext4 "$BOOT_PART" || return 1 fi # set BOOT_DEVICE for BIOS grub by removing digit from the end BOOT_DEVICE="${BOOT_PART%[1-9]}" PART_NUM="${BOOT_PART#$BOOT_DEVICE}" parted -s $BOOT_DEVICE set $PART_NUM boot on 2>$ERR check_for_errors "parted -s $BOOT_DEVICE set $PART_NUM boot on" || return 1 fi return 0 } select_root_partition() { if (( LUKS == 1 && LVM == 0 )); then ROOT_PART="/dev/mapper/$LUKS_NAME" decrease_part_count "$LUKS_PART" elif (( LUKS == 1 && LVM == 1 )); then decrease_part_count "$LUKS_PART" decrease_part_count "/dev/mapper/$LUKS_NAME" ROOT_PART="" fi if [[ $COUNT -eq 1 && $ROOT_PART == "" ]]; then ROOT_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")" infobox "$_PrepMount" "\nOnly one partition available for root (/): $ROOT_PART\n" elif [[ $ROOT_PART == "" || $LVM -eq 1 ]]; then tput civis ROOT_PART="$(dialog --cr-wrap --stdout --backtitle "$BT" --title "$_PrepMount" \ --menu "$_SelRootBody" 0 0 0 $PARTS)" [[ $? != 0 || $ROOT_PART == "" ]] && return 1 else infobox "$_PrepMount" "\nUsing $([[ $LUKS -eq 1 ]] && echo -n "encrypted ")root partition: $ROOT_PART\n" fi select_filesystem "$ROOT_PART" || { ROOT_PART=""; return 1; } mount_partition "$ROOT_PART" "" || { ROOT_PART=""; return 1; } return 0 } select_mountpoint() { tput cnorm EXTRA_MNT="$(getinput "$_PrepMount $part" "$_ExtPartBody1 /home /var\n" "/")" [[ $? != 0 || $EXTRA_MNT == "" ]] && return 1 # bad mountpoint if [[ ${EXTRA_MNT:0:1} != "/" || ${#EXTRA_MNT} -le 1 || $EXTRA_MNT =~ \ |\' ]]; then msgbox "$_ErrTitle" "$_ExtErrBody" select_mountpoint || return 1 fi return 0 } select_extra_partitions() { while (( COUNT > 0 )); do tput civis local part part="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepMount " \ --menu "$_ExtPartBody" 0 0 0 "$_Done" "-" $PARTS)" # cancel or done, exit normally [[ $? != 0 || $part == "$_Done" || $part == "" ]] && break # choose what filesystem and get mountpoint select_filesystem "$part" || { break; return 1; } select_mountpoint || { break; return 1; } # mount it mount_partition "$part" "$EXTRA_MNT" || { break; return 1; } # if the mountpoint was /usr add 'usr' to MKINIT_HOOKS [[ $EXTRA_MNT == "/usr" && $MKINIT_HOOKS != *usr* ]] && MKINIT_HOOKS="usr $MKINIT_HOOKS" done return 0 } select_install_partitions() { msgbox "$_PrepMount" "$_WarnMount" lvm_detect # prepare partition list PARTS for dialog unmount_partitions find_partitions 'part|lvm|crypt' || return 1 # remove boot partition from dialog list if we auto partitioned one [[ $BOOT_PART != "" ]] && decrease_part_count "$BOOT_PART" select_root_partition || return 1 if [[ $BOOT_PART == "" ]]; then if [[ $SYS == "UEFI" ]]; then select_efi_partition || { BOOT_PART=""; return 1; } elif (( COUNT > 0 )); then select_bios_boot_partition || { BOOT_PART=""; return 1; } fi else infobox "$_PrepMount" "\nUsing boot partition: $BOOT_PART\n" fi select_boot_setup || { BOOTLOADER=""; return 1; } select_swap || return 1 select_extra_partitions || return 1 return 0 } check_part_is_crypt_or_lvm() { local part="$1" local devs="$(lsblk -lno NAME,FSTYPE,TYPE)" # Identify if $part is "crypt" (LUKS on LVM, or LUKS alone) if [[ $(lsblk -lno TYPE "$part") =~ 'crypt' ]]; then LUKS=1 LUKS_NAME="${part#/dev/mapper/}" for dev in $(awk '/lvm/ && /crypto_LUKS/ {print "/dev/mapper/"$1}' <<< "$devs" | uniq); do if grep -q "$LUKS_NAME" <<< "$(lsblk -lno NAME "$dev")"; then LUKS_DEV="$LUKS_DEV cryptdevice=$dev:$LUKS_NAME" LVM=1 break fi done for dev in $(awk '/part/ && /crypto_LUKS/ {print "/dev/"$1}' <<< "$devs" | uniq); do if grep -q "$LUKS_NAME" <<< "$(lsblk -lno NAME "$dev")"; then LUKS_UUID="$(lsblk -lno UUID,TYPE,FSTYPE "$dev" | awk '/part/ && /crypto_LUKS/ {print $1}')" LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME" break fi done elif [[ $(lsblk -lno TYPE "$part") =~ 'lvm' ]]; then LVM=1 VOLUME_NAME="${part#/dev/mapper/}" for dev in $(awk '/crypt/ && /lvm2_member/ {print "/dev/mapper/"$1}' <<< "$devs" | uniq); do if grep -q "$VOLUME_NAME" <<< "$(lsblk -lno NAME "$dev")"; then LUKS_NAME="$(sed 's~/dev/mapper/~~g' <<< "$dev")" break fi done for dev in $(awk '/part/ && /crypto_LUKS/ {print "/dev/"$1}' <<< "$devs" | uniq); do if grep -q "$LUKS_NAME" <<< "$(lsblk -lno NAME "$dev")"; then LUKS_UUID="$(lsblk -lno UUID,TYPE,FSTYPE "$dev" | awk '/part/ && /crypto_LUKS/ {print $1}')" LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME" LUKS=1 break fi done fi } ###################################################################### ## Encryption (dm_crypt) Functions ## ###################################################################### luks_open() { LUKS_PART="" modprobe -a dm-mod dm_crypt unmount_partitions find_partitions 'part|crypt|lvm' tput civis if (( COUNT == 1 )); then LUKS_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")" infobox "$_LuksOpen" "\nOnly one partition available: $LUKS_PART\n" else tput civis LUKS_PART="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_LuksOpen " \ --menu "$_LuksMenuBody" 0 0 0 $PARTS)" [[ $? != 0 || $LUKS_PART == "" ]] && return 1 fi # get password and name for encryption luks_input_values "$_LuksOpen" "$LUKS_NAME" || return 1 infobox "$_LuksOpen" "$_LuksWaitBody $LUKS_NAME $_LuksWaitBody2 $LUKS_PART\n" echo "$LUKS_PASS" | cryptsetup open --type luks "$LUKS_PART" "$LUKS_NAME" 2>$ERR check_for_errors "cryptsetup open --type luks $LUKS_PART $LUKS_NAME" || return 1 LUKS=1 luks_show return 0 } luks_input_values() { local title="$1" local name="$2" LUKS_PASS="" tput cnorm local values values="$(dialog --stdout --separator '~' --ok-label "Submit" --backtitle "$BT" \ --title " $title " --insecure --mixedform "$_LuksOpenBody" 16 75 4 \ "$_Name" 1 1 "$name" 1 $((${#_Name} + 2)) 71 0 0 \ "$_Password" 2 1 "" 2 $((${#_Password} + 2)) 71 0 1 \ "$_Password2" 3 1 "" 3 $((${#_Password2} + 2)) 71 0 1)" [[ $? != 0 || $values == "" ]] && return 1 name="$(awk -F'~' '{print $1}' <<< "$values")" local pass pass2 pass="$(awk -F'~' '{print $2}' <<< "$values")" pass2="$(awk -F'~' '{print $3}' <<< "$values")" if [[ $pass == "" || "$pass" != "$pass2" ]]; then msgbox "$_ErrTitle" "$_PassErr\n$_TryAgain" luks_input_values "$title" "$name" || return 1 fi LUKS_PASS="$pass" LUKS_NAME="$name" return 0 } luks_setup() { LUKS_PART="" modprobe -a dm-mod dm_crypt unmount_partitions if [[ $ROOT_PART == "" || $LVM -eq 1 ]]; then find_partitions 'part|lvm' [[ $BOOT_PART != "" ]] && decrease_part_count "$BOOT_PART" if (( COUNT == 1 )); then LUKS_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")" infobox "$_LuksEncrypt" "\nOnly one partition available: $LUKS_PART\n" else tput civis LUKS_PART="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title "$_LuksEncrypt" --menu "$_LuksEncryptBody" 0 0 0 $PARTS)" [[ $? != 0 || $LUKS_PART == "" ]] && return 1 fi else infobox "$_PrepMount" "\nUsing root partition created in auto partitioning: $ROOT_PART\n" LUKS_PART="$ROOT_PART" fi # get password and name for encryption luks_input_values "$_LuksEncrypt" "$LUKS_NAME" || return 1 return 0 } luks_default() { luks_setup || return 1 local msg="$_LuksWaitBody $LUKS_NAME $_LuksWaitBody2 $LUKS_PART\n" infobox "$_LuksEncrypt" "$msg" echo "$LUKS_PASS" | cryptsetup -q luksFormat "$LUKS_PART" 2>$ERR check_for_errors "cryptsetup -q luksFormat $LUKS_PART" || return 1 echo "$LUKS_PASS" | cryptsetup open "$LUKS_PART" "$LUKS_NAME" 2>$ERR check_for_errors "cryptsetup open $LUKS_PART $LUKS_NAME" || return 1 LUKS=1 luks_show return 0 } luks_cipher_key() { if luks_setup; then tput cnorm local cipher cipher="$(getinput "$_PrepLUKS" "$_LuksCipherKey" "-s 512 -c aes-xts-plain64")" [[ $? != 0 || $cipher == "" ]] && return 1 infobox "$_LuksEncryptAdv" "$_LuksWaitBody $LUKS_NAME $_LuksWaitBody2 $LUKS_PART\n" echo "$LUKS_PASS" | cryptsetup -q $cipher luksFormat $LUKS_PART 2>$ERR check_for_errors "cryptsetup -q $cipher luksFormat $LUKS_PART" || return 1 echo "$LUKS_PASS" | cryptsetup open $LUKS_PART "$LUKS_NAME" 2>$ERR check_for_errors "cryptsetup open $LUKS_PART $LUKS_NAME" || return 1 luks_show return 0 fi return 1 } luks_show() { tput civis sleep 0.5 echo -e "$_LuksEncryptSucc" > /tmp/.devlist lsblk -o NAME,TYPE,FSTYPE,SIZE $LUKS_PART >> /tmp/.devlist dialog --cr-wrap --backtitle "$BT" --title " $_LuksEncrypt " --textbox /tmp/.devlist 0 0 } luks_menu() { tput civis local choice choice="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepLUKS " \ --menu "${_LuksMenuBody}${_LuksMenuBody2}${_LuksMenuBody3}" 0 0 0 \ "$_LuksEncrypt" "cryptsetup -q luksFormat" \ "$_LuksOpen" "cryptsetup open --type luks" \ "$_LuksEncryptAdv" "cryptsetup -q -s -c luksFormat" \ "$_Back" "-")" case $choice in "$_LuksEncrypt") luks_default && return 0 ;; "$_LuksOpen") luks_open && return 0 ;; "$_LuksEncryptAdv") luks_cipher_key && return 0 ;; *) return 0 esac luks_menu } luks_keyfile() { # Only used when choosing grub as bootloader. Without a keyfile, during boot # the user will be asked to enter password for decryption twice, this is annoying if [[ ! -e $MNT/crypto_keyfile.bin ]] && yesno "$_LuksKeyFileTitle" "$_LuksKeyFile"; then infobox "$_LuksKeyFileTitle" "$_LuksKeyFileCreate" local dev dev="$(lsblk -lno NAME,UUID,TYPE | awk "/$LUKS_UUID/"' && /part|crypt|lvm/ {print $1}')" dev="/dev/$dev" local keycmd keycmd="dd bs=512 count=8 if=/dev/urandom of=/crypto_keyfile.bin && chmod 000 /crypto_keyfile.bin && echo '$LUKS_PASS' | cryptsetup luksAddKey $dev /crypto_keyfile.bin" chroot_cmd "$keycmd" 2>$ERR check_for_errors "$keycmd" || return 1 sed -i 's/FILES=()/FILES=(\/crypto_keyfile.bin)/g' $MNT/etc/mkinitcpio.conf 2>$ERR check_for_errors 'sed -i "s/FILES=()/FILES=(/crypto_keyfile.bin)/g"' || return 1 chroot_cmd "mkinitcpio -p linux" 2>$ERR >/dev/null 2>&1 check_for_errors "mkinitcpio -p linux" || return 1 fi return 0 } ###################################################################### ## Logical Volume Management Functions ## ###################################################################### lvm_detect() { PHYSICAL_VOLUMES="$(pvs -o pv_name --noheading 2>/dev/null)" VOLUME_GROUP="$(vgs -o vg_name --noheading 2>/dev/null)" VOLUMES="$(lvs -o vg_name,lv_name --noheading --separator - 2>/dev/null)" if [[ $VOLUMES && $VOLUME_GROUP && $PHYSICAL_VOLUMES ]]; then infobox "$_PrepLVM" "$_LvmDetBody" modprobe dm-mod 2>$ERR check_for_errors 'modprobe dm-mod' || return 1 vgscan >/dev/null 2>&1 vgchange -ay >/dev/null 2>&1 fi } lvm_show_vg() { DEL_VG="" VOL_GROUP_LIST="" for i in $(lvs --noheadings | awk '{print $2}' | uniq); do VOL_GROUP_LIST="$VOL_GROUP_LIST $i $(vgdisplay "$i" | awk '/VG Size/ {print $3$4}')" done if [[ $VOL_GROUP_LIST == "" ]]; then msgbox "$_ErrTitle" "$_LvmVGErr" return 1 fi tput civis DEL_VG="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_PrepLVM " --menu "$_LvmSelVGBody" 18 70 10 $VOL_GROUP_LIST)" [[ $? != 0 || $DEL_VG == "" ]] && return 1 } get_lv_size() { tput cnorm local ttl=" $_LvmCreateVG (LV:$VOL_COUNT) " local msg="${VOLUME_GROUP}: ${VOL_GROUP_SIZE}$VOL_GROUP_SIZE_TYPE (${VOL_GROUP_MB}MB $_LvmLvSizeBody1).$_LvmLvSizeBody2" VOLUME_SIZE="$(getinput "$ttl" "$msg" "")" [[ $? != 0 || $VOLUME_SIZE == "" ]] && return 1 ERR_SIZE=0 # if the size is empty or 0 (( ${#VOLUME_SIZE} == 0 || ${VOLUME_SIZE:0:1} == 0 )) && ERR_SIZE=1 if (( ERR_SIZE == 0 )); then # number of characters in VOLUME_SIZE minus the last, which should be a letter local lv="$((${#VOLUME_SIZE} - 1))" # loop each character (except the last) in VOLUME_SIZE and ensure they are numbers for (( i=0; i= VOL_GROUP_MB )); then ERR_SIZE=1 else VOL_GROUP_MB=$((VOL_GROUP_MB - m)) fi ;; [Mm]) if (( ${VOLUME_SIZE:0:$lv} >= VOL_GROUP_MB )); then ERR_SIZE=1 else VOL_GROUP_MB=$((VOL_GROUP_MB - s)) fi ;; *) ERR_SIZE=1 esac fi fi fi if (( ERR_SIZE == 1 )); then msgbox "$_ErrTitle" "$_LvmLvSizeErrBody" get_lv_size || return 1 fi return 0 } lvm_volume_name() { local msg="$1" local default="volmain" (( VOL_COUNT > 1 )) && default="volextra" tput cnorm local name name="$(getinput "$_LvmCreateVG (LV:$VOL_COUNT)" "$msg" "$default")" [[ $? != 0 || $name == "" ]] && return 1 # bad volume name answer or name already in use if [[ ${name:0:1} == "/" || ${#name} -eq 0 || $name =~ \ |\' ]] || grep -q "$name" <<< "$(lsblk)"; then msgbox "$_ErrTitle" "$_LvmLvNameErrBody" lvm_volume_name "$msg" || return 1 fi VOLUME_NAME="$name" return 0 } lvm_group_name() { tput cnorm local group group="$(getinput "$_LvmCreateVG" "$_LvmNameVgBody" "VolGroup")" [[ $? != 0 || $group == "" ]] && return 1 # bad answer or group name already taken if [[ ${group:0:1} == "/" || ${#group} -eq 0 || $group =~ \ |\' ]] || grep -q "$group" <<< "$(lsblk)"; then msgbox "$_ErrTitle" "$_LvmNameVgErr" lvm_group_name || return 1 fi VOLUME_GROUP="$group" return 0 } lvm_extra_lvs() { while (( VOL_COUNT > 1 )); do # get the name and size lvm_volume_name "$_LvmLvNameBody1" || { break; return 1; } get_lv_size || { break; return 1; } # create it lvcreate -L "$VOLUME_SIZE" "$VOLUME_GROUP" -n "$VOLUME_NAME" 2>$ERR check_for_errors "lvcreate -L $VOLUME_SIZE $VOLUME_GROUP -n $VOLUME_NAME" || { break; return 1; } msgbox "$_LvmCreateVG (LV:$VOL_COUNT)" "$_Done LV $VOLUME_NAME ($VOLUME_SIZE) $_LvmPvDoneBody2." ((VOL_COUNT--)) # decrement the number of volumes chosen to end the loop done return 0 } lvm_volume_count() { VOL_COUNT=$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_LvmCreateVG " \ --radiolist "$_LvmLvNumBody1 $VOLUME_GROUP\n$_LvmLvNumBody2" 0 0 0 \ "1" "-" off "2" "-" off "3" "-" off "4" "-" off "5" "-" off "6" "-" off "7" "-" off "8" "-" off "9" "-" off) [[ $? != 0 || $VOL_COUNT == "" ]] && return 1 return 0 } lvm_create() { VOLUME_GROUP="" VOL_GROUP_PARTS="" VOL_GROUP_MB=0 unmount_partitions if [[ $LUKS -eq 1 && $LUKS_NAME != "" ]]; then VOL_GROUP_PARTS="/dev/mapper/$LUKS_NAME" infobox "$_LvmCreateVG" "\nUsing encrypted partition created earlier: $VOL_GROUP_PARTS\n" else find_partitions 'part|crypt' tput civis PARTS="$(awk 'NF > 0 {print $0 " off"}' <<< "$PARTS")" # choose partitions VOL_GROUP_PARTS="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title "$_LvmCreateVG" --checklist "$_LvmPvSelBody" 0 0 0 $PARTS)" [[ $? != 0 || $VOL_GROUP_PARTS == "" ]] && return 1 fi # get volume group name then confirm or bail out lvm_group_name || return 1 yesno "$_LvmCreateVG" "$_LvmPvConfBody1 $VOLUME_GROUP\n\n$_LvmPvConfBody2 $VOL_GROUP_PARTS\n" || return 1 # create it infobox "$_LvmCreateVG" "$_LvmPvActBody1 $VOLUME_GROUP\n" vgcreate -f "$VOLUME_GROUP" "$VOL_GROUP_PARTS" >/dev/null 2>$ERR check_for_errors "vgcreate -f $VOLUME_GROUP $VOL_GROUP_PARTS" || return 1 # get volume size and size type (MB, GB, etc..) VOL_GROUP_SIZE=$(vgdisplay "$VOLUME_GROUP" | awk '/VG Size/ {gsub(/\..*|[^0-9]*/, ""); print}') VOL_GROUP_SIZE_TYPE="$(vgdisplay "$VOLUME_GROUP" | awk '/VG Size/ {print $4}')" # transform size to MB if needed if [[ ${VOL_GROUP_SIZE_TYPE:0:1} == "G" ]]; then VOL_GROUP_MB=$((VOL_GROUP_SIZE * 1000)) else VOL_GROUP_MB=$VOL_GROUP_SIZE fi # finished volume group creation msgbox "$_LvmCreateVG" "$_LvmPvDoneBody1 $VOLUME_GROUP ($VOL_GROUP_SIZE $VOL_GROUP_SIZE_TYPE) $_LvmPvDoneBody2\n" tput civis # how many logical volumes lvm_volume_count || return 1 # if we chose more than one logical volume create all but the last lvm_extra_lvs || return 1 # last or only logical volume lvm_volume_name "$_LvmLvNameBody1 $_LvmLvNameBody2 (${VOL_GROUP_MB}MB)" || return 1 # create it lvcreate -l +100%FREE "$VOLUME_GROUP" -n "$VOLUME_NAME" 2>$ERR check_for_errors "lvcreate -l +100%FREE $VOLUME_GROUP -n $VOLUME_NAME" || return 1 LVM=1 # offer the user to see the device tree yesno "$_LvmCreateVG" "$_LvmCompBody" && show_devices return 0 } lvm_del_vg() { if lvm_show_vg; then yesno "$_LvmDelVG" "$_LvmDelQ" && vgremove -f "$DEL_VG" >/dev/null 2>&1 fi return 0 } lvm_del_all() { PHYSICAL_VOLUMES="$(pvs -o pv_name --noheading 2>/dev/null)" VOLUME_GROUP="$(vgs -o vg_name --noheading 2>/dev/null)" VOLUMES="$(lvs -o vg_name,lv_name --noheading --separator - 2>/dev/null)" if yesno "$_LvMDelAll" "$_LvmDelQ"; then for i in $VOLUMES; do lvremove -f "/dev/mapper/$i" >/dev/null 2>&1 done for i in $VOLUME_GROUP; do vgremove -f "$i" >/dev/null 2>&1 done for i in $PHYSICAL_VOLUMES; do pvremove -f "$i" >/dev/null 2>&1 done LVM=0 fi return 0 } lvm_menu() { lvm_detect tput civis local choice choice="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepLVM " \ --menu "$_LvmMenu" 0 0 0 \ "$_LvmCreateVG" "vgcreate -f, lvcreate -L -n" \ "$_LvmDelVG" "vgremove -f" \ "$_LvMDelAll" "lvrmeove, vgremove, pvremove -f" \ "$_Back" "-")" case $choice in "$_LvmCreateVG") lvm_create && return 0 ;; "$_LvmDelVG") lvm_del_vg ;; "$_LvMDelAll") lvm_del_all ;; *) return 0 esac lvm_menu } ###################################################################### ## Installation Functions ## ###################################################################### install_main() { if [[ $UNPACKED_BASE != true ]]; then # whether to use a custom mirror sorting command later update_mirrorlist_cmd || MIRROR_CMD="reflector --score 100 -l 50 -f 10 --sort rate" choose_window_manager || return 1 select_extra_packages # user can choose to bail at this point unpack_base_system || { initialize_variables; return 1; } fi genfstab -U $MNT > $MNT/etc/fstab 2>$ERR check_for_errors "genfstab -U $MNT > $MNT/etc/fstab" || return 1 if [[ -f $MNT/swapfile ]]; then sed -i "s~${MNT}~~" $MNT/etc/fstab 2>$ERR check_for_errors "sed -i s~${MNT}~~ $MNT/etc/fstab" || return 1 fi run_mkinitcpio || return 1 setup_bootloader || return 1 if [[ $HAS_NETWORK == true && $DONE_UPDATE != true ]]; then update_mirrorlist update_system DONE_UPDATE=true else chroot_cmd "pacman -Rs archlabs-installer --noconfirm" >/dev/null 2>&1 if ! grep -qi "hypervisor" <<< "$(dmesg)" && pacman -Qsq virtualbox-guest &>/dev/null; then chroot_cmd "pacman -Rns $(pacman -Qsq virtualbox-guest) --noconfirm" >/dev/null 2>&1 [[ -e $MNT/etc/xdg/autostart/vboxclient.desktop ]] && rm -f $MNT/etc/xdg/autostart/vboxclient.desktop fi fi # these also only should need to be run once, when done jump to the config edit menu if [[ $FULL_DONE != true ]]; then configure_menu if edit_config_menu; then [[ $FULL_DONE == true ]] && return 0 fi fi } unpack_base_system() { # continue or bail local msg="Boot Partition: ${BOOT_PART:-none}\nBootloader: $BOOTLOADER\nSwapfile: $SWAP_FILE" yesno "$_InstTitle" "$_BeginInst $ROOT_PART\n$msg\n\n$_ContinueYN" || return 1 # create a loading bar while copying files using find, < <(), and a loop # using awk, sed, or other means doesn't seem to work well (or at all). local files=($(find /run/archiso/sfs/airootfs/ -maxdepth 1 -mindepth 1)) local total=${#files[@]} local increment=0 tput civis dialog --cr-wrap --backtitle "$BT" --title " $_InstTitle " \ --gauge "\nUnpacking the system\n" 7 70 < <( for f in "${files[@]}"; do current_percent=$((100 * (++increment) / total)) base="${f#/run/archiso/sfs/airootfs/}" cat </dev/null | dialog --cr-wrap --backtitle "$BT" \ --title " Install Packages " --progressbox "\nInstalling packages chosen during install setup.\n\n$pkgs" 30 90 [[ $LOGIN_TYPE == 'lightdm' ]] && setup_lightdm return 0 } select_extra_packages() { if yesno "$_ExtraPackages" "$_ChooseExtraPackages"; then EXTRA_PACKAGES="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_WMChoice " --checklist "$_WMChoiceBody\n" 0 0 20 \ "firefox" "A popular open-source graphical web browser from Mozilla" off \ "chromium" "an open-source graphical web browser based on the Blink rendering engine" off \ "opera" "Fast and secure, free of charge web browser from Opera Software" off \ "qutebrowser" "A keyboard-focused vim-like web browser based on Python and PyQt5" off \ "atom" "An open-source text editor developed by GitHub that is licensed under the MIT License" off \ "geany" "A fast and lightweight IDE" off \ "emacs" "An extensible, customizable, self-documenting real-time display editor" off \ "neovim" "A fork of Vim aiming to improve user experience, plugins, and GUIs." off \ "mousepad" "A simple text editor" off \ "urxvt" "A unicode enabled rxvt-clone terminal emulator" off \ "tilix" "A tiling terminal emulator for Linux using GTK+ 3" off \ "terminator" "Terminal emulator that supports tabs and grids" off \ "tilda" "A Gtk based drop down terminal for Linux and Unix" off \ "xfce4-terminal" "A terminal emulator primarily for the XFCE desktop" off \ "termite" "A minimal VTE-based terminal emulator" off \ "pcmanfm" "A fast and lightweight file manager" off \ "gnome-disk-utility" "Disk Management Utility" off \ "gnome-system-monitor" "View current processes and monitor system state" off \ "steam steam-native-runtime" "A popular game distribution platform by Valve" off \ "vlc qt4" "a free and open source cross-platform multimedia player" off \ "audacious" "A free and advanced audio player based on GTK+" off \ "nicotine+" "A graphical client for Soulseek" off \ "lollypop" "A new music playing application" off \ "rhythmbox" "Music playback and management application" off \ "deadbeef" "A GTK+ audio player for GNU/Linux" off \ "clementine" "A modern music player and library organizer" off \ "thunderbird" "Standalone mail and news reader from mozilla" off \ "geary" "A lightweight email client for the GNOME desktop" off \ "evolution" "Manage your email, contacts and schedule" off \ "mutt" "Small but very powerful text-based mail client" off \ "deluge" "A BitTorrent client written in python" off \ "transmission-gtk" "Free BitTorrent client GTK+ GUI" off \ "qbittorrent" "An advanced BitTorrent client" off \ "hexchat" "A popular and easy to use graphical IRC client" off \ "pidgin" "Multi-protocol instant messaging client" off \ "weechat" "Fast, light and extensible IRC client" off \ "irssi" "Modular text mode IRC client" off \ "libreoffice-fresh" "Full featured office suite" off \ "abiword" "Fully-featured word processor" off \ "calligra" "A set of applications for productivity" off \ "evince" "Document viewer" off \ "zathura" "Minimalistic document viewer" off \ "qpdfview" "A tabbed PDF viewer" off \ "mupdf" "Lightweight PDF and XPS viewer" off \ "gimp" "GNU Image Manipulation Program" off \ "inkscape" "Professional vector graphics editor" off \ "krita" "Edit and paint images" off \ "simplescreenrecorder" "A feature-rich screen recorder" off \ "obs-studio" "Free opensource streaming/recording software" off \ "guvcview" "Capture video from camera devices" off \ "gpick" "Advanced color picker using GTK+ toolkit" off \ "gcolor2" "A simple GTK+2 color selector" off \ "plank" "Stupidly simple dock" off \ "docky" "The finest dock no money can buy" off \ "cairo-dock" "Light, eye-candy filled dock and desklets" off \ "ttf-hack" "A hand groomed and optically balanced typeface based on Bitstream Vera Mono." off \ "ttf-anonymous-pro" "A family of four fixed-width fonts designed especially with coding in mind" off \ "ttf-font-awesome" "Iconic font designed for Bootstrap" off \ "ttf-fira-code" "Monospaced font with programming ligatures" off \ "noto-fonts-cjk" "Google Noto CJK fonts (Chinese, Japanese, Korean)" off)" fi return 0 } setup_lightdm() { chroot_cmd 'systemctl enable lightdm.service && systemctl set-default graphical.target' >/dev/null 2>&1 local cfg="$MNT/etc/lightdm/lightdm-gtk-greeter.conf" sed -i '/#background=/ c background=/usr/share/backgrounds/archlabs/archlabs.jpg' $cfg sed -i '/#theme-name=/ c theme-name=ArchLabs-dARK' $cfg sed -i '/#icon-theme-name=/ c icon-theme-name=ArchLabs-Light' $cfg sed -i '/#position=/ c position=34%,end 66%,end' $cfg sed -i '/#font-name=/ c font-name=DejaVu Sans Mono 11' $cfg sed -i '/\[greeter]/ a default-user-image=/usr/share/icons/ArchLabs-Dark/64x64/places/distributor-logo-archlabs.png' $cfg sed -i '/\[greeter]/ a active-monitor=0' $cfg if [[ $AUTOLOGIN == true ]]; then chroot_cmd 'groupadd -r nopasswdlogin' >/dev/null 2>&1 sed -i '/#%PAM-1.0/ a auth sufficient pam_succeed_if.so user ingroup nopasswdlogin' $MNT/etc/pam.d/lightdm sed -i "/#autologin-session=/ c autologin-session=${LOGIN_WM}" $MNT/etc/lightdm/lightdm.conf fi } update_system() { local pkgcmd pkgcmd="pacman -Syyu --noconfirm; pacman -S iputils --noconfirm; pacman -S base-devel git --needed --noconfirm" if ! grep -qi "hypervisor" <<< "$(dmesg)" && pacman -Qsq virtualbox-guest &>/dev/null; then pkgcmd="pacman -Rs archlabs-installer $(pacman -Qsq virtualbox-guest) --noconfirm; $pkgcmd" else pkgcmd="pacman -Rs archlabs-installer --noconfirm; $pkgcmd" fi tput civis chroot_cmd "$pkgcmd" 2>/dev/null | dialog --cr-wrap --backtitle "$BT" \ --title " $_UpdSysTitle " --progressbox "$_UpdSysBody\n" 30 90 install_packages return 0 } update_mirrorlist_cmd() { if ! yesno "$_MirrorTitle" "$_MirrorSetup" "Automatic Sort" "Customize Sort"; then infobox "$_MirrorTitle" "\nGathering mirror countries..\n" local countries countries="$(reflector --list-countries | awk 'NF > 1 {print $1 " -"}')" if [[ $countries != "" ]]; then tput civis local country country="$(dialog --cr-wrap --stdout --no-cancel --backtitle "$BT" \ --title "$_MirrorTitle" --menu "$_MirrorCountry" 22 70 10 $countries)" MIRROR_CMD="reflector --country $country --score 80 --latest 40 --fastest 10 --sort rate" fi local ref=" --score n Limit the list to the n servers with the highest score. -l n, --latest n Limit the list to the n most recently synchronized servers. -f n, --fastest n Return the n fastest mirrors that meet the other criteria. --sort {age,rate,country,score,delay} 'age': last server synchronization; 'rate': download rate; 'country': server's location; 'score': MirrorStatus score; 'delay': MirrorStatus delay." tput cnorm MIRROR_CMD="$(dialog --cr-wrap --no-cancel --stdout --backtitle "$BT" \ --title " $_MirrorTitle " --inputbox "$_MirrorCmd\n\n$ref\n" 0 0 "$cmd")" fi return 0 } update_mirrorlist() { infobox "$_MirrorTitle" "$_MirrorSort" if ! $MIRROR_CMD --save $MNT/etc/pacman.d/mirrorlist; then infobox "$_ErrTitle" "\nAn error occurred while updating the mirrorlist.\n\nFalling back to automatic sorting...\n" reflector --score 100 -l 50 -f 10 --sort rate --save $MNT/etc/pacman.d/mirrorlist fi return 0 } bootloader_config() { ROOT_PART_ID="$ROOT_PART" if ! grep -q "/dev/mapper/" <<< "$ROOT_PART"; then ROOT_PART_ID="UUID=$(blkid -s PARTUUID $ROOT_PART | sed 's/.*=//g; s/"//g')" [[ $BOOTLOADER == "systemd-boot" ]] && ROOT_PART_ID="PART$ROOT_PART_ID" fi if [[ $BOOTLOADER == "grub" ]]; then local cfg="$MNT/etc/default/grub" sed -i "s/GRUB_DISTRIBUTOR=.*/GRUB_DISTRIBUTOR=\"${DIST}\"/g; s/GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"\"/g" $cfg if (( LUKS == 1 )); then sed -i "s~#GRUB_ENABLE_CRYPTODISK~GRUB_ENABLE_CRYPTODISK~g; s~GRUB_CMDLINE_LINUX=.*~GRUB_CMDLINE_LINUX=\"${LUKS_DEV}\"~g" $cfg fi if [[ $SYS != "UEFI" ]]; then if (( LVM == 1 && SEPERATE_BOOT == 0 )) || (( SEPERATE_BOOT == 2 )); then sed -i "s/GRUB_PRELOAD_MODULES=.*/GRUB_PRELOAD_MODULES=\"lvm\"/g" $cfg fi fi elif [[ $BOOTLOADER == "syslinux" ]]; then local cfgdir="$MNT/boot/syslinux" mkdir -p $cfgdir cp -f /usr/lib/syslinux/bios/vesamenu.c32 $cfgdir/ cp -f /run/archiso/bootmnt/arch/boot/syslinux/splash.png $cfgdir/ cat > $cfgdir/syslinux.cfg << EOF UI vesamenu.c32 DEFAULT archlabs PROMPT 0 MENU TITLE $DIST Syslinux Boot Menu MENU BACKGROUND splash.png TIMEOUT 50 MENU WIDTH 78 MENU MARGIN 4 MENU ROWS 5 MENU VSHIFT 10 MENU TIMEOUTROW 13 MENU TABMSGROW 11 MENU CMDLINEROW 11 MENU HELPMSGROW 16 MENU HELPMSGENDROW 29 # Refer to https://www.syslinux.org/wiki/index.php/Comboot/menu.c32 MENU COLOR border 30;44 #40ffffff #a0000000 std MENU COLOR title 1;36;44 #9033ccff #a0000000 std MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all MENU COLOR unsel 37;44 #50ffffff #a0000000 std MENU COLOR help 37;40 #c0ffffff #a0000000 std MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std MENU COLOR msg07 37;40 #90ffffff #a0000000 std MENU COLOR tabmsg 31;40 #30ffffff #00000000 std LABEL archlabs MENU LABEL $DIST Linux LINUX ../vmlinuz-linux APPEND root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && echo -n "$LUKS_DEV ")rw INITRD ../initramfs-linux.img LABEL archlabsfallback MENU LABEL $DIST Linux Fallback LINUX ../vmlinuz-linux APPEND root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && echo -n "$LUKS_DEV ")rw INITRD ../initramfs-linux-fallback.img LABEL hdt MENU LABEL HDT (Hardware Detection Tool) COM32 hdt.c32 LABEL reboot MENU LABEL Reboot COM32 reboot.c32 LABEL poweroff MENU LABEL Poweroff COM32 poweroff.c32 EOF else # systemd-boot requires this before running bootctl systemd-machine-id-setup --root="$MNT" >/dev/null 2>&1 mkdir -p $MNT/boot/loader/entries cat > $MNT/boot/loader/loader.conf << EOF default archlabs timeout 5 editor no EOF cat > $MNT/boot/loader/entries/${DIST,,}.conf << EOF title $DIST Linux linux /vmlinuz-linux initrd /initramfs-linux.img options root=$ROOT_PART_ID rw EOF for file in $MNT/boot/loader/entries/arch*?.conf; do (( LUKS == 1 )) && sed -i "s~rw~$LUKS_DEV rw~g" "$file" done fi return 0 } grub_uefi_fallback() { # some UEFI firmware is finicky and requires a specific folder in # /boot/efi/EFI/ and named 'boot', 'Boot', or 'BOOT' local fb="boot" local esp="${MNT}${BOOT_MNTS[$SYS-$BOOTLOADER]}/EFI/" for i in $(find "$esp" -maxdepth 1 -mindepth 1 -type d 2>/dev/null); do if grep -qi "boot" <<< "$(basename $i)"; then fb="$(basename $i)" break fi done # copy grub's efi stub binary to that directory as $stub mkdir -p ${esp}$fb cp -f ${esp}$DIST/grubx64.efi ${esp}$fb/bootx64.efi return 0 } setup_bootloader() { chroot_cmd "export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/bin/core_perl" local msg="$_InstBootloader $BOOTLOADER\n" [[ $BOOT_PART != "" ]] && msg="$msg\n$_InstBootDev $BOOT_PART\n" infobox "$_InstBootTitle" "$msg\nMountpoint: ${BOOT_MNTS[$SYS-$BOOTLOADER]}\n" if [[ $SYS == "UEFI" ]]; then # ensure efivarfs is mounted in host and in chroot local eficmd="mount -o remount,rw -t efivarfs efivarfs /sys/firmware/efi/efivars" $eficmd >/dev/null 2>&1 BOOT_CMDS[$BOOTLOADER]="$eficmd ; ${BOOT_CMDS[$BOOTLOADER]}" # remove old EFI boot entries from previous installs find $MNT/boot/efi/EFI/ -maxdepth 1 -mindepth 1 -name '[aA][rR][cC][hH][lL]abs*' \ -type d -exec rm -rf '{}' \; >/dev/null 2>&1 if [[ $GRUB_UEFI_FALLBACK == true ]]; then find $MNT/boot/efi/EFI/ -maxdepth 1 -mindepth 1 -name '[Bb][oO][oO][tT]' \ -type d -exec rm -rf '{}' \; >/dev/null 2>&1 fi fi # grub is finicky and requires special treatment if [[ $BOOTLOADER == "grub" ]]; then # needed for os-prober module to work properly in the chroot local udevcmd="mkdir -p /run/udev && mount --rbind /run/udev /run/udev" $udevcmd >/dev/null 2>&1 BOOT_CMDS[grub]="$udevcmd ; ${BOOT_CMDS[grub]}" # BIOS uses the base device name, eg. /dev/sda.. so add it to the end of BOOT_CMDS[grub] [[ $SYS == "BIOS" ]] && BOOT_CMDS[grub]="${BOOT_CMDS[grub]} $BOOT_DEVICE" # add grub-mkconfig last BOOT_CMDS[grub]="${BOOT_CMDS[grub]} && grub-mkconfig -o /boot/grub/grub.cfg" else # for other bootloaders remove grub and it's configs rm -rf $MNT/etc/default/grub find $MNT/boot/ -name 'grub*' -exec rm -rf '{}' \; >/dev/null 2>&1 BOOT_CMDS[$BOOTLOADER]="pacman -Rs grub --noconfirm && ${BOOT_CMDS[$BOOTLOADER]}" fi # create the bootloader configs and run the setup commands, BOOT_CMDS[$BOOTLOADER] bootloader_config chroot_cmd "${BOOT_CMDS[$BOOTLOADER]}" 2>$ERR >/dev/null 2>&1 check_for_errors "${BOOT_CMDS[$BOOTLOADER]}" || return 1 # copy grub efi stub to generic catch all [[ $GRUB_UEFI_FALLBACK == true ]] && grub_uefi_fallback BOOT_DONE=true # offer to setup a keyfile for LUKS.. Only when choosing grub and system is UEFI if [[ $LUKS -eq 1 && $SYS == 'UEFI' && $BOOTLOADER == 'grub' && $LUKS_PASS && $LUKS_UUID ]]; then luks_keyfile || return 1 fi return 0 } run_mkinitcpio() { local conf="$MNT/etc/mkinitcpio.conf" # new HOOKS needed in /etc/mkinitcpio.conf if we used LUKS and/or LVM local add (( LVM == 1 )) && add="lvm2" (( LUKS == 1 )) && add="encrypt$([[ $add != "" ]] && echo -n " $add")" if [[ ! -e $MNT/boot/vmlinuz-linux ]]; then cp -f /run/archiso/bootmnt/arch/boot/x86_64/vmlinuz $MNT/boot/vmlinuz-linux fi sed -i "s/block filesystems/block ${add} filesystems ${MKINIT_HOOKS}/g" $conf 2>$ERR check_for_errors "sed -i 's/block filesystems/block ${add} filesystems ${MKINIT_HOOKS}/g' $conf" || return 1 tput civis chroot_cmd "mkinitcpio -p linux" 2>$ERR | dialog --cr-wrap --backtitle "$BT" \ --title " $_RunMkinit " --progressbox "$_RunMkinitBody\nHOOKS: $MKINIT_HOOKS $add\n" 30 90 check_for_errors "mkinitcpio -p linux" || return 1 return 0 } ###################################################################### ## Menu Dialogs ## ###################################################################### main_menu() { if [[ $FIRST_PREP != true ]]; then FIRST_PREP=true prepare_menu fi if [[ $CURRENT_MENU != "main" ]]; then MENU_HIGHLIGHT=1 CURRENT_MENU="main" elif (( MENU_HIGHLIGHT < 5 )); then ((MENU_HIGHLIGHT++)) fi tput civis MENU_HIGHLIGHT=$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_MainTitle " --default-item $MENU_HIGHLIGHT --menu "$_MainBody" 0 0 0 \ "1" "$_PrepTitle" "2" "$_InstTitle" "3" "$_ConfTitle" "4" "$_EditTitle" "5" "$_Done") if [[ -n $MENU_HIGHLIGHT ]]; then # if trying to unpack the system make sure the partitions are mounted if (( MENU_HIGHLIGHT == 2 )) && ! check_parts_are_mounted; then return 1 elif (( MENU_HIGHLIGHT == 3 || MENU_HIGHLIGHT == 4 )); then # when trying to use config_menu() or edit config files make sure # the system is unpacked and the partitions are mounted if ! (check_parts_are_mounted && check_base_unpacked); then return 1 fi fi fi case $MENU_HIGHLIGHT in 1) prepare_menu ;; 2) install_main ;; 3) configure_menu ;; 4) edit_config_menu ;; *) wrap_up "$_CloseInstBody" 'Exit' 'Back' 'exit' esac } prepare_menu() { if [[ $ROOT_PART != "" && $BOOTLOADER != "" ]]; then if check_parts_are_mounted; then # this is where all the action happens, the rest is mostly automated # the user will likely reboot after and never get back here, but if we do # returning goes to the main menu install_main && return 0 fi fi if [[ $CURRENT_MENU != "prep" ]]; then MENU_HIGHLIGHT=1 CURRENT_MENU="prep" elif (( MENU_HIGHLIGHT < 7 )); then ((MENU_HIGHLIGHT++)) fi tput civis MENU_HIGHLIGHT=$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_PrepTitle " \ --default-item $MENU_HIGHLIGHT --menu "$_PrepBody" 0 0 0 \ "1" "$_PrepLayout" "2" "$_PrepShowDev" "3" "$_PrepParts" "4" "$_PrepLUKS" \ "5" "$_PrepLVM" "6" "$_PrepMount" "7" "$_Back") case $MENU_HIGHLIGHT in 1) set_keymap ;; 2) show_devices ;; 3) unmount_partitions && select_device 'root' && create_partitions "$DEVICE" ;; 4) luks_menu ;; 5) lvm_menu ;; 6) select_install_partitions ;; *) return 0 esac prepare_menu } configure_menu() { chroot_cmd "export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/core_perl" if [[ $FIRST_CONFIG != true ]]; then FIRST_CONFIG=true set_hostname && set_locale && create_user [[ $FULL_DONE == true ]] && return 0 elif [[ $CURRENT_MENU != "config" ]]; then MENU_HIGHLIGHT=1 CURRENT_MENU="config" elif (( MENU_HIGHLIGHT < 4 )); then ((MENU_HIGHLIGHT++)) fi tput civis MENU_HIGHLIGHT=$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_ConfTitle " \ --default-item $MENU_HIGHLIGHT --menu "$_ConfBody" 0 0 0 \ "1" "$_ConfHost" "2" "$_ConfLocale" "3" "$_ConfUser" "4" "$_Back") case $MENU_HIGHLIGHT in 1) set_hostname ;; 2) set_locale ;; 3) create_user ;; *) return 0 esac configure_menu } edit_config_menu() { if [[ $CURRENT_MENU != "edit" ]]; then MENU_HIGHLIGHT=1 CURRENT_MENU="edit" elif (( MENU_HIGHLIGHT < 10 )); then ((MENU_HIGHLIGHT++)) fi local msg [[ $FULL_DONE == true ]] && msg="${_Final}$_EditBody" || msg="$_EditBody" tput civis MENU_HIGHLIGHT=$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $_EditTitle " \ --default-item $MENU_HIGHLIGHT --menu "$msg" 0 0 0 \ "1" "$_Done" "2" "keymaps" "3" "locale" "4" "hostname" "5" "sudoers" \ "6" "mkinitcpio.conf" "7" "fstab" "8" "crypttab" "9" "$BOOTLOADER" "10" "pacman.conf") if [[ $MENU_HIGHLIGHT == "" || $MENU_HIGHLIGHT == 1 ]]; then if [[ $FULL_DONE == true && $CONFIRM_DONE != true ]]; then CONFIRM_DONE=true wrap_up "$_InstFinBody" 'Exit & Reboot' 'Go Back' 'reboot' fi return 0 fi local existing_files="" for f in $(echo "${EDIT_FILES[$MENU_HIGHLIGHT]}"); do [[ -e ${MNT}$f ]] && existing_files="$existing_files ${MNT}$f" done if [[ $existing_files != "" ]]; then if [[ $EDITOR_CHOICE == "" && $DISPLAY ]] && hash geany >/dev/null 2>&1; then if yesno "$_EditTitle" "\nOpen file(s) in Geany or Vim?\n" "Geany" "Vim"; then EDITOR_CHOICE="geany -i" geany -i $existing_files else EDITOR_CHOICE="vim -O" vim -O $existing_files fi elif [[ $EDITOR_CHOICE ]]; then $EDITOR_CHOICE $existing_files else vim -O $existing_files fi else msgbox "$_ErrTitle" "$_NoFileErr" fi edit_config_menu } for arg in "$@"; do [[ $arg == "--debug" || $arg == "-d" ]] && set_debug done initialize_variables luks_variable_init select_language check_requirements identify_system # welcome message msgbox "$_WelTitle $DIST Installer" "$_WelBody" while true; do main_menu done