Automating a full disk encryption Arch Linux install
Today I’ll explain my install method of choice for a reliable, secure, fully encrypted, and reproducible Arch Linux environment. By applying the Infrastructure as Code (IaC) principles, this script automates the deployment process, significantly reduces human error and saves time.
This script follows the best practices of the standard Arch Linux install guide and the Arch Linux encrypted install guide.
Target audience for this script
This script is designed for users who want to understand the load-bearing structures of their Linux system, rather than relying on a complex installer that obscures the process.
It serves as a highly modular blueprint. You can easily adapt the variables to install less or more packages and configure it to your liking.
If you want a reproducible environment that you can deploy reliably without manual interventions, this is built for you.
Configuration and usage
Pre-Installation Drive Wiping: If you are deploying this on a previously used drive, it is highly recommended to perform a hardware-level secure erase before running this script to ensure no residual data remains.
- For SATA SSDs, use
hdparm --security-erase- For NVMe drives, use
nvme-format- For HDDs,
shred -vfz -n 3 /dev/sdXordd if=/dev/zero of=/dev/sdX bs=1Mwithdd if=/dev/random of=/dev/sdX bs=1Mshould be sufficient.
A great bundled tool is nwipe, a fork of dwipe used in DBAN.For more information, I reccomend reading this article from the Thomas-Krenn Wiki about erasing SSDs using hdparm.
You can download the files here. Simply download and extract the archive, then change the variables at the top of the first 01_system.sh file. After that, follow the instructions below.
# --- Core Configuration ---
TARGET_DISK="/dev/sda"
PART_EFI="${TARGET_DISK}1"
PART_BOOT="${TARGET_DISK}2"
PART_ROOT="${TARGET_DISK}3"
# --- System Preferences ---
HOST_NAME="example"
USERNAME="user"
# Note: passwords set here will be securely shredded after the install
SYS_PASS="root"
LUKS_PASS="luks"
TIMEZONE="Europe/Berlin"
KBD_LAYOUT="de" # Console keymap
KBD_LOCALE="de-latin1" # vconsole keymap
SYS_LOCALE="de_DE.UTF-8" # System-wide language
USER_SHELL="bash" # e.g. bash, zsh, fish, you can choose any shell available in the default Arch Linux repository
AUR_HELPER="" # e.g. yay, paru, leave empty for none
CPU_UCODE="intel-ucode" # e.g. amd-ucode or intel-ucodeSecurity Note for Production Environments: For demonstration and fully unattended automation purposes, credentials like
SYS_PASSandLUKS_PASSare provided as variables within the script. In a production environment, these would be passed securely via cmd exports, a secrets manager or injected dynamically during CI/CD execution.
Deployment: Copy the script to your Arch Linux live USB (or secondary thumb drive).
Execution: Run the script as root.
Cleanup: The script is engineered to securely shred itself and its own temporary credential files once the install is completed.
Advantages of this script over other Arch Linux install scripts
- Smooth Execution: Unlike standard interactive scripts that stall while waiting for keyboard input, this architecture runs completely automatically from start to finish.
- Transparency: Everything is plainly laid out in Bash. There are no hidden dependencies or complex menus.
- Defensive Coding: It handles critical security setups, like LUKS encryption, programmatically and reliably.
Going through the script step by step
Two-Stage install
To install an operating system from a live USB, the automation is divided into two phases using arch-chroot: Pre-Chroot (foundation) and Post-Chroot (configuration).
---
config:
theme: 'neutral'
flowchart:
padding: 20
subGraphTitleMargin:
top: 15
bottom: 15
---
flowchart TD
Start((Boot Live USB)) --> Stage1
subgraph Stage1 [Stage 1: Pre-Chroot Foundation]
direction LR
S1[sfdisk
Wipe & Partition] --> S2[cryptsetup
LUKS Encryption]
S2 --> S3[mkfs & mount
Format & Mount]
S3 --> S4[pacstrap
Install Base System]
S4 --> S5[genfstab
Generate fstab]
S5 --> S6[Copy Stage 2 Script]
end
Stage1 -- "arch-chroot" --> Stage2
subgraph Stage2 [Stage 2: Post-Chroot Configuration]
direction LR
S7[Timezone & Locales] --> S8[mkinitcpio
Encryption Hooks]
S8 --> S9[GRUB
Config Bootloader]
S9 --> S10[Provision Users]
S10 --> S11[Install AUR Helper]
S11 --> S12[shred
Destroy Sensitive Data]
end
Stage2 --> Finish((Reboot to Secure OS))
Stage One
This stage prepares the hardware and bootstraps the operating system.
Partitioning: The script wipes the target disk and programmatically creates the EFI, Boot, and Root partitions.
sfdisk "${TARGET_DISK}" <<EOF
label: gpt
size=512M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI"
size=512M, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="BOOT"
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="ROOT"
EOF
mkfs.fat -F32 -n EFI "${PART_EFI}"
mkfs.ext4 -L BOOT "${PART_BOOT}"This will create the following Drive layout:
---
config:
theme: 'neutral'
flowchart:
padding: 20
subGraphTitleMargin:
top: 15
bottom: 15
---
graph TD
subgraph Drive ["Physical Drive (e.g., /dev/sda)"]
direction TB
P1["/dev/sda1 (512MB)
EFI System Partition
FAT32 | Mount: /boot/efi"]
P2["/dev/sda2 (512MB)
Boot Partition
Ext4 | Mount: /boot"]
subgraph P3 ["/dev/sda3 (Remaining Space)"]
direction TB
LUKS["LUKS Encrypted Container
(AES-XTS-PLAIN64)"]
subgraph Mapped ["Mapped Device (/dev/mapper/cryptroot)"]
Root["Root Filesystem
Ext4 | Mount: /"]
end
LUKS --- Mapped
end
end
Cryptographic Setup: It formats the Root partition using LUKS (cryptsetup) to ensure the system drive is securely encrypted.
echo -n "${LUKS_PASS}" | cryptsetup -v --batch-mode --cipher aes-xts-plain64 --key-size 512 --hash sha512 --iter-time 5000 --use-random luksFormat "${PART_ROOT}" -
echo -n "${LUKS_PASS}" | cryptsetup open "${PART_ROOT}" cryptroot -
mkfs.ext4 -L ROOT /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt
mkdir -p /mnt/boot/efi
mount "${PART_BOOT}" /mnt/boot
mount "${PART_EFI}" /mnt/boot/efiBootstrapping: Using pacstrap, it pulls the base Linux kernel, firmware, and development packages directly onto the newly mounted drives.
echo ">>> Installing base system and shell"
pacstrap /mnt base base-devel linux linux-firmware "${CPU_UCODE}" vim nano git net-tools htop grub efibootmgr networkmanager sudo "${USER_SHELL}"Transfer: Finally, it generates the fstab (file system table) using genfstab and securely passes the configuration variables into the new environment.
genfstab -Up /mnt >> /mnt/etc/fstab
mkdir -p /mnt/root
cat <<EOF > /mnt/root/install_vars.sh
TARGET_DISK="${TARGET_DISK}"
PART_ROOT="${PART_ROOT}"
HOST_NAME="${HOST_NAME}"
USERNAME="${USERNAME}"
SYS_PASS="${SYS_PASS}"
TIMEZONE="${TIMEZONE}"
KBD_LOCALE="${KBD_LOCALE}"
SYS_LOCALE="${SYS_LOCALE}"
USER_SHELL="${USER_SHELL}"
AUR_HELPER="${AUR_HELPER}"
EOF
chmod 600 /mnt/root/install_vars.sh
cp ./02_chroot_system.sh /mnt/root/02_chroot_system.sh
chmod +x /mnt/root/02_chroot_system.shStage two
Once the base system is installed, the script uses the arch-chroot command to execute the remaining configuration from inside the newly built system.
arch-chroot /mnt /bin/bash /root/02_chroot_system.sh
source /root/install_vars.shLocalization and network: System clock hwclock, timezone, hostname, networking, and correct system locales are automatically generated and configured.
ln -sf "/usr/share/zoneinfo/${TIMEZONE}" /etc/localtime
hwclock --systohc
sed -i -e "s/#${SYS_LOCALE} UTF-8/${SYS_LOCALE} UTF-8/g" /etc/locale.gen
sed -i -e "s/#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g" /etc/locale.gen
locale-gen
echo "LANG=${SYS_LOCALE}" > /etc/locale.conf
echo ">>> Setting vconsole and hostname"
cat <<EOF > /etc/vconsole.conf
KEYMAP=${KBD_LOCALE}
FONT=lat9w-16
EOF
echo "${HOST_NAME}" > /etc/hostname
cat <<EOF > /etc/hosts
127.0.0.1 localhost
::1 localhost
127.0.1.1 ${HOST_NAME}.localdomain ${HOST_NAME}
EOFBootloader & Encryption Mapping: The script modifies mkinitcpio.conf to include encryption hooks. It then fetches the UUIDs of the encrypted and decrypted drives to configure GRUB, ensuring the system knows what partition to unlock on the next boot.
sed -i '/^HOOKS=/c\HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)' /etc/mkinitcpio.conf
mkinitcpio -P
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arch_grub --recheck
ENCRYPTED_UUID=$(blkid -o value -s UUID "${PART_ROOT}")
DECRYPTED_UUID=$(blkid -o value -s UUID /dev/mapper/cryptroot)
GRUB_CMDLINE="cryptdevice=UUID=${ENCRYPTED_UUID}:cryptroot root=UUID=${DECRYPTED_UUID}"
sed -i -e "s|GRUB_CMDLINE_LINUX=\"\"|GRUB_CMDLINE_LINUX=\"${GRUB_CMDLINE}\"|g" /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfgUsers: It creates the standard user, assigns the chosen shell (e.g. Bash, zsh, fish), and sets up the network manager.
systemctl enable NetworkManager.service
echo "root:${SYS_PASS}" | chpasswd
SHELL_PATH=$(which "${USER_SHELL}")
useradd -m -g users -G wheel -s "${SHELL_PATH}" "${USERNAME}"
echo "${USERNAME}:${SYS_PASS}" | chpasswdAUR: If configured, an AUR (Arch User Repository) helper will be installed as well. To make that possible, temporary root access is granted passwordless for a short time.
if [[ -n "${AUR_HELPER}" ]]; then
echo ">>> Temporarily grant passwordless sudo for AUR helper install"
echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel
chmod 440 /etc/sudoers.d/wheel
# Switch to non-root user for cloning
sudo -u "${USERNAME}" bash << EOF
set -euo pipefail
USER_HOME="/home/${USERNAME}"
export TERM=xterm-256color # Prevents terminal errors during Vim plug install
echo ">>> Installing ${AUR_HELPER} (AUR Helper)"
mkdir -p "\${USER_HOME}/Downloads"
cd "\${USER_HOME}/Downloads"
git clone "https://aur.archlinux.org/${AUR_HELPER}.git"
cd "${AUR_HELPER}"
makepkg -si --noconfirm
EOF
echo ">>> Reverting sudoers to standard secure configuration"
echo "%wheel ALL=(ALL) ALL" > /etc/sudoers.d/wheel
fiSecure Cleanup: Before the final reboot, it executes a shred command to permanently delete the temporary file that held the passwords.
shred -u /root/install_vars.sh
shred -u /root/02_chroot_system.shSSD Wear-Leveling & LUKS: Normally, using
shredon a modern Solid State Drive (SSD) can be unreliable. SSD controllers use wear-leveling algorithms that may write the “shredded” random data to an entirely different physical memory block, leaving the original plaintext data intact on the flash chips.However, because this script generates the temporary credentials inside the newly created LUKS encrypted container, the physical SSD only ever sees encrypted blocks. Even if wear-leveling leaves an old block behind, the data remains securely encrypted and inaccessible without the LUKS key.
Secure the system
The system is by default only secured with a basic root, user, and drive password. To secure it, I recommend changing these passwords to something you can remember.
Changing the root/user passwords:
# user
sudo passwd $USERNAME
# root
sudo passwd rootChanging the LUKS password:
LUKS is key-based, which means you can specify up to 8 working keys to decrypt a single partition. You can either change your key like this:
sudo cryptsetup luksChangeKey /dev/sdXor create a new key and delete the old key like this:
sudo cryptsetup luksAddKey ${PART_ROOT}
# type the new passphrase
sudo cryptsetup luksRemoveKey ${PART_ROOT}
# type the old passphrase, LUKS will delete the matching keyChallenges faced:
LUKS Keymap lockout: When testing out the script initially, the passphrase for the encrypted drive wouldn’t work. The simple fix was changing the mkinitcpio hook order by switching the keymap and encrypt hooks. Why does this matter? The system needs the correct keyboard map loaded into memory (the initramfs) before it prompts for the passphrase. Otherwise, special characters typed on a non-US keyboard will not match the LUKS key. For the implementation, a simple sed replacement was sufficient.
The AUR Helper: The first few times the automatic install of the AUR helper failed, I engineered a way to allow temporary passwordless sudo access for the installation to finish successfully.
Technical Stack
| Category | Technologies & Concepts |
|---|---|
| Scripting & Automation | Bash Scripting (Sub-shells, Privilege Delegation) |
| Systems Architecture | Linux Boot Process, Initramfs (mkinitcpio), UEFI, GRUB |
| Security & Cryptography | LUKS Block Device Encryption, cryptsetup, Secure Data Deletion (shred) |
| Storage & Filesystems | Automated Partitioning (sfdisk), Ext4/FAT32, Mount Namespaces (genfstab) |
| Infrastructure Provisioning | pacstrap, arch-chroot |
| User-Space Orchestration | Package Management (Pacman, AUR), Git |
Licensing
This project is licensed under the MIT License here.