#!/usr/bin/bash # vim:ft=sh:fdm=marker:fmr={,} # archlabs installer library script file # this file is not meant to be run directly # sourcing this file in a non bash shell is not advised # shellcheck disable=2154,2034,2153 # mutable globals { declare -g WARN=false declare -g AUTOLOGIN=false declare -g CONFIG_DONE=false declare -g SEPERATE_BOOT=false declare -g BROADCOM_WL=false declare -g BT="$DIST Installer - (x86_64) - Version $VER" declare -g ROOT_PART="" declare -g BOOT_DEVICE="" declare -g BOOT_PART="" declare -g BOOTLDR="" declare -g EXTRA_MNT="" declare -g EXTRA_MNTS="" declare -g SWAP_PART="" declare -g SWAP_SIZE="" 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 KERNEL="" declare -g WM_PACKAGES="" declare -g PACKAGES="" declare -g MYSHELL="" declare -g MKINIT_HOOKS="shutdown" # match the wm name with the actual session name used for xinit declare -gA WM_SESSIONS=( [i3-gaps]='i3' [dwm]='dwm' [openbox]='openbox-session' [bspwm]='bspwm' [xfce4]='startxfce4' [gnome]='gnome-session' [cinnamon]='cinnamon-session' ) # additional packages installed for the given window manager declare -gA WM_EXT=( [bspwm]="sxhkd libmpdclient jsoncpp archlabs-screenlock archlabs-polybar rofi" [gnome]="gnome-extra" [i3-gaps]="i3status perl-anyevent-i3 libmpdclient jsoncpp archlabs-screenlock archlabs-polybar rofi" [xfce4]="xfce4-goodies xfce4-pulseaudio-plugin" [openbox]="archlabs-obkey obconf archlabs-kickshaw tint2 archlabs-oblogout jgmenu tint2 archlabs-skippy-xd conky thunar termite libmpdclient jsoncpp archlabs-screenlock archlabs-paranoid archlabs-polybar rofi" ) # files the user can edit during the final stage of install declare -gA EDIT_FILES=( [2]="/etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard /etc/vconsole.conf" [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" [11]="" # login files populated later, /home/USER?/FILES? ... lightdm? ) # } msgbox() { tput civis dialog --cr-wrap --backtitle "$BT" --title " $1 " --msgbox "$2\n" 0 0 } menubox() { local title="$1" local body="$2" local h=$3 local w=$4 local n=$5 shift 5 local response if ! response="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $title " --menu "$body" $h $w $n "$@")"; then return 1 fi printf "%s" "$response" } checkbox() { local title="$1" local body="$2" local h=$3 local w=$4 local n=$5 shift 5 local response if ! response="$(dialog --cr-wrap --stdout --backtitle "$BT" --title " $title " --checklist "$body" $h $w $n "$@")"; then return 1 fi printf "%s" "$response" } getinput() { local answer if ! answer="$(dialog --cr-wrap --max-input 63 --stdout --no-cancel --backtitle "$BT" --title " $1 " --inputbox "$2" 0 0 "$3")" || [[ $answer == '' ]]; then return 1 fi printf "%s" "$answer" } infobox() { local sec="$3" tput civis dialog --cr-wrap --backtitle "$BT" --title " $1 " --infobox "$2\n" 0 0 sleep ${sec:-2} } yesno() { # usage: yesno <text> [<yes_label> <no_label> [<no>]] # three options: one --default-no and custom labels, one just custom labels, and one basic. tput civis if [[ $# -eq 5 && $5 == "no" ]]; then dialog --cr-wrap --backtitle "$BT" --defaultno --title " $1 " \ --yes-label "$3" --no-label "$4" --yesno "$2\n" 0 0 elif [[ $# -eq 4 ]]; then dialog --cr-wrap --backtitle "$BT" --title " $1 " --yes-label "$3" \ --no-label "$4" --yesno "$2\n" 0 0 else dialog --cr-wrap --backtitle "$BT" --title " $1 " --yesno "$2\n" 0 0 fi } select_language() { tput civis local lang local title="\nLanguage - sprache - taal - språk - lingua - idioma - nyelv - língua\n" lang=$(menubox "Select Language" "$title" 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)") local srcdir="/usr/share/archlabs/installer/lang" src $srcdir/english.trans FONT="ter-i16n" case $lang in 1) LOC="en_US.UTF-8" ;; 2) src $srcdir/spanish.trans && LOC="es_ES.UTF-8" ;; 3) src $srcdir/p_brasil.trans && LOC="pt_BR.UTF-8" ;; 4) src $srcdir/portuguese.trans && LOC="pt_PT.UTF-8" ;; 5) src $srcdir/french.trans && LOC="fr_FR.UTF-8" ;; 6) src $srcdir/russian.trans && LOC="ru_RU.UTF-8" FONT="LatKaCyrHeb-16" ;; 7) src $srcdir/italian.trans && LOC="it_IT.UTF-8" ;; 8) src $srcdir/dutch.trans && LOC="nl_NL.UTF-8" ;; 9) src $srcdir/hungarian.trans && LOC="hu_HU.UTF-8" FONT="lat2-16" ;; 10) src $srcdir/chinese.trans && LOC="zh_CN.UTF-8" ;; *) die esac sed -i "s/#en_US.UTF-8/en_US.UTF-8/" /etc/locale.gen if [[ $LOC != "en_US.UTF-8" ]]; then sed -i "s/#${LOC}/${LOC}/" /etc/locale.gen locale-gen >/dev/null 2>&1 fi [[ $TERM == 'linux' ]] && setfont $FONT >/dev/null 2>&1 export LANG="$LOC" return 0 } user_creation() { tput cnorm local values if ! values="$(dialog --stdout --no-cancel --separator '~' \ --ok-label "Submit" --backtitle "$BT" --title " $_UserTitle " \ --insecure --mixedform "$_UserBody" 27 75 10 \ "$_Username" 1 1 "" 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 | openssl enc -pbkdf2 -a -salt -pass pass:$SALT)"; then return 1 fi # username doesn't need to be re-encrypted local user user="$(openssl enc -pbkdf2 -a -d -salt -pass pass:$SALT <<< "$values" | awk -F'~' '{print $1}')" # all of this is a bit hacky, but we don't ever want the passwords to be stored in plain text # so it decrypts the string '$values', gets the field we want, and re-encrypts it local pass pass2 pass="$(openssl enc -pbkdf2 -a -d -salt -pass pass:$SALT <<< "$values" | awk -F'~' '{print $2}' | openssl enc -pbkdf2 -a -salt -pass pass:$SALT)" pass2="$(openssl enc -pbkdf2 -a -d -salt -pass pass:$SALT <<< "$values" | awk -F'~' '{print $3}' | openssl enc -pbkdf2 -a -salt -pass pass:$SALT)" local rpass rpass2 rpass="$(openssl enc -pbkdf2 -a -d -salt -pass pass:$SALT <<< "$values" | awk -F'~' '{print $5}' | openssl enc -pbkdf2 -a -salt -pass pass:$SALT)" rpass2="$(openssl enc -pbkdf2 -a -d -salt -pass pass:$SALT <<< "$values" | awk -F'~' '{print $6}' | openssl enc -pbkdf2 -a -salt -pass pass:$SALT)" # due to encrypting the string, when empty, once encrypted it wont be empty local empty empty="$(openssl enc -pbkdf2 -a -salt -pass pass:$SALT <<< "")" # both root passwords are empty, so use the user passwords instead [[ $rpass == "$empty" && $rpass2 == "$empty" ]] && { rpass="$pass"; rpass2="$pass2"; } # make sure a username was entered and that the passwords match if [[ ${#user} -eq 0 || $user =~ \ |\' || $user =~ [^a-z0-9] || $pass == "$empty" || "$pass" != "$pass2" || "$rpass" != "$rpass2" ]]; then if [[ $pass == "$empty" || "$pass" != "$pass2" || "$rpass" != "$rpass2" ]]; then # password was left empty or doesn't match if [[ $pass == "$empty" ]]; then msgbox "$_ErrTitle" "\nUser password CANNOT be left empty.\n$_TryAgain" elif [[ "$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 cancels user || return 1 else export NEWUSER="$user" export USER_PASS="$pass" export ROOT_PASS="$rpass" fi return 0 } select_keymap() { tput civis if ! KEYMAP="$(menubox "$_PrepLayout" "$_XMapBody" 20 70 12 \ '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' 'ro' 'Romanian' \ 'no' 'Norwegian' 'rs' 'Serbian' 'si' 'Slovenian' 'tj' 'Tajik' 'lk' 'Sinhala' \ 'tr' 'Turkish' 'uz' 'Uzbek' 'ie' 'Irish' 'pk' 'Urdu' 'mv' 'Dhivehi' \ 'np' 'Nepali' 'et' 'Amharic' 'sn' 'Wolof' 'ml' 'Bambara' 'tz' 'Swahili' \ 'ke' 'Swahili' 'bw' 'Tswana' 'ph' 'Filipino' 'my' 'Malay' 'tm' 'Turkmen' \ 'id' 'Indonesian' 'bt' 'Dzongkha' 'lv' 'Latvian' 'md' 'Moldavian' 'mao' 'Maori' \ 'by' 'Belarusian' 'az' 'Azerbaijani' 'mk' 'Macedonian' 'kh' 'Khmer' 'epo' 'Esperanto' \ 'me' 'Montenegrin')"; then return 1 fi # when a matching console map is not available open a selection dialog if [[ $CMAPS == *"$KEYMAP"* ]]; then CMAP="$KEYMAP" else if ! CMAP="$(menubox "$_CMapTitle" "$_CMapBody" 20 70 12 $CMAPS)"; then return 1 fi fi if [[ $DISPLAY && $TERM != 'linux' ]]; then setxkbmap $KEYMAP >/dev/null 2>&1 else loadkeys $CMAP >/dev/null 2>&1 fi return 0 } select_timezone() { # create associative array for SUBZONES[zone] local f="/usr/share/zoneinfo/zone.tab" declare -A SUBZONES for i in America Australia Asia Atlantic Africa Europe Indian Pacific Arctic Antarctica; do SUBZONES[$i]="$(awk '/'"$i"'\// {gsub(/'"$i"'\//, ""); print $3, $1}' $f)" done tput civis if ! ZONE="$(menubox "$_TimeZTitle" "$_TimeZBody" 20 70 10 \ 'America' '-' 'Australia' '-' 'Asia' '-' 'Atlantic' '-' 'Africa' '-' \ 'Europe' '-' 'Indian' '-' 'Pacific' '-' 'Arctic' '-' 'Antarctica' '-')"; then return 1 fi if ! SUBZONE="$(menubox "$_TimeZTitle" "$_TimeSubZBody" 20 70 12 ${SUBZONES[$ZONE]})"; then return 1 fi yesno "$_TimeZTitle" "$_TimeZQ $ZONE/$SUBZONE?\n" && return 0 || select_timezone } select_wm_or_de() { tput civis if ! INSTALL_WMS="$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_WMChoice " --checklist "$_WMChoiceBody\n" 0 0 0 \ "i3-gaps" "A fork of i3 window manager with more features including gaps" off \ "dwm" "A customized fork of dwm, with patches and modifications" off \ "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 \ "gnome" "A desktop environment that aims to be simple and easy to use" off \ "cinnamon" "A desktop environment combining a traditional desktop layout with modern graphical effects" off \ "xfce4" "A lightweight and modular desktop environment based on GTK+ 2 and 3" off)"; then return 1 fi WM_NUM=$(awk '{print NF}' <<< "$INSTALL_WMS") WM_PACKAGES="${INSTALL_WMS/dwm/}" # remove dwm from package list WM_PACKAGES="${WM_PACKAGES// / }" # remove double spaces # packages needed for the selected WMs/DEs for wm in $INSTALL_WMS; do LOGIN_CHOICES+="$wm - " WM_PACKAGES+=" ${WM_EXT[$wm]}" done # choose how to log in select_login_method || return 1 # choose which WM/DE to start at login, only for xinit if [[ $LOGIN_TYPE == 'xinit' ]]; then if [[ $WM_NUM -eq 1 ]]; then LOGIN_WM="${WM_SESSIONS[$INSTALL_WMS]}" else if ! LOGIN_WM="$(menubox "$_WMLogin" "$_WMLoginBody" 0 0 0 $LOGIN_CHOICES)"; then return 1 else LOGIN_WM="${WM_SESSIONS[$LOGIN_WM]}" fi fi yesno "$_WMLogin" "$_AutoLoginBody\n" && AUTOLOGIN=true || AUTOLOGIN=false else AUTOLOGIN=false fi # add packages to the main package list declare -g PACKAGES="$WM_PACKAGES" } select_login_method() { if ! LOGIN_TYPE="$(menubox "$_WMLogin" "$_LoginTypeBody" 0 0 0 \ "xinit" "Console login without a display manager" \ "lightdm" "Lightweight display manager with a gtk greeter")"; then return 1 fi if [[ $LOGIN_TYPE == 'lightdm' ]]; then WM_PACKAGES+=" lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings accountsservice" EDIT_FILES[11]="/etc/lightdm/lightdm.conf /etc/lightdm/lightdm-gtk-greeter.conf" else EDIT_FILES[11]="/home/$NEWUSER/.xinitrc /home/$NEWUSER/.xprofile" fi } select_packages() { if [[ $CURRENT_MENU != "packages" ]]; then declare -g SAVED=$SELECTED SELECTED=1 CURRENT_MENU="packages" elif (( SELECTED < 9 )); then ((SELECTED++)) # increment the highlighted menu item fi tput civis SELECTED=$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_Packages " --default-item $SELECTED --menu "$_PackageMenu" 0 0 0 \ "1" "Web Browsers" "2" "Text Editors" "3" "Terminal Emulators" \ "4" "Music and Video Players" "5" "Mail and Chat" "6" "Office and Editing" \ "7" "Management and Fonts" "8" "Miscellaneous" "9" "$_Done") if [[ $SELECTED -lt 9 ]]; then case $SELECTED in 1) PACKAGES+=" $(select_browsers)" ;; 2) PACKAGES+=" $(select_editors)" ;; 3) PACKAGES+=" $(select_terminals)" ;; 4) PACKAGES+=" $(select_music_and_video)" ;; 5) PACKAGES+=" $(select_mail_and_chat)" ;; 6) PACKAGES+=" $(select_office_and_editing)" ;; 7) PACKAGES+=" $(select_managment)" ;; 8) PACKAGES+=" $(select_extra)" ;; esac select_packages else # add any extras for each package for pkg in $PACKAGES; do [[ ${PKG_EXT[$pkg]} ]] && PACKAGES+=" ${PKG_EXT[$pkg]}" done # add mksh to package list if it was chosen as the login shell if [[ $MYSHELL == *mksh ]]; then declare -g PACKAGES="mksh $PACKAGES" fi # remove duplicates and leading spaces PACKAGES="$(uniq <<< "${PACKAGES/^ /}")" return 0 fi } select_mirrorcmd() { local ip c local key="5f29642060ab983b31fdf4c2935d8c56" if hash reflector >/dev/null 2>&1; then MIRROR_CMD="reflector --score 100 -l 50 -f 10 --sort rate --verbose" yesno "$_MirrorTitle" "$_MirrorSetup" "Automatic" "Custom" && return 0 ip="$(json 'ip' "check&?access_key=${key}&fields=ip")" c="$(json 'country_name' "${ip}?access_key=${key}&fields=country_name")" MIRROR_CMD="reflector --country $c --score 80 --latest 40 --fastest 10 --sort rate --verbose" tput cnorm MIRROR_CMD="$(dialog --cr-wrap --no-cancel --stdout --backtitle "$BT" \ --title " $_MirrorTitle " --inputbox "$_MirrorCmd\n --score n Limit the list to the n servers with the highest score. --latest n Limit the list to the n most recently synchronized servers. --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 location; 'score': MirrorStatus score; 'delay': MirrorStatus delay.\n" 0 0 "$MIRROR_CMD")" else ip="$(json 'ip' "check&?access_key=${key}&fields=ip")" c="$(json 'country_code' "${ip}?access_key=${key}&fields=country_code")" local w="https://www.archlinux.org/mirrorlist" if [[ $c ]]; then if [[ $c =~ (CA|US) ]]; then MIRROR_CMD="curl -s '$w/?country=US&country=CA&protocol=https&use_mirror_status=on'" else MIRROR_CMD="curl -s '$w/?country=${c}&protocol=https&use_mirror_status=on'" fi else MIRROR_CMD="curl -s '$w/?country=US&country=CA&country=NZ&country=GB&country=AU&protocol=https&use_mirror_status=on'" fi fi return 0 } display_system_settings() { local cmd mnt pkgs cmd="${BCMDS[$BOOTLDR]}" mnt="${BMNTS[$SYS-$BOOTLDR]}" msgbox "$_PrepTitle" " ---------- PARTITION CONFIGURATION ------------ Root: ${ROOT_PART:-None} Boot: ${BOOT_PART:-${BOOT_DEVICE:-None}} Swap: ${SWAP_PART:-None} Size: ${SWAP_SIZE:-None} Extra: ${EXTRA_MNTS:-${EXTRA_MNT:-None}} Hooks: ${MKINIT_HOOKS:-None} LVM: ${LVM:-None} LUKS: ${LUKS:-None} ---------- BOOTLOADER CONFIGURATION ----------- Loader: ${BOOTLDR:-None} Mount: ${mnt:-None} Command: ${cmd:-None} ------------ SYSTEM CONFIGURATION ------------- Locale: ${LOCALE:-None} Keymap: ${KEYMAP:-None} Hostname: ${HOSTNAME:-None} Timezone: ${ZONE:-None}/${SUBZONE:-None} ------------ LOGIN CONFIGURATION -------------- User: ${NEWUSER:-None} Shell: ${MYSHELL:-None} Session: ${LOGIN_WM:-None} Autologin: ${AUTOLOGIN:-None} Management: ${LOGIN_TYPE:-None} ------------ PACKAGES AND MIRRORS ------------- Kernel: ${KERNEL:-None} Sessions: ${INSTALL_WMS:-None} Mirrors: ${MIRROR_CMD:-None} Packages: $(print4 "${PACKAGES:-None}") " } configure_system_settings() { tput cnorm if ! HOSTNAME="$(getinput "$_ConfHost" "$_HostNameBody" "${DIST,,}")"; then return 1 fi tput civis if ! LOCALE="$(menubox "$_ConfLocale" "$_LocaleBody" 25 70 20 $LOCALES)"; then return 1 fi select_timezone || return 1 user_creation || return 1 tput civis if ! MYSHELL="$(menubox "$_ShellTitle" "$_ShellBody" 0 0 0 '/usr/bin/zsh' '-' '/bin/bash' '-' '/usr/bin/mksh' '-')"; then return 1 fi if ! KERNEL="$(menubox "$_KernelTitle" "$_KernelBody" 0 0 0 'linux' '-' 'linux-lts' '-')"; then return 1 fi yesno "$_DevelTitle" "$_DevelBody" && BASEDEV=true || BASEDEV=false select_wm_or_de || return 1 select_packages || return 1 select_mirrorcmd || return 1 export CONFIG_DONE=true return 0 } edit_system_configs() { if [[ $CURRENT_MENU != "edit" ]]; then SELECTED=1; CURRENT_MENU="edit" elif (( SELECTED < 11 )); then (( SELECTED++ )) fi tput civis local exitstr [[ $DEBUG == true ]] && exitstr="View debug log before the exit & reboot" || exitstr="Exit & reboot" SELECTED=$(dialog --cr-wrap --stdout --backtitle "$BT" \ --title " $_EditTitle " --default-item $SELECTED --menu "$_EditBody" 0 0 0 \ "1" "$exitstr" "2" "Keyboard" "3" "Locale" "4" "Hostname" \ "5" "Sudoers" "6" "Mkinitcpio.conf" "7" "Fstab" "8" "Crypttab" \ "9" "${BOOTLDR^}" "10" "Pacman.conf" "11" "${LOGIN_TYPE^}") if [[ ! $SELECTED || $SELECTED -eq 1 ]]; then [[ $DEBUG == true && -r $DBG ]] && more $DBG # when die() is passed 127 as the exit code it will issue `systemctl -i reboot` die 127 else local existing_files="" for f in $(printf "%s" "${EDIT_FILES[$SELECTED]}"); do [[ -e ${MNT}$f ]] && existing_files+=" ${MNT}$f" done if [[ $existing_files ]]; then if hash vim >/dev/null 2>&1; then vim -O $existing_files else for f in $existing_files; do if hash nano >/dev/null 2>&1; then nano "$f" else vi "$f" fi done fi else msgbox "$_ErrTitle" "$_NoFileErr" fi fi edit_system_configs }