diff --git a/public/scripts/archinstall.sh b/public/scripts/archinstall.sh new file mode 100644 index 0000000..a6376c1 --- /dev/null +++ b/public/scripts/archinstall.sh @@ -0,0 +1,1085 @@ +#!/usr/bin/env bash +# Interactive Arch Linux + i3/Xorg workstation installer. +# Run as root from an official Arch Linux live ISO. +# +# This installer ERASES the selected disk. +set -Eeuo pipefail +IFS=$'\n\t' + +LOG=/tmp/arch-i3-install.log +exec > >(tee -a "$LOG") 2>&1 +trap 'echo "ERROR near line $LINENO. See $LOG" >&2' ERR + +BOLD=$'\e[1m'; RED=$'\e[31m'; GREEN=$'\e[32m' +YELLOW=$'\e[33m'; PURPLE=$'\e[35m'; RESET=$'\e[0m' + +say() { printf "\n%s==>%s %s\n" "$PURPLE" "$RESET" "$*"; } +warn() { printf "%sWARNING:%s %s\n" "$YELLOW" "$RESET" "$*" >&2; } +die() { printf "%sERROR:%s %s\n" "$RED" "$RESET" "$*" >&2; exit 1; } + +ask() { + local prompt=$1 default=${2-} answer + if [[ -n "$default" ]]; then + read -r -p "$prompt [$default]: " answer + printf '%s' "${answer:-$default}" + else + read -r -p "$prompt: " answer + printf '%s' "$answer" + fi +} + +yesno() { + local prompt=$1 default=${2:-y} answer marker + [[ "$default" == y ]] && marker='[Y/n]' || marker='[y/N]' + while true; do + read -r -p "$prompt $marker " answer + answer=${answer:-$default} + case "${answer,,}" in + y|yes) return 0 ;; + n|no) return 1 ;; + esac + done +} + +choose() { + local prompt=$1; shift + local opts=("$@") n answer + printf "\n%s\n" "$prompt" >&2 + for n in "${!opts[@]}"; do + printf " %d) %s\n" "$((n+1))" "${opts[$n]}" >&2 + done + while true; do + read -r -p "Choose [1-${#opts[@]}]: " answer + [[ "$answer" =~ ^[0-9]+$ ]] || continue + (( answer >= 1 && answer <= ${#opts[@]} )) || continue + printf '%s' "${opts[$((answer-1))]}" + return + done +} + +part_path() { + [[ "$1" =~ (nvme|mmcblk|loop) ]] && printf '%sp%s' "$1" "$2" || printf '%s%s' "$1" "$2" +} + +[[ $EUID -eq 0 ]] || die "Run this as root." +[[ -d /sys/firmware/efi/efivars ]] || die "Boot the Arch ISO in UEFI mode." +command -v pacstrap >/dev/null || die "Use the official Arch Linux ISO." +ping -c 1 -W 3 archlinux.org >/dev/null 2>&1 || die "No network connection. Ethernet must be connected." +timedatectl set-ntp true + +clear +cat <<'EOF' +╭──────────────────────────────────────────────────────────────╮ +│ Arch Linux + i3/Xorg Workstation Installer │ +│ Native packages • Paru • Picom • keyboard-first desktop │ +╰──────────────────────────────────────────────────────────────╯ +EOF + +warn "This installer permanently erases the disk you select." + +# --------------------------------------------------------------------------- +# Identity +# --------------------------------------------------------------------------- +while true; do + USERNAME=$(ask "Username" "owen") + [[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] && break + warn "Use a lowercase Unix username." +done + +FULLNAME=$(ask "Display name" "Owen Rummage") +HOSTNAME=$(ask "Hostname" "workstation") +TIMEZONE=$(ask "Timezone" "America/Chicago") +[[ -e "/usr/share/zoneinfo/$TIMEZONE" ]] || die "Unknown timezone." +KEYMAP=$(ask "Console keyboard layout" "us") +XKB_LAYOUT=$(ask "Xorg keyboard layout" "us") + +# --------------------------------------------------------------------------- +# Disk +# --------------------------------------------------------------------------- +say "Available disks" +lsblk -dpo NAME,SIZE,MODEL,TRAN,TYPE | awk '$NF=="disk"{print}' + +while true; do + DISK=$(ask "Exact disk to erase, such as /dev/nvme0n1") + [[ -b "$DISK" ]] || { warn "Not a block device."; continue; } + [[ "$(lsblk -dno TYPE "$DISK")" == disk ]] || { warn "Choose a whole disk."; continue; } + break +done + +ENCRYPT=false +yesno "Encrypt the root filesystem with LUKS?" y && ENCRYPT=true + +KERNELS=$(choose "Kernel selection" \ + "linux + linux-lts" \ + "linux only" \ + "linux-lts only") + +SWAP_SIZE=$(ask "Swapfile size in GiB; enter 0 for none" "16") +[[ "$SWAP_SIZE" =~ ^[0-9]+$ ]] || die "Swap size must be a whole number." + +# --------------------------------------------------------------------------- +# Visual choices +# --------------------------------------------------------------------------- +THEME=$(choose "Color preset" \ + "Purple dusk" \ + "Blue graphite" \ + "Green slate" \ + "Warm amber" \ + "Custom") + +case "$THEME" in + "Purple dusk") + ACCENT="#b48ead"; ACCENT2="#c099ff"; BG="#111318"; PANEL="#191c24"; FG="#e6e9ef"; MUTED="#9aa3b2" ;; + "Blue graphite") + ACCENT="#7aa2f7"; ACCENT2="#89b4fa"; BG="#101216"; PANEL="#181b22"; FG="#e5e9f0"; MUTED="#929aaa" ;; + "Green slate") + ACCENT="#8fbc8f"; ACCENT2="#a6e3a1"; BG="#101411"; PANEL="#182019"; FG="#e3e9e4"; MUTED="#93a097" ;; + "Warm amber") + ACCENT="#e5b567"; ACCENT2="#f9c74f"; BG="#15120e"; PANEL="#211b14"; FG="#eee7dc"; MUTED="#a89d8c" ;; + *) + ACCENT=$(ask "Primary accent hex" "#b48ead") + ACCENT2=$(ask "Bright accent hex" "#c099ff") + BG=$(ask "Background hex" "#111318") + PANEL=$(ask "Panel hex" "#191c24") + FG=$(ask "Foreground hex" "#e6e9ef") + MUTED=$(ask "Muted text hex" "#9aa3b2") ;; +esac + +for c in "$ACCENT" "$ACCENT2" "$BG" "$PANEL" "$FG" "$MUTED"; do + [[ "$c" =~ ^#[0-9A-Fa-f]{6}$ ]] || die "Invalid color: $c" +done + +CORNER_RADIUS=$(ask "Picom corner radius" "12") +GAP_INNER=$(ask "Inner gap" "8") +GAP_OUTER=$(ask "Outer gap" "14") + +BLUR=false +yesno "Enable Picom blur and subtle transparency?" y && BLUR=true + +ANIMATIONS=false +yesno "Enable Picom window animations? They may be less consistent than macOS." n && ANIMATIONS=true + +MODIFIER=$(choose "Primary modifier" "Super/Windows" "Alt") +[[ "$MODIFIER" == "Super/Windows" ]] && I3MOD=Mod4 || I3MOD=Mod1 + +TERM_BIND=$(choose "Terminal shortcut" "$MODIFIER + Enter" "$MODIFIER + T") +[[ "$TERM_BIND" == *Enter ]] && TERM_KEY=Return || TERM_KEY=t + +FOCUS_FOLLOWS=false +yesno "Focus follows mouse?" n && FOCUS_FOLLOWS=true + +# --------------------------------------------------------------------------- +# Monitor setup +# --------------------------------------------------------------------------- +MONITOR_MODE=$(choose "Initial two-monitor layout" \ + "Auto; configure later with arandr" \ + "Secondary monitor is right of primary" \ + "Secondary monitor is left of primary" \ + "Secondary monitor is above primary" \ + "Secondary monitor is below primary") + +PRIMARY_OUTPUT=$(ask "Primary Xorg output, or auto (examples: DisplayPort-0, HDMI-A-0)" "auto") +SECONDARY_OUTPUT=$(ask "Secondary Xorg output, or auto" "auto") + +FIXED_WORKSPACES=false +yesno "Pin workspaces 1-5 to primary and 6-10 to secondary?" y && FIXED_WORKSPACES=true + +# --------------------------------------------------------------------------- +# Software +# --------------------------------------------------------------------------- +BROWSER=$(choose "Browser" "Firefox" "Chromium" "Both") +FILE_MANAGER=$(choose "File manager" "Thunar" "Nautilus") +SHELL=$(choose "Shell" "Zsh" "Bash" "Fish") + +INSTALL_VESKTOP=true +yesno "Install Vesktop using paru?" y || INSTALL_VESKTOP=false +INSTALL_CODE=true +yesno "Install Code - OSS?" y || INSTALL_CODE=false +INSTALL_OFFICE=true +yesno "Install LibreOffice?" y || INSTALL_OFFICE=false +INSTALL_DOCKER=true +yesno "Install Docker and Docker Compose?" y || INSTALL_DOCKER=false +INSTALL_VIRT=false +yesno "Install KVM/libvirt and Virt Manager?" n && INSTALL_VIRT=true +INSTALL_DEV=true +yesno "Install web, Go, Rust, Python, and C/C++ development tools?" y || INSTALL_DEV=false +INSTALL_ESP=true +yesno "Install ESP-IDF from Espressif upstream?" y || INSTALL_ESP=false +INSTALL_PRINT=false +yesno "Install printing support?" n && INSTALL_PRINT=true + +LOGIN=$(choose "Login behavior" \ + "LightDM graphical login" \ + "Console login and startx") + +# --------------------------------------------------------------------------- +# Confirmation +# --------------------------------------------------------------------------- +clear +cat </dev/null || true +cryptsetup close cryptroot 2>/dev/null || true + +wipefs -af "$DISK" +sgdisk --zap-all "$DISK" +sgdisk -n 1:0:+2G -t 1:ef00 -c 1:ARCH_EFI "$DISK" +sgdisk -n 2:0:0 -t 2:8309 -c 2:ARCH_ROOT "$DISK" +partprobe "$DISK" +udevadm settle + +EFI_PART=$(part_path "$DISK" 1) +ROOT_PART=$(part_path "$DISK" 2) + +mkfs.fat -F32 -n ARCH_EFI "$EFI_PART" + +if $ENCRYPT; then + cryptsetup luksFormat --type luks2 "$ROOT_PART" + cryptsetup open "$ROOT_PART" cryptroot + ROOT_DEVICE=/dev/mapper/cryptroot +else + ROOT_DEVICE=$ROOT_PART +fi + +mkfs.btrfs -f -L ARCH_ROOT "$ROOT_DEVICE" +mount "$ROOT_DEVICE" /mnt + +for sub in @ @home @snapshots @var_log @var_cache_pacman; do + btrfs subvolume create "/mnt/$sub" +done +umount /mnt + +BTRFS_OPTS="noatime,compress=zstd:3,ssd,discard=async,space_cache=v2" +mount -o "$BTRFS_OPTS,subvol=@" "$ROOT_DEVICE" /mnt +mkdir -p /mnt/{boot,home,.snapshots,var/log,var/cache/pacman/pkg} +mount -o "$BTRFS_OPTS,subvol=@home" "$ROOT_DEVICE" /mnt/home +mount -o "$BTRFS_OPTS,subvol=@snapshots" "$ROOT_DEVICE" /mnt/.snapshots +mount -o "$BTRFS_OPTS,subvol=@var_log" "$ROOT_DEVICE" /mnt/var/log +mount -o "$BTRFS_OPTS,subvol=@var_cache_pacman" "$ROOT_DEVICE" /mnt/var/cache/pacman/pkg +mount "$EFI_PART" /mnt/boot + +# --------------------------------------------------------------------------- +# Bootstrap +# --------------------------------------------------------------------------- +say "Bootstrapping the base system" + +BOOTSTRAP=(base base-devel linux-firmware amd-ucode btrfs-progs sudo git rust) +case "$KERNELS" in + "linux + linux-lts") BOOTSTRAP+=(linux linux-headers linux-lts linux-lts-headers) ;; + "linux only") BOOTSTRAP+=(linux linux-headers) ;; + "linux-lts only") BOOTSTRAP+=(linux-lts linux-lts-headers) ;; +esac + +pacstrap -K /mnt "${BOOTSTRAP[@]}" +genfstab -U /mnt >> /mnt/etc/fstab + +printf '%s\n' "$HOSTNAME" > /mnt/etc/hostname +cat > /mnt/etc/hosts < /mnt/etc/locale.conf +echo "KEYMAP=$KEYMAP" > /mnt/etc/vconsole.conf + +sed -i 's/^#Color/Color/' /mnt/etc/pacman.conf +sed -i 's/^#ParallelDownloads.*/ParallelDownloads = 10/' /mnt/etc/pacman.conf +grep -q '^ILoveCandy' /mnt/etc/pacman.conf || sed -i '/^\[options\]/a ILoveCandy' /mnt/etc/pacman.conf +sed -i '/^\#\[multilib\]/,/^#Include/ s/^#//' /mnt/etc/pacman.conf + +arch-chroot /mnt useradd -m -G wheel,audio,video,input,storage "$USERNAME" +printf 'root:%s\n%s:%s\n' "$ROOT_PASSWORD" "$USERNAME" "$USER_PASSWORD" | arch-chroot /mnt chpasswd +unset ROOT_PASSWORD ROOT_PASSWORD2 USER_PASSWORD USER_PASSWORD2 + +echo '%wheel ALL=(ALL:ALL) ALL' > /mnt/etc/sudoers.d/10-wheel +echo "$USERNAME ALL=(ALL:ALL) NOPASSWD: ALL" > /mnt/etc/sudoers.d/99-installer +chmod 440 /mnt/etc/sudoers.d/{10-wheel,99-installer} + +# --------------------------------------------------------------------------- +# Paru and application packages +# --------------------------------------------------------------------------- +say "Building paru" +arch-chroot /mnt runuser -u "$USERNAME" -- bash -lc ' + cd /tmp + rm -rf paru + git clone https://aur.archlinux.org/paru.git + cd paru + makepkg -si --noconfirm +' + +REPO_PACKAGES=( + networkmanager openssh reflector pacman-contrib man-db man-pages + bash-completion curl wget rsync + pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber + mesa vulkan-radeon libva-mesa-driver mesa-vdpau + xorg-server xorg-xinit xorg-xrandr xorg-xsetroot xorg-xprop xorg-xinput + i3-wm i3lock i3status polybar rofi dunst dex + feh flameshot xclip xsel arandr + polkit-gnome gvfs gvfs-smb udiskie udisks2 + ghostty pavucontrol pamixer playerctl + fastfetch tmux fzf ripgrep fd jq tree unzip zip p7zip + noto-fonts noto-fonts-emoji ttf-dejavu ttf-liberation + ttf-jetbrains-mono-nerd papirus-icon-theme adwaita-cursors + gtk3 gtk4 imagemagick + snapper +) + +case "$BROWSER" in + Firefox) REPO_PACKAGES+=(firefox) ;; + Chromium) REPO_PACKAGES+=(chromium) ;; + Both) REPO_PACKAGES+=(firefox chromium) ;; +esac + +case "$FILE_MANAGER" in + Thunar) REPO_PACKAGES+=(thunar thunar-archive-plugin tumbler file-roller) ;; + Nautilus) REPO_PACKAGES+=(nautilus file-roller) ;; +esac + +case "$SHELL" in + Zsh) REPO_PACKAGES+=(zsh zsh-completions) ;; + Fish) REPO_PACKAGES+=(fish) ;; +esac + +$INSTALL_CODE && REPO_PACKAGES+=(code) +$INSTALL_OFFICE && REPO_PACKAGES+=(libreoffice-fresh) +$INSTALL_DOCKER && REPO_PACKAGES+=(docker docker-compose docker-buildx) +$INSTALL_VIRT && REPO_PACKAGES+=(qemu-full libvirt virt-manager dnsmasq bridge-utils edk2-ovmf) +$INSTALL_DEV && REPO_PACKAGES+=(go rustup nodejs npm pnpm python python-pip python-pipx cmake ninja gcc gdb clang llvm lldb make) +$INSTALL_ESP && REPO_PACKAGES+=(python python-pip python-virtualenv cmake ninja ccache dfu-util libusb) +$INSTALL_PRINT && REPO_PACKAGES+=(cups cups-pdf system-config-printer) +[[ "$LOGIN" == LightDM* ]] && REPO_PACKAGES+=(lightdm lightdm-gtk-greeter) +$ANIMATIONS || REPO_PACKAGES+=(picom) + +mapfile -t REPO_PACKAGES < <(printf '%s\n' "${REPO_PACKAGES[@]}" | awk '!seen[$0]++') + +say "Installing packages through paru" +printf -v PKG_STRING '%q ' "${REPO_PACKAGES[@]}" +arch-chroot /mnt runuser -u "$USERNAME" -- bash -lc "paru -S --needed --noconfirm $PKG_STRING" + +if $INSTALL_VESKTOP; then + say "Installing Vesktop from the AUR" + arch-chroot /mnt runuser -u "$USERNAME" -- bash -lc 'paru -S --needed --noconfirm vesktop-bin' +fi + +if $ANIMATIONS; then + say "Installing the animation-capable Picom fork from the AUR" + arch-chroot /mnt runuser -u "$USERNAME" -- bash -lc 'paru -S --needed --noconfirm picom-ftlabs-git' +fi + +rm -f /mnt/etc/sudoers.d/99-installer + +# --------------------------------------------------------------------------- +# Boot setup +# --------------------------------------------------------------------------- +if $ENCRYPT; then + sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)/' /mnt/etc/mkinitcpio.conf +else + sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck)/' /mnt/etc/mkinitcpio.conf +fi +arch-chroot /mnt mkinitcpio -P +arch-chroot /mnt bootctl install + +ROOT_UUID=$(blkid -s UUID -o value "$ROOT_PART") +BTRFS_UUID=$(blkid -s UUID -o value "$ROOT_DEVICE") +PARAMS="root=UUID=$BTRFS_UUID rootflags=subvol=@ rw quiet loglevel=3 amd_pstate=active" +$ENCRYPT && PARAMS="cryptdevice=UUID=$ROOT_UUID:cryptroot $PARAMS" + +cat > /mnt/boot/loader/loader.conf < "/mnt/boot/loader/entries/$1.conf" < "$HOME_DIR/.xinitrc" <<'EOF' +#!/bin/sh +if [ -d /etc/X11/xinit/xinitrc.d ]; then + for f in /etc/X11/xinit/xinitrc.d/?*.sh; do + [ -x "$f" ] && . "$f" + done + unset f +fi +export GTK_THEME=Adwaita:dark +export XDG_CURRENT_DESKTOP=i3 +exec i3 +EOF +chmod +x "$HOME_DIR/.xinitrc" + +FOCUS_MOUSE=no +$FOCUS_FOLLOWS && FOCUS_MOUSE=yes + +MONITOR_COMMANDS="" +if [[ "$PRIMARY_OUTPUT" != auto && "$SECONDARY_OUTPUT" != auto ]]; then + case "$MONITOR_MODE" in + *right*) MONITOR_COMMANDS="exec --no-startup-id xrandr --output $PRIMARY_OUTPUT --auto --primary --output $SECONDARY_OUTPUT --auto --right-of $PRIMARY_OUTPUT" ;; + *left*) MONITOR_COMMANDS="exec --no-startup-id xrandr --output $PRIMARY_OUTPUT --auto --primary --output $SECONDARY_OUTPUT --auto --left-of $PRIMARY_OUTPUT" ;; + *above*) MONITOR_COMMANDS="exec --no-startup-id xrandr --output $PRIMARY_OUTPUT --auto --primary --output $SECONDARY_OUTPUT --auto --above $PRIMARY_OUTPUT" ;; + *below*) MONITOR_COMMANDS="exec --no-startup-id xrandr --output $PRIMARY_OUTPUT --auto --primary --output $SECONDARY_OUTPUT --auto --below $PRIMARY_OUTPUT" ;; + esac +fi + +WORKSPACE_OUTPUTS="" +if $FIXED_WORKSPACES && [[ "$PRIMARY_OUTPUT" != auto && "$SECONDARY_OUTPUT" != auto ]]; then + for n in 1 2 3 4 5; do WORKSPACE_OUTPUTS+="workspace $n output $PRIMARY_OUTPUT"$'\n'; done + for n in 6 7 8 9 10; do WORKSPACE_OUTPUTS+="workspace $n output $SECONDARY_OUTPUT"$'\n'; done +fi + +BROWSER_CMD=firefox +[[ "$BROWSER" == Chromium ]] && BROWSER_CMD=chromium +FILE_CMD=thunar +[[ "$FILE_MANAGER" == Nautilus ]] && FILE_CMD=nautilus + +cat > "$CFG/i3/config" < "$CFG/picom/picom.conf" < "$CFG/polybar/config.ini" < +label-focused = %index% +label-focused-background = \${colors.background-alt} +label-focused-foreground = \${colors.secondary} +label-focused-padding = 2 +label-unfocused = %index% +label-unfocused-foreground = \${colors.muted} +label-unfocused-padding = 2 +label-urgent = %index% +label-urgent-background = \${colors.alert} +label-urgent-padding = 2 + +[module/xwindow] +type = internal/xwindow +label = %title:0:50:...% +label-foreground = \${colors.muted} + +[module/date] +type = internal/date +interval = 1 +date = %a %b %d +time = %I:%M %p +label = %date% %time% + +[module/filesystem] +type = internal/fs +interval = 30 +mount-0 = / +label-mounted = 󰋊 %percentage_used%% + +[module/cpu] +type = internal/cpu +interval = 2 +label = 󰻠 %percentage%% + +[module/memory] +type = internal/memory +interval = 2 +label = 󰍛 %percentage_used%% + +[module/pulseaudio] +type = internal/pulseaudio +format-volume = 󰕾 +label-muted = 󰖁 muted +click-right = pavucontrol + +[module/eth] +type = internal/network +interface-type = wired +interval = 3 +label-connected = 󰈀 %local_ip% +label-disconnected = 󰈂 offline + +[module/powermenu] +type = custom/text +content = 󰐥 +content-foreground = \${colors.secondary} +click-left = ~/.local/bin/power-menu + +[settings] +screenchange-reload = true +pseudo-transparency = true +EOF + +cat > "$CFG/polybar/launch.sh" <<'EOF' +#!/usr/bin/env bash +killall -q polybar || true +while pgrep -u "$UID" -x polybar >/dev/null; do sleep 0.2; done +if command -v xrandr >/dev/null; then + mapfile -t monitors < <(xrandr --query | awk '/ connected/{print $1}') + for monitor in "${monitors[@]}"; do + MONITOR="$monitor" polybar --reload main & + done +else + polybar --reload main & +fi +EOF +chmod +x "$CFG/polybar/launch.sh" + +cat > "$CFG/rofi/config.rasi" < "$CFG/dunst/dunstrc" < "$CFG/ghostty/config" < "$CFG/gtk-3.0/settings.ini" < "$HOME_DIR/.local/bin/power-menu" <<'EOF' +#!/usr/bin/env bash +choice=$(printf 'Lock\nLog out\nSuspend\nReboot\nShut down' | rofi -dmenu -p Power) +case "$choice" in + Lock) i3lock -c 111318 ;; + "Log out") i3-msg exit ;; + Suspend) systemctl suspend ;; + Reboot) systemctl reboot ;; + "Shut down") systemctl poweroff ;; +esac +EOF +chmod +x "$HOME_DIR/.local/bin/power-menu" + +# Wallpaper: generate a high-resolution PNG locally. +cat > /tmp/wallpaper.svg < + + + + + + + + + + + + + + + +EOF +cp /tmp/wallpaper.svg "$HOME_DIR/.local/share/wallpapers/workstation.svg" +arch-chroot /mnt magick /home/"$USERNAME"/.local/share/wallpapers/workstation.svg \ + /home/"$USERNAME"/.local/share/wallpapers/workstation.png + +# Shell configuration. +cat > "$HOME_DIR/.bashrc" <<'EOF' +[[ $- != *i* ]] && return +alias ll='ls -lah --color=auto' +alias update='paru -Syu' +alias install='paru -S' +alias remove='paru -Rns' +alias search='paru -Ss' +export EDITOR=code +export PATH="$HOME/.local/bin:$PATH" +eval "$(fzf --bash)" +fastfetch +EOF + +cat > "$HOME_DIR/.zshrc" <<'EOF' +autoload -Uz compinit && compinit +setopt autocd hist_ignore_dups share_history +HISTFILE=$HOME/.zsh_history +HISTSIZE=10000 +SAVEHIST=10000 +bindkey -e +alias ll='ls -lah --color=auto' +alias update='paru -Syu' +alias install='paru -S' +alias remove='paru -Rns' +alias search='paru -Ss' +export EDITOR=code +export PATH="$HOME/.local/bin:$PATH" +source <(fzf --zsh) +PROMPT='%F{magenta}%n%f@%F{blue}%m%f %F{cyan}%~%f %# ' +fastfetch +EOF + +mkdir -p "$CFG/fish" +cat > "$CFG/fish/config.fish" <<'EOF' +alias ll='ls -lah --color=auto' +alias update='paru -Syu' +alias install='paru -S' +alias remove='paru -Rns' +alias search='paru -Ss' +set -gx EDITOR code +fish_add_path ~/.local/bin +if status is-interactive + fastfetch +end +EOF + +cat > "$HOME_DIR/.gitconfig" <> "$HOME_DIR/.bashrc" <<'EOF' +alias get_idf='. "$HOME/esp/esp-idf/export.sh"' +EOF + cat >> "$HOME_DIR/.zshrc" <<'EOF' +alias get_idf='. "$HOME/esp/esp-idf/export.sh"' +EOF + cat > /mnt/etc/udev/rules.d/99-esp32.rules <<'EOF' +SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", MODE="0660", GROUP="uucp" +SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", MODE="0660", GROUP="uucp" +SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", MODE="0660", GROUP="uucp" +SUBSYSTEM=="tty", ATTRS{idVendor}=="303a", MODE="0660", GROUP="uucp" +EOF + arch-chroot /mnt usermod -aG uucp "$USERNAME" +fi + +# Snapper. +umount /mnt/.snapshots +rmdir /mnt/.snapshots +arch-chroot /mnt snapper -c root create-config / +btrfs subvolume delete /mnt/.snapshots +mkdir /mnt/.snapshots +mount -o "$BTRFS_OPTS,subvol=@snapshots" "$ROOT_DEVICE" /mnt/.snapshots +chmod 750 /mnt/.snapshots +arch-chroot /mnt systemctl enable snapper-timeline.timer snapper-cleanup.timer + +# Swapfile. +if (( SWAP_SIZE > 0 )); then + mkdir -p /mnt/swap + btrfs subvolume create /mnt/swap/swapvol + chattr +C /mnt/swap/swapvol + btrfs filesystem mkswapfile --size "${SWAP_SIZE}G" /mnt/swap/swapvol/swapfile + echo '/swap/swapvol/swapfile none swap defaults 0 0' >> /mnt/etc/fstab +fi + +# Reflector, Ethernet-only NetworkManager behavior. +mkdir -p /mnt/etc/xdg/reflector +cat > /mnt/etc/xdg/reflector/reflector.conf <<'EOF' +--save /etc/pacman.d/mirrorlist +--protocol https +--country US +--latest 20 +--sort rate +EOF + +mkdir -p /mnt/etc/NetworkManager/conf.d +cat > /mnt/etc/NetworkManager/conf.d/10-ethernet.conf <<'EOF' +[main] +plugins=keyfile + +[device] +wifi.scan-rand-mac-address=no +EOF + +arch-chroot /mnt chown -R "$USERNAME:$USERNAME" "/home/$USERNAME" +arch-chroot /mnt bash -n "/home/$USERNAME/.local/bin/power-menu" +arch-chroot /mnt bash -n "/home/$USERNAME/.config/polybar/launch.sh" + +say "Installation complete" +cat < Install a package + paru -Ss Search + paru -Rns Remove a package + +First login: + • LightDM starts automatically if selected. + • Otherwise log in and run: startx + • Open Ghostty with $MODIFIER + $( [[ "$TERM_KEY" == Return ]] && echo Enter || echo T ) + • Open Rofi with $MODIFIER + Space + • Configure monitors graphically with: arandr + +No syncing, Wi-Fi utilities, or Bluetooth utilities were installed. +NetworkManager remains installed for wired Ethernet configuration. + +Log: $LOG +EOF + +if yesno "Unmount now?" y; then + sync + umount -R /mnt + $ENCRYPT && cryptsetup close cryptroot +fi + +yesno "Reboot now?" n && reboot +