OSDN Git Service

d9dd26185aa3793878f85fc3ed9a9d333e2d1f21
[pacbang-linux/installer-arch.git] / archlabs-installer
1 #!/bin/bash
2
3 # This program is free software, provided under the GNU GPL
4 # Written by Nathaniel Maia for use in Archlabs
5 # Some ideas and code reworked from other resources
6 # AIF, Cnichi, Calamares, Arch Wiki.. Credit where credit is due
7
8 VER="2.0.55"     # installer version
9 DIST="ArchLabs"  # linux distributor
10 MNT="/mnt"       # install mountpoint
11 ANS="/tmp/ans"   # dialog answer file
12
13 # ------------------------------------------------------ #
14 # When manually mounting your partitions, you need
15 # to set these values to avoid using the mount menu.
16 # ------------------------------------------------------ #
17
18 # root partition, eg. /dev/sda2
19 ROOT_PART=''
20
21 # boot partition, required on UEFI eg. /dev/sda1
22 BOOT_PART=''
23
24 # boot device, used for some bootloaders eg. /dev/sda
25 BOOT_DEV=''
26
27 # bootloader to use, can be:
28 #    UEFI: grub, syslinux, efistub, systemd-boot, refind-efi
29 #    BIOS: grub, syslinux
30 BOOTLDR=''
31
32 # directory to mount the boot partition (if any) for most bootloaders
33 # this will need to be 'boot' however a popular one for grub is 'boot/efi'
34 BOOTDIR='boot'
35
36 # ------------------------------------------------------ #
37
38 # bulk default values {
39
40 # these values will be selected during the install, it is not recommended
41 # to edit them here unless you know what you're doing.
42
43 UCODE=''          # cpu microcode (if any), can be: amd-ucode, intel-ucode
44 KERNEL=''         # linux kernel, can be: linux, linux-lts, linux-zen, or linux-hardened
45 MYSHELL=''        # full path for the shell, eg. /usr/bin/zsh
46
47 SWAP_PART=''      # swap partition or file path
48 SWAP_SIZE=''      # swap size, only used when creating a swapfile
49
50 EXMNT=''          # holder for additional partitions while mounting
51 EXMNTS=''         # when an extra partition is mounted append it's info
52
53 FONT="ter-i16n"   # font used for the linux console
54 HOOKS="shutdown"  # list of additional HOOKS to add in /etc/mkinitcpio.conf
55
56 LOGIN_TYPE=''     # login manager can be: lightdm, xinit
57 LOGIN_WM=''       # default login session to be placed in ~/.xinitrc
58 LOGINRC=''        # login shell rc file, eg. .zprofile, .bash_profile, .profile
59
60 INSTALL_WMS=''    # space separated list of chosen wm/de
61
62 WM_PKGS=''        # list of packages added during wm/de choice (not user edited)
63 PACKAGES=''       # list of all packages to install including WM_PKGS (not user edited)
64 USER_PKGS=''      # packages selected by the user during install
65
66 NEWUSER=''        # username for the new user
67 USER_PASS=''      # new user's password
68 ROOT_PASS=''      # root password
69
70 LUKS=''           # empty when not using encryption
71 LUKS_DEV=''       # boot parameter string for LUKS
72 LUKS_PART=''      # partition used for encryption
73 LUKS_PASS=''      # encryption password
74 LUKS_UUID=''      # encrypted partition UUID
75 LUKS_NAME=''      # name used for encryption
76
77 LVM=''            # empty when not using lvm
78 LVM_PARTS=''      # partitions used for volume group
79 VGROUP_MB=0       # available space in volume group
80
81 WARN=''           # issued mounting/partitioning warning
82 SEP_BOOT=''       # separate boot partition for BIOS
83 AUTOLOGIN=''      # enable autologin for xinit
84 CONFIG_DONE=''    # basic configuration is finished
85 BROADCOM_WL=''    # fixes for broadcom cards eg. BCM4352
86
87 FORMATTED=''      # partitions we formatted and should allow skipping
88 AUTO_ROOT_PART='' # root value from auto partition
89 AUTO_BOOT_PART='' # boot value from auto partition
90
91 # iso base, pacstrap when running the installer from a stock arch iso
92 ISO_BASE="b43-firmware b43-fwcutter broadcom-wl clonezilla dhclient dhcpcd ethtool wpa_supplicant "
93 ISO_BASE+="exfat-utils f2fs-tools gptfdisk vim hdparm ipw2100-fw ipw2200-fw nfs-utils nilfs-utils ntfs-3g "
94 ISO_BASE+="pacman-contrib parted rsync sdparm smartmontools wget wireless_tools wpa_actiond xl2tpd dialog parted "
95 ISO_BASE+="alsa-firmware alsa-lib alsa-plugins pulseaudio pulseaudio-alsa networkmanager w3m htop wireless-regdb "
96 ISO_BASE+="lm_sensors lsb-release p7zip pamixer reflector unrar ranger terminus-font ttf-dejavu archlabs-keyring"
97
98 # archlabs base packages
99 AL_BASE_PKGS="archlabs-skel-base archlabs-fonts archlabs-themes archlabs-dARK archlabs-icons archlabs-wallpapers archlabs-scripts"
100
101 # baseline (usually installed in the background)
102 BASE_PKGS="base-devel xorg xorg-drivers xorg-xinit sudo git gvfs gtk3 gtk-engines gtk-engine-murrine pavucontrol tumbler "
103 BASE_PKGS+="playerctl ffmpeg gstreamer libmad libmatroska gst-libav gst-plugins-base gst-plugins-good scrot"
104
105 # extras for window managers
106 WM_BASE_PKGS="arandr archlabs-networkmanager-dmenu xdg-user-dirs nitrogen polkit-gnome volumeicon xclip exo "
107 WM_BASE_PKGS+="xdotool compton wmctrl gnome-keyring dunst feh gsimplecal xfce4-power-manager xfce4-settings laptop-detect"
108
109
110 SEL=0                                 # currently selected menu item
111 ERR="/tmp/errlog"                     # error log used internally
112 DBG="/tmp/debuglog"                   # debug log when passed -d
113 RUN="/run/archiso/bootmnt/arch/boot"  # path for live /boot
114 VM="$(dmesg | grep -i "hypervisor")"  # is the system a vm
115 SYS='Unknown'
116
117 export DIALOGOPTS="--cr-wrap"
118
119 # }
120
121 # giant ugly variable container :P {
122
123 # system RAM in MB
124 SYS_MEM="$(awk '/MemTotal/ {print int($2 / 1024) "M"}' /proc/meminfo)"
125
126 # locales from /etc/locale.gen
127 LOCALES="$(awk '/\.UTF-8/ {gsub(/# .*|#/, ""); if ($1) {print $1 " - "}}' /etc/locale.gen)"
128
129 # console keyboard mappings
130 CMAPS="$(find /usr/share/kbd/keymaps -name '*.map.gz' | awk '{gsub(/\.map\.gz|.*\//, ""); print $1 " - "}' | sort)"
131
132 # terminal size
133 [[ $LINES ]] || LINES=$(tput lines)
134 [[ $COLUMNS ]] || COLUMNS=$(tput cols)
135 SHL=$((LINES - 20))
136
137 # associative arrays
138 # {
139
140 # commands used to install each bootloader, however most get modified during runtime {
141 declare -A BCMDS=(
142 [refind-efi]='refind-install'                    # minor modification
143 [grub]='grub-install --recheck --force'          # heavily modified
144 [syslinux]='syslinux-install_update -i -a -m'    # modified on UEFI
145 [efistub]='efibootmgr -v -d /dev/sda -p 1 -c -l' # heavily modified
146 [systemd-boot]='bootctl --path=/boot install'    # not modified
147 ) # }
148
149 # executable name for each wm/de used in ~/.xinitrc {
150 declare -A WM_SESSIONS=(
151 [dwm]='dwm'
152 [i3-gaps]='i3'
153 [bspwm]='bspwm'
154 [awesome]='awesome'
155 [plasma]='startkde'
156 [xfce4]='startxfce4'
157 [gnome]='gnome-session'
158 [fluxbox]='startfluxbox'
159 [openbox]='openbox-session'
160 [cinnamon]='cinnamon-session'
161 ) # }
162
163 # packages installed for each wm/de, most are depends of the skel packages {
164 declare -A WM_EXT=(
165 [dwm]=''                                    # NA
166 [gnome]=''                                  # NA
167 [awesome]=''                                # NA
168 [plasma]='kdebase-meta'                     # base plasma application set
169 [bspwm]='archlabs-skel-bspwm'               # see deps of archlabs-skel-bspwm
170 [fluxbox]='archlabs-skel-fluxbox'           # see deps of archlabs-skel-fluxbox
171 [i3-gaps]='archlabs-skel-i3-gaps'           # see deps of archlabs-skel-i3-gaps
172 [openbox]='archlabs-skel-openbox'           # see deps of archlabs-skel-openbox
173 [xfce4]='archlabs-skel-xfce4 xfce4-goodies' # see deps of archlabs-skel-xfce4
174 ) # }
175
176 # files offered for editing after install is complete {
177 declare -A EDIT_FILES=(
178 [login]='' # login is populated once we know the username and shell
179 [fstab]='/etc/fstab'
180 [sudoers]='/etc/sudoers'
181 [crypttab]='/etc/crypttab'
182 [pacman]='/etc/pacman.conf'
183 [console]='/etc/vconsole.conf'
184 [mkinitcpio]='/etc/mkinitcpio.conf'
185 [hostname]='/etc/hostname /etc/hosts'
186 [bootloader]="/boot/loader/entries/$DIST.conf"
187 [locale]='/etc/locale.conf /etc/default/locale'
188 [keyboard]='/etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard'
189 ) # }
190
191 # mkfs command and flags for filesystem formatting {
192 declare -A FS_CMDS=(
193 [f2fs]='mkfs.f2fs'
194 [jfs]='mkfs.jfs -q'
195 [xfs]='mkfs.xfs -f'
196 [ntfs]='mkfs.ntfs -q'
197 [ext2]='mkfs.ext2 -q'
198 [ext3]='mkfs.ext3 -q'
199 [ext4]='mkfs.ext4 -q'
200 [vfat]='mkfs.vfat -F32'
201 [nilfs2]='mkfs.nilfs2 -q'
202 [reiserfs]='mkfs.reiserfs -q'
203 ) # }
204
205 # mount options for each filesystem {
206 declare -A FS_OPTS=(
207 [vfat]=''  # NA
208 [ntfs]=''  # NA
209 [ext2]=''  # NA
210 [ext3]=''  # NA
211 [jfs]='discard errors=continue errors=panic nointegrity'
212 [reiserfs]='acl nolog notail replayonly user_xattr off'
213 [ext4]='discard dealloc nofail noacl relatime noatime nobarrier nodelalloc'
214 [xfs]='discard filestreams ikeep largeio noalign nobarrier norecovery noquota wsync'
215 [nilfs2]='discard nobarrier errors=continue errors=panic order=relaxed order=strict norecovery'
216 [f2fs]='discard fastboot flush_merge data_flush inline_xattr inline_data noinline_data inline_dentry no_heap noacl nobarrier norecovery noextent_cache disable_roll_forward disable_ext_identify'
217 ) # }
218
219 # packages installed for each login option {
220 declare -A LOGIN_PKGS=(
221 [xinit]='xorg-xinit'
222 [lightdm]='lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings accountsservice'
223 ) # }
224
225 # extras installed for user selected packages {
226 # if a package requires additional packages that aren't already dependencies
227 # they can be added here eg. [package]="extra"
228 declare -A PKG_EXT=(
229 [vlc]='qt4'
230 [mpd]='mpc'
231 [mupdf]='mupdf-tools'
232 [qt5ct]='qt5-styleplugins'
233 [vlc]='qt5ct qt5-styleplugins'
234 [zathura]='zathura-pdf-poppler'
235 [noto-fonts]='noto-fonts-emoji'
236 [cairo-dock]='cairo-dock-plug-ins'
237 [qutebrowser]='qt5ct qt5-styleplugins'
238 [qbittorrent]='qt5ct qt5-styleplugins'
239 [transmission-qt]='qt5ct qt5-styleplugins'
240 [kdenlive]='kdebase-meta dvdauthor frei0r-plugins breeze breeze-gtk qt5ct qt5-styleplugins'
241 ) # }
242
243 # }
244
245 # dialog text variables
246 # {
247
248 # Basics (somewhat in order)
249 _keymap="\nPick which keymap to use for the system from the list below\n\nThis is used once a graphical environment is running (Xorg).\n\nSystem default: us"
250 _vconsole="\nSelect the console keymap, the console is the tty shell you reach before starting a graphical environment (Xorg).\n\nIts keymap is seperate from the one used by the graphical environments, though many do use the same such as 'us' English.\n\nSystem default: us"
251 _device="\nSelect a device to use from the list below.\n\nDevices (/dev) are the available drives on the system. /sda, /sdb, /sdc ..."
252 _resize="\nSelect a new filesystem size in MB, a new partition will be created from the free space but will be left unformatted.\nThe lowest size is just enough to fit the currently in use space on the partition while the default is set to split the free space evenly.\n\nUse Tab or the arrow keys move the cursor between the buttons and the value, when the cursor is on the value, you can edit it by:\n\n - left/right cursor movement to select a digit to modify\n - +/-  characters to increment/decrement the digit by one\n - 0 through 9 to set the digit to the given value\n\nSome keys are also recognized in all cursor positions:\n\n - Home/End set the value to its maximum or minimum\n - Pageup/Pagedown increment the value so that the slider moves by one column."
253 _mount="\nUse [Space] to toggle mount options from below, press [Enter] when done to confirm selection.\n\nNot selecting any and confirming will run an automatic mount."
254 _warn="\nIMPORTANT:\n\nChoose carefully when editing, formatting, and mounting partitions or your DATA MAY BE LOST.\n\nTo mount a partition without formatting it, select 'skip' when prompted to choose a filesystem during the mounting stage.\nThis can only be used for partitions that already contain a filesystem and cannot be the root (/) partition, it needs to be formatted before install.\n"
255 _part="\nFull device auto partitioning is available for beginners otherwise cfdisk is recommended.\n\n  - All systems will require a root partition (8G or greater).\n  - UEFI (and BIOS using LUKS without LVM) require a separate boot partition (100-512M)."
256 _uefi="\nSelect the EFI boot partition (/boot), required for UEFI boot.\n\nIt's usually the first partition on the device, 100-512M, and will be formatted as vfat/fat32 if not already."
257 _bios="\nDo you want to use a separate boot partition? (optional)\n\nIt's usually the first partition on the device, 100-512M, and will be formatted as ext3/4 if not already."
258 _biosluks="\nSelect the boot partition (/boot), required for LUKS.\n\nIt's usually the first partition on the device, 100-512M, and will be formatted as ext3/4 if not already."
259 _format="is already formatted correctly.\n\nFor a clean install, previously existing partitions should be reformatted, however this removes ALL data (bootloaders) on the partition so choose carefully.\n\nDo you want to reformat the partition?\n"
260 _swapsize="\nEnter the size of the swapfile in megabytes (M) or gigabytes (G).\n\neg. 100M will create a 100 megabyte swapfile, while 10G will create a 10 gigabyte swapfile.\n\nFor ease of use and as an example it is filled in to match the size of your system memory (RAM).\n\nMust be greater than 1, contain only whole numbers, and end with either M or G."
261 _expart="\nYou can now choose any additional partitions you want mounted, you'll be asked for a mountpoint after.\n\nSelect 'done' to finish the mounting step and begin unpacking the base system in the background."
262 _exmnt="\nWhere do you want the partition mounted?\n\nEnsure the name begins with a slash (/).\nExamples include: /usr, /home, /var, etc."
263 _user="\nEnter a name and password for the new user account.\n\nThe name must not use capital letters, contain any periods (.), end with a hyphen (-), or include any colons (:)\n\nNOTE: Use [Up], [Down], or [Tab] to switch between fields, and [Enter] to accept."
264 _hostname="\nEnter a hostname for the new system.\n\nA hostname is used to identify systems on the network.\n\nIt's restricted to alphanumeric characters (a-z, A-Z, 0-9).\nIt can contain hyphens (-) BUT NOT at the beggining or end."
265 _locale="\nLocale determines the system language and currency formats.\n\nThe format for locale names is languagecode_COUNTRYCODE\n\neg. en_US is: english United States\n    en_GB is: english Great Britain"
266 _timez="\nThe time zone is used to set the system clock.\n\nSelect your country or continent from the list below"
267 _timesubz="\nSelect the nearest city to you or one with the same time zone.\n\nTIP: Pressing the first letter of the city name repeatedly will navigate between entries beggining with that letter."
268 _sessions="\nUse [Space] to toggle available sessions, use [Enter] to accept the selection and continue.\n\nA basic package set will be installed for compatibility and functionality."
269 _login="\nSelect which of your session choices to use for the initial login.\n\nYou can be change this later by editing your ~/.xinitrc"
270 _autologin="\nDo you want autologin enabled for USER?\n\nIf so the following two files will be created (disable autologin by removing them):\n\n - /home/USER/RC (run startx when logging in on tty1)\n - /etc/systemd/system/getty@tty1.service.d/autologin.conf (login USER without password)\n"
271 _packages="\nUse [Space] to move a package into the selected area and press [Enter] to accept the selection.\n\nPackages may be installed by your DE/WM (if any), or for the packages you select."
272 _edit="\nBefore exiting you can select configuration files to review/change.\n\nIf you need to make other changes with the drives still mounted, use Ctrl-z to pause the installer, when finished type 'fg' and [Enter] or Ctrl-z again to resume the installer, if you want to avoid the automatic reboot using Ctrl-c will cleanly exit."
273
274 # LUKS
275 _luksnew="Basic LUKS Encryption"
276 _luksadv="Advanced LUKS Encryption"
277 _luksopen="Open Existing LUKS Partition"
278 _luksmenu="\nA seperate boot partition without encryption or logical volume management (LVM) is required (except BIOS systems using grub).\n\nBasic uses the default encryption settings, and is recommended for beginners. Advanced allows cypher and key size parameters to be entered manually."
279 _luksomenu="\nEnter a name and password for the encrypted device.\n\nIt is not necessary to prefix the name with /dev/mapper/,an example has been provided."
280 _lukskey="Once the specified flags have been amended, they will automatically be used with the 'cryptsetup -q luksFormat /dev/...' command.\n\nNOTE: Do not specify any additional flags such as -v (--verbose) or -y (--verify-passphrase)."
281
282 # LVM
283 _lvmmenu="\nLogical volume management (LVM) allows 'virtual' hard drives (volume groups) and partitions (logical volumes) to be created from existing device partitions.\n\nA volume group must be created first, then one or more logical volumes within it.\n\nLVM can also be used with an encrypted partition to create multiple logical volumes (e.g. root and home) within it."
284 _lvmnew="Create Volume Group and Volume(s)"
285 _lvmdel="Delete an Existing Volume Group"
286 _lvmdelall="Delete ALL Volume Group(s) and Volume(s)"
287 _lvmvgname="\nEnter a name for the volume group (VG) being created from the partition(s) selected."
288 _lvmlvname="\nEnter a name for the logical volume (LV) being created.\n\nThis is similar to setting a label for a partition."
289 _lvmlvsize="\nEnter what size you want the logical volume (LV) to be in megabytes (M) or gigabytes (G).\n\neg. 100M will create a 100 megabyte volume, 10G will create a 10 gigabyte volume."
290 _lvmdelask="\nConfirm deletion of volume group(s) and logical volume(s).\n\nDeleting a volume group, will delete all logical volumes within it.\n"
291
292 # Errors
293 _errexpart="\nCannot mount partition due to a problem with the mountpoint.\n\nEnsure it begins with a slash (/) followed by atleast one character.\n"
294 _errpart="\nYou need create the partiton(s) first.\n\n\nBIOS systems require at least one partition (ROOT).\n\nUEFI systems require at least two (ROOT and EFI).\n"
295 _lukserr="\nA minimum of two partitions are required for encryption:\n\n 1. root (/) - standard or LVM.\n 2. boot (/boot) - standard (unless using LVM on BIOS systems).\n"
296 _lvmerr="\nThere are no viable partitions available to use for LVM, a minimum of one is required.\n\nIf LVM is already in use, deactivating it will allow the partition(s) to be used again.\n"
297 _lvmerrvgname="\nInvalid name entered.\n\nThe volume group name may be alpha-numeric, but may not contain spaces, start with a '/', or already be in use.\n"
298 _lvmerlvname="\nInvalid name entered.\n\nThe logical volume (LV) name may be alpha-numeric, but may not contain spaces or be preceded with a '/'\n"
299 _lvmerrlvsize="\nInvalid value Entered.\n\nMust be a numeric value with 'M' (megabytes) or 'G' (gigabytes) at the end.\n\neg. 400M, 10G, 250G, etc...\n\nThe value may also not be equal to or greater than the remaining size of the volume group.\n"
300
301 # }
302
303 # }
304
305 ###############################################################################
306 # selection menus
307 # main is the entry point which calls functions including outside of its block
308 # once those functions finished they always are returned here with the
309 # exception of install_main(), it exits upon completion
310
311 main()
312 {
313         (( SEL < 12 )) && (( SEL++ ))
314         tput civis
315         dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " Prepare " --default-item $SEL --cancel-label 'Exit' --menu "$_prep" 0 0 0 \
316                 1 "Device tree (optional)" \
317                 2 "Partitioning (optional)" \
318                 3 "LUKS setup (optional)" \
319                 4 "LVM setup (optional)" \
320                 5 "Mount partitions" \
321                 6 "System bootloader" \
322                 7 "User and password" \
323                 8 "System configuration" \
324                 9 "Select WM/DE (optional)" \
325                 10 "Select Packages (optional)" \
326                 11 "View configuration (optional)" \
327                 12 "Start the installation" 2>"$ANS"
328
329         read -r SEL < "$ANS"
330         [[ -z $WARN && $SEL =~ (2|5) ]] && { msg "Data Warning" "$_warn"; WARN=true; }
331         case $SEL in
332                 1) part_show ;;
333                 2) part_menu || (( SEL-- )) ;;
334                 3) luks_menu || (( SEL-- )) ;;
335                 4) lvm_menu || (( SEL-- )) ;;
336                 5) mount_menu || (( SEL-- )) ;;
337                 6) prechecks 0 && { select_boot || (( SEL-- )); } ;;
338                 7) prechecks 1 && { select_mkuser || (( SEL-- )); } ;;
339                 8) prechecks 2 && { select_config || (( SEL-- )); } ;;
340                 9) prechecks 3 && { select_sessions || (( SEL-- )); } ;;
341                 10) prechecks 3 && { select_packages || (( SEL-- )); } ;;
342                 11) prechecks 3 && select_show ;;
343                 12) prechecks 3 && install_main ;;
344                 *) yesno "Exit" "\nUnmount partitions (if any) and exit the installer?\n" && die 0
345         esac
346 }
347
348 select_boot()
349 {
350         if [[ $SYS == 'BIOS' ]]; then
351                 dlg BOOTLDR menu "BIOS Bootloader" "\nSelect which bootloader to use." \
352                         "grub"     "The Grand Unified Bootloader, standard among many Linux distributions" \
353                         "syslinux" "A collection of boot loaders for booting drives, CDs, or over the network" || return 1
354         else
355                 dlg BOOTLDR menu "UEFI Bootloader" "\nSelect which bootloader to use." \
356                         "systemd-boot" "A simple UEFI boot manager which executes configured EFI images" \
357                         "grub"         "The Grand Unified Bootloader, standard among many Linux distributions" \
358                         "refind-efi"   "A UEFI boot manager that aims to be platform neutral and simplify multi-boot" \
359                         "efistub"      "Boot the kernel image directly (no chainloading support)" \
360                         "syslinux"     "A collection of boot loaders for booting drives, CDs, or over the network (no chainloading support)" || return 1
361         fi
362         setup_${BOOTLDR}
363 }
364
365 select_show()
366 {
367         local mnt="none"
368         local cmd="${BCMDS[$BOOTLDR]}"
369         local pkgs="${USER_PKGS//  / } ${PACKAGES//  / }"
370         local exmnt="${EXMNT:-none}"
371         [[ $BOOT_PART ]] && mnt="/$BOOTDIR"
372         [[ $INSTALL_WMS == *dwm* ]] && pkgs="dwm st dmenu $pkgs"
373         pkgs="${pkgs//  / }"
374         msg "Show Configuration" "
375
376 ---------- PARTITION CONFIGURATION ------------
377
378   Root:  ${ROOT_PART:-none}
379   Boot:  ${BOOT_PART:-${BOOT_DEV:-none}}
380   Swap:  ${SWAP_PART:-none}
381   Size:  ${SWAP_SIZE:-none}
382   Extra: ${EXMNTS:-$exmnt}
383   Hooks: ${HOOKS:-none}
384
385   LVM:   ${LVM:-none}
386   LUKS:  ${LUKS:-none}
387
388 ---------- BOOTLOADER CONFIGURATION -----------
389
390   Bootloader: ${BOOTLDR:-none}
391   Mountpoint: ${mnt:-none}
392   Command:    ${cmd:-none}
393
394
395 ------------ SYSTEM CONFIGURATION -------------
396
397   Locale:   ${MYLOCALE:-none}
398   Keymap:   ${KEYMAP:-none}
399   Hostname: ${MYHOST:-none}
400   Timezone: ${ZONE:-none}/${SUBZ:-none}
401
402
403 ------------ USER CONFIGURATION --------------
404
405   User:        ${NEWUSER:-none}
406   Shell:       ${MYSHELL:-none}
407   Session:     ${LOGIN_WM:-none}
408   Autologin:   ${AUTOLOGIN:-none}
409   DM or xinit: ${LOGIN_TYPE:-none}
410
411
412 ------------ PACKAGES AND MIRRORS -------------
413
414   Kernel:   ${KERNEL:-none}
415   Sessions: ${INSTALL_WMS:-none}
416   Packages: ${USER_PKGS//  / } ${PACKAGES//  / }
417 "
418 }
419
420 select_login()
421 {
422         [[ $INSTALL_WMS ]] || return 0
423
424         if [[ -z $LOGIN_TYPE ]]; then
425                 dlg LOGIN_TYPE menu "Login Management" "\nSelect what kind of login management to use." \
426                         "xinit"   "Console login without a display manager" \
427                         "lightdm" "Lightweight display manager with a gtk greeter" || return 1
428         fi
429
430         if [[ $LOGIN_TYPE == 'lightdm' ]]; then
431                 AUTOLOGIN=''
432                 EDIT_FILES[login]="/etc/lightdm/lightdm.conf /etc/lightdm/lightdm-gtk-greeter.conf"
433         else
434                 # define what wm/de to use for xinit
435                 if (( $(wc -w <<< "$INSTALL_WMS") > 1 )); then
436                         dlg LOGIN_WM menu "Login Management" "$_login" $LOGIN_CHOICES || return 1
437                         LOGIN_WM="${WM_SESSIONS[$LOGIN_WM]}"
438                 fi
439                 [[ -z $LOGIN_WM ]] && LOGIN_WM="${WM_SESSIONS[${INSTALL_WMS%% *}]}"
440
441                 # autologin
442                 yesno "Autologin" "$(sed "s|USER|$NEWUSER|g; s|RC|$LOGINRC|g" <<< "$_autologin")" && AUTOLOGIN=true || AUTOLOGIN=''
443
444                 EDIT_FILES[login]="/home/$NEWUSER/.xinitrc /home/$NEWUSER/.xprofile"
445         fi
446 }
447
448 select_config()
449 {
450         typeset -i i=0
451         CONFIG_DONE=''
452
453         until [[ $CONFIG_DONE ]]; do
454                 case $i in
455                         0) dlg MYSHELL menu "Shell" "\nChoose which shell to use." \
456                                         /usr/bin/zsh  'A very advanced and programmable command interpreter (shell) for UNIX' \
457                                         /bin/bash     'The GNU Bourne Again shell, standard in many GNU/Linux distributions' \
458                                         /usr/bin/mksh 'The MirBSD Korn Shell - an enhanced version of the public domain ksh' || return 1
459                                 ;;
460                         1) dlg MYHOST input "Hostname" "$_hostname" "${DIST,,}" limit || { i=0; continue; } ;;
461                         2) dlg MYLOCALE menu "Locale" "$_locale" $LOCALES || { i=1; continue; } ;;
462                         3) ZONE='' SUBZ=''
463                                 until [[ $ZONE && $SUBZ ]]; do
464                                         dlg ZONE menu "Timezone" "$_timez" America - Australia - Asia - Atlantic - Africa - Europe - Indian - Pacific - Arctic - Antarctica - || break
465                                         dlg SUBZ menu "Timezone" "$_timesubz" $(awk '/'"$ZONE"'\// {gsub(/'"$ZONE"'\//, ""); print $3 " - "}' /usr/share/zoneinfo/zone.tab | sort) || continue
466                                         yesno "Timezone" "\nConfirm time zone: $ZONE/$SUBZ\n" || unset ZONE
467                                 done
468                                 [[ $ZONE && $SUBZ ]] || { i=2; continue; } ;;
469                         4) dlg KERNEL menu "Kernel" "\nChoose which kernel to use." \
470                                         linux          'Vanilla linux kernel and modules, with a few patches applied' \
471                                         linux-lts      'Long-term support (LTS) linux kernel and modules' \
472                                         linux-zen      'A effort of kernel hackers to provide the best kernel for everyday systems' \
473                                         linux-hardened 'A security-focused linux kernel with hardening patches to mitigate exploits' || { i=3; continue; }
474                                 CONFIG_DONE=true
475                                 ;;
476                 esac
477                 (( i++ )) # progress through to the next choice
478         done
479
480         case $MYSHELL in
481                 '/bin/bash') LOGINRC='.bash_profile' ;;
482                 '/usr/bin/zsh') LOGINRC='.zprofile' ;;
483                 '/usr/bin/mksh') LOGINRC='.profile' ;;
484         esac
485
486         return 0
487 }
488
489 select_mkuser()
490 {
491         NEWUSER=''
492         local v='' u='' p='' p2='' rp='' rp2=''
493
494         until [[ $NEWUSER ]]; do
495                 i=0
496                 tput cnorm
497                 dialog --insecure --backtitle "$DIST Installer - $SYS - v$VER" --separator $'\n' --title " User " --mixedform "$_user" 0 0 0 \
498                         "Username:"  1 1 "$u" 1 11 "$COLUMNS" 0 0 \
499                         "Password:"  2 1 ''   2 11 "$COLUMNS" 0 1 \
500                         "Password2:" 3 1 ''   3 12 "$COLUMNS" 0 1 \
501                         "--- Root password, if left empty the user password will be used ---" 6 1 '' 6 68 "$COLUMNS" 0 2 \
502                         "Password:"  8 1 ''   8 11 "$COLUMNS" 0 1 \
503                         "Password2:" 9 1 ''   9 12 "$COLUMNS" 0 1 2>"$ANS" || return 1
504
505                 while read -r line; do
506                         case $i in 0) u="$line" ;; 1) p="$line" ;; 2) p2="$line" ;; 3) rp="$line" ;; 4) rp2="$line" ;; esac
507                         (( i++ ))
508                 done < "$ANS"
509
510                 # root passwords empty, so use the user passwords
511                 [[ -z $rp && -z $rp2 ]] && { rp="$p"; rp2="$p2"; }
512
513                 # make sure a username was entered and that the passwords match
514                 if [[ ${#u} -eq 0 || $u =~ \ |\' || $u =~ [^a-z0-9] ]]; then
515                         msg "Invalid Username" "\nIncorrect user name.\n\nPlease try again.\n"; u=''
516                 elif [[ -z $p ]]; then
517                         msg "Empty Password" "\nThe user password cannot be left empty.\n\nPlease try again.\n"
518                 elif [[ "$p" != "$p2" ]]; then
519                         msg "Password Mismatch" "\nThe user passwords do not match.\n\nPlease try again.\n"
520                 elif [[ "$rp" != "$rp2" ]]; then
521                         msg "Password Mismatch" "\nThe root passwords do not match.\n\nPlease try again.\n"
522                 else
523                         NEWUSER="$u"
524                         USER_PASS="$p"
525                         ROOT_PASS="$rp"
526                 fi
527         done
528         return 0
529 }
530
531 select_keymap()
532 {
533         dlg KEYMAP menu "Keyboard Layout" "$_keymap" \
534                 us English    cm    English     gb English    au English    gh English \
535                 za English    ng    English     ca French    'cd' French    gn French \
536                 tg French     fr    French      de German     at German     ch German \
537                 es Spanish    latam Spanish     br Portuguese pt Portuguese ma Arabic \
538                 sy Arabic     ara   Arabic      ua Ukrainian  cz Czech      ru Russian \
539                 sk Slovak     nl    Dutch       it Italian    hu Hungarian  cn Chinese \
540                 tw Taiwanese  vn    Vietnamese  kr Korean     jp Japanese   th Thai \
541                 la Lao        pl    Polish      se Swedish    is Icelandic 'fi' Finnish \
542                 dk Danish     be    Belgian     in Indian     al Albanian   am Armenian \
543                 bd Bangla     ba    Bosnian    'bg' Bulgarian dz Berber     mm Burmese \
544                 hr Croatian   gr    Greek       il Hebrew     ir Persian    iq Iraqi \
545                 af Afghani    fo    Faroese     ge Georgian   ee Estonian   kg Kyrgyz \
546                 kz Kazakh     lt    Lithuanian  mt Maltese    mn Mongolian  ro Romanian \
547                 no Norwegian  rs    Serbian     si Slovenian  tj Tajik      lk Sinhala \
548                 tr Turkish    uz    Uzbek       ie Irish      pk Urdu      'mv' Dhivehi \
549                 np Nepali     et    Amharic     sn Wolof      ml Bambara    tz Swahili \
550                 ke Swahili    bw    Tswana      ph Filipino   my Malay      tm Turkmen \
551                 id Indonesian bt    Dzongkha    lv Latvian    md Moldavian mao Maori \
552                 by Belarusian az    Azerbaijani mk Macedonian kh Khmer     epo Esperanto \
553                 me Montenegrin || return 1
554
555         if [[ $CMAPS == *"$KEYMAP"* ]]; then
556                 CMAP="$KEYMAP"
557         else
558                 dlg CMAP menu "Console Keymap" "$_vconsole" $CMAPS || return 1
559         fi
560
561         if [[ $DISPLAY && $TERM != 'linux' ]]; then
562                 setxkbmap "$KEYMAP" >/dev/null 2>&1
563         else
564                 loadkeys "$CMAP" >/dev/null 2>&1
565         fi
566
567         return 0
568 }
569
570 select_sessions()
571 {
572         LOGIN_CHOICES=''
573
574         dlg INSTALL_WMS check "Sessions" "$_sessions\n" \
575                 i3-gaps "A fork of i3wm with more features including gaps" "$(ofn i3-gaps "${INSTALL_WMS[*]}")" \
576                 openbox "A lightweight, powerful, and highly configurable stacking wm" "$(ofn openbox "${INSTALL_WMS[*]}")" \
577                 awesome "A customized Awesome WM session created by @elanapan" "$(ofn awesome "${INSTALL_WMS[*]}")" \
578                 dwm "A dynamic WM for X that manages windows in tiled, floating, or monocle layouts" "$(ofn dwm "${INSTALL_WMS[*]}")" \
579                 bspwm "A tiling wm that represents windows as the leaves of a binary tree" "$(ofn bspwm "${INSTALL_WMS[*]}")" \
580                 fluxbox "A lightweight and highly-configurable window manager" "$(ofn fluxbox "${INSTALL_WMS[*]}")" \
581                 gnome "A desktop environment that aims to be simple and easy to use" "$(ofn gnome "${INSTALL_WMS[*]}")" \
582                 cinnamon "A desktop environment combining traditional desktop with modern effects" "$(ofn cinnamon "${INSTALL_WMS[*]}")" \
583                 plasma "A kde software project currently comprising a full desktop environment" "$(ofn plasma "${INSTALL_WMS[*]}")" \
584                 xfce4 "A lightweight and modular desktop environment based on gtk+2/3" "$(ofn xfce4 "${INSTALL_WMS[*]}")"
585
586         [[ $INSTALL_WMS ]] || return 0
587
588         WM_PKGS="${INSTALL_WMS/dwm/}" # remove dwm from package list
589         WM_PKGS="${WM_PKGS//  / }"    # remove double spaces
590         WM_PKGS="${WM_PKGS# }"        # remove leading space
591
592         # add archlabs base when choosing any session
593         [[ $WM_PKGS ]] && WM_PKGS+=" $AL_BASE_PKGS"
594
595         for i in $INSTALL_WMS; do
596                 LOGIN_CHOICES+="$i - "
597                 [[ ${WM_EXT[$i]} && $WM_PKGS != *"${WM_EXT[$i]}"* ]] && WM_PKGS+=" ${WM_EXT[$i]}"
598         done
599
600         select_login || return 1
601
602         while IFS=' ' read -r pkg; do
603                 [[ $PACKAGES != *"$pkg"* ]] && PACKAGES+=" $pkg"
604         done <<< "$WM_PKGS"
605
606         return 0
607 }
608
609 select_packages()
610 {
611         dlg USER_PKGS check " Packages " "$_packages" \
612                 abiword "A Fully-featured word processor" "$(ofn abiword "${USER_PKGS[*]}")" \
613                 alacritty "A cross-platform, GPU-accelerated terminal emulator" "$(ofn alacritty "${USER_PKGS[*]}")" \
614                 atom "An open-source text editor developed by GitHub" "$(ofn atom "${USER_PKGS[*]}")" \
615                 audacious "A free and advanced audio player based on GTK+" "$(ofn audacious "${USER_PKGS[*]}")" \
616                 audacity "A program that lets you manipulate digital audio waveforms" "$(ofn audacity "${USER_PKGS[*]}")" \
617                 cairo-dock "Light eye-candy fully themable animated dock" "$(ofn cairo-dock "${USER_PKGS[*]}")" \
618                 calligra "A set of applications for productivity" "$(ofn calligra "${USER_PKGS[*]}")" \
619                 chromium "An open-source web browser based on the Blink rendering engine" "$(ofn chromium "${USER_PKGS[*]}")" \
620                 clementine "A modern music player and library organizer" "$(ofn clementine "${USER_PKGS[*]}")" \
621                 cmus "A small, fast and powerful console music player" "$(ofn cmus "${USER_PKGS[*]}")" \
622                 deadbeef "A GTK+ audio player for GNU/Linux" "$(ofn deadbeef "${USER_PKGS[*]}")" \
623                 deluge "A BitTorrent client written in python" "$(ofn deluge "${USER_PKGS[*]}")" \
624                 docky "Full fledged dock for opening applications and managing windows" "$(ofn docky "${USER_PKGS[*]}")" \
625                 emacs "An extensible, customizable, self-documenting real-time display editor" "$(ofn emacs "${USER_PKGS[*]}")" \
626                 epiphany "A GNOME web browser based on the WebKit rendering engine" "$(ofn epiphany "${USER_PKGS[*]}")" \
627                 evince "A document viewer" "$(ofn evince "${USER_PKGS[*]}")" \
628                 evolution "Manage your email, contacts and schedule" "$(ofn evolution "${USER_PKGS[*]}")" \
629                 file-roller "Create and modify archives" "$(ofn file-roller "${USER_PKGS[*]}")" \
630                 firefox "A popular open-source web browser from Mozilla" "$(ofn firefox "${USER_PKGS[*]}")" \
631                 gcolor2 "A simple GTK+2 color selector" "$(ofn gcolor2 "${USER_PKGS[*]}")" \
632                 geany "A fast and lightweight IDE" "$(ofn geany "${USER_PKGS[*]}")" \
633                 geary "A lightweight email client for the GNOME desktop" "$(ofn geary "${USER_PKGS[*]}")" \
634                 gimp "GNU Image Manipulation Program" "$(ofn gimp "${USER_PKGS[*]}")" \
635                 gnome-disk-utility "Disk Management Utility" "$(ofn gnome-disk-utility "${USER_PKGS[*]}")" \
636                 gnome-system-monitor "View current processes and monitor system state" "$(ofn gnome-system-monitor "${USER_PKGS[*]}")" \
637                 gparted "A GUI frontend for creating and manipulating partition tables" "$(ofn gparted "${USER_PKGS[*]}")" \
638                 gpick "Advanced color picker using GTK+ toolkit" "$(ofn gpick "${USER_PKGS[*]}")" \
639                 gpicview "Lightweight image viewer" "$(ofn gpicview "${USER_PKGS[*]}")" \
640                 guvcview "Capture video from camera devices" "$(ofn guvcview "${USER_PKGS[*]}")" \
641                 hexchat "A popular and easy to use graphical IRC client" "$(ofn hexchat "${USER_PKGS[*]}")" \
642                 inkscape "Professional vector graphics editor" "$(ofn inkscape "${USER_PKGS[*]}")" \
643                 irssi "Modular text mode IRC client" "$(ofn irssi "${USER_PKGS[*]}")" \
644                 kdenlive "A popular non-linear video editor for Linux" "$(ofn kdenlive "${USER_PKGS[*]}")" \
645                 krita "Edit and paint images" "$(ofn krita "${USER_PKGS[*]}")" \
646                 libreoffice-fresh "Full featured office suite" "$(ofn libreoffice-fresh "${USER_PKGS[*]}")" \
647                 lollypop "A new music playing application" "$(ofn lollypop "${USER_PKGS[*]}")" \
648                 mousepad "A simple text editor" "$(ofn mousepad "${USER_PKGS[*]}")" \
649                 mpd "A flexible, powerful, server-side application for playing music" "$(ofn mpd "${USER_PKGS[*]}")" \
650                 mpv "A media player based on mplayer" "$(ofn mpv "${USER_PKGS[*]}")" \
651                 mupdf "Lightweight PDF and XPS viewer" "$(ofn mupdf "${USER_PKGS[*]}")" \
652                 mutt "Small but very powerful text-based mail client" "$(ofn mutt "${USER_PKGS[*]}")" \
653                 nautilus "The default file manager for Gnome" "$(ofn nautilus "${USER_PKGS[*]}")" \
654                 ncmpcpp "A mpd client and almost exact clone of ncmpc with some new features" "$(ofn ncmpcpp "${USER_PKGS[*]}")" \
655                 neovim "A fork of Vim aiming to improve user experience, plugins, and GUIs." "$(ofn neovim "${USER_PKGS[*]}")" \
656                 nicotine+ "A graphical client for Soulseek" "$(ofn nicotine+ "${USER_PKGS[*]}")" \
657                 noto-fonts "Google Noto fonts" "$(ofn noto-fonts "${USER_PKGS[*]}")" \
658                 noto-fonts-cjk "Google Noto CJK fonts (Chinese, Japanese, Korean)" "$(ofn noto-fonts-cjk "${USER_PKGS[*]}")" \
659                 obs-studio "Free opensource streaming/recording software" "$(ofn obs-studio "${USER_PKGS[*]}")" \
660                 openshot "An open-source, non-linear video editor for Linux" "$(ofn openshot "${USER_PKGS[*]}")" \
661                 opera "A Fast and secure, free of charge web browser from Opera Software" "$(ofn opera "${USER_PKGS[*]}")" \
662                 pcmanfm "A fast and lightweight file manager based in Lxde" "$(ofn pcmanfm "${USER_PKGS[*]}")" \
663                 pidgin "Multi-protocol instant messaging client" "$(ofn pidgin "${USER_PKGS[*]}")" \
664                 plank "An elegant, simple, and clean dock" "$(ofn plank "${USER_PKGS[*]}")" \
665                 qbittorrent "An advanced BitTorrent client" "$(ofn qbittorrent "${USER_PKGS[*]}")" \
666                 qpdfview "A tabbed PDF viewer" "$(ofn qpdfview "${USER_PKGS[*]}")" \
667                 qt5ct "GUI for managing Qt based application themes, icons, and fonts" "$(ofn qt5ct "${USER_PKGS[*]}")" \
668                 qutebrowser "A keyboard-focused vim-like web browser based on Python and PyQt5" "$(ofn qutebrowser "${USER_PKGS[*]}")" \
669                 rhythmbox "A Music playback and management application" "$(ofn rhythmbox "${USER_PKGS[*]}")" \
670                 rxvt-unicode "A unicode enabled rxvt-clone terminal emulator" "$(ofn rxvt-unicode "${USER_PKGS[*]}")" \
671                 sakura "A terminal emulator based on GTK and VTE" "$(ofn sakura "${USER_PKGS[*]}")" \
672                 simplescreenrecorder "A feature-rich screen recorder" "$(ofn simplescreenrecorder "${USER_PKGS[*]}")" \
673                 steam "A popular game distribution platform by Valve" "$(ofn steam "${USER_PKGS[*]}")" \
674                 surf "A simple web browser based on WebKit2/GTK+" "$(ofn surf "${USER_PKGS[*]}")" \
675                 terminator "Terminal emulator that supports tabs and grids" "$(ofn terminator "${USER_PKGS[*]}")" \
676                 termite "A minimal VTE-based terminal emulator" "$(ofn termite "${USER_PKGS[*]}")" \
677                 thunar "A modern file manager for the Xfce Desktop Environment" "$(ofn thunar "${USER_PKGS[*]}")" \
678                 thunderbird "Standalone mail and news reader from mozilla" "$(ofn thunderbird "${USER_PKGS[*]}")" \
679                 tilda "A GTK based drop down terminal for Linux and Unix" "$(ofn tilda "${USER_PKGS[*]}")" \
680                 tilix "A tiling terminal emulator for Linux using GTK+ 3" "$(ofn tilix "${USER_PKGS[*]}")" \
681                 transmission-cli "Free BitTorrent client CLI" "$(ofn transmission-cli "${USER_PKGS[*]}")" \
682                 transmission-gtk "Free BitTorrent client GTK+ GUI" "$(ofn transmission-gtk "${USER_PKGS[*]}")" \
683                 transmission-qt "Free BitTorrent client Qt GUI" "$(ofn transmission-qt "${USER_PKGS[*]}")" \
684                 ttf-anonymous-pro "A family fixed-width fonts designed with code in mind" "$(ofn ttf-anonymous-pro "${USER_PKGS[*]}")" \
685                 ttf-fira-code "Monospaced font with programming ligatures" "$(ofn ttf-fira-code "${USER_PKGS[*]}")" \
686                 ttf-font-awesome "Iconic font designed for Bootstrap" "$(ofn ttf-font-awesome "${USER_PKGS[*]}")" \
687                 ttf-hack "A hand groomed typeface based on Bitstream Vera Mono" "$(ofn ttf-hack "${USER_PKGS[*]}")" \
688                 vlc "A free and open source cross-platform multimedia player" "$(ofn vlc "${USER_PKGS[*]}")" \
689                 weechat "Fast, light and extensible IRC client" "$(ofn weechat "${USER_PKGS[*]}")" \
690                 xarchiver "A GTK+ frontend to various command line archivers" "$(ofn xarchiver "${USER_PKGS[*]}")" \
691                 xfce-terminal "A terminal emulator based in the Xfce Desktop Environment" "$(ofn xfce-terminal "${USER_PKGS[*]}")" \
692                 xterm "The standard terminal emulator for the X window system" "$(ofn xterm "${USER_PKGS[*]}")" \
693                 zathura "Minimalistic document viewer" "$(ofn zathura "${USER_PKGS[*]}")"
694
695         if [[ $USER_PKGS ]]; then
696                 for i in $USER_PKGS; do
697                         [[ ${PKG_EXT[$i]} && $USER_PKGS != *"${PKG_EXT[$i]}"* ]] && USER_PKGS+=" ${PKG_EXT[$i]}"
698                 done
699         fi
700
701         return 0
702 }
703
704 ###############################################################################
705 # partitioning menus
706 # non-essential partitioning helpers called by the user when using the optional
707 # partition menu and selecting a device to edit
708
709 part_menu()
710 {
711         is_bg_install || return 0
712         local device choice devhash
713         devhash="$(lsblk -f | base64)"
714         umount_dir $MNT
715         part_device || return 1
716         device="$DEVICE"
717
718         while :; do
719                 choice=""
720                 if [[ $DISPLAY && $TERM != 'linux' ]] && hash gparted >/dev/null 2>&1; then
721                         dlg choice menu "Edit Partitions" "$_part" \
722                                 "auto"    "Whole device automatic partitioning" \
723                                 "shrink"  "Shrink an existing ext2/3/4 or ntfs partition to make room for a new partition" \
724                                 "gparted" "GUI front end to parted" \
725                                 "cfdisk"  "Curses based variant of fdisk" \
726                                 "parted"  "GNU partition editor" \
727                                 "fdisk"   "Dialog-driven creation and manipulation of partitions" \
728                                 "done"    "Return to the main menu" || return 0
729                 else
730                         dlg choice menu "Edit Partitions" "$_part" \
731                                 "auto"   "Whole device automatic partitioning" \
732                                 "shrink" "Shrink an existing ext2/3/4 or ntfs partition to make room for a new partition" \
733                                 "cfdisk" "Curses based variant of fdisk" \
734                                 "parted" "GNU partition editor" \
735                                 "fdisk"  "Dialog-driven creation and manipulation of partitions" \
736                                 "done"   "Return to the main menu" || return 0
737                 fi
738
739                 if [[ $choice == 'done' ]]; then
740                         return 0
741                 elif [[ $choice == 'shrink' ]]; then
742                         part_shrink "$device"
743                 elif [[ $choice == 'auto' ]]; then
744                         local root_size txt table boot_fs
745                         root_size=$(lsblk -lno SIZE "$device" | awk 'NR == 1 {
746                                 if ($1 ~ "G") {
747                                         sub(/G/, "")
748                                         print ($1 * 1000 - 512) / 1000 "G"
749                                 } else {
750                                         sub(/M/, "")
751                                         print ($1 - 512) "M"
752                                 }
753                         }')
754                         txt="\nWARNING:\n\nALL data on $device will be destroyed and the following partitions will be created\n\n- "
755                         if [[ $SYS == 'BIOS' ]]; then
756                                 table="msdos" boot_fs="ext4"
757                                 txt+="An $boot_fs boot partition with the boot flag enabled (512M)\n- "
758                         else
759                                 table="gpt" boot_fs="fat32"
760                                 txt+="A $boot_fs efi boot partition (512M)\n- "
761                         fi
762                         txt+="An ext4 partition using all remaining space ($root_size)\n\nDo you want to continue?\n"
763                         yesno "Auto Partition" "$txt" && part_auto "$device" "$table" "$boot_fs" "$root_size"
764                 else
765                         clear
766                         tput cnorm
767                         $choice "$device"
768                 fi
769                 if [[ $devhash != "$(lsblk -f | base64)" ]]; then
770                         msg "Probing Partitions" "\nInforming the kernel of partition changes using partprobe.\n" 0
771                         partprobe >/dev/null 2>&1
772                 fi
773         done
774 }
775
776 part_show()
777 {
778         local txt
779         if [[ $IGNORE_DEV ]]; then
780                 txt="$(lsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE,MOUNTPOINT | awk "!/$IGNORE_DEV/"' && /disk|part|lvm|crypt|NAME/')"
781         else
782                 txt="$(lsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE,MOUNTPOINT | awk '/disk|part|lvm|crypt|NAME/')"
783         fi
784         msg "Device Tree" "\n\n$txt\n\n"
785 }
786
787 part_swap()
788 {
789         if [[ $1 == "$MNT/swapfile" && $SWAP_SIZE ]]; then
790                 fallocate -l $SWAP_SIZE "$1" 2>$ERR
791                 errshow "fallocate -l $SWAP_SIZE $1"
792                 chmod 600 "$1" 2>$ERR
793                 errshow "chmod 600 $1"
794         fi
795         mkswap "$1" >/dev/null 2>$ERR
796         errshow "mkswap $1"
797         swapon "$1" >/dev/null 2>$ERR
798         errshow "swapon $1"
799         return 0
800 }
801
802 part_auto()
803 {
804         local device="$1" table="$2" boot_fs="$3" size="$4" dev_info=""
805         dev_info="$(parted -s "$device" print)"
806
807         msg "Auto Partition" "\nRemoving partitions on $device and setting table to $table\n" 1
808
809         swapoff -a
810         while read -r PART; do
811                 parted -s "$device" rm "$PART" >/dev/null 2>&1
812         done <<< "$(awk '/^ [1-9][0-9]?/ {print $1}' <<< "$dev_info" | sort -r)"
813
814         [[ $(awk '/Table:/ {print $3}' <<< "$dev_info") != "$table" ]] && parted -s "$device" mklabel "$table" >/dev/null 2>&1
815
816         msg "Auto Partition" "\nCreating a 512M $boot_fs boot partition.\n" 1
817         if [[ $SYS == "BIOS" ]]; then
818                 parted -s "$device" mkpart primary "$boot_fs" 1MiB 513MiB >/dev/null 2>&1
819         else
820                 parted -s "$device" mkpart ESP "$boot_fs" 1MiB 513MiB >/dev/null 2>&1
821         fi
822
823         sleep 0.5
824         BOOT_DEV="$device"
825         AUTO_BOOT_PART=$(lsblk -lno NAME,TYPE "$device" | awk 'NR==2 {print "/dev/" $1}')
826
827         if [[ $SYS == "BIOS" ]]; then
828                 mkfs.ext4 -q "$AUTO_BOOT_PART" >/dev/null 2>&1
829         else
830                 mkfs.vfat -F32 "$AUTO_BOOT_PART" >/dev/null 2>&1
831         fi
832
833         msg "Auto Partition" "\nCreating a $size ext4 root partition.\n" 0
834         parted -s "$device" mkpart primary ext4 513MiB 100% >/dev/null 2>&1
835         sleep 0.5
836         AUTO_ROOT_PART="$(lsblk -lno NAME,TYPE "$device" | awk 'NR==3 {print "/dev/" $1}')"
837         mkfs.ext4 -q "$AUTO_ROOT_PART" >/dev/null 2>&1
838         sleep 0.5
839         FORMATTED+="$AUTO_BOOT_PART $AUTO_ROOT_PART "
840         msg "Auto Partition" "\nProcess complete.\n\n$(lsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE "$device")\n"
841 }
842
843 part_shrink()
844 {
845         part=""
846         typeset -i size num
847         local device="$1" fs=""
848
849         part_find "${device##*/}[^ ]" || return 1
850         (( COUNT == 1 )) && part="$(awk '{print $1}' <<< "${PARTS[@]}" )"
851         
852         if (( COUNT == 1 )) || dlg part menu "Resize" "\nWhich partition on $device do you want to resize?" $PARTS; then
853                 fs=$(lsblk -lno FSTYPE "$part")
854                 case "$fs" in
855                         ext*|ntfs)
856                                 msg "Resize" "\nGathering device size info.\n" 0
857                                 num="${part: -1}"
858                                 end=$(parted -s "$device" unit KiB print | awk '/^\s*'"$num"'/ {print $3}')                    # part size in KiB
859                                 devsize=$(parted -s "$device" unit KiB print | awk '/Disk '"${device//\//\\/}"':/ {print $3}') # whole device size in KiB
860                                 mount "$part" $MNT >/dev/null 2>&1; sleep 0.5
861                                 min=$(df --output=used --block-size=MiB "$part" | awk 'NR == 2 {print int($1) + 256}')
862                                 max=$(df --output=avail --block-size=MiB "$part" | awk 'NR == 2 {print int($1)}')
863                                 umount_dir $MNT
864                                 tput cnorm
865                                 if dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " Resize: $part " --rangebox "$_resize" 17 "$COLUMNS" "$min" "$max" $((max / 2)) 2>$ANS; then
866                                         size=$(< "$ANS")
867                                         size=$((size * 1024))
868                                 else
869                                         return 1
870                                 fi
871                                 clear
872                                 case "$fs" in
873                                         ntfs)
874                                                 if ntfsresize -fc "$part"; then
875                                                         ntfsresize -ff --size $(( (size * 1024) / 1000 ))k "$part" 2>$ERR # k=10^3 bytes
876                                                         errshow "ntfsresize -f -s $(( (size * 1024) / 1000 ))k $part" || return 1
877                                                 else
878                                                         msg "Resize" "\nThe ntfs partition $part cannot be resized because it is scheduled for a consistency check.\n\nTo do a consistency check in windows open command prompt as admin and run:\n\n\tchkdsk /f /r /x\n"
879                                                         return 1
880                                                 fi
881                                                 ;;
882                                         *)
883                                                 e2fsck -f "$part"; sleep 0.5
884                                                 resize2fs -f "$part" ${size}K 2>$ERR # K=2^10 bytes
885                                                 errshow "resize2fs -f $part ${size}K" || return 1
886                                                 ;;
887                                 esac
888                                 sleep 0.5
889                                 parted "$device" resizepart "$num" ${size}KiB || return 1
890                                 (( size++ ))
891                                 sleep 0.5
892                                 if [[ $devsize == "$end" ]]; then
893                                         parted -s "$device" mkpart primary ext4 ${size}KiB 100% 2>$ERR
894                                         errshow "parted -s $device mkpart primary ext4 ${size}KiB 100%" || return 1
895                                 else
896                                         parted -s "$device" mkpart primary ext4 ${size}KiB ${end}KiB 2>$ERR
897                                         errshow "parted -s $device mkpart primary ext4 ${size}KiB ${end}KiB" || return 1
898                                 fi
899                                 msg "Resize Complete" "\n$part has been successfully resized to $((size / 1024))M.\n" 1
900                                 ;;
901                         "") msg "No Filesystem" "\nFor unformatted partitions, cfdisk can be used in the partition menu.\n" ;;
902                         *) msg "Invalid Filesystem: $fs" "\nResizing only supports ext and ntfs.\n" ;;
903                 esac
904         fi
905 }
906
907 ###############################################################################
908 # partition management functions
909 # these are helpers for use by other functions to do essential setup/teardown
910
911 part_find()
912 {
913         local regexp="$1" err=''
914
915         # string of partitions as /TYPE/PART SIZE
916         if [[ $IGNORE_DEV ]]; then
917                 PARTS="$(lsblk -lno TYPE,NAME,SIZE |
918                         awk "/$regexp/"' && !'"/$IGNORE_DEV/"' {
919                                 sub(/^part/, "/dev/")
920                                 sub(/^lvm|^crypt/, "/dev/mapper/")
921                                 print $1$2, $3
922                         }')"
923         else
924                 PARTS="$(lsblk -lno TYPE,NAME,SIZE |
925                         awk "/$regexp/"' {
926                                 sub(/^part/, "/dev/")
927                                 sub(/^lvm|^crypt/, "/dev/mapper/")
928                                 print $1$2 " " $3
929                         }')"
930         fi
931
932         # number of partitions total
933         COUNT=0
934         while read -r line; do
935                 (( COUNT++ ))
936         done <<< "$PARTS"
937
938         # ensure we have enough partitions for the system and action type
939         case "$str" in
940                 'part|lvm|crypt') [[ $COUNT -lt 1 || ($SYS == 'UEFI' && $COUNT -lt 2) ]] && err="$_errpart" ;;
941                 'part|crypt') (( COUNT < 1 )) && err="$_lvmerr" ;;
942                 'part|lvm') (( COUNT < 2 )) && err="$_lukserr" ;;
943         esac
944
945         # if there aren't enough partitions show the relevant error message
946         [[ $err ]] && { msg "Not Enough Partitions" "$err" 2; return 1; }
947
948         return 0
949 }
950
951 part_mount()
952 {
953         local part="$1" mountp="${MNT}$2" fs=""
954         fs="$(lsblk -lno FSTYPE "$part")"
955         mkdir -p "$mountp"
956
957         if [[ $fs && ${FS_OPTS[$fs]} && $part != "$BOOT_PART" ]] && select_mntopts "$fs"; then
958                 mount -o "$MNT_OPTS" "$part" "$mountp" >/dev/null 2>&1
959         else
960                 mount "$part" "$mountp" >/dev/null 2>&1
961         fi
962
963         part_mountconf "$part" "$mountp" || return 1
964         part_cryptlv "$part"
965
966         return 0
967 }
968
969 part_format()
970 {
971         local part="$1" fs="$2" delay="$3"
972
973         msg "Format" "\nFormatting $part as $fs\n" 0
974         ${FS_CMDS[$fs]} "$part" >/dev/null 2>$ERR
975         errshow "${FS_CMDS[$fs]} $part" || return 1
976         FORMATTED+="$part "
977         sleep "${delay:-0}"
978 }
979
980 part_device()
981 {
982         if [[ $DEV_COUNT -eq 1 && $SYS_DEVS ]]; then
983                 DEVICE="$(awk '{print $1}' <<< "$SYS_DEVS")"
984         elif (( DEV_COUNT > 1 )); then
985                 if [[ $1 ]]; then
986                         dlg DEVICE menu "Boot Device" "\nSelect the device to use for bootloader install." $SYS_DEVS
987                 else
988                         dlg DEVICE menu "Select Device" "$_device" $SYS_DEVS
989                 fi
990                 [[ $DEVICE ]] || return 1
991         elif [[ $DEV_COUNT -lt 1 && ! $1 ]]; then
992                 msg "Device Error" "\nNo available devices.\n\nExiting..\n" 2
993                 die 1
994         fi
995
996         [[ $1 ]] && BOOT_DEV="$DEVICE"
997
998         return 0
999 }
1000
1001 part_bootdev()
1002 {
1003         BOOT_DEV="${BOOT_PART%[1-9]}"
1004         BOOT_PART_NUM="${BOOT_PART: -1}"
1005         [[ $BOOT_PART = /dev/nvme* ]] && BOOT_DEV="${BOOT_PART%p[1-9]}"
1006         if [[ $SYS == 'UEFI' ]]; then
1007                 parted -s $BOOT_DEV set $BOOT_PART_NUM esp on >/dev/null 2>&1
1008         else
1009                 parted -s $BOOT_DEV set $BOOT_PART_NUM boot on >/dev/null 2>&1
1010         fi
1011         return 0
1012 }
1013
1014 part_cryptlv()
1015 {
1016         local part="$1" devs=""
1017         devs="$(lsblk -lno NAME,FSTYPE,TYPE)"
1018
1019         # Identify if $part is LUKS+LVM, LVM+LUKS, LVM alone, or LUKS alone
1020         if lsblk -lno TYPE "$part" | grep -q 'crypt'; then
1021                 LUKS='encrypted'
1022                 LUKS_NAME="${part#/dev/mapper/}"
1023
1024                 for dev in $(awk '/lvm/ && /crypto_LUKS/ {print "/dev/mapper/"$1}' <<< "$devs" | uniq); do
1025                         if lsblk -lno NAME "$dev" | grep -q "$LUKS_NAME"; then
1026                                 LUKS_DEV="$LUKS_DEV cryptdevice=$dev:$LUKS_NAME"
1027                                 LVM='logical volume'
1028                                 break
1029                         fi
1030                 done
1031
1032                 for dev in $(awk '/part/ && /crypto_LUKS/ {print "/dev/"$1}' <<< "$devs" | uniq); do
1033                         if lsblk -lno NAME "$dev" | grep -q "$LUKS_NAME"; then
1034                                 LUKS_UUID="$(lsblk -lno UUID,TYPE,FSTYPE "$dev" | awk '/part/ && /crypto_LUKS/ {print $1}')"
1035                                 LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME"
1036                                 break
1037                         fi
1038                 done
1039
1040         elif lsblk -lno TYPE "$part" | grep -q 'lvm'; then
1041                 LVM='logical volume'
1042                 VNAME="${part#/dev/mapper/}"
1043
1044                 for dev in $(awk '/crypt/ && /lvm2_member/ {print "/dev/mapper/"$1}' <<< "$devs" | uniq); do
1045                         if lsblk -lno NAME "$dev" | grep -q "$VNAME"; then
1046                                 LUKS_NAME="${dev/\/dev\/mapper\//}"
1047                                 break
1048                         fi
1049                 done
1050
1051                 for dev in $(awk '/part/ && /crypto_LUKS/ {print "/dev/"$1}' <<< "$devs" | uniq); do
1052                         if lsblk -lno NAME "$dev" | grep -q "$LUKS_NAME"; then
1053                                 LUKS_UUID="$(lsblk -lno UUID,TYPE,FSTYPE "$dev" | awk '/part/ && /crypto_LUKS/ {print $1}')"
1054                                 LUKS_DEV="$LUKS_DEV cryptdevice=UUID=$LUKS_UUID:$LUKS_NAME"
1055                                 LUKS='encrypted'
1056                                 break
1057                         fi
1058                 done
1059         fi
1060 }
1061
1062 part_countdec()
1063 {
1064         for pt; do
1065                 if (( COUNT > 0 )); then
1066                         PARTS="$(sed "/${pt//\//\\/}/d" <<< "$PARTS")"
1067                         (( COUNT-- ))
1068                 fi
1069         done
1070 }
1071
1072 part_mountconf()
1073 {
1074         if grep -qw "$1" /proc/mounts; then
1075                 msg "Mount Success" "\nPartition $1 mounted at $2\n" 1
1076                 part_countdec "$1"
1077                 return 0
1078         else
1079                 msg "Mount Fail" "\nPartition $1 failed to mount at $2\n" 2
1080                 return 1
1081         fi
1082 }
1083
1084 ###############################################################################
1085 # mounting menus
1086 # mount_menu is the entry point which calls all other functions
1087 # once finished it returns to the main menu: main()
1088
1089 mount_menu()
1090 {
1091         msg "Info" "\nGathering device info.\n" 0
1092         is_bg_install || return 0
1093         lvm_detect
1094         umount_dir $MNT
1095         part_find 'part|lvm|crypt' || { SEL=2; return 1; }
1096
1097         [[ $LUKS && $LUKS_PART ]] && part_countdec $LUKS_PART
1098         [[ $LVM && $LVM_PARTS ]] && part_countdec $LVM_PARTS
1099
1100         select_root_partition || return 1
1101
1102         if [[ $SYS == 'UEFI' ]]; then
1103                 select_efi_partition || { BOOT_PART=''; return 1; }
1104         elif (( COUNT > 0 )); then
1105                 select_boot_partition || { BOOT_PART=''; return 1; }
1106         fi
1107
1108         if [[ $BOOT_PART ]]; then
1109                 part_mount "$BOOT_PART" "/$BOOTDIR" && SEP_BOOT=true || return 1
1110                 part_bootdev
1111         fi
1112
1113         select_swap || return 1
1114         select_extra_partitions || return 1
1115         install_background
1116
1117         return 0
1118 }
1119
1120 select_swap()
1121 {
1122         dlg SWAP_PART menu "Swap Setup" "\nSelect whether to use a swapfile, swap partition, or none." \
1123                 "none" "Don't allocate any swap space" \
1124                 "swapfile" "Allocate $SYS_MEM at /swapfile" \
1125                 $PARTS
1126
1127         if [[ -z $SWAP_PART || $SWAP_PART == "none" ]]; then
1128                 SWAP_PART=''
1129                 return 0
1130         elif [[ $SWAP_PART == "swapfile" ]]; then
1131                 local i=0
1132                 until [[ ${SWAP_SIZE:0:1} =~ [1-9] && ${SWAP_SIZE: -1} =~ (M|G) ]]; do
1133                         (( i > 0 )) && msg "Swap Size Error" "\nSwap size must be 1(M|G) or greater, and can only contain whole numbers\n\nSize entered: $SWAP_SIZE\n" 2
1134                         dlg SWAP_SIZE input "Swap Setup" "$_swapsize" "$SYS_MEM" || { SWAP_PART=''; SWAP_SIZE=''; return 1; }
1135                         (( i++ ))
1136                 done
1137                 part_swap "$MNT/$SWAP_PART"
1138                 SWAP_PART="/$SWAP_PART"
1139         elif [[ $PARTS == *"$SWAP_PART"* ]]; then
1140                 part_swap $SWAP_PART
1141                 part_countdec $SWAP_PART
1142                 SWAP_SIZE="$(lsblk -lno SIZE $SWAP_PART)"
1143         else
1144                 return 1
1145         fi
1146
1147         return 0
1148 }
1149
1150 select_mntopts()
1151 {
1152         local fs="$1" opts=''
1153         local title="${fs^} Mount Options"
1154
1155         for i in ${FS_OPTS[$fs]}; do
1156                 opts+="$i - off "
1157         done
1158
1159         until [[ $MNT_OPTS ]]; do
1160                 dlg MNT_OPTS check "$title" "$_mount" $opts
1161                 [[ $MNT_OPTS ]] || return 1
1162                 MNT_OPTS="${MNT_OPTS// /,}"
1163                 yesno "$title" "\nConfirm the following options: $MNT_OPTS\n" || MNT_OPTS=''
1164         done
1165
1166         return 0
1167 }
1168
1169 select_mountpoint()
1170 {
1171         EXMNT=''
1172         until [[ $EXMNT ]]; do
1173                 dlg EXMNT input "Extra Mount $part" "$_exmnt" "/" || return 1
1174                 if [[ ${EXMNT:0:1} != "/" || ${#EXMNT} -le 1 || $EXMNT =~ \ |\' || $EXMNTS == *"$EXMNT"* ]]; then
1175                         msg "Mountpoint Error" "$_errexpart"
1176                         EXMNT=''
1177                 fi
1178         done
1179         return 0
1180 }
1181
1182 select_filesystem()
1183 {
1184         local part="$1" fs='' cur=''
1185         local txt="\nSelect which filesystem to use for: $part\n\nDefault:  ext4"
1186         cur="$(lsblk -lno FSTYPE "$part" 2>/dev/null)"
1187
1188         until [[ $fs ]]; do
1189                 if [[ $cur && ($part != "$ROOT_PART" || $FORMATTED == *"$part"*) ]]; then
1190                         dlg fs menu "Filesystem" "$txt\nCurrent:  $cur" skip - ext4 - ext3 - ext2 - vfat - ntfs - f2fs - jfs - xfs - nilfs2 - reiserfs - || return 1
1191                 else
1192                         dlg fs menu "Filesystem" "$txt" ext4 - ext3 - ext2 - vfat - ntfs - f2fs - jfs - xfs - nilfs2 - reiserfs - || return 1
1193                 fi
1194                 [[ $fs == 'skip' ]] && return 0
1195                 yesno "Filesystem" "\nFormat $part as $fs?\n" || fs=''
1196         done
1197         part_format "$part" "$fs"
1198 }
1199
1200 select_efi_partition()
1201 {
1202         if [[ $AUTO_BOOT_PART ]]; then
1203                 msg "EFI Boot Partition" "\nUsing partition created during automatic format.\n" 2
1204                 BOOT_PART="$AUTO_BOOT_PART"; return 0 # were done here
1205         else
1206                 local pts size dev isize bsize ptcount=0
1207
1208                 # walk partition list and skip ones that are too small/big for boot
1209                 while read -r dev size; do
1210                         size_t="${size: -1:1}"  # size type eg. K, M, G, T
1211                         isize=${size:0:-1}      # remove trailing size type character
1212                         isize=${isize%.*}       # remove any decimal (round down)
1213                         [[ $size_t =~ [KT] || ($size_t == 'G' && $isize -gt 2) || ($size_t == 'M' && $isize -lt 100) ]] || { pts+="$dev $size "; (( ptcount++ )); }
1214                 done <<< "$PARTS"
1215
1216                 if (( ptcount == 1 )); then
1217                         msg "EFI Boot Partition" "\nOnly one partition available that meets size requirements.\n" 1
1218                         BOOT_PART="$(awk 'NF > 0 {print $1}' <<< "$pts")"
1219                 else
1220                         dlg BOOT_PART menu "EFI Partition" "$_uefi" $pts
1221                 fi
1222         fi
1223
1224         [[ $BOOT_PART ]] || return 1
1225
1226         if grep -q 'fat' <<< "$(fsck -N "$BOOT_PART")"; then
1227                 local txt="\nIMPORTANT:\n\nThe EFI partition $BOOT_PART $_format"
1228                 if yesno "Format EFI Partition" "$txt" "Format $BOOT_PART" "Skip Formatting" 1; then
1229                         part_format "$BOOT_PART" "vfat" 2
1230                 fi
1231         else
1232                 part_format "$BOOT_PART" "vfat" 2
1233         fi
1234
1235         return 0
1236 }
1237
1238 select_boot_partition()
1239 {
1240         if [[ $AUTO_BOOT_PART && ! $LVM ]]; then
1241                 msg "BIOS Boot Partition" "\nUsing partition created during automatic format.\n" 2
1242                 BOOT_PART="$AUTO_BOOT_PART"; return 0 # were done here
1243         else
1244                 local pts size dev isize bsize ptcount=0
1245
1246                 # walk partition list and skip ones that are too small/big for boot
1247                 while read -r dev size; do
1248                         size_t="${size: -1:1}"  # size type eg. K, M, G, T
1249                         isize=${size:0:-1}      # remove trailing size type character
1250                         isize=${isize%.*}       # remove any decimal (round down)
1251                         [[ $size_t =~ [KT] || ($size_t == 'G' && $isize -gt 2) || ($size_t == 'M' && $isize -lt 100) ]] || { pts+="$dev $size "; (( ptcount++ )); }
1252                 done <<< "$PARTS"
1253
1254                 if [[ $LUKS && ! $LVM ]]; then
1255                         dlg BOOT_PART menu "Boot Partition" "$_biosluks" $pts
1256                         [[ $BOOT_PART ]] || return 1
1257                 else
1258                         dlg BOOT_PART menu "Boot Partition" "$_bios" "skip" "don't use a separate boot" $pts
1259                         [[ -z $BOOT_PART || $BOOT_PART == "skip" ]] && { BOOT_PART=''; return 0; }
1260                 fi
1261         fi
1262
1263         if grep -q 'ext[34]' <<< "$(fsck -N "$BOOT_PART")"; then
1264                 local txt="\nIMPORTANT:\n\nThe boot partition $BOOT_PART $_format"
1265                 if yesno "Format Boot Partition" "$txt" "Format $BOOT_PART" "Skip Formatting" 1; then
1266                         part_format "$BOOT_PART" "ext4" 2
1267                 fi
1268         else
1269                 part_format "$BOOT_PART" "ext4" 2
1270         fi
1271         return 0
1272 }
1273
1274 select_root_partition()
1275 {
1276         if [[ $AUTO_ROOT_PART && -z $LVM && -z $LUKS ]]; then
1277                 ROOT_PART="$AUTO_ROOT_PART"
1278                 msg "Select Root Partition (/)" "\nUsing partition created during automatic format.\n" 2
1279                 part_mount "$ROOT_PART" || { ROOT_PART=''; return 1; }
1280                 return 0  # we're done here
1281         else
1282                 local pts size dev isize bsize ptcount=0
1283
1284                 # walk partition list and skip ones that are too small for / (root)
1285                 while read -r dev size; do
1286                         size_t="${size: -1:1}"  # size type eg. K, M, G, T
1287                         isize=${size:0:-1}      # remove trailing size type character
1288                         isize=${isize%.*}       # remove any decimal (round down)
1289                         [[ $size_t =~ [MK] || ($size_t == 'G' && $isize -lt 4) ]] || { pts+="$dev $size "; (( ptcount++ )); }
1290                 done <<< "$PARTS"
1291
1292                 if (( ptcount == 1 )); then  # only one available device
1293                         msg "Select Root Partition (/)" "\nOnly one partition available that meets size requirements.\n" 2
1294                         ROOT_PART="$(awk 'NR==1 {print $1}' <<< "$pts")"
1295                 else
1296                         dlg ROOT_PART menu "Mount Root" "\nSelect the root (/) partition, this is where $DIST will be installed.\n\nDevices smaller than 8G will not be shown here." $pts
1297                 fi
1298         fi
1299         [[ $ROOT_PART ]] || return 1
1300
1301         select_filesystem "$ROOT_PART" || { ROOT_PART=''; return 1; }
1302         part_mount "$ROOT_PART" || { ROOT_PART=''; return 1; }
1303
1304         return 0
1305 }
1306
1307 select_extra_partitions()
1308 {
1309         local part size dev
1310
1311         # walk partition list and skip ones that are too small to be usable
1312         while read -r dev size; do
1313                 [[ ${size: -1:1} =~ [KM] ]] && part_countdec "$dev"
1314         done <<< "$PARTS"
1315
1316         while (( COUNT > 0 )); do
1317                 part=''
1318                 dlg part menu 'Mount Extra' "$_expart" 'done' 'finish mounting step' $PARTS || break
1319                 if [[ $part == 'done' ]]; then
1320                         break
1321                 elif select_filesystem "$part" && select_mountpoint && part_mount "$part" "$EXMNT"; then
1322                         EXMNTS+="$part: $EXMNT "
1323                         [[ $EXMNT == '/usr' && $HOOKS != *usr* ]] && HOOKS="usr $HOOKS"
1324                 else
1325                         return 1
1326                 fi
1327         done
1328         return 0
1329 }
1330
1331 ###############################################################################
1332 # installation
1333 # main is the entry point which calls all other install functions, once
1334 # complete it shows a dialog to edit files on the new system before reboot
1335
1336 install_main()
1337 {
1338         clear
1339         tput cnorm
1340         install_base
1341         genfstab -U $MNT >$MNT/etc/fstab 2>$ERR
1342         errshow 1 "genfstab -U $MNT >$MNT/etc/fstab"
1343         [[ -f $MNT/swapfile ]] && sed -i "s~${MNT}~~" $MNT/etc/fstab
1344         install_packages
1345         install_mkinitcpio
1346         install_boot
1347         chrun "hwclock --systohc --utc" || chrun "hwclock --systohc --utc --directisa"
1348         install_user
1349         install_login
1350         chrun "chown -Rf $NEWUSER:users /home/$NEWUSER"
1351         sleep 1
1352
1353         while :; do
1354                 dlg choice menu "Finalization" "$_edit" \
1355                         finished   "exit the installer and reboot" \
1356                         keyboard   "${EDIT_FILES[keyboard]}" \
1357                         console    "${EDIT_FILES[console]}" \
1358                         locale     "${EDIT_FILES[locale]}" \
1359                         hostname   "${EDIT_FILES[hostname]}" \
1360                         sudoers    "${EDIT_FILES[sudoers]}" \
1361                         mkinitcpio "${EDIT_FILES[mkinitcpio]}" \
1362                         fstab      "${EDIT_FILES[fstab]}" \
1363                         crypttab   "${EDIT_FILES[crypttab]}" \
1364                         bootloader "${EDIT_FILES[bootloader]}" \
1365                         pacman     "${EDIT_FILES[pacman]}" \
1366                         login      "${EDIT_FILES[login]}"
1367
1368                 if [[ -z $choice || $choice == 'finished' ]]; then
1369                         [[ $DEBUG == true && -r $DBG ]] && vim $DBG
1370                         die 127
1371                 else
1372                         local exists=''
1373                         for f in ${EDIT_FILES[$choice]}; do
1374                                 [[ -e ${MNT}$f ]] && exists+=" ${MNT}$f"
1375                         done
1376                         if [[ $exists ]]; then
1377                                 vim -O $exists
1378                         else
1379                                 msg "File Missing" "\nThe file(s) selected do not exist:\n\n${EDIT_FILES[$choice]}\n"
1380                         fi
1381                 fi
1382         done
1383 }
1384
1385 install_base()
1386 {
1387         if [[ $RSYNC_PID || $MIRROR_PID ]]; then
1388                 local oldmsg="" msg=""
1389                 printf "\nOne or more background install processes are still running, grabbing their output...\n"
1390                 while kill -0 "$RSYNC_PID" 2>/dev/null || kill -0 "$MIRROR_PID" 2>/dev/null; do
1391                         msg="$(tail -n 1 /tmp/bg_out)"
1392                         if [[ "$msg" != "$oldmsg" ]]; then
1393                                 printf "\n%s" "$msg"
1394                                 oldmsg="$msg"
1395                         else
1396                                 printf "."
1397                                 sleep 0.5
1398                         fi
1399                 done
1400                 trap - EXIT
1401                 unset RSYNC_PID MIRROR_PID
1402         else
1403                 rsync -ahv /run/archiso/sfs/airootfs/ $MNT/ 2>$ERR
1404                 errshow 1 "rsync -ahv /run/archiso/sfs/airootfs/ $MNT/"
1405                 install_mirrorlist "$MNT/etc/pacman.d/mirrorlist"
1406                 chrun "pacman -Syyu --noconfirm && pacman -S $BASE_PKGS --needed --noconfirm"
1407         fi
1408
1409         rm -rf $MNT/etc/mkinitcpio-archiso.conf
1410         find $MNT/usr/lib/initcpio -name 'archiso*' -type f -delete
1411         sed -i 's/volatile/auto/g' $MNT/etc/systemd/journald.conf
1412         find $MNT/boot -name '*-ucode.img' -delete
1413
1414         if [[ $VM ]]; then
1415                 find $MNT/etc/X11/xorg.conf.d/ -name '*.conf' -delete
1416         elif lspci | grep ' VGA ' | grep -q 'Intel'; then
1417                 cat > $MNT/etc/X11/xorg.conf.d/20-intel.conf <<- EOF
1418                 Section "Device"
1419                     Identifier  "Intel Graphics"
1420                     Driver      "intel"
1421                     Option      "TearFree" "true"
1422                 EndSection
1423                 EOF
1424         fi
1425
1426         [[ -e /run/archiso/sfs/airootfs && $KERNEL == 'linux' ]] && cp -vf $RUN/x86_64/vmlinuz $MNT/boot/vmlinuz-linux
1427
1428         cp -fv /etc/resolv.conf $MNT/etc/
1429         [[ -e /etc/NetworkManager/system-connections ]] && cp -rvf /etc/NetworkManager/system-connections $MNT/etc/NetworkManager/
1430
1431         echo "LANG=$MYLOCALE" > $MNT/etc/locale.conf
1432         cp -f $MNT/etc/locale.conf $MNT/etc/default/locale
1433         sed -i "s/#en_US.UTF-8/en_US.UTF-8/g; s/#${MYLOCALE}/${MYLOCALE}/g" $MNT/etc/locale.gen
1434         chrun "locale-gen"
1435         chrun "ln -svf /usr/share/zoneinfo/$ZONE/$SUBZ /etc/localtime"
1436
1437         if [[ $BROADCOM_WL ]]; then
1438                 echo 'blacklist bcma' >> $MNT/etc/modprobe.d/blacklist.conf
1439                 rm -f $MNT/etc/modprobe/
1440         fi
1441
1442         cat > $MNT/etc/X11/xorg.conf.d/00-keyboard.conf <<- EOF
1443         # Use localectl(1) to instruct systemd-localed to update it.
1444         Section "InputClass"
1445             Identifier      "system-keyboard"
1446             MatchIsKeyboard "on"
1447             Option          "XkbLayout" "$KEYMAP"
1448         EndSection
1449         EOF
1450
1451         cat > $MNT/etc/default/keyboard <<- EOF
1452         # KEYBOARD CONFIGURATION FILE
1453         # Consult the keyboard(5) manual page.
1454         XKBMODEL=""
1455         XKBLAYOUT="$KEYMAP"
1456         XKBVARIANT=""
1457         XKBOPTIONS=""
1458         BACKSPACE="guess"
1459         EOF
1460         printf "KEYMAP=%s\nFONT=%s\n" "$CMAP" "$FONT" > $MNT/etc/vconsole.conf
1461         echo "$MYHOST" > $MNT/etc/hostname
1462         cat > $MNT/etc/hosts <<- EOF
1463         127.0.0.1       localhost
1464         127.0.1.1       $MYHOST
1465         ::1                     localhost ip6-localhost ip6-loopback
1466         ff02::1         ip6-allnodes
1467         ff02::2         ip6-allrouters
1468         EOF
1469 }
1470
1471 install_boot()
1472 {
1473         if [[ $ROOT_PART == /dev/mapper* ]]; then
1474                 ROOT_PART_ID="$ROOT_PART"
1475         else
1476                 local uuid_type="UUID"
1477                 [[ $BOOTLDR =~ (systemd-boot|refind-efi|efistub) ]] && uuid_type="PARTUUID"
1478                 ROOT_PART_ID="$uuid_type=$(blkid -s $uuid_type -o value $ROOT_PART)"
1479         fi
1480
1481         if [[ $SYS == 'UEFI' ]]; then
1482                 # our old installs
1483                 find $MNT/$BOOTDIR/EFI/ -maxdepth 1 -mindepth 1 -iname "$DIST" -type d -delete
1484                 # generic BOOT/ dir
1485                 find $MNT/$BOOTDIR/EFI/ -maxdepth 1 -mindepth 1 -iname 'BOOT' -type d -delete
1486         fi
1487
1488         if [[ ! -e $MNT/$BOOTDIR/$UCODE ]]; then
1489                 chrun "pacman -S $UCODE --noconfirm"
1490                 [[ -e $MNT/$BOOTDIR/$UCODE ]] || UCODE=""
1491         fi
1492
1493         prerun_$BOOTLDR
1494         chrun "${BCMDS[$BOOTLDR]}" 2>$ERR
1495         errshow 1 "${BCMDS[$BOOTLDR]}"
1496
1497         if [[ -d $MNT/hostrun ]]; then
1498                 # cleanup the bind mounts we made earlier for the grub-probe module
1499                 umount_dir $MNT/hostrun/{udev,lvm}
1500                 rm -rf $MNT/hostrun >/dev/null 2>&1
1501         fi
1502
1503         if [[ $SYS == 'UEFI' ]]; then
1504                 # some UEFI firmware requires a generic esp/BOOT/BOOTX64.EFI
1505                 mkdir -pv $MNT/$BOOTDIR/EFI/BOOT
1506                 if [[ $BOOTLDR == 'grub' ]]; then
1507                         cp -fv $MNT/$BOOTDIR/EFI/$DIST/grubx64.efi $MNT/$BOOTDIR/EFI/BOOT/BOOTX64.EFI
1508                 elif [[ $BOOTLDR == 'syslinux' ]]; then
1509                         cp -rf $MNT/$BOOTDIR/EFI/syslinux/* $MNT/$BOOTDIR/EFI/BOOT/
1510                         cp -f $MNT/$BOOTDIR/EFI/syslinux/syslinux.efi $MNT/$BOOTDIR/EFI/BOOT/BOOTX64.EFI
1511                 elif [[ $BOOTLDR == 'refind-efi' ]]; then
1512                         sed -i '/#extra_kernel_version_strings/ c extra_kernel_version_strings linux-hardened,linux-zen,linux-lts,linux' $MNT/$BOOTDIR/EFI/refind/refind.conf
1513                         cp -fv $MNT/$BOOTDIR/EFI/refind/refind_x64.efi $MNT/$BOOTDIR/EFI/BOOT/BOOTX64.EFI
1514                 fi
1515         fi
1516
1517         return 0
1518 }
1519
1520 install_user()
1521 {
1522         rm -f $MNT/root/.zshrc  # remove welcome message from root zshrc
1523
1524         chrun "chpasswd <<< 'root:$ROOT_PASS'" 2>$ERR
1525         errshow 1 "set root password"
1526         if [[ $MYSHELL != "/usr/bin/zsh" ]]; then # root uses zsh by default, change it if something else was chosen
1527                 chrun "usermod -s $MYSHELL root" 2>$ERR
1528                 errshow 1 "usermod -s $MYSHELL root"
1529                 # copy the default mkshrc to /root if mksh was picked
1530                 [[ $MYSHELL == '/usr/bin/mksh' ]] && cp -fv $MNT/etc/skel/.mkshrc $MNT/root/.mkshrc
1531         fi
1532
1533         local groups='audio,floppy,log,network,rfkill,scanner,storage,optical,power,wheel'
1534
1535         chrun "useradd -m -u 1000 -g users -G $groups -s $MYSHELL $NEWUSER" 2>$ERR
1536         errshow 1 "useradd -m -u 1000 -g users -G $groups -s $MYSHELL $NEWUSER"
1537         chrun "chpasswd <<< '$NEWUSER:$USER_PASS'" 2>$ERR
1538         errshow 1 "set $NEWUSER password"
1539
1540         # if neovim was picked copy vim configs to ~/.config/nvim
1541         if [[ $USER_PKGS == *neovim* ]]; then
1542                 mkdir -p $MNT/home/$NEWUSER/.config/nvim
1543                 cp -fv $MNT/home/$NEWUSER/.vimrc $MNT/home/$NEWUSER/.config/nvim/init.vim
1544                 cp -rfv $MNT/home/$NEWUSER/.vim/colors $MNT/home/$NEWUSER/.config/nvim/colors
1545         fi
1546
1547         case $INSTALL_WMS in *awesome*) install_awesome ;; *dwm*) install_suckless ;; esac
1548
1549         [[ $WM_PKGS == *xfce* ]] && echo 'volumeicon &' >> $MNT/home/$NEWUSER/.xprofile
1550
1551         # remove some commands from ~/.xprofile when using KDE or Gnome as the login session
1552         if [[ $LOGIN_WM =~ (startkde|gnome-session) || ($LOGIN_TYPE == 'lightdm' && $WM_PKGS =~ (plasma|gnome)) ]]; then
1553                 sed -i '/super/d' $MNT/home/$NEWUSER/.xprofile $MNT/root/.xprofile
1554                 sed -i '/nitrogen/d' $MNT/home/$NEWUSER/.xprofile $MNT/root/.xprofile
1555                 sed -i '/compton/d' $MNT/home/$NEWUSER/.xprofile $MNT/root/.xprofile
1556         fi
1557         
1558         return 0
1559 }
1560
1561 install_xinit()
1562 {
1563
1564         if [[ $INSTALL_WMS ]]; then
1565                 if [[ -e $MNT/home/$NEWUSER/.xinitrc ]] && grep -q 'exec' "$MNT/home/$NEWUSER/.xinitrc"; then
1566                         sed -i "/exec/ c exec ${LOGIN_WM}" "$MNT/home/$NEWUSER/.xinitrc"
1567                 elif [[ $INSTALL_WMS ]]; then
1568                         printf "exec %s\n" "$LOGIN_WM" >> "$MNT/home/$NEWUSER/.xinitrc"
1569                 else
1570                         printf "exec %s\n" "$LOGIN_WM" >> "$MNT/home/$NEWUSER/.xinitrc"
1571                 fi
1572         elif [[ -e $MNT/home/$NEWUSER/.xinitrc ]]; then
1573                 # no sessions available so remove the exec from ~/.xinitrc and return
1574                 sed -i '/exec/d' "$MNT/home/$NEWUSER/.xinitrc"
1575                 return 0
1576         fi
1577
1578         [[ ${EDIT_FILES[login]} == *"$LOGINRC"* ]] || EDIT_FILES[login]+=" /home/$NEWUSER/$LOGINRC"
1579
1580         if [[ $AUTOLOGIN ]]; then
1581                 sed -i "s/root/${NEWUSER}/g" $SERVICE/autologin.conf
1582                 cat > "$MNT/home/$NEWUSER/$LOGINRC" <<- EOF
1583                 # ~/$LOGINRC
1584                 # sourced by ${MYSHELL##*/} when used as a login shell
1585
1586                 # automatically run startx when logging in on tty1
1587                 [ -z \$DISPLAY ] && [ \$XDG_VTNR -eq 1 ] && exec startx
1588                 EOF
1589         else
1590                 rm -rf $SERVICE
1591         fi
1592 }
1593
1594 install_login()
1595 {
1596         SERVICE="$MNT/etc/systemd/system/getty@tty1.service.d"
1597         install_${LOGIN_TYPE:-xinit}
1598 }
1599
1600 install_lightdm()
1601 {
1602         rm -rf "$SERVICE" "$MNT/home/$NEWUSER"/.{xinitrc,profile,zprofile,bash_profile}
1603         chrun 'systemctl enable lightdm.service' 2>$ERR
1604         errshow 1 "systemctl enable lightdm.service"
1605         cat > $MNT/etc/lightdm/lightdm-gtk-greeter.conf <<- EOF
1606         # LightDM GTK+ Configuration
1607
1608         [greeter]
1609         default-user-image=/usr/share/icons/ArchLabs-Dark/64x64/places/distributor-logo-archlabs.png
1610         background=/usr/share/backgrounds/archlabs/archlabs.jpg
1611         theme-name=Adwaita-dark
1612         icon-theme-name=Adwaita
1613         font-name=DejaVu Sans Mono 11
1614         position=30%,end 50%,end
1615         EOF
1616 }
1617
1618 install_awesome()
1619 {
1620         # downloads and sets up @elenapan's awesome WM config hosted on github
1621         if chrun "git clone https://github.com/elenapan/archlabs-awesome /home/$NEWUSER/archlabs-awesome"; then
1622                 cp -rT "/mnt/home/$NEWUSER/archlabs-awesome" "/mnt/home/$NEWUSER"
1623                 cp -rT "/mnt/home/$NEWUSER/archlabs-awesome" /mnt/etc/skel
1624                 rm -rf "/home/$NEWUSER/"{.git,archlabs-awesome,screenshots}
1625                 printf "You will need to install pamac separately using: 'baph -i pamac-aur'\n"
1626                 sleep 3
1627         else
1628                 printf "failed to clone awesome config repo\n"
1629         fi
1630 }
1631
1632 install_packages()
1633 {
1634         local rmpkg=""
1635         local inpkg="$BASE_PKGS ${LOGIN_PKGS[$LOGIN_TYPE]} $PACKAGES $USER_PKGS $UCODE "
1636
1637         if pacman -Qsq 'archlabs-installer' >/dev/null 2>&1; then
1638                 rmpkg+="archlabs-installer "
1639         elif [[ -e "$MNT/usr/bin/archlabs-installer" ]]; then
1640                 rm -f "$MNT/usr/bin/archlabs-installer"
1641         fi
1642
1643         # add extra packages chosen throughout the install
1644         if [[ $MYSHELL == '/usr/bin/zsh' ]]; then
1645                 inpkg+="zsh-completions "
1646         else
1647                 rmpkg+="zsh "
1648                 [[ $MYSHELL == '/usr/bin/mksh' ]] && inpkg+="mksh "
1649         fi
1650         if [[ $KERNEL != 'linux' ]]; then
1651                 inpkg+="$KERNEL "
1652                 rmpkg+="linux "
1653         fi
1654
1655         # gnome, plasma, and cinnamon have their own superkey bind and ksuperkey conflicts
1656         [[ $INSTALL_WMS =~ ^(plasma|gnome|cinnamon)$ ]] || inpkg+="archlabs-ksuperkey "
1657
1658         # window manager only packages
1659         [[ $INSTALL_WMS =~ (openbox|bspwm|i3-gaps|dwm|fluxbox) ]] && inpkg+="$WM_BASE_PKGS "
1660
1661         # ensure a terminal gets installed if one wasn't chosen or a DE that installs one
1662         [[ $inpkg =~ (term|urxvt|tilix|alacritty|sakura|tilda|plasma|cinnamon) || $INSTALL_WMS == *dwm* ]] || inpkg+="xterm "
1663
1664         # update first to avoid issues
1665         chrun "pacman -Syyu --noconfirm"
1666
1667         # remove the packages we don't want on the installed system
1668         [[ $rmpkg ]] && chrun "pacman -Rns $rmpkg --noconfirm"
1669
1670         # reinstalling iputils fixes the network issue for non-root users
1671         chrun "pacman -S iputils --noconfirm"
1672
1673         # install the packages chosen throughout the install
1674         chrun "pacman -S $inpkg --needed --noconfirm" 2>$ERR
1675         errshow 1 "pacman -S $inpkg --needed --noconfirm"
1676
1677         # bootloader packages
1678         if [[ $BOOTLDR == 'grub' ]]; then
1679                 [[ $SYS == 'UEFI' ]] && local efib="efibootmgr"
1680                 chrun "pacman -S os-prober grub $efib --needed --noconfirm" 2>$ERR
1681                 errshow 1 "pacman -S os-prober grub $efib --needed --noconfirm"
1682         elif [[ $BOOTLDR == 'refind-efi' ]]; then
1683                 chrun "pacman -S refind-efi efibootmgr --needed --noconfirm" 2>$ERR
1684                 errshow 1 "pacman -S refind-efi efibootmgr --needed --noconfirm"
1685         elif [[ $SYS == 'UEFI' ]]; then
1686                 chrun "pacman -S efibootmgr --needed --noconfirm" 2>$ERR
1687                 errshow 1 "pacman -S efibootmgr --needed --noconfirm"
1688         fi
1689
1690         # allow members of the wheel group to run commands as root
1691         sed -i "s/# %wheel ALL=(ALL) ALL/%wheel ALL=(ALL) ALL/g" $MNT/etc/sudoers
1692
1693         return 0
1694 }
1695
1696 install_suckless()
1697 {
1698         mkdir -pv "$MNT/home/$NEWUSER/suckless"
1699
1700         for i in dwm dmenu st; do
1701                 if chrun "git clone https://git.suckless.org/$i /home/$NEWUSER/suckless/$i"; then
1702                         chrun "cd /home/$NEWUSER/suckless/$i; make PREFIX=/usr install; make clean; rm config.h"
1703                 else
1704                         printf "failed to clone %s repo\n" "$i"
1705                 fi
1706         done
1707
1708         if [[ -x $MNT/usr/bin/dwm ]]; then
1709                 printf "To configure dwm edit %s\n" "/home/$NEWUSER/suckless/dwm/config.h"
1710                 printf "You can then recompile it with 'sudo make clean install'\n"
1711                 sleep 2
1712         fi
1713 }
1714
1715 install_mkinitcpio()
1716 {
1717         local add=''
1718
1719         # luks keyfile creation
1720         # currently not used due to not prompting for passphrase on startup
1721         # [[ $LUKS_UUID && $LUKS_PASS && $SYS == 'UEFI' && $BOOTLDR == 'grub' ]] && luks_keyfile
1722
1723         [[ $LUKS ]] && add="encrypt"
1724         [[ $LVM ]] && { [[ $add ]] && add+=" lvm2" || add+="lvm2"; }
1725         sed -i "s/block filesystems/block ${add} filesystems ${HOOKS}/g" $MNT/etc/mkinitcpio.conf
1726         chrun "mkinitcpio -p $KERNEL" 2>$ERR
1727         errshow 1 "mkinitcpio -p $KERNEL"
1728 }
1729
1730 install_mirrorlist()
1731 {
1732         local mfile="$1"  # output mirrorlist file
1733
1734         if hash reflector >/dev/null 2>&1; then
1735                 reflector --score 80 -l 40 -f 5 --sort rate --save "$mfile"
1736         elif hash rankmirrors >/dev/null 2>&1; then
1737                 local key="access_key=5f29642060ab983b31fdf4c2935d8c56"
1738                 ip_add="$(curl -fsSL "http://api.ipstack.com/check&?$key&fields=ip" | python -c "import sys, json; print(json.load(sys.stdin)['ip'])")"
1739                 country="$(curl -fsSL "http://api.ipstack.com/$ip_add?$key&fields=country_code" | python -c "import sys, json; print(json.load(sys.stdin)['country_code'])")"
1740                 if [[ "$country" ]]; then
1741                         if [[ $country =~ (CA|US) ]]; then
1742                                 # use both CA and US mirrors for CA or US countries
1743                                 mirror="https://www.archlinux.org/mirrorlist/?country=US&country=CA&use_mirror_status=on"
1744                         elif [[ $country =~ (AU|NZ) ]]; then
1745                                 # use both AU and NZ mirrors for AU or NZ countries
1746                                 mirror="https://www.archlinux.org/mirrorlist/?country=AU&country=NZ&use_mirror_status=on"
1747                         else
1748                                 mirror="https://www.archlinux.org/mirrorlist/?country=${country}&use_mirror_status=on"
1749                         fi
1750                 else # no country code so just grab all mirrors, will be a very slow sort but we don't have other options
1751                         mirror="https://www.archlinux.org/mirrorlist/?country=all&use_mirror_status=on"
1752                 fi
1753                 curl -fsSL "$mirror" | sed -e 's/^#Server/Server/' -e '/^#/d' | rankmirrors -n 6 - >"$mfile"
1754         fi
1755
1756         return 0
1757 }
1758
1759 install_background()
1760 {
1761         yesno "Background Install" "\nBegin install in the background?\n" || return 0
1762
1763         rsync -a /run/archiso/sfs/airootfs/ $MNT/ &
1764         RSYNC_PID=$!
1765
1766         mkdir -p $MNT/var/lib/pacman # can help with pacman errors
1767         ( install_mirrorlist "$MNT/etc/pacman.d/mirrorlist" && sleep 1 && chrun "pacman -Syyu $BASE_PKGS --needed --noconfirm" >> /tmp/bg_out 2>&1 ) &
1768         MIRROR_PID=$!
1769
1770         # end the background processes before exiting
1771         trap "kill $RSYNC_PID $MIRROR_PID 2>/dev/null" EXIT
1772 }
1773
1774 ###############################################################################
1775 # bootloader setup
1776 # prerun_* set up the configs needed before actually running the commands
1777 # setup_* are run after selecting a bootloader and build the command used later
1778 # they can also be used for further user input as these run before control is taken away
1779
1780 setup_grub()
1781 {
1782         EDIT_FILES[bootloader]="/etc/default/grub"
1783
1784         if [[ $SYS == 'BIOS' ]]; then
1785                 [[ $BOOT_DEV ]] || { part_device 1 || return 1; }
1786                 BCMDS[grub]="grub-install --recheck --force --target=i386-pc $BOOT_DEV"
1787         else
1788                 # since we're not using the keyfile this is dead code
1789                 # if [[ $ROOT_PART == */dev/mapper/* && -z $LVM && -z $LUKS_PASS ]]; then
1790                 #       luks_pass "$_luksopen" 1 || return 1
1791                 # fi
1792                 BCMDS[grub]="mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1793                 grub-install --recheck --force --target=x86_64-efi --efi-directory=/$BOOTDIR --bootloader-id=$DIST"
1794                 grep -q /sys/firmware/efi/efivars /proc/mounts || mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1795         fi
1796
1797         BCMDS[grub]="mkdir -p /run/udev /run/lvm &&
1798                 mount --bind /hostrun/udev /run/udev &&
1799                 mount --bind /hostrun/lvm /run/lvm &&
1800                 ${BCMDS[grub]} &&
1801                 grub-mkconfig -o /boot/grub/grub.cfg &&
1802                 sleep 1 && umount /run/udev /run/lvm"
1803
1804         return 0
1805 }
1806
1807 prerun_grub()
1808 {
1809         sed -i "s/GRUB_DISTRIBUTOR=.*/GRUB_DISTRIBUTOR=\"${DIST}\"/g; s/GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"\"/g" $MNT/etc/default/grub
1810
1811         if [[ $LUKS_DEV ]]; then
1812                 sed -i "s~#GRUB_ENABLE_CRYPTODISK~GRUB_ENABLE_CRYPTODISK~g; s~GRUB_CMDLINE_LINUX=.*~GRUB_CMDLINE_LINUX=\"${LUKS_DEV}\"~g" $MNT/etc/default/grub 2>$ERR
1813                 errshow 1 "sed -i 's~#GRUB_ENABLE_CRYPTODISK~GRUB_ENABLE_CRYPTODISK~g; s~GRUB_CMDLINE_LINUX=.*~GRUB_CMDLINE_LINUX=\"${LUKS_DEV}\"~g' $MNT/etc/default/grub"
1814         fi
1815
1816         if [[ $SYS == 'BIOS' && $LVM && -z $SEP_BOOT ]]; then
1817                 sed -i "s/GRUB_PRELOAD_MODULES=.*/GRUB_PRELOAD_MODULES=\"lvm\"/g" $MNT/etc/default/grub 2>$ERR
1818                 errshow 1 "sed -i 's/GRUB_PRELOAD_MODULES=.*/GRUB_PRELOAD_MODULES=\"lvm\"/g' $MNT/etc/default/grub"
1819         fi
1820
1821         # setup for os-prober module
1822         mkdir -p /run/{lvm,udev} $MNT/hostrun/{lvm,udev}
1823         mount --bind /run/lvm $MNT/hostrun/lvm
1824         mount --bind /run/udev $MNT/hostrun/udev
1825
1826         return 0
1827 }
1828
1829 setup_efistub()
1830 {
1831         EDIT_FILES[bootloader]=""
1832 }
1833
1834 prerun_efistub()
1835 {
1836         BCMDS[systemd-boot]="mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1837                 efibootmgr -v -d $BOOT_DEV -p $BOOT_PART_NUM -c -L '${DIST} Linux' -l /vmlinuz-${KERNEL} \
1838                 -u 'root=$ROOT_PART_ID rw $([[ $UCODE ]] && printf 'initrd=\%s.img ' "$UCODE")initrd=\initramfs-${KERNEL}.img'"
1839 }
1840
1841 setup_syslinux()
1842 {
1843         if [[ $SYS == 'BIOS' ]]; then
1844                 EDIT_FILES[bootloader]="/boot/syslinux/syslinux.cfg"
1845         else
1846                 EDIT_FILES[bootloader]="/boot/EFI/syslinux/syslinux.cfg"
1847                 BCMDS[syslinux]="mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1848                 efibootmgr -v -c -d $BOOT_DEV -p $BOOT_PART_NUM -l /EFI/syslinux/syslinux.efi -L $DIST"
1849         fi
1850 }
1851
1852 prerun_syslinux()
1853 {
1854         local c="$MNT/boot/syslinux" s="/usr/lib/syslinux/bios" d=".."
1855         [[ $SYS == 'UEFI' ]] && { c="$MNT/boot/EFI/syslinux"; s="/usr/lib/syslinux/efi64/"; d=''; }
1856
1857         mkdir -pv "$c" && cp -rfv $s/* "$c/" && cp -f "$RUN/syslinux/splash.png" "$c/"
1858         cat > "$c/syslinux.cfg" <<- EOF
1859         UI vesamenu.c32
1860         MENU TITLE $DIST Boot Menu
1861         MENU BACKGROUND splash.png
1862         TIMEOUT 50
1863         DEFAULT $DIST
1864
1865         # see: https://www.syslinux.org/wiki/index.php/Comboot/menu.c32
1866         MENU WIDTH 78
1867         MENU MARGIN 4
1868         MENU ROWS 4
1869         MENU VSHIFT 10
1870         MENU TIMEOUTROW 13
1871         MENU TABMSGROW 14
1872         MENU CMDLINEROW 14
1873         MENU HELPMSGROW 16
1874         MENU HELPMSGENDROW 29
1875         MENU COLOR border       30;44   #40ffffff #a0000000 std
1876         MENU COLOR title        1;36;44 #9033ccff #a0000000 std
1877         MENU COLOR sel          7;37;40 #e0ffffff #20ffffff all
1878         MENU COLOR unsel        37;44   #50ffffff #a0000000 std
1879         MENU COLOR help         37;40   #c0ffffff #a0000000 std
1880         MENU COLOR timeout_msg  37;40   #80ffffff #00000000 std
1881         MENU COLOR timeout      1;37;40 #c0ffffff #00000000 std
1882         MENU COLOR msg07        37;40   #90ffffff #a0000000 std
1883         MENU COLOR tabmsg       31;40   #30ffffff #00000000 std
1884
1885         LABEL $DIST
1886         MENU LABEL $DIST Linux
1887         LINUX $d/vmlinuz-$KERNEL
1888         APPEND root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && printf "%s " "$LUKS_DEV")rw
1889         INITRD $([[ $UCODE ]] && printf "%s" "$d/$UCODE.img,")$d/initramfs-$KERNEL.img
1890
1891         LABEL ${DIST}fallback
1892         MENU LABEL $DIST Linux Fallback
1893         LINUX $d/vmlinuz-$KERNEL
1894         APPEND root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && printf "%s " "$LUKS_DEV")rw
1895         INITRD $([[ $UCODE ]] && printf "%s" "$d/$UCODE.img,")$d/initramfs-$KERNEL-fallback.img
1896         EOF
1897         return 0
1898 }
1899
1900 setup_refind-efi()
1901 {
1902         EDIT_FILES[bootloader]="/boot/refind_linux.conf"
1903         BCMDS[refind-efi]="mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1904                 refind-install"
1905 }
1906
1907 prerun_refind-efi()
1908 {
1909         cat > $MNT/boot/refind_linux.conf <<- EOF
1910         "$DIST Linux"          "root=$ROOT_PART_ID $([[ $LUKS_DEV ]] &&
1911                                                 printf "%s " "$LUKS_DEV")rw add_efi_memmap $([[ $UCODE ]] &&
1912                                                 printf "initrd=%s " "/$UCODE.img")initrd=/initramfs-$KERNEL.img"
1913         "$DIST Linux Fallback" "root=$ROOT_PART_ID $([[ $LUKS_DEV ]] &&
1914                                                 printf "%s " "$LUKS_DEV")rw add_efi_memmap $([[ $UCODE ]] &&
1915                                                 printf "initrd=%s " "/$UCODE.img")initrd=/initramfs-$KERNEL-fallback.img"
1916         EOF
1917         mkdir -p $MNT/etc/pacman.d/hooks
1918         cat > $MNT/etc/pacman.d/hooks/refind.hook <<- EOF
1919         [Trigger]
1920         Operation = Upgrade
1921         Type = Package
1922         Target = refind-efi
1923
1924         [Action]
1925         Description = Updating rEFInd on ESP
1926         When = PostTransaction
1927         Exec = /usr/bin/refind-install
1928         EOF
1929 }
1930
1931 setup_systemd-boot()
1932 {
1933         EDIT_FILES[bootloader]="/boot/loader/entries/$DIST.conf"
1934         BCMDS[systemd-boot]="mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
1935                 bootctl --path=/boot install"
1936 }
1937
1938 prerun_systemd-boot()
1939 {
1940         mkdir -p $MNT/boot/loader/entries
1941         cat > $MNT/boot/loader/loader.conf <<- EOF
1942         default  $DIST
1943         timeout  5
1944         editor   no
1945         EOF
1946         cat > $MNT/boot/loader/entries/$DIST.conf <<- EOF
1947         title   $DIST Linux
1948         linux   /vmlinuz-${KERNEL}$([[ $UCODE ]] && printf "\ninitrd  %s" "/$UCODE.img")
1949         initrd  /initramfs-$KERNEL.img
1950         options root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && printf "%s " "$LUKS_DEV")rw
1951         EOF
1952         cat > $MNT/boot/loader/entries/$DIST-fallback.conf <<- EOF
1953         title   $DIST Linux Fallback
1954         linux   /vmlinuz-${KERNEL}$([[ $UCODE ]] && printf "\ninitrd  %s" "/$UCODE.img")
1955         initrd  /initramfs-$KERNEL-fallback.img
1956         options root=$ROOT_PART_ID $([[ $LUKS_DEV ]] && printf "%s " "$LUKS_DEV")rw
1957         EOF
1958         mkdir -p $MNT/etc/pacman.d/hooks
1959         cat > $MNT/etc/pacman.d/hooks/systemd-boot.hook <<- EOF
1960         [Trigger]
1961         Type = Package
1962         Operation = Upgrade
1963         Target = systemd
1964
1965         [Action]
1966         Description = Updating systemd-boot
1967         When = PostTransaction
1968         Exec = /usr/bin/bootctl update
1969         EOF
1970         systemd-machine-id-setup --root="$MNT"
1971         return 0
1972 }
1973
1974 ###############################################################################
1975 # lvm functions
1976
1977 lvm_menu()
1978 {
1979         is_bg_install || return 1
1980         lvm_detect
1981         local choice
1982         dlg choice menu "Logical Volume Management" "$_lvmmenu" \
1983                 "$_lvmnew"    "vgcreate -f, lvcreate -L -n" \
1984                 "$_lvmdel"    "vgremove -f" \
1985                 "$_lvmdelall" "lvrmeove, vgremove, pvremove -f" \
1986                 "back"        "return to the main menu"
1987
1988         case "$choice" in
1989                 "$_lvmnew") lvm_create || return 1 ;;
1990                 "$_lvmdel") lvm_delgroup && yesno "$_lvmdel" "$_lvmdelask" && vgremove -f "$DEL_VG" >/dev/null 2>&1 ;;
1991                 "$_lvmdelall") lvm_del_all ;;
1992         esac
1993
1994         return 0
1995 }
1996
1997 lvm_detect()
1998 {
1999         local v pv
2000         pv="$(pvs -o pv_name --noheading 2>/dev/null)"
2001         v="$(lvs -o vg_name,lv_name --noheading --separator - 2>/dev/null)"
2002         VGROUP="$(vgs -o vg_name --noheading 2>/dev/null)"
2003
2004         if [[ $VGROUP && $v && $pv ]]; then
2005                 msg "Logical Volume Management" "\nActivating existing logical volume management (LVM).\n\n" 1
2006                 modprobe dm-mod >/dev/null 2>$ERR
2007                 errshow 'modprobe dm-mod'
2008                 vgscan >/dev/null 2>&1
2009                 vgchange -ay >/dev/null 2>&1
2010         fi
2011 }
2012
2013 lvm_create()
2014 {
2015         VGROUP='' LVM_PARTS='' VGROUP_MB=0
2016         umount_dir $MNT
2017         lvm_mkgroup || return 1
2018         local txt="\nThe last (or only) logical volume will automatically use all remaining space in the volume group."
2019         dlg VOL_COUNT menu "$_lvmnew" "\nSelect the number of logical volumes (LVs) to create in: $VGROUP\n$txt" 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9
2020         [[ $VOL_COUNT ]] || return 1
2021         lvm_extra_lvs || return 1
2022         lvm_volume_name "$_lvmlvname\nNOTE: This LV will use up all remaining space in the volume group (${VGROUP_MB}MB)" || return 1
2023         lvcreate -l +100%FREE "$VGROUP" -n "$VNAME" >/dev/null 2>$ERR
2024         errshow "lvcreate -l +100%FREE $VGROUP -n $VNAME" || return 1
2025         LVM='logical volume'; sleep 0.5
2026         txt="\nDone, volume: $VGROUP-$VNAME (${VOLUME_SIZE:-${VGROUP_MB}MB}) has been created.\n"
2027         msg "$_lvmnew (LV:$VOL_COUNT)" "$txt\n$(lsblk -o NAME,MODEL,TYPE,FSTYPE,SIZE $LVM_PARTS)\n"
2028         return 0
2029 }
2030
2031 get_lv_size()
2032 {
2033         local txt="${VGROUP}: ${SIZE}$SIZE_UNIT (${VGROUP_MB}MB remaining).$_lvmlvsize"
2034
2035         while :; do
2036                 ERR_SIZE=0
2037                 dlg VOLUME_SIZE input "$_lvmnew (LV:$VOL_COUNT)" "$txt" ''
2038                 if [[ -z $VOLUME_SIZE ]]; then
2039                         ERR_SIZE=1
2040                         break # allow bailing with escape or an empty choice
2041                 elif (( ${VOLUME_SIZE:0:1} == 0 )); then
2042                         ERR_SIZE=1 # size values can't begin with '0'
2043                 else
2044                         # walk the string and make sure all but the last char are digits
2045                         local lv=$((${#VOLUME_SIZE} - 1))
2046                         for (( i=0; i<lv; i++ )); do
2047                                 [[ ${VOLUME_SIZE:$i:1} =~ [0-9] ]] || { ERR_SIZE=1; break; }
2048                         done
2049                         if (( ERR_SIZE != 1 )); then
2050                                 case ${VOLUME_SIZE:$lv:1} in
2051                                         [mMgG]) local s=${VOLUME_SIZE:0:$lv} m=$((s * 1000))
2052                                                 case ${VOLUME_SIZE:$lv:1} in
2053                                                         [Gg]) (( m >= VGROUP_MB )) && ERR_SIZE=1 || VGROUP_MB=$((VGROUP_MB - m)) ;;
2054                                                         [Mm]) (( ${VOLUME_SIZE:0:$lv} >= VGROUP_MB )) && ERR_SIZE=1 || VGROUP_MB=$((VGROUP_MB - s)) ;;
2055                                                         *) ERR_SIZE=1
2056                                                 esac ;;
2057                                         *) ERR_SIZE=1
2058                                 esac
2059                         fi
2060                 fi
2061                 if (( ERR_SIZE )); then
2062                         msg "Invalid Logical Volume Size" "$_lvmerrlvsize"
2063                 else
2064                         break
2065                 fi
2066         done
2067
2068         return $ERR_SIZE
2069 }
2070
2071 lvm_mkgroup()
2072 {
2073         local named=''
2074         local txt="\nConfirm creation of volume group: $VGROUP\n\nwith the following partition(s):"
2075
2076         until [[ $named ]]; do
2077                 lvm_partitions || return 1
2078                 lvm_group_name || return 1
2079                 yesno "$_lvmnew" "$txt $LVM_PARTS\n" && named=true
2080         done
2081
2082         vgcreate -f "$VGROUP" $LVM_PARTS >/dev/null 2>$ERR
2083         errshow "vgcreate -f $VGROUP $LVM_PARTS" || return 1
2084
2085         SIZE=$(vgdisplay "$VGROUP" | awk '/VG Size/ { gsub(/[^0-9.]/, ""); print int($0) }')
2086         SIZE_UNIT="$(vgdisplay "$VGROUP" | awk '/VG Size/ { print substr($NF, 0, 1) }')"
2087
2088         if [[ $SIZE_UNIT == 'G' ]]; then
2089                 VGROUP_MB=$((SIZE * 1000))
2090         else
2091                 VGROUP_MB=$SIZE
2092         fi
2093
2094         msg "$_lvmnew" "\nVolume group: $VGROUP ($SIZE $SIZE_UNIT) has been created\n"
2095         return 0
2096 }
2097
2098 lvm_del_all()
2099 {
2100         local v pv
2101         pv="$(pvs -o pv_name --noheading 2>/dev/null)"
2102         v="$(lvs -o vg_name,lv_name --noheading --separator - 2>/dev/null)"
2103         VGROUP="$(vgs -o vg_name --noheading 2>/dev/null)"
2104
2105         if [[ $VGROUP || $v || $pv ]]; then
2106                 if yesno "$_lvmdelall" "$_lvmdelask"; then
2107                         for i in $v; do lvremove -f "/dev/mapper/$i" >/dev/null 2>&1; done
2108                         for i in $VGROUP; do vgremove -f "$i" >/dev/null 2>&1; done
2109                         for i in $pv; do pvremove -f "$i" >/dev/null 2>&1; done
2110                         LVM=''
2111                 fi
2112         else
2113                 msg "Delete LVM" "\nNo LVMs to remove...\n" 2
2114                 LVM=''
2115         fi
2116 }
2117
2118 lvm_delgroup()
2119 {
2120         DEL_VG=''
2121         VOL_GROUP_LIST=''
2122
2123         for i in $(lvs --noheadings | awk '{print $2}' | uniq); do
2124                 VOL_GROUP_LIST+="$i $(vgdisplay "$i" | awk '/VG Size/ {print $3$4}') "
2125         done
2126         [[ $VOL_GROUP_LIST ]] || { msg "No Groups" "\nNo volume groups found."; return 1; }
2127
2128         dlg DEL_VG menu "Logical Volume Management" "\nSelect volume group to delete.\n\nAll logical volumes within will also be deleted." $VOL_GROUP_LIST
2129         [[ $DEL_VG ]]
2130 }
2131
2132 lvm_extra_lvs()
2133 {
2134         while (( VOL_COUNT > 1 )); do
2135                 lvm_volume_name "$_lvmlvname" && get_lv_size || return 1
2136                 lvcreate -L "$VOLUME_SIZE" "$VGROUP" -n "$VNAME" >/dev/null 2>$ERR
2137                 errshow "lvcreate -L $VOLUME_SIZE $VGROUP -n $VNAME" || return 1
2138                 msg "$_lvmnew (LV:$VOL_COUNT)" "\nDone, logical volume (LV) $VNAME ($VOLUME_SIZE) has been created.\n"
2139                 (( VOL_COUNT-- ))
2140         done
2141         return 0
2142 }
2143
2144 lvm_partitions()
2145 {
2146         part_find 'part|crypt' || return 1
2147         PARTS="$(awk 'NF > 0 {print $0 " off"}' <<< "$PARTS")"
2148         dlg LVM_PARTS check "$_lvmnew" "\nSelect the partition(s) to use for the physical volume (PV)." $PARTS
2149         [[ $LVM_PARTS ]]
2150 }
2151
2152 lvm_group_name()
2153 {
2154         VGROUP=''
2155         until [[ $VGROUP ]]; do
2156                 dlg VGROUP input "$_lvmnew" "$_lvmvgname" "mygroup"
2157                 if [[ -z $VGROUP ]]; then
2158                         return 1
2159                 elif [[ ${VGROUP:0:1} == "/" || $VGROUP =~ \ |\' ]] || lsblk | grep -q "$VGROUP"; then
2160                         msg "LVM Name Error" "$_lvmerrvgname"
2161                         VGROUP=''
2162                 fi
2163         done
2164         return 0
2165 }
2166
2167 lvm_volume_name()
2168 {
2169         VNAME=''
2170         local txt="$1" default="mainvolume"
2171         (( VOL_COUNT > 1 )) && default="extravolume$VOL_COUNT"
2172         until [[ $VNAME ]]; do
2173                 dlg VNAME input "$_lvmnew (LV:$VOL_COUNT)" "\n$txt" "$default"
2174                 if [[ -z $VNAME ]]; then
2175                         return 1
2176                 elif [[ ${VNAME:0:1} == "/" || $VNAME =~ \ |\' ]] || lsblk | grep -q "$VNAME"; then
2177                         msg "LVM Name Error" "$_lvmerlvname"
2178                         VNAME=''
2179                 fi
2180         done
2181         return 0
2182 }
2183
2184 ###############################################################################
2185 # luks functions
2186
2187 luks_menu()
2188 {
2189         local choice
2190         is_bg_install || return 1
2191         dlg choice menu "LUKS Encryption" "$_luksmenu" \
2192                 "$_luksnew"  "cryptsetup -q luksFormat" \
2193                 "$_luksopen" "cryptsetup open --type luks" \
2194                 "$_luksadv"  "cryptsetup -q -s -c luksFormat" \
2195                 "back"       "Return to the main menu"
2196
2197         case "$choice" in
2198                 "$_luksnew") luks_basic || return 1 ;;
2199                 "$_luksopen") luks_open || return 1 ;;
2200                 "$_luksadv") luks_advanced || return 1 ;;
2201         esac
2202
2203         return 0
2204 }
2205
2206 luks_open()
2207 {
2208         modprobe -a dm-mod dm_crypt >/dev/null 2>&1
2209         umount_dir $MNT
2210         part_find 'part|crypt|lvm' || return 1
2211
2212         if (( COUNT == 1 )); then
2213                 LUKS_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")"
2214         else
2215                 dlg LUKS_PART menu "$_luksopen" "\nSelect which partition to open." $PARTS
2216         fi
2217
2218         [[ $LUKS_PART ]] || return 1
2219
2220         luks_pass "$_luksopen" || return 1
2221         msg "$_luksopen" "\nOpening encrypted partition: $LUKS_NAME\n\nDevice or volume used: $LUKS_PART\n" 0
2222         cryptsetup open --type luks "$LUKS_PART" "$LUKS_NAME" <<< "$LUKS_PASS" 2>$ERR
2223         errshow "cryptsetup open --type luks $LUKS_PART $LUKS_NAME" || return 1
2224         LUKS='encrypted'; luks_show
2225         return 0
2226 }
2227
2228 luks_pass()
2229 {
2230         LUKS_PASS=''
2231         local t="$1" op="$2" v='' p='' p2=''
2232
2233         until [[ $LUKS_PASS ]]; do
2234                 i=0
2235                 tput cnorm
2236                 if [[ $op ]]; then
2237                         dialog --insecure --backtitle "$DIST Installer - $SYS - v$VER" --separator $'\n' --title " $t " \
2238                                 --mixedform "\nEnter the password to decrypt $ROOT_PART\n\nThis is needed to create a keyfile." 0 0 0 \
2239                                 "Password:"  1 1 '' 1 11 "$COLUMNS" 0 1 \
2240                                 "Password2:" 2 1 '' 2 12 "$COLUMNS" 0 1 2>"$ANS" || return 1
2241
2242                 else
2243                         dialog --insecure --backtitle "$DIST Installer - $SYS - v$VER" --separator $'\n' --title " $t " --mixedform "$_luksomenu" 0 0 0 \
2244                                 "Name:"      1 1 "${LUKS_NAME:-cryptroot}" 1  7 "$COLUMNS" 0 0 \
2245                                 "Password:"  2 1 ''                        2 11 "$COLUMNS" 0 1 \
2246                                 "Password2:" 3 1 ''                        3 12 "$COLUMNS" 0 1 2>"$ANS" || return 1
2247
2248                 fi
2249
2250                 while read -r line; do
2251                         if [[ $op ]]; then
2252                                 case $i in
2253                                         0) p="$line" ;;
2254                                         1) p2="$line" ;;
2255                                 esac
2256                         else
2257                                 case $i in
2258                                         0) n="$line" ;;
2259                                         1) p="$line" ;;
2260                                         2) p2="$line" ;;
2261                                 esac
2262                         fi
2263                         (( i++ ))
2264                 done < "$ANS"
2265
2266                 if [[ -z $op && -z $n ]]; then
2267                         msg "Name Empty" "\nEncrypted device name cannot be empty.\n\nPlease try again.\n" 2
2268                 elif [[ -z $p || "$p" != "$p2" ]]; then
2269                         [[ $op ]] || LUKS_NAME="$n"
2270                         msg "Password Mismatch" "\nThe passwords entered do not match.\n\nPlease try again.\n" 2
2271                 else
2272                         [[ $op ]] || LUKS_NAME="$n"
2273                         LUKS_PASS="$p"
2274                 fi
2275         done
2276
2277         return 0
2278 }
2279
2280 luks_show()
2281 {
2282         sleep 0.5
2283         msg "$_luksnew" "\nEncrypted partition opened and ready for mounting.\n\n$(lsblk -o NAME,MODEL,SIZE,TYPE,FSTYPE "$LUKS_PART")\n\n"
2284 }
2285
2286 luks_setup()
2287 {
2288         modprobe -a dm-mod dm_crypt >/dev/null 2>&1
2289         umount_dir $MNT
2290         part_find 'part|lvm' || return 1
2291
2292         if [[ $AUTO_ROOT_PART ]]; then
2293                 LUKS_PART="$AUTO_ROOT_PART"
2294         elif (( COUNT == 1 )); then
2295                 LUKS_PART="$(awk 'NF > 0 {print $1}' <<< "$PARTS")"
2296         else
2297                 dlg LUKS_PART menu "$_luksnew" "\nSelect the partition you want to encrypt." $PARTS
2298         fi
2299
2300         [[ $LUKS_PART ]] || return 1
2301         luks_pass "$_luksnew"
2302 }
2303
2304 luks_basic()
2305 {
2306         luks_setup || return 1
2307         msg "$_luksnew" "\nCreating encrypted partition: $LUKS_NAME\n\nDevice or volume used: $LUKS_PART\n" 0
2308         cryptsetup -q luksFormat "$LUKS_PART" <<< "$LUKS_PASS" 2>$ERR
2309         errshow "cryptsetup -q luksFormat $LUKS_PART" || return 1
2310         cryptsetup open "$LUKS_PART" "$LUKS_NAME" <<< "$LUKS_PASS" 2>$ERR
2311         errshow "cryptsetup open $LUKS_PART $LUKS_NAME" || return 1
2312         LUKS='encrypted'; luks_show
2313         return 0
2314 }
2315
2316 luks_keyfile()
2317 {
2318         if [[ ! -e $MNT/crypto_keyfile.bin ]]; then
2319                 # printf "Creating LUKS keyfile /crypto_keyfile.bin\n"
2320                 local n
2321                 n="$(lsblk -lno NAME,UUID,TYPE | awk "/$LUKS_UUID/"' && /part|crypt|lvm/ {print $1}')"
2322                 local mkkey="dd bs=512 count=8 if=/dev/urandom of=/crypto_keyfile.bin"
2323                 mkkey="$mkkey && chmod 000 /crypto_keyfile.bin"
2324                 mkkey="$mkkey && cryptsetup luksAddKey /dev/$n /crypto_keyfile.bin <<< '$LUKS_PASS'"
2325                 chrun "$mkkey"
2326                 sed -i 's/FILES=()/FILES=(\/crypto_keyfile.bin)/g' $MNT/etc/mkinitcpio.conf 2>$ERR
2327         fi
2328
2329         return 0
2330 }
2331
2332 luks_advanced()
2333 {
2334         if luks_setup; then
2335                 local cipher
2336                 dlg cipher input "LUKS Encryption" "$_lukskey" "-s 512 -c aes-xts-plain64"
2337                 [[ $cipher ]] || return 1
2338                 msg "$_luksadv" "\nCreating encrypted partition: $LUKS_NAME\n\nDevice or volume used: $LUKS_PART\n" 0
2339                 cryptsetup -q $cipher luksFormat "$LUKS_PART" <<< "$LUKS_PASS" 2>$ERR
2340                 errshow "cryptsetup -q $cipher luksFormat $LUKS_PART" || return 1
2341                 cryptsetup open "$LUKS_PART" "$LUKS_NAME" <<< "$LUKS_PASS" 2>$ERR
2342                 errshow "cryptsetup open $LUKS_PART $LUKS_NAME" || return 1
2343                 luks_show
2344                 return 0
2345         fi
2346         return 1
2347 }
2348
2349 ###############################################################################
2350 # simple functions
2351 # some help avoid repetition and improve usability of some commands
2352 # others are initial setup functions used before reaching the main loop
2353
2354 ofn()
2355 {
2356         # does $2 contain $1
2357         [[ "$2" == *"$1"* ]] && printf "on" || printf "off"
2358 }
2359
2360 die()
2361 {
2362         # cleanup and exit the installer cleanly with exit code $1
2363         # when ecode is 127 unmount /run/archiso/bootmnt and reboot
2364         local ecode="$1"
2365
2366         trap - INT
2367         tput cnorm
2368         if [[ -d $MNT ]] && command cd /; then
2369                 umount_dir $MNT
2370                 if (( ecode == 127 )); then
2371                         umount_dir /run/archiso/bootmnt
2372                         sleep 0.5
2373                         reboot -f
2374                 fi
2375         fi
2376         termcol
2377         exit "$ecode"
2378 }
2379
2380 dlg()
2381 {
2382         local var="$1"   # assign output from dialog to var
2383         local dlg_t="$2" # display a dialog of type dlg_t (menu, check, input)
2384         local title="$3" # dialog title
2385         local body="$4"  # dialog message
2386         local n=0        # number of items to display
2387
2388         shift 4  # shift off args assigned above
2389
2390         # when passed a large amount of arguments (menu list) adjust menu height
2391         (( ($# / 2) > SHL )) && n=$SHL
2392         
2393         tput civis
2394         case "$dlg_t" in
2395                 menu) dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " $title " --menu "$body" 0 0 $n "$@" 2>"$ANS" || return 1 ;;
2396                 check) dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " $title " --checklist "$body" 0 0 $n "$@" 2>"$ANS" || return 1 ;;
2397                 input)
2398                         tput cnorm
2399                         local def="$1" # assign default value for input
2400                         shift
2401                         if [[ $1 == 'limit' ]]; then
2402                                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --max-input 63 --title " $title " --inputbox "$body" 0 0 "$def" 2>"$ANS" || return 1
2403                         else
2404                                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " $title " --inputbox "$body" 0 0 "$def" 2>"$ANS" || return 1
2405                         fi
2406                         ;;
2407         esac
2408         # if answer file isn't empty read from it into $var
2409         [[ -s "$ANS" ]] && printf -v "$var" "%s" "$(< "$ANS")"
2410 }
2411
2412 msg()
2413 {
2414         # displays a message dialog
2415         # when more than 2 args the message will disappear after sleep time ($3)
2416         local title="$1"
2417         local body="$2"
2418         shift 2
2419         tput civis
2420         if (( $# )); then
2421                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --sleep "$1" --title " $title " --infobox "$body\n" 0 0
2422         else
2423                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " $title " --msgbox "$body\n" 0 0
2424         fi
2425 }
2426
2427 yesno()
2428 {
2429         local title="$1" body="$2" yes='Yes' no='No'
2430         (( $# >= 3 )) && yes="$3"
2431         (( $# >= 4 )) && no="$4"
2432         tput civis
2433         if (( $# == 5 )); then
2434                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --defaultno --title " $title " --yes-label "$yes" --no-label "$no" --yesno "$body\n" 0 0
2435         else
2436                 dialog --backtitle "$DIST Installer - $SYS - v$VER" --title " $title " --yes-label "$yes" --no-label "$no" --yesno "$body\n" 0 0
2437         fi
2438 }
2439
2440 chrun()
2441 {
2442         arch-chroot "$MNT" bash -c "$1"
2443 }
2444
2445 debug()
2446 {
2447         export PS4='| ${BASH_SOURCE} LINE:${LINENO} FUNC:${FUNCNAME[0]:+ ${FUNCNAME[0]}()} |>  '
2448         set -x
2449         exec 3>| $DBG
2450         BASH_XTRACEFD=3
2451         DEBUG=true
2452 }
2453
2454 sigint()
2455 {
2456         printf "\n^C caught, cleaning up...\n"
2457         die 1
2458 }
2459
2460 termcol()
2461 {
2462         local colors=(
2463         "\e]P0191919" # #191919
2464         "\e]P1D15355" # #D15355
2465         "\e]P2609960" # #609960
2466         "\e]P3FFCC66" # #FFCC66
2467         "\e]P4255A9B" # #255A9B
2468         "\e]P5AF86C8" # #AF86C8
2469         "\e]P62EC8D3" # #2EC8D3
2470         "\e]P7949494" # #949494
2471         "\e]P8191919" # #191919
2472         "\e]P9D15355" # #D15355
2473         "\e]PA609960" # #609960
2474         "\e]PBFF9157" # #FF9157
2475         "\e]PC4E88CF" # #4E88CF
2476         "\e]PDAF86C8" # #AF86C8
2477         "\e]PE2ec8d3" # #2ec8d3
2478         "\e]PFE1E1E1" # #E1E1E1
2479         )
2480
2481         [[ $TERM == 'linux' ]] && printf "%b" "${colors[@]}" && clear
2482 }
2483
2484 errshow()
2485 {
2486         [ $? -eq 0 ] && return 0
2487
2488         local fatal=0 err=""
2489         err="$(sed 's/[^[:print:]]//g; s/\[[0-9\;:]*\?m//g; s/==> //g; s/] ERROR:/]\nERROR:/g' "$ERR")"
2490
2491         (( $1 == 1 )) && { fatal=1; shift; }
2492
2493         local txt="\nThe command exited abnormally:\n\n$1\n\n"
2494
2495         if [[ $err ]]; then
2496                 txt+="With the following message:\n\n$err\n\n"
2497         else
2498                 txt+="With no error message:\n\n"
2499         fi
2500
2501         if (( fatal )); then
2502                 txt+="Errors at this stage are fatal and the install cannot continue.\n"
2503         else
2504                 txt+="Errors at this stage are non-fatal and can be either fixed or ignored depending on the error.\n"
2505         fi
2506         msg "Install Error" "$txt"
2507
2508         if (( fatal )); then
2509                 [[ -r $DBG && $TERM == 'linux' ]] && less "$DBG"
2510                 die 1
2511         fi
2512
2513         return 1
2514 }
2515
2516 load_bcm()
2517 {
2518         msg "Broadcom Wireless Setup" "\nDetected chipset is Broadcom BCM4352\n\nDisabling bcma/b43 modules and loading wl module.\n" 1
2519         { rmmod wl; rmmod bcma; rmmod b43; rmmod ssb; modprobe wl; depmod -a; } >/dev/null 2>&1
2520         BROADCOM_WL=true
2521 }
2522
2523 prechecks()
2524 {
2525         local i=1
2526
2527         if (( $1 >= 0 )) && ! grep -qw "$MNT" /proc/mounts; then
2528                 msg "Not Mounted" "\nPartition(s) must be mounted first.\n" 2
2529                 SEL=4 i=0
2530         elif [[ $1 -ge 1 && -z $BOOTLDR ]]; then
2531                 msg "No Bootloader" "\nBootloader must be selected first.\n" 2
2532                 SEL=5 i=0
2533         elif [[ $1 -ge 2 && (-z $NEWUSER || -z $USER_PASS) ]]; then
2534                 msg "No User" "\nA user must be created first.\n" 2
2535                 SEL=6 i=0
2536         elif [[ $1 -ge 3 && -z $CONFIG_DONE ]]; then
2537                 msg "Not Configured" "\nSystem configuration must be done first.\n" 2
2538                 SEL=7 i=0
2539         fi
2540         (( i )) # return code
2541 }
2542
2543 umount_dir()
2544 {
2545         mount | grep -q 'swap' && swapoff -a
2546         for dir; do
2547                 [[ -d $dir && "$(mount | grep "on $dir ")" ]] || continue
2548                 umount "$dir" 2>/dev/null || { sleep 0.5; umount -f "$dir" 2>/dev/null || umount -l "$dir"; }
2549         done
2550 }
2551
2552 chk_connect()
2553 {
2554         msg "Network Connect" "\nVerifying network connection\n" 1
2555         curl -sIN --connect-timeout 5 'https://www.archlinux.org/' | sed '1q' | grep -q '200'
2556 }
2557
2558 net_connect()
2559 {
2560         if chk_connect; then
2561                 return 0
2562         else
2563                 if hash nmtui >/dev/null 2>&1; then
2564                         tput civis
2565                         if [[ $TERM == 'linux' ]]; then
2566                                 printf "%b" "\e]P1191919" "\e]P4191919"
2567                                 nmtui-connect
2568                                 printf "%b" "\e]P1D15355" "\e]P4255a9b"
2569                         else
2570                                 nmtui-connect
2571                         fi
2572                         chk_connect
2573                 else
2574                         return 1
2575                 fi
2576         fi
2577 }
2578
2579 is_bg_install()
2580 {
2581         [[ $RSYNC_PID || $MIRROR_PID ]] || return 0
2582         msg "Install Running" "\nA background install process is currently running.\n" 2
2583         return 1
2584 }
2585
2586 system_devices()
2587 {
2588         IGNORE_DEV="$(lsblk -lno NAME,MOUNTPOINT | awk '/\/run\/archiso\/bootmnt/ {sub(/[1-9]/, ""); print $1}')"
2589
2590         if [[ $IGNORE_DEV ]]; then
2591                 SYS_DEVS="$(lsblk -lno NAME,SIZE,TYPE | awk '/disk/ && !'"/$IGNORE_DEV/"' {print "/dev/" $1 " " $2}')"
2592         else
2593                 SYS_DEVS="$(lsblk -lno NAME,SIZE,TYPE | awk '/disk/ {print "/dev/" $1 " " $2}')"
2594         fi
2595
2596         if [[ -z $SYS_DEVS ]]; then
2597                 msg "Device Error" "\nNo available devices...\n\nExiting..\n" 2
2598                 die 1
2599         fi
2600
2601         DEV_COUNT=0
2602         while read -r line; do
2603                 (( DEV_COUNT++ ))
2604         done <<< "$SYS_DEVS"
2605 }
2606
2607 system_identify()
2608 {
2609         if [[ $VM ]]; then
2610                 UCODE=''
2611         elif grep -q 'AuthenticAMD' /proc/cpuinfo; then
2612                 UCODE="amd-ucode"
2613         elif grep -q 'GenuineIntel' /proc/cpuinfo; then
2614                 UCODE="intel-ucode"
2615         fi
2616
2617         modprobe -q efivarfs >/dev/null 2>&1
2618
2619         _prep="\nOnce a step is finished a step you will be returned here, if the step was successful the cursor will be advanced to the next step.\nIf a step is unsuccessful the cursor will be placed on the step required to advance (when possible).\n\nTo begin the install you should have:\n\n  - A root (/) partition mounted."
2620         if [[ -d /sys/firmware/efi/efivars ]]; then
2621                 export SYS="UEFI"
2622                 grep -q /sys/firmware/efi/efivars /proc/mounts || mount -t efivarfs efivarfs /sys/firmware/efi/efivars
2623                 _prep+="\n  - An EFI boot partition mounted."
2624         else
2625                 export SYS="BIOS"
2626         fi
2627         _prep+="\n\nOnce finished mounting, a portion of the install can be done in the background while you continue configuring the system:\n"
2628         _prep+="\n  - Choose the system bootloader.\n  - Create a user and password."
2629         _prep+="\n  - Basic system configuration, kernel, shell, login, packages, etc..\n\nOnce you're happy with the choices and the required steps are complete, the main install can be started."
2630 }
2631
2632 ###############################################################################
2633 # entry point
2634
2635 # enable some nicer colours in the linux console
2636 termcol
2637
2638 if (( UID != 0 )); then
2639         msg "Not Root" "\nThis installer must be run as root or using sudo.\n\nExiting..\n" 2
2640         die 1
2641 elif ! grep -qwm 1 'lm' /proc/cpuinfo; then
2642         msg "Not x86_64 Architecture" "\nThis installer only supports x86_64 architectures.\n\nExiting..\n" 2
2643         die 1
2644 elif [[ $1 =~ (-d|--debug) ]]; then
2645         debug
2646 fi
2647
2648 # trap ^C to perform cleanup
2649 trap sigint INT
2650
2651 system_identify
2652 system_devices
2653
2654 msg "Welcome to the $DIST Installer" "\nThis will help you get $DIST setup on your system.\nHaving previous GNU/Linux and shell experience will be an asset, however we try our best to keep things simple.\n\nIf you are unsure about an option, a default will be listed or\nthe first selected option will usually be the default (excluding language and timezone).\n\n\nMenu Navigation:\n\n - Select items with the arrow keys or the option number.\n - Use [Space] to toggle options and [Enter] to confirm.\n - Switch between buttons using [Tab] or the arrow keys.\n - Use [Page Up] and [Page Down] to jump whole pages\n - Press the highlighted key of an option to select it.\n"
2655
2656 select_keymap || { clear; die 0; }
2657
2658 # try to fix problematic broadcom wireless chipset before network check
2659 lspci -vnn -d 14e4: | grep -q 'BCM4352' && load_bcm
2660
2661 net_connect || { msg "Not Connected" "\nThis installer requires an active internet connection.\n\nExiting..\n" 2; die 1; }
2662
2663 while :; do
2664         main
2665 done
2666
2667 # vim:fdm=marker:fmr={,}