export LUKS_PASSPHRASE='passphrase'
sudo --preserve-env=LUKS_PASSPHRASE ./archinstall.sh
| is this the correct way to use the script #!/usr/bin/env bash | |
| set -euo pipefail | |
| # ArchISO guided installer for: | |
| # - Encrypted /boot (LUKS1) + encrypted LVM PV (LUKS) | |
| # - LVM: swap/root/home (home=100%FREE) | |
| # - GRUB with cryptodisk | |
| # - mkinitcpio custom install hook to embed keyfiles into initramfs | |
| # | |
| # WARNING: THIS DESTROYS THE SELECTED DISK. | |
| log() { printf "\n==> %s\n" "$*"; } | |
| warn() { printf "\nWARNING: %s\n" "$*" >&2; } | |
| die() { printf "\nERROR: %s\n" "$*" >&2; exit 1; } | |
| require_root() { [[ "$(id -u)" -eq 0 ]] || die "Run as root."; } | |
| require_luks_env() { | |
| if [[ -z "${LUKS_PASSPHRASE:-}" ]]; then | |
| die "LUKS_PASSPHRASE is not set. Run: export LUKS_PASSPHRASE='your-passphrase' then rerun." | |
| fi | |
| } | |
| require_cmds() { | |
| local missing=() | |
| for c in "$@"; do | |
| command -v "$c" >/dev/null 2>&1 || missing+=("$c") | |
| done | |
| if ((${#missing[@]})); then | |
| die "Missing commands: ${missing[*]}" | |
| fi | |
| } | |
| prompt() { | |
| local var="$1"; shift | |
| local text="$1"; shift | |
| local default="${1:-}" | |
| local input="" | |
| if [[ -n "$default" ]]; then | |
| read -r -p "$text [$default]: " input | |
| input="${input:-$default}" | |
| else | |
| read -r -p "$text: " input | |
| fi | |
| printf -v "$var" '%s' "$input" | |
| } | |
| confirm() { | |
| local text="$1" | |
| read -r -p "$text (type YES to continue): " ans | |
| [[ "$ans" == "YES" ]] || die "Aborted." | |
| } | |
| is_uefi() { [[ -d /sys/firmware/efi ]]; } | |
| part_path() { | |
| local disk="$1" num="$2" | |
| if [[ "$disk" =~ nvme|mmcblk ]]; then echo "${disk}p${num}"; else echo "${disk}${num}"; fi | |
| } | |
| pick_disk_menu() { | |
| log "Scanning disks..." | |
| mapfile -t DISK_LINES < <(lsblk -d -p -n -o NAME,SIZE,MODEL,TYPE | awk '$4=="disk"{print $1"|" $2"|" substr($0, index($0,$3))}') | |
| ((${#DISK_LINES[@]})) || die "No disks found." | |
| echo | |
| echo "Select target disk:" | |
| local i=1 | |
| for line in "${DISK_LINES[@]}"; do | |
| IFS='|' read -r name size rest <<<"$line" | |
| printf " %d) %s %s %s\n" "$i" "$name" "$size" "$rest" | |
| ((i++)) | |
| done | |
| echo | |
| local choice | |
| while true; do | |
| prompt choice "Enter choice number" | |
| [[ "$choice" =~ ^[0-9]+$ ]] || { warn "Please enter a number."; continue; } | |
| (( choice>=1 && choice<=${#DISK_LINES[@]} )) || { warn "Out of range."; continue; } | |
| break | |
| done | |
| IFS='|' read -r DISK _ _ <<<"${DISK_LINES[$((choice-1))]}" | |
| [[ -b "$DISK" ]] || die "Disk $DISK not found." | |
| echo | |
| lsblk -p "$DISK" || true | |
| confirm "ALL DATA ON $DISK WILL BE ERASED. Continue?" | |
| } | |
| wipe_disk() { | |
| local disk="$1" | |
| log "Wiping old signatures on $disk" | |
| wipefs -a "$disk" || true | |
| } | |
| partition_disk() { | |
| local disk="$1" mode="$2" # uefi|bios | |
| log "Partitioning $disk for $mode (GPT + gdisk)" | |
| if [[ "$mode" == "uefi" ]]; then | |
| gdisk "$disk" <<'EOF' | |
| o | |
| y | |
| n | |
| +512M | |
| ef00 | |
| n | |
| +1G | |
| 8300 | |
| n | |
| 8e00 | |
| w | |
| y | |
| EOF | |
| else | |
| gdisk "$disk" <<'EOF' | |
| o | |
| y | |
| n | |
| +1M | |
| ef02 | |
| n | |
| +1G | |
| 8300 | |
| n | |
| 8e00 | |
| w | |
| y | |
| EOF | |
| fi | |
| partprobe "$disk" || true | |
| sleep 1 | |
| } | |
| format_esp_if_needed() { | |
| local mode="$1" esp_part="$2" | |
| [[ "$mode" == "uefi" ]] || return 0 | |
| log "Formatting ESP as FAT32: $esp_part" | |
| mkfs.vfat -F 32 "$esp_part" | |
| } | |
| setup_luks_and_lvm() { | |
| local boot_part="$1" lvm_part="$2" | |
| log "Creating LUKS1 container for /boot on $boot_part" | |
| printf '%s' "$LUKS_PASSPHRASE" | cryptsetup -q --batch-mode --key-file=- \ | |
| -v --key-size 512 --type luks1 --hash sha256 --iter-time 5000 --use-random luksFormat "$boot_part" | |
| log "Creating LUKS container for LVM PV on $lvm_part" | |
| printf '%s' "$LUKS_PASSPHRASE" | cryptsetup -q --batch-mode --key-file=- \ | |
| -v --key-size 512 --hash sha256 --iter-time 5000 --use-random luksFormat "$lvm_part" | |
| log "Opening LUKS containers" | |
| cryptsetup open "$boot_part" encrypted-boot | |
| cryptsetup open "$lvm_part" encrypted-lvm | |
| log "Creating LVM PV/VG" | |
| pvcreate /dev/mapper/encrypted-lvm | |
| vgcreate Main /dev/mapper/encrypted-lvm | |
| echo | |
| warn "LV size inputs are passed to 'lvcreate -L'. Examples: 16G, 32768M" | |
| prompt SWAP_SIZE "Swap LV size" "16G" | |
| prompt ROOT_SIZE "Root LV size" "25G" | |
| log "Creating LVs: swap=$SWAP_SIZE root=$ROOT_SIZE home=100%FREE" | |
| lvcreate -L "$SWAP_SIZE" Main -n swap | |
| lvcreate -L "$ROOT_SIZE" Main -n root | |
| lvcreate -l 100%FREE Main -n home | |
| log "Creating filesystems (ext4) and swap" | |
| mkfs.ext4 /dev/mapper/Main-root | |
| mkfs.ext4 /dev/mapper/Main-home | |
| mkswap /dev/mapper/Main-swap | |
| log "Creating filesystem for encrypted /boot (ext4)" | |
| mkfs.ext4 /dev/mapper/encrypted-boot | |
| } | |
| mount_all() { | |
| local mode="$1" esp_part="${2:-}" | |
| log "Mounting filesystems to /mnt" | |
| mount /dev/mapper/Main-root /mnt | |
| mkdir -p /mnt/boot | |
| mount /dev/mapper/encrypted-boot /mnt/boot | |
| if [[ "$mode" == "uefi" ]]; then | |
| mkdir -p /mnt/boot/efi | |
| mount "$esp_part" /mnt/boot/efi | |
| fi | |
| mkdir -p /mnt/home | |
| mount /dev/mapper/Main-home /mnt/home | |
| swapon /dev/mapper/Main-swap | |
| } | |
| collect_i18n_inputs() { | |
| log "Interactive timezone/locale/keymap setup" | |
| prompt TIMEZONE "Timezone (exists under /usr/share/zoneinfo, e.g. Europe/Oslo, America/New_York)" "UTC" | |
| if [[ ! -e "/usr/share/zoneinfo/$TIMEZONE" ]]; then | |
| warn "Timezone '/usr/share/zoneinfo/$TIMEZONE' not found. Falling back to UTC." | |
| TIMEZONE="UTC" | |
| fi | |
| prompt LOCALE "Primary locale (e.g. en_US.UTF-8)" "en_US.UTF-8" | |
| prompt KEYMAP "Console keymap (e.g. us, no, de-latin1)" "us" | |
| } | |
| collect_microcode_choice() { | |
| log "CPU microcode package" | |
| echo "Select microcode to install:" | |
| echo " 1) intel-ucode (recommended for Intel CPUs)" | |
| echo " 2) amd-ucode" | |
| echo " 3) none" | |
| echo | |
| local c | |
| while true; do | |
| prompt c "Enter choice number" "1" | |
| case "$c" in | |
| 1) MICROCODE_PKG="intel-ucode"; break;; | |
| 2) MICROCODE_PKG="amd-ucode"; break;; | |
| 3) MICROCODE_PKG=""; break;; | |
| *) warn "Invalid choice. Pick 1, 2, or 3.";; | |
| esac | |
| done | |
| if [[ -n "${MICROCODE_PKG}" ]]; then | |
| log "Will install: ${MICROCODE_PKG}" | |
| else | |
| log "Will not install microcode package" | |
| fi | |
| } | |
| install_base() { | |
| local mode="$1" | |
| log "Installing base system with pacstrap" | |
| local pkgs=(base base-devel linux linux-firmware lvm2 vim grub mkinitcpio) | |
| if [[ "$mode" == "uefi" ]]; then pkgs+=(efibootmgr); fi | |
| if [[ -n "${MICROCODE_PKG:-}" ]]; then pkgs+=("$MICROCODE_PKG"); fi | |
| pacstrap /mnt "${pkgs[@]}" | |
| log "Generating fstab" | |
| genfstab -U /mnt >> /mnt/etc/fstab | |
| } | |
| configure_system_in_chroot() { | |
| local mode="$1" disk="$2" boot_part="$3" lvm_part="$4" | |
| local timezone="$5" locale="$6" keymap="$7" | |
| log "Entering chroot to configure system" | |
| arch-chroot /mnt /usr/bin/env LUKS_PASSPHRASE="$LUKS_PASSPHRASE" /bin/bash -euo pipefail <<EOF | |
| set -euo pipefail | |
| echo "Configuring timezone/clock" | |
| ln -sf "/usr/share/zoneinfo/$timezone" /etc/localtime | |
| hwclock --systohc | |
| echo "Configuring locale" | |
| if grep -qE "^#?$locale[[:space:]]" /etc/locale.gen; then | |
| sed -i "s/^#\\($locale[[:space:]].*\\)/\\1/" /etc/locale.gen | |
| else | |
| echo "$locale UTF-8" >> /etc/locale.gen | |
| fi | |
| locale-gen | |
| cat > /etc/locale.conf <<LC | |
| LANG=$locale | |
| LC | |
| echo "Configuring vconsole keymap" | |
| cat > /etc/vconsole.conf <<VC | |
| KEYMAP=$keymap | |
| VC | |
| echo "Configuring mkinitcpio hooks" | |
| cp -a /etc/mkinitcpio.conf /etc/mkinitcpio.conf.bak | |
| sed -i 's/^HOOKS=.*/HOOKS=(base udev keyboard keymap consolefont autodetect modconf block encrypt lvm2 resume decryption-keys filesystems fsck)/' /etc/mkinitcpio.conf | |
| mkdir -p /etc/initcpio/install | |
| cat > /etc/initcpio/install/decryption-keys <<'HOOK' | |
| #!/bin/bash | |
| build() { | |
| for file in /etc/initcpio/keys/*; do | |
| add_file "\$file" "/\$(basename "\$file")" 0400 | |
| done | |
| } | |
| HOOK | |
| chmod 0755 /etc/initcpio/install/decryption-keys | |
| echo "Creating keyfiles to embed into initramfs" | |
| mkdir -p /etc/initcpio/keys | |
| dd bs=512 count=8 iflag=fullblock if=/dev/urandom of=/etc/initcpio/keys/encrypted-boot.key | |
| dd bs=512 count=8 iflag=fullblock if=/dev/urandom of=/etc/initcpio/keys/encrypted-lvm.key | |
| chmod 0000 /etc/initcpio/keys/* | |
| chattr +i /etc/initcpio/keys/* | |
| echo "Adding keyfiles to LUKS keyslots (non-interactive via LUKS_PASSPHRASE)" | |
| printf '%s' "\$LUKS_PASSPHRASE" | cryptsetup -q --batch-mode --key-file=- luksAddKey "$boot_part" /etc/initcpio/keys/encrypted-boot.key | |
| printf '%s' "\$LUKS_PASSPHRASE" | cryptsetup -q --batch-mode --key-file=- luksAddKey "$lvm_part" /etc/initcpio/keys/encrypted-lvm.key | |
| echo "Building initramfs" | |
| mkinitcpio -p linux | |
| chmod 0400 /boot/initramfs-linux*.img || true | |
| echo "Adding pacman hook to chmod initramfs after kernel/initcpio updates" | |
| mkdir -p /etc/pacman.d/hooks | |
| cat > /etc/pacman.d/hooks/99-initramfs-chmod.hook <<'PAC' | |
| [Trigger] | |
| Type = File | |
| Operation = Install | |
| Operation = Upgrade | |
| Target = boot/vmlinuz-linux | |
| Target = usr/lib/initcpio/* | |
| [Action] | |
| Description = Setting proper permissions for linux initcpios... | |
| When = PostTransaction | |
| Exec = /usr/bin/chmod 0400 /boot/initramfs-linux.img /boot/initramfs-linux-fallback.img | |
| PAC | |
| echo "Configuring GRUB for cryptodisk + kernel cmdline" | |
| if grep -q '^#GRUB_ENABLE_CRYPTODISK=y' /etc/default/grub; then | |
| sed -i 's/^#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/' /etc/default/grub | |
| elif ! grep -q '^GRUB_ENABLE_CRYPTODISK=y' /etc/default/grub; then | |
| echo 'GRUB_ENABLE_CRYPTODISK=y' >> /etc/default/grub | |
| fi | |
| LVM_UUID=\$(blkid -o value -s UUID "$lvm_part") | |
| SWAP_DEV=/dev/mapper/Main-swap | |
| ROOT_DEV=/dev/mapper/Main-root | |
| GRUB_CMDLINE="cryptdevice=UUID=\${LVM_UUID}:encrypted-lvm root=\${ROOT_DEV} resume=\${SWAP_DEV} cryptkey=rootfs:/encrypted-lvm.key" | |
| if grep -q '^GRUB_CMDLINE_LINUX=' /etc/default/grub; then | |
| sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\\"\${GRUB_CMDLINE}\\"|" /etc/default/grub | |
| else | |
| echo "GRUB_CMDLINE_LINUX=\\"\${GRUB_CMDLINE}\\"" >> /etc/default/grub | |
| fi | |
| echo "Installing GRUB bootloader ($mode)" | |
| if [[ "$mode" == "uefi" ]]; then | |
| mountpoint -q /boot/efi || { echo "/boot/efi is not mounted"; exit 1; } | |
| grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=grub --recheck | |
| else | |
| grub-install --target=i386-pc "$disk" | |
| fi | |
| grub-mkconfig -o /boot/grub/grub.cfg | |
| echo "Configuring /etc/crypttab to auto-open encrypted-boot after boot" | |
| BOOT_UUID=\$(blkid -o value -s UUID "$boot_part") | |
| cat > /etc/crypttab <<CT | |
| encrypted-boot UUID=\${BOOT_UUID} /etc/initcpio/keys/encrypted-boot.key luks | |
| CT | |
| echo "Done inside chroot." | |
| EOF | |
| } | |
| post_install_notes() { | |
| cat <<'NOTES' | |
| Next manual steps (not automated here): | |
| - Set hostname: echo myhost > /mnt/etc/hostname | |
| - Set root password: arch-chroot /mnt passwd | |
| - Create user + sudo | |
| - Install and enable networking (e.g. NetworkManager) | |
| Reboot: | |
| umount -R /mnt | |
| swapoff -a | |
| reboot | |
| NOTES | |
| } | |
| main() { | |
| require_root | |
| require_luks_env | |
| require_cmds lsblk gdisk wipefs cryptsetup pvcreate vgcreate lvcreate mkfs.ext4 mkswap mount swapon pacstrap genfstab arch-chroot grub-install grub-mkconfig mkinitcpio blkid sed dd chattr partprobe | |
| local mode="bios" | |
| if is_uefi; then mode="uefi"; fi | |
| log "Detected boot mode: $mode" | |
| pick_disk_menu | |
| collect_i18n_inputs | |
| collect_microcode_choice | |
| wipe_disk "$DISK" | |
| partition_disk "$DISK" "$mode" | |
| local esp_part="" boot_part="" lvm_part="" | |
| if [[ "$mode" == "uefi" ]]; then | |
| esp_part="$(part_path "$DISK" 1)" | |
| boot_part="$(part_path "$DISK" 2)" | |
| lvm_part="$(part_path "$DISK" 3)" | |
| else | |
| boot_part="$(part_path "$DISK" 2)" | |
| lvm_part="$(part_path "$DISK" 3)" | |
| fi | |
| log "Using partitions:" | |
| if [[ "$mode" == "uefi" ]]; then | |
| echo " ESP : $esp_part" | |
| else | |
| echo " BIOS boot: $(part_path "$DISK" 1)" | |
| fi | |
| echo " BOOT: $boot_part" | |
| echo " LVM : $lvm_part" | |
| if [[ "$mode" == "uefi" ]]; then | |
| format_esp_if_needed "$mode" "$esp_part" | |
| fi | |
| setup_luks_and_lvm "$boot_part" "$lvm_part" | |
| if [[ "$mode" == "uefi" ]]; then | |
| mount_all "$mode" "$esp_part" | |
| else | |
| mount_all "$mode" | |
| fi | |
| install_base "$mode" | |
| configure_system_in_chroot "$mode" "$DISK" "$boot_part" "$lvm_part" "$TIMEZONE" "$LOCALE" "$KEYMAP" | |
| log "Install completed." | |
| post_install_notes | |
| } | |
| main "$@" | |