Skip to content

Instantly share code, notes, and snippets.

@Ensamisten
Last active March 31, 2026 11:48
Show Gist options
  • Select an option

  • Save Ensamisten/9281efd832569a07476dc8f693bc7c72 to your computer and use it in GitHub Desktop.

Select an option

Save Ensamisten/9281efd832569a07476dc8f693bc7c72 to your computer and use it in GitHub Desktop.

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 "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment