Krolyxon
Commit 892d77e

Branches

Refs →

aurora892d77e

install.sh: full install from the Arch ISO

Author
krolyxon
Date
2026-11-08 18:54 +0530
Parent
70456f7
Commit
892d77ef215443778f59ba62f78299af3c880d8a

+544 −11

2 files changed

  1. M install.sh +75 −11
  2. A install/base.sh +469 −0
M install.sh View
@@ -7,25 +7,63 @@
77 # hardware (Lenovo Legion, Intel + NVIDIA hybrid, linux-zen) and my habits.
88 # It is NOT a general purpose installer and I'm not trying to make it one.
99 #
10−# Only run it on a FRESHLY INSTALLED, UNCONFIGURED Arch Linux system.
11−# It installs a lot of packages, moves your existing configs out of the way,
12−# changes your login shell and can enable system services.
10+# From the Arch ISO it installs everything: it ERASES the disk you pick (or uses
11+# partitions you pick), installs Arch, GRUB and your user, then the desktop.
12+# On an installed system it only does the desktop part. Only run that on a
13+# FRESHLY INSTALLED, UNCONFIGURED Arch Linux system: it installs a lot of packages,
14+# moves your existing configs out of the way, changes your login shell and can
15+# enable system services.
1316 #
1417 # If you just want a piece of the setup, copy the files you like instead.
1518 #
1619 # Usage:
17−# ./install.sh interactive
18−# ./install.sh --yes pre-select everything (still asks the fresh-system question)
19−# ./install.sh --check compare installed packages with packages/*.txt, changes nothing
20+# From the Arch ISO (as root, network up):
21+# bash <(curl -fsSL https://git.krolyxon.com/aurora/raw/master/install.sh)
22+#
23+# ./install.sh on the Arch ISO: full install (disk, Arch, desktop)
24+# on an installed Arch: the desktop only (= --desktop)
25+# ./install.sh --desktop desktop only: packages, dotfiles (stow), zsh, services
26+# ./install.sh --base Arch ISO only: disk, Arch, GRUB, user, no desktop
27+# ./install.sh --yes pre-select everything, don't ask (still asks the fresh-system question)
28+# ./install.sh --check compare installed packages with packages/*.txt, changes nothing
2029
2130 trap 'log WARN "Interrupted by user"; exit 130' INT
2231
2332 INSTALL_STATUS="none" # none | partial | complete | failed
33+MODE="" # full | base | desktop
2434 AUTO_YES=0
2535 CHECK_ONLY=0
36+PICKS="" # file with the packages to install instead of the pickers
2637 BACKUP_DIR=""
2738
39+REPO_URLS=(https://git.krolyxon.com/aurora https://github.com/krolyxon/aurora)
40+
2841 currentDir="$(dirname "$(readlink -f "$0")")"
42+
43+###############
44+## Bootstrap ##
45+###############
46+# Started without the repo (bash <(curl ...)): clone it and run the clone's install.sh.
47+# On the ISO the clone is temporary (the full install copies it to the new home);
48+# elsewhere it's the checkout the dotfiles get stowed from, so it goes to ~/aurora.
49+if [[ ! -f "$currentDir/install/lib.sh" ]]; then
50+ if [[ $EUID -eq 0 && -d /run/archiso ]]; then
51+ dest=/tmp/aurora
52+ command -v git >/dev/null || pacman -Sy --needed --noconfirm git || exit 1
53+ else
54+ dest="$HOME/aurora"
55+ command -v git >/dev/null || sudo pacman -S --needed --noconfirm git || exit 1
56+ fi
57+ if [[ ! -f "$dest/install/lib.sh" ]]; then
58+ [[ -e "$dest" ]] && { echo "$dest exists but isn't an aurora checkout, move it away first." >&2; exit 1; }
59+ for url in "${REPO_URLS[@]}"; do
60+ git clone "$url" "$dest" && break
61+ done
62+ fi
63+ [[ -f "$dest/install/lib.sh" ]] || { echo "Couldn't clone aurora." >&2; exit 1; }
64+ exec bash "$dest/install.sh" "$@"
65+fi
66+
2967 cd "$currentDir" || exit 1
3068 PKG_DIR="$currentDir/packages"
3169
@@ -35,24 +73,37 @@ source "$currentDir/install/lib.sh"
3573 #######################
3674 ## Arguement Parsing ##
3775 #######################
38−for arg in "$@"; do
39− case "$arg" in
76+while (($#)); do
77+ case "$1" in
78+ --desktop)
79+ MODE=desktop
80+ ;;
81+ --base)
82+ MODE=base
83+ ;;
4084 --yes|--ci|--non-interactive)
4185 AUTO_YES=1
4286 ;;
4387 --check)
4488 CHECK_ONLY=1
4589 ;;
90+ --picks)
91+ # internal: the full install hands the pickers' answers to the desktop part
92+ PICKS="${2:-}"
93+ shift
94+ [[ -f "$PICKS" ]] || { log ERROR "--picks: no such file: $PICKS"; exit 1; }
95+ ;;
4696 -h|--help)
4797 usage
4898 exit 0
4999 ;;
50100 *)
51− log ERROR "Unknown option: $arg"
101+ log ERROR "Unknown option: $1"
52102 usage
53103 exit 1
54104 ;;
55105 esac
106+ shift
56107 done
57108
58109 if ((CHECK_ONLY)); then
@@ -60,5 +111,18 @@ if ((CHECK_ONLY)); then
60111 exit 0
61112 fi
62113
63−# shellcheck source=install/desktop.sh
64−source "$currentDir/install/desktop.sh"
114+# No mode given: the ISO means a bare machine, anything else already runs Arch
115+if [[ -z "$MODE" ]]; then
116+ if [[ $EUID -eq 0 && -d /run/archiso ]]; then MODE=full; else MODE=desktop; fi
117+fi
118+
119+case "$MODE" in
120+ full|base)
121+ # shellcheck source=install/base.sh
122+ source "$currentDir/install/base.sh"
123+ ;;
124+ desktop)
125+ # shellcheck source=install/desktop.sh
126+ source "$currentDir/install/desktop.sh"
127+ ;;
128+esac
A install/base.sh View
@@ -0,0 +1,469 @@
1+# shellcheck shell=bash
2+# Base system from the Arch ISO: disk, Arch, GRUB, user, then (unless --base) the desktop part
3+# inside the new system's chroot. Sourced by install.sh, runs as root.
4+#
5+# Every question comes first and nothing is touched until the summary is confirmed
6+# (except cfdisk in manual mode, which only writes when you tell it to).
7+# After that it runs unattended.
8+
9+LOG=/tmp/aurora-install.log
10+CRYPT_NAME=cryptroot
11+
12+##############
13+## Preflight ##
14+##############
15+if [[ $EUID -ne 0 || ! -d /run/archiso ]]; then
16+ log ERROR "The full install (and --base) only runs from the Arch ISO, as root."
17+ log INFO "On an installed system use: ./install.sh --desktop"
18+ exit 1
19+fi
20+
21+if [[ "$(cat /sys/firmware/efi/fw_platform_size 2>/dev/null)" != 64 ]]; then
22+ log ERROR "The ISO isn't booted in 64-bit UEFI mode. Legacy BIOS isn't supported."
23+ exit 1
24+fi
25+
26+if ! curl -fsI --max-time 10 https://archlinux.org >/dev/null; then
27+ log ERROR "No network."
28+ cat >&2 <<'EOF'
29+Ethernet should just work. Wi-Fi:
30+ iwctl device list
31+ iwctl station <device> get-networks
32+ iwctl station <device> connect <SSID>
33+(Wi-Fi networks you connect to here are carried over to the new system.)
34+EOF
35+ exit 1
36+fi
37+
38+log INFO "Preparing the live system"
39+timedatectl set-ntp true
40+# the ISO fills its keyring at boot: wait for that before using pacman
41+for _ in {1..60}; do
42+ systemctl is-active -q pacman-init.service && break
43+ sleep 1
44+done
45+sed -i 's/^#\?ParallelDownloads.*/ParallelDownloads = 15/' /etc/pacman.conf
46+pacman -Sy --needed --noconfirm archlinux-keyring gum || exit 1
47+
48+
49+##############
50+## Helpers ##
51+##############
52+abort() {
53+ log WARN "Aborted, nothing was changed."
54+ exit 1
55+}
56+
57+# ask VAR "Prompt" [default]
58+ask() {
59+ local -n _out="$1"
60+ _out="$(gum input --prompt "$2: " --value "${3:-}")" || abort
61+}
62+
63+# ask_secret VAR "Label" [allow_empty]
64+ask_secret() {
65+ local -n _out="$1"
66+ local a b
67+ while :; do
68+ a="$(gum input --password --prompt "$2: ")" || abort
69+ if [[ -z "$a" ]]; then
70+ if ((${3:-0})); then _out=""; return; fi
71+ gum_warn "Can't be empty" >&2
72+ continue
73+ fi
74+ b="$(gum input --password --prompt "$2 (again): ")" || abort
75+ [[ "$a" == "$b" ]] && { _out="$a"; return; }
76+ gum_warn "They don't match, try again" >&2
77+ done
78+}
79+
80+# yes/no question, VAR=1/0. ask_yn VAR "Question" default(yes|no)
81+ask_yn() {
82+ local -n _out="$1"
83+ local rc
84+ gum confirm --default="$([[ $3 == yes ]] && echo true || echo false)" "$2"
85+ rc=$?
86+ ((rc > 1)) && abort # ctrl+c / esc
87+ ((rc == 0)) && _out=1 || _out=0
88+}
89+
90+# One lsblk column of one device, without padding
91+field() {
92+ lsblk -dno "$1" "$2" | sed 's/^ *//; s/ *$//'
93+}
94+
95+# The disk the ISO itself was booted from (a USB stick): never offered
96+iso_disk="$(lsblk -no PKNAME "$(findmnt -no SOURCE /run/archiso/bootmnt 2>/dev/null)" 2>/dev/null | head -1)"
97+
98+disk_lines() {
99+ local name type
100+ while read -r name type; do
101+ [[ "$type" == disk && "$name" != /dev/zram* && "$name" != "/dev/$iso_disk" ]] || continue
102+ printf '%-16s %8s %s\n' "$name" "$(field SIZE "$name")" "$(field MODEL "$name")"
103+ done < <(lsblk -dpno NAME,TYPE)
104+}
105+
106+part_lines() {
107+ local name type
108+ while read -r name type; do
109+ [[ "$type" == part ]] || continue
110+ [[ -n "$iso_disk" && "$(field PKNAME "$name")" == "$iso_disk" ]] && continue
111+ [[ " $* " == *" $name "* ]] && continue # already picked
112+ printf '%-16s %8s %-11s %s\n' "$name" "$(field SIZE "$name")" \
113+ "$(field FSTYPE "$name")" "$(field PARTTYPENAME "$name")"
114+ done < <(lsblk -lpno NAME,TYPE)
115+}
116+
117+# pick VAR "Header" < <(lines): the first column of the chosen line.
118+# Not in a pipe: VAR would only be set in the pipe's subshell.
119+pick() {
120+ local -n _out="$1"
121+ local lines line
122+ lines="$(cat)"
123+ [[ -n "$lines" ]] || { log ERROR "Nothing to choose from: $2"; exit 1; }
124+ line="$(gum choose --header "$2" <<< "$lines")" || abort
125+ _out="${line%% *}"
126+}
127+
128+# Run a command on a pseudo-terminal: the output goes through tee into the log, and
129+# pacman/paru only draw their progress bars on a terminal (silent for minutes otherwise)
130+with_tty() {
131+ script -qefc "$(printf '%q ' "$@")" /dev/null
132+}
133+
134+STEP=""
135+step() {
136+ STEP="$1"
137+ log INFO "== $1"
138+}
139+
140+
141+###############
142+## Questions ##
143+###############
144+gum style --border double --border-foreground 196 --padding "1 2" --bold \
145+"⚠️ aurora: full install
146+
147+This installs Arch Linux + MY desktop (Hyprland + aurora), built for MY machine
148+(Lenovo Legion, Intel + NVIDIA hybrid, linux-zen). It is NOT general purpose.
149+
150+It will ERASE the disk you pick (or format the partitions you pick).
151+Nothing happens until you confirm the summary at the end.
152+
153+Provided AS IS, WITHOUT ANY WARRANTY. Proceed at your OWN RISK."
154+
155+## Disk ##
156+DISK_MODE="$(gum choose --header "Partitioning" \
157+ "auto erase a whole disk: 1 GiB EFI + the rest for Arch" \
158+ "manual partition yourself (cfdisk), then pick the partitions")" || abort
159+DISK_MODE="${DISK_MODE%% *}"
160+
161+DISK="" ESP="" ROOT="" FORMAT_ESP=1
162+if [[ "$DISK_MODE" == auto ]]; then
163+ pick DISK "Disk to ERASE" < <(disk_lines)
164+else
165+ cfdisk_disk=""
166+ pick cfdisk_disk "Run cfdisk on" < <(disk_lines; echo "skip (already partitioned)")
167+ if [[ "$cfdisk_disk" != skip ]]; then
168+ cfdisk "$cfdisk_disk"
169+ partprobe "$cfdisk_disk" 2>/dev/null
170+ udevadm settle
171+ fi
172+
173+ pick ESP "EFI system partition (mounted at /boot)" < <(part_lines)
174+ if (( $(lsblk -bdno SIZE "$ESP") < 500 * 1024 * 1024 )); then
175+ gum_warn "$ESP is smaller than 500 MiB. /boot holds two kernels + initramfs (~300 MiB)." >&2
176+ gum confirm --default=false "Use it anyway?" || abort
177+ fi
178+ if [[ "$(field FSTYPE "$ESP")" == vfat ]]; then
179+ # an existing ESP probably boots another OS too: keep it unless asked
180+ ask_yn FORMAT_ESP "$ESP is already FAT. Format it? (No keeps other systems' boot loaders)" no
181+ else
182+ log INFO "$ESP isn't FAT yet, it will be formatted"
183+ fi
184+
185+ pick ROOT "Root partition (will be FORMATTED)" < <(part_lines "$ESP")
186+fi
187+
188+## Encryption ##
189+ask_yn LUKS "Encrypt the system with LUKS2? (asks for the passphrase at every boot)" yes
190+LUKS_PASS=""
191+((LUKS)) && ask_secret LUKS_PASS "Encryption passphrase"
192+
193+## Host and user ##
194+while :; do
195+ ask HOSTNAME_NEW "Hostname" "arch"
196+ [[ "$HOSTNAME_NEW" =~ ^[a-z0-9][a-z0-9-]{0,62}$ ]] && break
197+ gum_warn "Lowercase letters, digits and '-' only" >&2
198+done
199+while :; do
200+ ask USERNAME_NEW "Username"
201+ [[ "$USERNAME_NEW" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] && break
202+ gum_warn "Lowercase letters, digits, '_' and '-', starting with a letter" >&2
203+done
204+ask_secret USER_PASS "Password for $USERNAME_NEW"
205+ask_secret ROOT_PASS "Root password (empty: root login disabled, use sudo)" 1
206+
207+## Locale ##
208+TIMEZONE="Asia/Kolkata"
209+ask_yn tz_ok "Timezone $TIMEZONE?" yes
210+if ! ((tz_ok)); then
211+ TIMEZONE="$(timedatectl list-timezones | gum filter --header "Timezone")" || abort
212+fi
213+while :; do
214+ ask LOCALE "Locale" "en_US.UTF-8"
215+ grep -q "^#\?${LOCALE//./\\.} " /etc/locale.gen && break
216+ gum_warn "Not in /etc/locale.gen" >&2
217+done
218+while :; do
219+ ask KEYMAP "Console keymap" "us"
220+ localectl list-keymaps | grep -qx "$KEYMAP" && break
221+ gum_warn "Unknown keymap" >&2
222+done
223+
224+## GRUB ##
225+ask_yn GRUB_HIDDEN "Skip the GRUB menu at boot? (hold Shift/Esc to see it)" yes
226+ask_yn OS_PROBER "Add other installed systems (Windows, ...) to GRUB? (os-prober)" no
227+
228+## Desktop packages ##
229+SELECTED_PKGS=()
230+[[ "$MODE" == full ]] && pick_packages
231+
232+## Summary ##
233+case "$(grep -m1 -o 'GenuineIntel\|AuthenticAMD' /proc/cpuinfo)" in
234+ GenuineIntel) UCODE=(intel-ucode) ;;
235+ AuthenticAMD) UCODE=(amd-ucode) ;;
236+ *) UCODE=() ;;
237+esac
238+
239+if [[ "$DISK_MODE" == auto ]]; then
240+ disk_desc="$DISK ($(field SIZE "$DISK") $(field MODEL "$DISK")): ERASED, 1 GiB EFI + Arch"
241+ CONFIRM_TARGET="$DISK"
242+else
243+ disk_desc="EFI $ESP ($( ((FORMAT_ESP)) && echo formatted || echo kept)), Arch on $ROOT (formatted)"
244+ CONFIRM_TARGET="$ROOT"
245+fi
246+if [[ "$MODE" == full ]]; then
247+ desktop_desc="Hyprland + aurora, $(read_list desktop | wc -l) base + ${#SELECTED_PKGS[@]} picked packages"
248+else
249+ desktop_desc="none (--base), run ./install.sh --desktop later"
250+fi
251+
252+gum style --border rounded --padding "1 2" \
253+"Summary
254+
255+Disk: $disk_desc
256+Encryption: $( ((LUKS)) && echo "LUKS2" || echo "none")
257+System: $(read_list base | grep -c .) packages (linux-zen + linux) ${UCODE[*]}, GRUB, zram swap
258+Host/user: $HOSTNAME_NEW / $USERNAME_NEW (sudo), root login $( [[ -n $ROOT_PASS ]] && echo enabled || echo disabled)
259+Locale: $TIMEZONE, $LOCALE, keymap $KEYMAP
260+GRUB: menu $( ((GRUB_HIDDEN)) && echo hidden || echo shown), os-prober $( ((OS_PROBER)) && echo on || echo off)
261+Desktop: $desktop_desc"
262+
263+answer="$(gum input --placeholder "$CONFIRM_TARGET" \
264+ --header "Type $CONFIRM_TARGET to FORMAT it and start the install")" || abort
265+[[ "$answer" == "$CONFIRM_TARGET" ]] || abort
266+
267+
268+##################
269+## Installation ##
270+##################
271+# From here on: unattended, logged, stop at the first error
272+exec 3>&1 4>&2
273+exec > >(tee -a "$LOG") 2>&1
274+set -Eeuo pipefail
275+trap 'log ERROR "Failed: $STEP (line $LINENO). Log: $LOG. /mnt is still mounted for a look around."' ERR
276+trap 'rm -f /mnt/etc/sudoers.d/99-aurora-install' EXIT
277+
278+# A previous attempt may have left things mounted/open
279+if mountpoint -q /mnt; then umount -R /mnt; fi
280+if [[ -e "/dev/mapper/$CRYPT_NAME" ]]; then cryptsetup close "$CRYPT_NAME"; fi
281+
282+if [[ "$DISK_MODE" == auto ]]; then
283+ step "Partitioning $DISK"
284+ # old signatures (LUKS headers, filesystems) first, then the partition table
285+ for p in $(lsblk -lpno NAME,TYPE "$DISK" | awk '$2 == "part" { print $1 }'); do
286+ wipefs -af "$p"
287+ done
288+ wipefs -af "$DISK"
289+ sgdisk --zap-all "$DISK"
290+ sgdisk -n1:0:+1G -t1:ef00 -c1:EFI \
291+ -n2:0:0 -t2:"$( ((LUKS)) && echo 8309 || echo 8304)" -c2:arch "$DISK"
292+ partprobe "$DISK" || true
293+ udevadm settle
294+ # partition paths from lsblk: nvme0n1p1 and sda1 alike
295+ mapfile -t parts < <(lsblk -lpno NAME,TYPE "$DISK" | awk '$2 == "part" { print $1 }')
296+ ESP="${parts[0]}"
297+ ROOT="${parts[1]}"
298+fi
299+
300+ROOT_DEV="$ROOT"
301+if ((LUKS)); then
302+ step "Encrypting $ROOT"
303+ printf '%s' "$LUKS_PASS" | cryptsetup luksFormat --type luks2 --batch-mode --key-file=- "$ROOT"
304+ printf '%s' "$LUKS_PASS" | cryptsetup open --key-file=- "$ROOT" "$CRYPT_NAME"
305+ ROOT_DEV="/dev/mapper/$CRYPT_NAME"
306+ LUKS_UUID="$(blkid -s UUID -o value "$ROOT")"
307+fi
308+
309+step "Formatting and mounting"
310+((FORMAT_ESP)) && mkfs.fat -F32 -n EFI "$ESP"
311+mkfs.ext4 -F -L arch "$ROOT_DEV"
312+mount "$ROOT_DEV" /mnt
313+mount --mkdir -o umask=0077 "$ESP" /mnt/boot
314+
315+step "Installing the base system (pacstrap)"
316+mapfile -t base_pkgs < <(read_list base)
317+with_tty pacstrap -K /mnt "${base_pkgs[@]}" "${UCODE[@]}"
318+genfstab -U /mnt >> /mnt/etc/fstab
319+
320+step "Configuring the system"
321+ln -sf "/usr/share/zoneinfo/$TIMEZONE" /mnt/etc/localtime
322+arch-chroot /mnt hwclock --systohc
323+sed -i "s/^#\(${LOCALE//./\\.} \)/\1/" /mnt/etc/locale.gen
324+arch-chroot /mnt locale-gen
325+echo "LANG=$LOCALE" > /mnt/etc/locale.conf
326+echo "KEYMAP=$KEYMAP" > /mnt/etc/vconsole.conf
327+echo "$HOSTNAME_NEW" > /mnt/etc/hostname
328+cat > /mnt/etc/hosts <<EOF
329+127.0.0.1 localhost
330+::1 localhost
331+127.0.1.1 $HOSTNAME_NEW.localdomain $HOSTNAME_NEW
332+EOF
333+sed -i 's/^#\?ParallelDownloads.*/ParallelDownloads = 15/; s/^#Color$/Color/' /mnt/etc/pacman.conf
334+# swap in compressed RAM instead of a partition/file (no hibernation anyway)
335+cat > /mnt/etc/systemd/zram-generator.conf <<'EOF'
336+[zram0]
337+zram-size = min(ram / 2, 8192)
338+compression-algorithm = zstd
339+EOF
340+
341+CMDLINE=""
342+if ((LUKS)); then
343+ step "initramfs with LUKS"
344+ # Use whatever hooks the installed mkinitcpio defaults to: systemd-based or busybox
345+ hooks="$(grep '^HOOKS=' /mnt/etc/mkinitcpio.conf)"
346+ if [[ " ${hooks//[()=]/ } " == *" systemd "* ]]; then
347+ sed -i '/^HOOKS=/ s/ filesystems/ sd-encrypt filesystems/' /mnt/etc/mkinitcpio.conf
348+ CMDLINE="rd.luks.name=$LUKS_UUID=$CRYPT_NAME root=/dev/mapper/$CRYPT_NAME"
349+ else
350+ sed -i '/^HOOKS=/ s/ filesystems/ encrypt filesystems/' /mnt/etc/mkinitcpio.conf
351+ CMDLINE="cryptdevice=UUID=$LUKS_UUID:$CRYPT_NAME root=/dev/mapper/$CRYPT_NAME"
352+ fi
353+ grep -q '^HOOKS=.*encrypt filesystems' /mnt/etc/mkinitcpio.conf
354+ arch-chroot /mnt mkinitcpio -P
355+fi
356+
357+step "Users"
358+arch-chroot /mnt useradd -m -G wheel -s /usr/bin/zsh "$USERNAME_NEW"
359+printf '%s:%s\n' "$USERNAME_NEW" "$USER_PASS" | arch-chroot /mnt chpasswd
360+if [[ -n "$ROOT_PASS" ]]; then
361+ printf 'root:%s\n' "$ROOT_PASS" | arch-chroot /mnt chpasswd
362+else
363+ arch-chroot /mnt passwd -l root
364+fi
365+echo '%wheel ALL=(ALL:ALL) ALL' > /mnt/etc/sudoers.d/10-wheel
366+chmod 0440 /mnt/etc/sudoers.d/10-wheel
367+arch-chroot /mnt visudo -cf /etc/sudoers.d/10-wheel
368+
369+step "GRUB"
370+arch-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=arch
371+# also EFI/BOOT/BOOTX64.EFI, for firmware that forgets its boot entries; only on a disk that's all ours
372+[[ "$DISK_MODE" == auto ]] && arch-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot --removable
373+g=/mnt/etc/default/grub
374+[[ -n "$CMDLINE" ]] && sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=\"\(.*\)\"|GRUB_CMDLINE_LINUX_DEFAULT=\"\1 $CMDLINE\"|" "$g"
375+# zen is the default entry, plain linux stays as the fallback
376+[[ -f /mnt/boot/vmlinuz-linux-zen ]] && echo 'GRUB_TOP_LEVEL="/boot/vmlinuz-linux-zen"' >> "$g"
377+if ((GRUB_HIDDEN)); then
378+ sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/; s/^#\?GRUB_TIMEOUT_STYLE=.*/GRUB_TIMEOUT_STYLE=hidden/' "$g"
379+fi
380+((OS_PROBER)) && sed -i 's/^#\?GRUB_DISABLE_OS_PROBER=.*/GRUB_DISABLE_OS_PROBER=false/' "$g"
381+arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg
382+
383+step "Services"
384+arch-chroot /mnt systemctl enable NetworkManager.service systemd-timesyncd.service fstrim.timer
385+
386+# Wi-Fi networks joined with iwctl on the ISO -> NetworkManager, so the first boot is online
387+for psk in /var/lib/iwd/*.psk; do
388+ [[ -f "$psk" ]] || continue
389+ ssid="$(basename "$psk" .psk)"
390+ # iwd hex-encodes names with special characters as =<hex>
391+ [[ "$ssid" == =* ]] && ssid="$(printf '%b' "$(sed 's/../\\x&/g' <<< "${ssid#=}")")"
392+ key="$(sed -n 's/^Passphrase=//p' "$psk")"
393+ [[ -n "$key" ]] || key="$(sed -n 's/^PreSharedKey=//p' "$psk")"
394+ [[ -n "$key" ]] || continue
395+ mkdir -p /mnt/etc/NetworkManager/system-connections
396+ conn="/mnt/etc/NetworkManager/system-connections/${ssid//[^A-Za-z0-9._-]/_}.nmconnection"
397+ cat > "$conn" <<EOF
398+[connection]
399+id=$ssid
400+uuid=$(cat /proc/sys/kernel/random/uuid)
401+type=wifi
402+
403+[wifi]
404+mode=infrastructure
405+ssid=$ssid
406+
407+[wifi-security]
408+key-mgmt=wpa-psk
409+psk=$key
410+
411+[ipv4]
412+method=auto
413+
414+[ipv6]
415+method=auto
416+EOF
417+ chmod 0600 "$conn"
418+ log INFO "Wi-Fi \"$ssid\" carried over"
419+done
420+
421+step "Copying aurora to /home/$USERNAME_NEW/aurora"
422+cp -a "$currentDir" "/mnt/home/$USERNAME_NEW/aurora"
423+arch-chroot /mnt chown -R "$USERNAME_NEW:$USERNAME_NEW" "/home/$USERNAME_NEW/aurora"
424+
425+DESKTOP_OK=1
426+if [[ "$MODE" == full ]]; then
427+ step "Desktop (install.sh --desktop inside the new system)"
428+ # makepkg/paru call sudo: no password while this runs, the EXIT trap removes it again
429+ echo "$USERNAME_NEW ALL=(ALL) NOPASSWD: ALL" > /mnt/etc/sudoers.d/99-aurora-install
430+ chmod 0440 /mnt/etc/sudoers.d/99-aurora-install
431+ picks="/home/$USERNAME_NEW/.aurora-picks"
432+ printf '%s\n' "${SELECTED_PKGS[@]}" > "/mnt$picks"
433+ if ! with_tty arch-chroot /mnt runuser -u "$USERNAME_NEW" -- \
434+ env HOME="/home/$USERNAME_NEW" USER="$USERNAME_NEW" LOGNAME="$USERNAME_NEW" \
435+ SHELL=/usr/bin/zsh AURORA_CHROOT=1 \
436+ bash "/home/$USERNAME_NEW/aurora/install.sh" --desktop --yes --picks "$picks"; then
437+ DESKTOP_OK=0
438+ log ERROR "The desktop part failed. After the reboot, log in and run: ~/aurora/install.sh --desktop"
439+ fi
440+ rm -f "/mnt$picks" /mnt/etc/sudoers.d/99-aurora-install
441+fi
442+
443+step "Finishing"
444+trap - ERR
445+set +Eeuo pipefail
446+exec 1>&3 2>&4 3>&- 4>&-
447+sleep 1 # let tee write the last lines
448+install -m 0600 "$LOG" /mnt/var/log/aurora-install.log
449+umount -R /mnt
450+((LUKS)) && cryptsetup close "$CRYPT_NAME"
451+
452+if ((DESKTOP_OK)); then
453+ gum style --border rounded --border-foreground 42 --padding "1 2" --bold \
454+"✔ Arch$([[ "$MODE" == full ]] && echo " + aurora") installed.
455+
456+Remove the install medium and reboot.$([[ "$MODE" == full ]] && echo "
457+Log in on tty1 as $USERNAME_NEW: Hyprland and aurora start by themselves." || echo "
458+For the desktop, log in and run: ~/aurora/install.sh --desktop")
459+The install log is in /var/log/aurora-install.log."
460+else
461+ gum style --border rounded --border-foreground 220 --padding "1 2" --bold \
462+"⚠ Arch is installed, the desktop part failed.
463+
464+Reboot, log in as $USERNAME_NEW and run: ~/aurora/install.sh --desktop
465+The install log is in /var/log/aurora-install.log."
466+fi
467+
468+gum confirm "Reboot now?" && reboot
469+exit 0