👋 A 2025 Update
While a lot of this guide is still relevant (I reference it from time to time), there are a few things I do differently now.
I'm now a tiling window person (Hyprland), which means I use a few different tools / configurations.
Rather than churn out the same post again, I've decided to create a TL;DR version for my new setup.
I've been a Linux user since the 2000s starting on Mandrake with KDE2, however, it never became my main desktop. I always stuck with Windows XP, Windows 7 and then Mac OSX; Linux was still my love for anything server side, but not desktop.
About 6 years ago I decided the Apple premium wasn't worth it any more. Linux had better package managers, cli tools, and I spent most of my time in the web browser or terminal. I felt ready for a Linux desktop. I sold all my Apple hardware, bought an extra SSD and dual booted again. Archlinux with KDE became my desktop of choice.
There are plenty of guides for installing Arch, the Arch wiki being a one such resource, but I wanted to document how I configure mine all in one place with the following:
- UEFI with systemd-boot
- Full disk encryption on ssd/nvme
- btrfs with subvolumes, compression and snapshots
- Swapfile for hibernation
- Unlock encryption with USB flash drive
Getting started
Boot the Archlinux live environment and configure networking. If you're using ethernet then DHCP will already be running. Verify with ping.
ping archlinux.orgIf you need Wi-Fi then run wifi-menu and enable the profile after:
wifi-menu # Let it scan networks, pick yours
netctl enable wlan-ssid # Can tab complete profile(On my systems I use NetworkManager for desktops and Systemd-networkd for servers, but netctl is available within the live environment and provides wifi-menu so I use it during install.)
Ensure the system clock is accurate with:
timedatectl set-ntp trueEdit /etc/pacman.d/mirrorlist and move faster mirrors to the top (i.e. the ones closer geographically).
Partition and file system set up
Find the disk you wish to install Arch on using lsblk. For example an nvme drive at /dev/nvme0n1.
Create two partitions one for boot the other for the main OS. Boot will contain EFI/systemd-boot, and main will contain subvolumes for root and home.
EFI should be at least 260 MiB, and I tend to use 512 MiB for full compatibility.
Run gdisk:
gdisk /dev/nvme0n1
o (create a new empty GUID partition table (GPT))
Proceed? (Y/N): y
n (add a new partition)
Partition number (1-128, default 1): 1
First sector : (hit enter)
Last sector : +512MB (at least 260MiB, 512MiB for compability with old UEFI)
Hex code or GUID: ef00
n (add a new partition)
Partition number (2-128, default 2): 2
First sector : (hit enter)
Last sector : (hit enter - rest of disk)
Hex code or GUID: (hit enter, default, 8300)
w
Do you want to proceed? (Y/N): ySet up encryption on the main partition:
cryptsetup luksFormat /dev/nvme0n1p2
Are you sure? YES
(enter passphrase)
cryptsetup luksOpen /dev/nvme0n1p2 cryptrootFormat the partitions:
mkfs.fat -F32 -n LINUXEFI /dev/nvme0n1p1
mkfs.btrfs -L Arch /dev/mapper/cryptrootNow for the main partition create subvolumes for root and home, along with snapshots/@ and snapshots/@home (so you can run backups and restore independently).
It's also recommended creating subvolumes for directories we don't want backed up e.g. /var/cache, a subvol for swap if you're going to use a swapfile, and if we have VMs or databases, to disable copy-on-write (COW).
mount -o compress=zstd,noatime /dev/mapper/cryptroot /mnt
btrfs subvol create /mnt/@
btrfs subvol create /mnt/@home
btrfs subvol create /mnt/@swap
mkdir /mnt/snapshots
btrfs subvol create /mnt/snapshots/@
btrfs subvol create /mnt/snapshots/@home
umount /mnt
mount -o compress=zstd,noatime,subvol=@ /dev/mapper/cryptroot /mnt
mkdir -p /mnt/{boot,home}
mount -o compress=zstd,noatime,subvol=@home /dev/mapper/cryptroot /mnt/home
mount /dev/nvme0n1p1 /mnt/boot
mkdir /mnt/var
btrfs subvol create /mnt/var/cache
btrfs subvol create /mnt/var/log
mkdir -p /mnt/var/lib/{mysql,postgres,machines}
chattr +C /mnt/var/lib/{mysql,postgres,machines}Note the use of noatime, and compress=zstd options.
noatime will disable writing of access times to each file when they're accessed. It's often recommended for COW file systems to help performance because writes cause COW.
compress=zstd adds transparent compression, helping to reduce file sizes; Its super fast, and I've never had performance issues.
Installation
Install the base system + extra packages:
pacstrap /mnt base base-devel linux linux-firmware intel-ucode amd-ucode \
wpa_supplicant btrfs-progs dosfstools e2fsprogs sudo zsh zsh-completions \
zsh-syntax-highlighting tmux rsync openssh git vim neovim htop networkmanager \
openvpn networkmanager-openvpn fzf ruby python nodejsGenerate fstab:
genfstab /mnt >> /mnt/etc/fstabNote: change the boot partition to use UUID.
Example fstab:
/dev/mapper/cryptroot / btrfs rw,noatime,compress=zstd:3,space_cache,subvol=@ 0 0
/dev/mapper/cryptroot /home btrfs rw,noatime,compress=zstd:3,space_cache,subvol=@home 0 0
UUID=559E-32F1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2
Chroot into our system to finish installation:
cp /etc/pacman.d/mirrorlist /mnt/etc/pacman.d/mirrorlist
arch-choot /mnt
nvim /etc/locale.gen # Uncomment en_GB.UTF-8
locale-gen
echo LANG=en_GB.UTF-8 > /etc/locale.conf
echo keymap=uk > /etc/vconsole.conf
ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime
hwclock --systohc
echo new-hostname > /etc/hostname
passwd # Set root passwordEdit /etc/hosts to match:
127.0.0.1 localhost
::1 localhost
127.0.1.1 new-hostname.localdomain new-hosthostname
Edit /etc/mkinitcpio.conf to add systemd, sd-vconsole, sd-encrypt and move keyboard.
- HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
+ HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt filesystems fsck)
- BINARIES=()
+ BINARIES=(btrfs)
Generate initramfs:
mkinitcpio -PInstall the systemd-boot loader:
bootctl --path=/boot installGet the UUID of the luks partition (not cryptroot, but /dev/nvme0n1p2 for example) with blkid. This is the partition we want to unlock to create cryptroot.
blkidCreate /boot/loader/entries/arch.conf:
title Arch Linux
linux /vmlinuz-linux
initrd /amd-ucode.img
initrd /initramfs-linux.img
options rd.luks.name=UUID_OF_LUKS_PARTITION=cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rd.luks.options=discard rwrd.luks.options=discard will be needed for TRIM support.
You need to pick the right ucode for your processor: amd-ucode or intel-ucode.
Change UUID_OF_LUKS_PARTITION to the UUID you found via blkid.
Edit /boot/loader/loader.conf:
default arch
timeout 2Reboot:
exit
umount -R /mnt
rebootYou should have a functioning base system at this point.
Post install
These steps are more personalised towards how I want my desktop to work. Feel free to tweak as you need to.
Lets login as root and get some networking; we installed NetworkManager before so start and enable that now:
systemctl start NetworkManager NetworkManager-wait-online
systemctl enable NetworkManager NetworkManager-wait-onlineIf you need Wi-Fi you can run the following:
nmcli device wifi list
nmcli device wifi connect SSID password mysecurepassEnable ssh and fstrim:
systemctl enable sshd fstrim.timerfstrim runs a periodic service for TRIM (needed for SSDs). It's recommended to run weekly rather than continuous.
Ensure time is synced:
timedatectl set-ntp trueCreate your user:
useradd -m -G wheel,users -s /usr/bin/zsh rich
passwd rich # Enter passwordEdit sudo to enable wheel group access:
EDITOR=nvim visudo
## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL) ALLSave the changes, exit and login as the new user.
Graphics:
sudo pacman -S xorg-server xf86-video-fbdev xf86-video-nouveau xf86-video-amdgpuI install a lot of AUR packages; to make this easier I use the yay AUR manager:
git clone https://aur.archlinux.org/yay.git /tmp/yay
cd /tmp/yay
makepkg -siInstall KDE desktop:
sudo pacman -S plasma-meta sddm kdialog konsole dolphin noto-fonts \
phonon-qt5-vlcYou may wish to review the following, but these are the packages, themes and fonts I use:
sudo pacman -S adobe-source-code-pro-fonts noto-fonts-emoji ttf-opensans \
ttf-roboto ttf-fira-mono ttf-bitstream-vera \
inetutils firefox chromium \
virt-manager edk2-ovmf qemu libvirt docker docker-compose \
kubectl helm colord-kde kdeconnect exfat-utilsyay -S otf-san-francisco sierrabreeze-kwin-decoration-git archlinux-artwork \
materia-kde materia-gtk-theme plasma5-applets-eventcalendarAdd some groups to our user to make docker/kvm easier:
sudo usermod -a -G docker,libvirt,kvm rich
sudo systemctl enable docker libvirtdTo configure login manager, create the following folder:
sudo mkdir /etc/sddm.conf.dThen create /etc/sddm.conf.d/kde_settings.conf:
[Autologin]
Relogin=false
Session=plasma
User=rich
[General]
HaltCommand=/usr/bin/systemctl poweroff
Numlock=none
RebootCommand=/usr/bin/systemctl reboot
[Theme]
Current=breeze
[Users]
MaximumUid=60000
MinimumUid=1000(Note I auto login my user.)
At this point I clone my dotfiles, but feel free to skip this step as they only contain my configs for KDE themes, zsh, git, etc.
git clone --bare --recursive https://github.com/RichGuk/dotfiles.git $HOME/.dotfiles
alias config='/usr/bin/git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
config checkout
config submodule update
config config --local status.showUntrackedFiles noEnable login manager and reboot.
sudo systemctl enable sddm
sudo rebootYou should be all set!
USB based keyfile
Find the drive you wish to use with lsblk. You can use an existing partition, or create a new partition with gdisk. For example ext4 on /dev/sde3.
Mount the file system and generate a keyfile to use:
sudo mkdir /mnt/usb-keyfiles
sudo mount /dev/sde3 /mnt/usb-keyfiles
sudo dd bs=512 count=4 if=/dev/random of=/mnt/usb-keyfiles/myhostname.key iflag=fullblock
sudo chmod 600 /mnt/usb-keyfiles/myhostname.key
Find your luks partition with lsblk (this is not the btrfs partition). Add the keyfile:
sudo cryptsetup luksAddKey /dev/nvme0n1p2 /mnt/usb-keyfiles/myhostname.key
(enter your existing encryption password)
Get the UUID flash drive:
sudo blkidEdit /boot/loader/entries/arch.conf:
title Arch Linux
linux /vmlinuz-linux
initrd /amd-ucode.img
initrd /initramfs-linux.img
options rd.luks.key=UUID_OF_LUKS_PARTITION=/myhostname.key:UUID=UUID_OF_USB_PARTITION rd.luks.name=UUID_OF_LUKS_PARTITION=cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rd.luks.options=discard,keyfile-timeout=10s rwAdd rd.luks.key and keyfile-timeout=10s to rd.luks.options.
rd.luks.key the first part is the luks we're trying to open, then the location of the keyfile (in our case root of flash drive), and then the UUID of the USB flash partition to use.
keyfile-timeout=10s needed to allow us to fallback to password unlock if USB key doesn't exist.
We need to add the partition type (in our case ext4) to initramfs. Edit /etc/mkinitcpio.conf:
- MODULES=()
+ MODULES=(ext4)
Regenerate initramfs:
mkinitcpio -PYou can now reboot with the USB flash drive plugged in and it should automatically unlock your encryption.
Swapfile / Hibernation
Swapfile cannot be on a snapshotted subvolume. This is why, in the earlier steps, we created the @swap subvolume. Lets mount it:
sudo mkdir /swapspace
sudo mount -o noatime,subvol=@swap /dev/mapper/cryptroot /swapspace(Don't mount with compression.)
Create the swapfile:
sudo truncate -s 0 /swapspace/swapfile
sudo chattr +C /swapspace/swapfile
sudo btrfs property set /swapspace/swapfile compression none
sudo fallocate -l 32G /swapspace/swapfile
sudo mkswap /swapspace/swapfile
sudo chmod 600 /swapspace/swapfileActivate the swapfile:
sudo swapon /swapspace/swapfileNow edit /etc/fstab to mount swapspace and swapfile on boot:
/dev/mapper/cryptroot /swapspace btrfs rw,noatime,space_cachesubvol=@swap 0 0
/swapspace/swapfile none swap defaults,discard 0 0If you want to be able to hibernate you need to follow this to get the offset. Then add resume and resume_offset to /boot/loader/entries/arch.conf:
title Arch Linux
linux /vmlinuz-linux
initrd /amd-ucode.img
initrd /initramfs-linux.img
options rd.luks.key=UUID_OF_LUKS_PARTITION=/myhostname.key:UUID=UUID_OF_USB_PARTITION rd.luks.name=UUID_OF_LUKS_PARTITION=cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rd.luks.options=discard,keyfile-timeout=10s resume=/dev/mapper/cryptroot resume_offset=OFFSET_CALC rw