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/sdX or dd if=/dev/zero of=/dev/sdX bs=1M with dd if=/dev/random of=/dev/sdX bs=1M should 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-ucode

Security Note for Production Environments: For demonstration and fully unattended automation purposes, credentials like SYS_PASS and LUKS_PASS are 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/efi

Bootstrapping: 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.sh

Stage 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.sh

Localization 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}
EOF

Bootloader & 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.cfg

Users: 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}" | chpasswd

AUR: 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
fi

Secure 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.sh

SSD Wear-Leveling & LUKS: Normally, using shred on 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 root

Changing 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/sdX

or 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 key

Challenges 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.