1086 lines
34 KiB
Bash
1086 lines
34 KiB
Bash
#!/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 <<EOF
|
|
${BOLD}Installation summary${RESET}
|
|
|
|
Disk: $DISK — WILL BE ERASED
|
|
Encryption: $ENCRYPT
|
|
Kernel: $KERNELS
|
|
Swap: ${SWAP_SIZE} GiB
|
|
User: $USERNAME
|
|
Hostname: $HOSTNAME
|
|
Desktop: i3-wm on Xorg with Picom
|
|
Theme: $THEME
|
|
Modifier: $MODIFIER
|
|
Browser: $BROWSER
|
|
File manager: $FILE_MANAGER
|
|
Shell: $SHELL
|
|
Package UI: paru
|
|
Vesktop: $INSTALL_VESKTOP
|
|
Syncing: not installed
|
|
Bluetooth: not installed
|
|
Wi-Fi tools: not installed
|
|
EOF
|
|
|
|
printf "\nType ${BOLD}ERASE %s${RESET} exactly: " "$DISK"
|
|
read -r CONFIRM
|
|
[[ "$CONFIRM" == "ERASE $DISK" ]] || die "Cancelled."
|
|
|
|
while true; do
|
|
read -r -s -p "Root password: " ROOT_PASSWORD; echo
|
|
read -r -s -p "Repeat root password: " ROOT_PASSWORD2; echo
|
|
[[ -n "$ROOT_PASSWORD" && "$ROOT_PASSWORD" == "$ROOT_PASSWORD2" ]] && break
|
|
warn "Passwords did not match."
|
|
done
|
|
|
|
while true; do
|
|
read -r -s -p "Password for $USERNAME: " USER_PASSWORD; echo
|
|
read -r -s -p "Repeat password: " USER_PASSWORD2; echo
|
|
[[ -n "$USER_PASSWORD" && "$USER_PASSWORD" == "$USER_PASSWORD2" ]] && break
|
|
warn "Passwords did not match."
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Partitioning
|
|
# ---------------------------------------------------------------------------
|
|
say "Partitioning and formatting"
|
|
swapoff -a || true
|
|
umount -R /mnt 2>/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 <<EOF
|
|
127.0.0.1 localhost
|
|
::1 localhost
|
|
127.0.1.1 $HOSTNAME.localdomain $HOSTNAME
|
|
EOF
|
|
|
|
ln -sf "/usr/share/zoneinfo/$TIMEZONE" /mnt/etc/localtime
|
|
arch-chroot /mnt hwclock --systohc
|
|
sed -i 's/^#en_US.UTF-8/en_US.UTF-8/' /mnt/etc/locale.gen
|
|
arch-chroot /mnt locale-gen
|
|
echo 'LANG=en_US.UTF-8' > /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 <<EOF
|
|
default arch.conf
|
|
timeout 3
|
|
console-mode max
|
|
editor no
|
|
EOF
|
|
|
|
boot_entry() {
|
|
cat > "/mnt/boot/loader/entries/$1.conf" <<EOF
|
|
title $2
|
|
linux /vmlinuz-$3
|
|
initrd /amd-ucode.img
|
|
initrd /$4
|
|
options $PARAMS
|
|
EOF
|
|
}
|
|
|
|
case "$KERNELS" in
|
|
"linux + linux-lts")
|
|
boot_entry arch "Arch Linux" linux initramfs-linux.img
|
|
boot_entry arch-lts "Arch Linux LTS" linux-lts initramfs-linux-lts.img ;;
|
|
"linux only")
|
|
boot_entry arch "Arch Linux" linux initramfs-linux.img ;;
|
|
"linux-lts only")
|
|
boot_entry arch-lts "Arch Linux LTS" linux-lts initramfs-linux-lts.img
|
|
sed -i 's/default arch.conf/default arch-lts.conf/' /mnt/boot/loader/loader.conf ;;
|
|
esac
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Services
|
|
# ---------------------------------------------------------------------------
|
|
arch-chroot /mnt systemctl enable NetworkManager
|
|
arch-chroot /mnt systemctl enable sshd
|
|
arch-chroot /mnt systemctl enable systemd-timesyncd
|
|
arch-chroot /mnt systemctl enable fstrim.timer
|
|
arch-chroot /mnt systemctl enable paccache.timer
|
|
arch-chroot /mnt systemctl enable reflector.timer
|
|
$INSTALL_DOCKER && {
|
|
arch-chroot /mnt systemctl enable docker
|
|
arch-chroot /mnt usermod -aG docker "$USERNAME"
|
|
}
|
|
$INSTALL_VIRT && {
|
|
arch-chroot /mnt systemctl enable libvirtd
|
|
arch-chroot /mnt usermod -aG libvirt "$USERNAME"
|
|
}
|
|
$INSTALL_PRINT && arch-chroot /mnt systemctl enable cups
|
|
[[ "$LOGIN" == LightDM* ]] && arch-chroot /mnt systemctl enable lightdm
|
|
|
|
case "$SHELL" in
|
|
Zsh) arch-chroot /mnt usermod -s /bin/zsh "$USERNAME" ;;
|
|
Fish) arch-chroot /mnt usermod -s /usr/bin/fish "$USERNAME" ;;
|
|
esac
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User desktop configuration
|
|
# ---------------------------------------------------------------------------
|
|
say "Writing i3, Picom, Polybar, Rofi, and terminal configuration"
|
|
|
|
HOME_DIR=/mnt/home/$USERNAME
|
|
CFG=$HOME_DIR/.config
|
|
mkdir -p "$CFG"/{i3,picom,polybar,rofi,dunst,ghostty,gtk-3.0}
|
|
mkdir -p "$HOME_DIR"/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos,Projects,Screenshots}
|
|
mkdir -p "$HOME_DIR/.local/bin" "$HOME_DIR/.local/share/wallpapers"
|
|
|
|
cat > "$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" <<EOF
|
|
set \$mod $I3MOD
|
|
set \$term ghostty
|
|
set \$browser $BROWSER_CMD
|
|
set \$files $FILE_CMD
|
|
|
|
font pango:JetBrainsMono Nerd Font 10
|
|
floating_modifier \$mod
|
|
focus_follows_mouse $FOCUS_MOUSE
|
|
workspace_auto_back_and_forth yes
|
|
popup_during_fullscreen smart
|
|
default_border pixel 2
|
|
default_floating_border pixel 2
|
|
hide_edge_borders smart
|
|
gaps inner $GAP_INNER
|
|
gaps outer $GAP_OUTER
|
|
smart_gaps on
|
|
smart_borders on
|
|
|
|
client.focused $ACCENT $ACCENT $BG $ACCENT2 $ACCENT
|
|
client.focused_inactive $PANEL $PANEL $FG $MUTED $PANEL
|
|
client.unfocused $PANEL $PANEL $MUTED $PANEL $PANEL
|
|
client.urgent #bf616a #bf616a #ffffff #bf616a #bf616a
|
|
|
|
$MONITOR_COMMANDS
|
|
$WORKSPACE_OUTPUTS
|
|
|
|
exec --no-startup-id picom --config ~/.config/picom/picom.conf
|
|
exec --no-startup-id ~/.config/polybar/launch.sh
|
|
exec --no-startup-id dunst
|
|
exec --no-startup-id feh --no-fehbg --bg-fill ~/.local/share/wallpapers/workstation.png
|
|
exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
|
exec --no-startup-id dex --autostart --environment i3
|
|
exec --no-startup-id udiskie --tray
|
|
exec --no-startup-id xset s 600 600
|
|
exec --no-startup-id xset dpms 0 0 900
|
|
|
|
bindsym \$mod+$TERM_KEY exec \$term
|
|
bindsym \$mod+b exec \$browser
|
|
bindsym \$mod+e exec \$files
|
|
bindsym \$mod+space exec rofi -show drun
|
|
bindsym \$mod+q kill
|
|
bindsym \$mod+Shift+q exec "i3-nagbar -t warning -m 'Exit i3?' -B 'Exit' 'i3-msg exit'"
|
|
bindsym \$mod+f fullscreen toggle
|
|
bindsym \$mod+v floating toggle
|
|
bindsym \$mod+Shift+Escape exec i3lock -c ${BG#\#}
|
|
bindsym Print exec flameshot gui
|
|
bindsym \$mod+Print exec flameshot full -p ~/Screenshots
|
|
|
|
bindsym \$mod+h focus left
|
|
bindsym \$mod+j focus down
|
|
bindsym \$mod+k focus up
|
|
bindsym \$mod+l focus right
|
|
bindsym \$mod+Left focus left
|
|
bindsym \$mod+Down focus down
|
|
bindsym \$mod+Up focus up
|
|
bindsym \$mod+Right focus right
|
|
|
|
bindsym \$mod+Shift+h move left
|
|
bindsym \$mod+Shift+j move down
|
|
bindsym \$mod+Shift+k move up
|
|
bindsym \$mod+Shift+l move right
|
|
bindsym \$mod+Shift+Left move left
|
|
bindsym \$mod+Shift+Down move down
|
|
bindsym \$mod+Shift+Up move up
|
|
bindsym \$mod+Shift+Right move right
|
|
|
|
bindsym \$mod+1 workspace number 1
|
|
bindsym \$mod+2 workspace number 2
|
|
bindsym \$mod+3 workspace number 3
|
|
bindsym \$mod+4 workspace number 4
|
|
bindsym \$mod+5 workspace number 5
|
|
bindsym \$mod+6 workspace number 6
|
|
bindsym \$mod+7 workspace number 7
|
|
bindsym \$mod+8 workspace number 8
|
|
bindsym \$mod+9 workspace number 9
|
|
bindsym \$mod+0 workspace number 10
|
|
|
|
bindsym \$mod+Shift+1 move container to workspace number 1
|
|
bindsym \$mod+Shift+2 move container to workspace number 2
|
|
bindsym \$mod+Shift+3 move container to workspace number 3
|
|
bindsym \$mod+Shift+4 move container to workspace number 4
|
|
bindsym \$mod+Shift+5 move container to workspace number 5
|
|
bindsym \$mod+Shift+6 move container to workspace number 6
|
|
bindsym \$mod+Shift+7 move container to workspace number 7
|
|
bindsym \$mod+Shift+8 move container to workspace number 8
|
|
bindsym \$mod+Shift+9 move container to workspace number 9
|
|
bindsym \$mod+Shift+0 move container to workspace number 10
|
|
|
|
bindsym XF86AudioRaiseVolume exec --no-startup-id pamixer -i 5
|
|
bindsym XF86AudioLowerVolume exec --no-startup-id pamixer -d 5
|
|
bindsym XF86AudioMute exec --no-startup-id pamixer -t
|
|
bindsym XF86AudioPlay exec --no-startup-id playerctl play-pause
|
|
bindsym XF86AudioNext exec --no-startup-id playerctl next
|
|
bindsym XF86AudioPrev exec --no-startup-id playerctl previous
|
|
|
|
for_window [class="Pavucontrol|Arandr"] floating enable, move position center
|
|
EOF
|
|
|
|
PICOM_BLUR=false
|
|
$BLUR && PICOM_BLUR=true
|
|
PICOM_ANIM=false
|
|
$ANIMATIONS && PICOM_ANIM=true
|
|
|
|
cat > "$CFG/picom/picom.conf" <<EOF
|
|
backend = "glx";
|
|
vsync = true;
|
|
use-damage = true;
|
|
corner-radius = $CORNER_RADIUS;
|
|
round-borders = 1;
|
|
shadow = true;
|
|
shadow-radius = 18;
|
|
shadow-offset-x = -8;
|
|
shadow-offset-y = -8;
|
|
shadow-opacity = 0.35;
|
|
fading = true;
|
|
fade-in-step = 0.045;
|
|
fade-out-step = 0.045;
|
|
inactive-opacity = $( $BLUR && echo 0.95 || echo 1.0 );
|
|
active-opacity = 1.0;
|
|
frame-opacity = 1.0;
|
|
blur-method = "$( $BLUR && echo dual_kawase || echo none )";
|
|
blur-strength = 4;
|
|
animations = $PICOM_ANIM;
|
|
animation-stiffness = 220.0;
|
|
animation-dampening = 28.0;
|
|
animation-clamping = true;
|
|
animation-mass = 0.8;
|
|
animation-for-open-window = "zoom";
|
|
animation-for-unmap-window = "zoom";
|
|
opacity-rule = [
|
|
"100:class_g = 'Firefox'",
|
|
"100:class_g = 'Chromium'",
|
|
"100:class_g = 'Code'"
|
|
];
|
|
wintypes:
|
|
{
|
|
tooltip = { fade = true; shadow = true; opacity = 0.96; focus = true; };
|
|
dock = { shadow = false; };
|
|
dnd = { shadow = false; };
|
|
};
|
|
EOF
|
|
|
|
cat > "$CFG/polybar/config.ini" <<EOF
|
|
[colors]
|
|
background = $PANEL
|
|
background-alt = $BG
|
|
foreground = $FG
|
|
muted = $MUTED
|
|
primary = $ACCENT
|
|
secondary = $ACCENT2
|
|
alert = #bf616a
|
|
|
|
[bar/main]
|
|
width = 99%
|
|
height = 34
|
|
offset-x = 0.5%
|
|
offset-y = 8
|
|
radius = 12
|
|
fixed-center = true
|
|
background = \${colors.background}
|
|
foreground = \${colors.foreground}
|
|
line-size = 2
|
|
border-size = 0
|
|
padding-left = 2
|
|
padding-right = 2
|
|
module-margin = 1
|
|
font-0 = JetBrainsMono Nerd Font:size=10;2
|
|
modules-left = i3 xwindow
|
|
modules-center = date
|
|
modules-right = filesystem cpu memory pulseaudio eth powermenu
|
|
tray-position = right
|
|
tray-padding = 2
|
|
wm-restack = i3
|
|
override-redirect = false
|
|
enable-ipc = true
|
|
|
|
[module/i3]
|
|
type = internal/i3
|
|
pin-workspaces = true
|
|
show-urgent = true
|
|
format = <label-state>
|
|
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-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" <<EOF
|
|
configuration {
|
|
modi: "drun,run,window";
|
|
show-icons: true;
|
|
display-drun: "Apps";
|
|
font: "Noto Sans 13";
|
|
}
|
|
* {
|
|
bg: $BG;
|
|
panel: $PANEL;
|
|
fg: $FG;
|
|
muted: $MUTED;
|
|
accent: $ACCENT;
|
|
}
|
|
window {
|
|
width: 42%;
|
|
border: 2px;
|
|
border-color: @accent;
|
|
border-radius: 14px;
|
|
background-color: @panel;
|
|
}
|
|
mainbox { padding: 12px; background-color: transparent; }
|
|
inputbar {
|
|
padding: 10px;
|
|
border-radius: 10px;
|
|
background-color: @bg;
|
|
text-color: @fg;
|
|
}
|
|
listview { margin: 10px 0 0; lines: 8; spacing: 4px; }
|
|
element { padding: 9px; border-radius: 9px; }
|
|
element selected { background-color: @accent; text-color: @bg; }
|
|
element-text { text-color: inherit; }
|
|
EOF
|
|
|
|
cat > "$CFG/dunst/dunstrc" <<EOF
|
|
[global]
|
|
monitor = 0
|
|
follow = mouse
|
|
width = 360
|
|
height = 120
|
|
origin = top-right
|
|
offset = 14x54
|
|
notification_limit = 5
|
|
progress_bar = true
|
|
font = Noto Sans 10
|
|
frame_width = 2
|
|
frame_color = "$ACCENT"
|
|
corner_radius = 12
|
|
separator_color = frame
|
|
padding = 14
|
|
horizontal_padding = 14
|
|
icon_position = left
|
|
max_icon_size = 48
|
|
|
|
[urgency_low]
|
|
background = "$PANEL"
|
|
foreground = "$FG"
|
|
timeout = 4
|
|
|
|
[urgency_normal]
|
|
background = "$PANEL"
|
|
foreground = "$FG"
|
|
timeout = 6
|
|
|
|
[urgency_critical]
|
|
background = "#3b2027"
|
|
foreground = "#ffffff"
|
|
frame_color = "#bf616a"
|
|
timeout = 0
|
|
EOF
|
|
|
|
cat > "$CFG/ghostty/config" <<EOF
|
|
font-family = JetBrainsMono Nerd Font
|
|
font-size = 12
|
|
background = $BG
|
|
foreground = $FG
|
|
selection-background = $ACCENT
|
|
selection-foreground = $BG
|
|
cursor-color = $ACCENT2
|
|
cursor-text = $BG
|
|
background-opacity = 0.97
|
|
window-padding-x = 12
|
|
window-padding-y = 10
|
|
window-decoration = none
|
|
confirm-close-surface = false
|
|
copy-on-select = clipboard
|
|
EOF
|
|
|
|
cat > "$CFG/gtk-3.0/settings.ini" <<EOF
|
|
[Settings]
|
|
gtk-theme-name=Adwaita-dark
|
|
gtk-icon-theme-name=Papirus-Dark
|
|
gtk-font-name=Noto Sans 10
|
|
gtk-cursor-theme-name=Adwaita
|
|
gtk-cursor-theme-size=24
|
|
gtk-application-prefer-dark-theme=1
|
|
EOF
|
|
|
|
cat > "$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
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="3840" height="2160">
|
|
<defs>
|
|
<radialGradient id="a" cx="72%" cy="18%" r="75%">
|
|
<stop offset="0" stop-color="$ACCENT" stop-opacity=".42"/>
|
|
<stop offset=".55" stop-color="$PANEL" stop-opacity=".35"/>
|
|
<stop offset="1" stop-color="$BG"/>
|
|
</radialGradient>
|
|
<radialGradient id="b" cx="12%" cy="88%" r="55%">
|
|
<stop offset="0" stop-color="$ACCENT2" stop-opacity=".18"/>
|
|
<stop offset="1" stop-color="$BG" stop-opacity="0"/>
|
|
</radialGradient>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill="$BG"/>
|
|
<rect width="100%" height="100%" fill="url(#a)"/>
|
|
<rect width="100%" height="100%" fill="url(#b)"/>
|
|
</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" <<EOF
|
|
[user]
|
|
name = $FULLNAME
|
|
[init]
|
|
defaultBranch = main
|
|
[core]
|
|
editor = code --wait
|
|
[color]
|
|
ui = auto
|
|
EOF
|
|
|
|
# ESP-IDF.
|
|
if $INSTALL_ESP; then
|
|
say "Installing ESP-IDF"
|
|
arch-chroot /mnt runuser -u "$USERNAME" -- bash -lc '
|
|
mkdir -p "$HOME/esp"
|
|
git clone --recursive --depth 1 https://github.com/espressif/esp-idf.git "$HOME/esp/esp-idf"
|
|
"$HOME/esp/esp-idf/install.sh" esp32,esp32s2,esp32s3,esp32c3,esp32c6
|
|
'
|
|
cat >> "$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 <<EOF
|
|
|
|
${GREEN}${BOLD}Arch Linux with i3/Xorg is installed.${RESET}
|
|
|
|
Package management:
|
|
paru -Syu Update official and AUR packages
|
|
paru -S <package> Install a package
|
|
paru -Ss <query> Search
|
|
paru -Rns <package> 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
|
|
|