Last modified: 2023-03-29

ArchLinux: Installation

Source: wiki.archlinux.org

This installation is mostly bare-bone installation, with following features:

  • encrypted disk (/boot is not encrypted)
  • local and remote unlock o encrypted disk via ssh
  • btrfs as file-system
  • doas instead of sudo
  • micro-code updates
  • secured GRUB with password (tampering prevention)
  • secured boot with chkboot (tampering detection)
  • early KMS to initialize graphics card

Verify the boot mode


Check if the motherboard supports UEFI (and if it is enabled).

\$ ls /sys/firmware/efi/efivars

If the directory does not exist, the system is likely booted in BIOS mode.


Update system clock


\$ timedatectl set-ntp true

To check service status:

\$ timedatectl status
               Local time: Sun 2019-12-08 10:55:34 CET
           Universal time: Sun 2019-12-08 09:55:34 UTC
                 RTC time: Sun 2019-12-08 09:55:34
                Time zone: Europe/Prague (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Prepare the disk


Since this installation is going to be encrypted, I recommend to wipe the disk.

HDD or SSD with deniable encryption

Write random data to it:

# dd if=/dev/urandom of=/dev/sdX bs=4M status=progress

or use shred:

# shred -v -n 1 /dev/sdX

SSD

Use secure-erase if available. To check availability look for supported: enhanced erase in Security:

# hdparm -I /dev/sdX
/dev/sda:

ATA device, with non-removable media
	Model Number:       XXXXXXX
	Serial Number:      XXXXXXX
	Firmware Revision:  XXXXXXX
	Transport:          Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0
Standards:
	...
Configuration:
	...
Capabilities:
	...
Commands/features:
	...
Security: 
	Master password revision code = 65534
		supported
	not	enabled
	not	locked
		frozen
	not	expired: security count
		supported: enhanced erase
	XXmin for SECURITY ERASE UNIT. XXmin for ENHANCED SECURITY ERASE UNIT.
Logical Unit WWN Device Identifier: XXXXXXX
	...
Checksum: correct

Secure-erase:

# hdparm --user-master u --security-set-pass NULL /dev/sdX
# hdparm --user-master u --security-erase NULL /dev/sdX

Partition the disk


I will be using single partition, encrypted with dm-crypt in LUKS2 mode and btrfs. The layout will be as follows:

/dev/sdX
|-- /dev/sdX1
    `-- ext4 : /boot
`-- /dev/sdX2
    `-- luks : hostname_system_luks
        `-- btrfs : hostname_system_btrfs
            |-- @ : /
            |   |-- @/var/cache/pacman/pkg
            |   |-- @/var/lib/libvirt/iso_images
            |   |-- @/var/lib/libvirt/virtual_machines
            |   |-- @/var/log
            |   `-- @/var/tmp
            |-- @home : /home
            `-- @snapshots : /.snapshots
# fdisk /dev/sdX
BIOSUEFI
Partition table
og
/boot partition
nn
default (p)
default (1)default (1)
defaultdefault
+100M+100M
at
default (1)
1
/ partition
nn
default (p)
default (2)default (2)
defaultdefault
defaultdefault

dm-crypt


Create a LUKS2 encrypted container:

# cryptsetup -v --type luks2 luksFormat /dev/sdX2
WARNING!
========
This will overwrite data on /dev/sdX2 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase for /dev/sdX2: 
Verify passphrase: 
Command successful.
18.77s user 1.18s system 99% cpu 20.051 total

Open the container:

# cryptsetup open /dev/sdX2 hostname_system_luks

SSD: Enable TRIM and disable workqueue:

# cryptsetup --allow-discards --perf-no_read_workqueue --perf-no_write_workqueue --persistent open /dev/sdX2 hostname_system_luks

Verify status:

# cryptsetup -v status hostname_system_luks
/dev/mapper/hostname_system_luks is active and is in use.
	type:	LUKS2
	cipher:	aes-xts-plain64
	keysize:	512 bits
	key location:	keyring
	device:	/dev/sdX2
	sector size:	512
	offset:	19888128 sectors
	mode:	read/write
Command successful.

Create file-systems


/boot partition for BIOS

Format the /boot partition:

# mkfs.ext4 -L hostname_boot /dev/sdX1

/boot partition for UEFI

Format the /boot partition:

# mkfs.fat -F32 /dev/sdX1

btrfs for the rest

Format the open LUKS2 container to btrfs:

# mkfs.btrfs -L hostname_system_btrfs /dev/mapper/hostname_system_luks

Create subvolumes:

# mount /dev/mapper/hostname_system_luks /mnt
# btrfs subvolume create /mnt/@
# btrfs subvolume create /mnt/@home
# btrfs subvolume create /mnt/@snapshots
# mkdir -p /mnt/@/var/cache/pacman
# btrfs subvolume create /mnt/@/var/cache/pacman/pkg
# mkdir -p /mnt/@/var/lib/libvirt
# btrfs subvolume create /mnt/@/var/lib/libvirt/iso_images
# btrfs subvolume create /mnt/@/var/lib/libvirt/virtual_machines
# btrfs subvolume create /mnt/@/var/log
# btrfs subvolume create /mnt/@/var/tmp

Verify:

# btrfs subvolume list /mnt

Remount everything to correct locations:

# umount /mnt
# mount -o compress=zstd,subvol=@ /dev/mapper/hostname_system_luks /mnt
# mkdir -p /mnt/{home,.snapshots,boot}
# mount /dev/sdX1 /mnt/boot
# mount -o compress=zstd,subvol=@home /dev/mapper/hostname_system_luks /mnt/home
# mount -o compress=zstd,subvol=@snapshots /dev/mapper/hostname_system_luks /mnt/.snapshots

TRIM: To enable asynchronous discard add mount option discard=async

Disable btrfs COW (Copy on Write) and compression for certain directories:

# chattr +C /mnt/var/lib/libvirt/iso_images
# chattr +C /mnt/var/lib/libvirt/virtual_machines

Can be verified with (for explanation of parameters check man chattr):

# lsattr -a /mnt/var/lib/libvirt/virtual_machines

Mirrors


Edit list of mirrors and move those geographically close to your location up in the list. The list will be copied into the newly installed system.

It might be useful to setup a pacman proxy cache, especially if you are installing or updating multiple machines.

Edit list manually:

# nano /etc/pacman.d/mirrorlist

To use local mirror or cache, simply add at the top (don't forget to replace IP address and port):

Server = http://192.168.8.1:7878/\$repo/os/\$arch

Or use reflector to benchmark and select fastest mirrors:

# reflector --verbose --age 24 --country Germany --protocol https --sort rate --save /etc/pacman.d/mirrorlist

Installation bare-minimum


Add sudo into list of ignored packages in pacman configuration:

# nano /etc/pacman.conf
...

# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
#IgnorePkg   =
IgnorePkg   = sudo
#IgnoreGroup =

...

Update package databases:

# pacman -Syy

Install following:

# pacstrap /mnt base base-devel linux linux-headers linux-firmware doas nano git btrfs-progs bash-completion grub openssh amd-ucode intel-ucode

Generate an /etc/fstab file:

# genfstab -U /mnt >> /mnt/etc/fstab

Configure some basics


chroot into the new system:

# arch-chroot /mnt

Set the time zone (replace REGION and CITY):

# ln -sf /usr/share/zoneinfo/REGION/CITY /etc/localtime

Run hwclock to generate /etc/adjtime:

# hwclock --systohc

Uncomment en_US.UTF-8 UTF-8 and other needed locales in /etc/locale.gen:

# nano /etc/locale.gen

The generate:

# locale-gen

Create the /etc/locale.conf file, and set the LANG variable accordingly:

# echo "LANG=en_US.UTF-8" > /etc/locale.conf

If you set the keyboard layout, make the changes persistent in /etc/vconsole.conf:

# echo "KEYMAP=en" > /etc/vconsole.conf

Create the /etc/hostname file:

# echo "HOSTNAME" > /etc/hostname

Add matching entries to /etc/hosts man/hosts:

# nano /etc/hosts

Example:

127.0.0.1  localhost
::1        localhost
127.0.1.1  hostname.localdomain  hostname

If the system has a permanent IP address, it should be used instead of 127.0.1.1.

Set the root password if required:

# passwd

Add non-root user and set password:

# useradd -m username
# passwd username
# gpasswd -a username wheel

Give wheel group dias privileges (configuration file must end with newline!):

# nano /etc/doas.conf
permit setenv { XAUTHORITY LANG LC_ALL } :wheel

Symlink doas to where sudo would be:

# ln -s \$(which doas) /usr/bin/sudo

Edit ~/.bashrc and add useful things (or global /etc/bash.bashrc):

\$ nano ~/.bashrc
alias sudo='doas'
alias sudoedit='doas rnano'

# Bash tab completion
complete -cf doas

Install AUR helper


I am going with yay, but feel free to choose another one.

Use non-privileged user:

# su username
\$ export EDITOR=nano
\$ cd /tmp
\$ git clone https://aur.archlinux.org/yay.git
\$ cd yay
\$ makepkg -si

Install additional packages


\$ yay -S pacman-cleanup-hook systemd-cleanup-pacman-hook

Install bootloader GRUB


grub-install for BIOS

Keep in mind to select entire disk and not only partition:

# grub-install --target=i386-pc /dev/sdX

grub-install for UEFI

# grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable

GRUB configuration

In configuration file /etc/default/grub change kernel parameters in GRUB_CMDLINE_LINUX_DEFAULT by adding parameter for LUKS:

# nano /etc/default/grub

The format of the new parameter is:

cryptdevice=UUID=<uuid>:devicemapper_name cryptkey=<path>

Where devicemapper_name is the device-mapper name given to the device after decryption, which will be available as /dev/mapper/devicemapper_name. cryptkey is optional.

dm-crypt device UUID can be found with:

# lsblk -f
NAME       FSTYPE      FSVER            LABEL            UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
loop0      squashfs    4.0                                                                                   
sr0        iso9660     Joliet Extension ARCH_202202      2022-02-01-17-06-09-00                              
vda                                                                                                          
├─vda1     ext4        1.0              aex_boot         c68779d4-96f2-4b17-9725-0e207d659ecb 1006.4M     6% /boot
└─vda2     crypto_LUKS 2                                 c7f63f65-2904-4f20-bfbb-db2c66cb6fb0                
  └─aex_system_luks
           btrfs                        aes_system_btrfs 734cce8c-f3b9-4b2c-aaf5-effe0ec98c2e   26.6G     7% /.snapshots
                                                                                                             /home
                                                                                                             /

The said UUID in this example is c7f63f65-2904-4f20-bfbb-db2c66cb6fb0.

Example of GRUB configuration:

# GRUB boot loader configuration

GRUB_DEFAULT=0
GRUB_TIMEOUT=3
GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 cryptdevice=UUID=c7f63f65-2904-4f20-bfbb-db2c66cb6fb0:hostname_system_luks ip=dhcp netconf_timeout=10"
GRUB_CMDLINE_LINUX=""

CONFIG_BLK_DEV_INITRD=y
CONFIG_MICROCODE=y
CONFIG_MICROCODE_INTEL=y
CONFIG_MICROCODE_AMD=y

Install additional helpful packages:

\$ yay -S update-grub grub-reboot-poweroff grub-netboot-archlinux

Useful only with full-disk-encryption (/boot is also encrypted):

# pacman -S grub-btrfs

Add password protection to GRUB menu:

# grub-mkpasswd-pbkdf2
[...]
Your PBKDF2 is grub.pbkdf2.sha512.10000.C8ABD3E93C4DFC83138B0C7A3D719BC650E6234310DA069E6FDB0DD4156313DA3D0D9BFFC2846C21D5A2DDA515114CF6378F8A064C94198D0618E70D23717E82.509BFA8A4217EAD0B33C87432524C0B6B64B34FBAD22D3E6E6874D9B101996C5F98AB1746FE7C7199147ECF4ABD8661C222EEEDB7D14A843261FFF2C07B1269A

Add this hashed password and username into /etc/grub.d/40_custom, where password is the string generated by grub-mkpasswd_pbkdf2:

# nano /etc/grub.d/40_custom
set superusers="username"
password_pbkdf2 username password

Edit /etc/grub.d/10_linux and add --unrestricted to the CLASS. This will allow boot, but prevent changes. You can also add --unrestricted to other files such as /etc/grub.d/90_reboot or /etc/grub.d/91_poweroff.

# nano /etc/grub.d/10_linux
CLASS="--class gnu-linux --class gnu --class os --unrestricted"

Now generate main configuration file:

# grub-mkconfig -o /boot/grub/grub.cfg

Or use update-grub:

# update-grub

Configure initramfs


Normally the initramfs was already generated and would be ready to go, however because of system disk encryption there are some changes required.

Disable generating of fallback initramfs (I have never used it and it only takes up time):

# nano /etc/mkinitcpio.d/linux.preset
PRESETS=('default')
# rm /boot/initramfs-linux-fallback.img

Install few additional items:

\$ yay -S chkboot
# systemctl enable chkboot
# pacman -S mkinitcpio-netconf mkinitcpio-tinyssh mkinitcpio-utils

Add pacman hook to update chkboot hashes on update:

# nano /etc/pacman.d/hooks/chkboot.hook
[Trigger]
Operation = Upgrade
Type = Package
Target = *

[Action]
Description = Updating hashes of all files in /boot
Depends = chkboot
When = PostTransaction
Exec = /usr/bin/chkboot --update
AbortOnFail

Optionally also install mkinitcpio-numlock to enable numlock on boot:

\$ yay -S mkinitcpio-numlock

Edit /etc/mkinitcpio.conf:

# nano /etc/mkinitcpio.conf

Make following changes:

  • Add support for btrfs to enable use of btrfs-check by BINARIES=("/usr/bin/btrfs")
  • Add remote dm-crypt unlock by HOOKS=(base udev autodetect modconf block netconf tinyssh encryptssh filesystems keyboard fsck)
  • Add early KMS start
    • amdgpu for AMDGPU, or radeon when using the legacy ATI driver
    • i915 for Intel graphics
    • nouveau for the open-source Nouveau driver
    • nvidia nvidia_modeset nvidia_uvm nvidia_drm for nvidia driver. See NVIDIA#DRM kernel mode setting for details.
    • mgag200 for Matrox graphics
    • Depending on QEMU graphics in use (qemu option -vga type or libvirt <video><model type='type'>):
      • bochs for std (qemu) and vga/bochs (libvirt)
      • virtio-gpu for virtio
      • qxl for qxl
      • vmwgfx for vmware (qemu) and vmvga (libvirt)
      • cirrus for cirrus
    • Depending on VirtualBox graphics controller:
      • vmwgfx for VMSVGA
      • vboxvideo for VBoxVGA or VBoxSVGA
# vim:set ft=sh
# MODULES
# The following modules are loaded before any boot hooks are
# run.  Advanced users may wish to specify all system modules
# in this array.  For instance:
#     MODULES=(piix ide_disk reiserfs)
MODULES=()

# BINARIES
# This setting includes any additional binaries a given user may
# wish into the CPIO image.  This is run last, so it may be used to
# override the actual binaries included by a given hook
# BINARIES are dependency parsed, so you may safely ignore libraries
BINARIES=("/usr/bin/btrfs")

# FILES
# This setting is similar to BINARIES above, however, files are added
# as-is and are not parsed in any way.  This is useful for config files.
FILES=()

# HOOKS
# This is the most important setting in this file.  The HOOKS control the
# modules and scripts added to the image, and what happens at boot time.
# Order is important, and it is recommended that you do not change the
# order in which HOOKS are added.  Run 'mkinitcpio -H <hook name>' for
# help on a given hook.
# 'base' is _required_ unless you know precisely what you are doing.
# 'udev' is _required_ in order to automatically load modules
# 'filesystems' is _required_ unless you specify your fs modules in MODULES
# Examples:
##   This setup specifies all modules in the MODULES setting above.
##   No raid, lvm2, or encrypted root is needed.
#    HOOKS=(base)
#
##   This setup will autodetect all modules for your system and should
##   work as a sane default
#    HOOKS=(base udev autodetect block filesystems)
#
##   This setup will generate a 'full' image which supports most systems.
##   No autodetection is done.
#    HOOKS=(base udev block filesystems)
#
##   This setup assembles a pata mdadm array with an encrypted root FS.
##   Note: See 'mkinitcpio -H mdadm' for more information on raid devices.
#    HOOKS=(base udev block mdadm encrypt filesystems)
#
##   This setup loads an lvm2 volume group on a usb device.
#    HOOKS=(base udev block lvm2 filesystems)
#
##   NOTE: If you have /usr on a separate partition, you MUST include the
#    usr, fsck and shutdown hooks.
HOOKS=(base udev autodetect modconf block netconf tinyssh encryptssh filesystems keyboard fsck)

# COMPRESSION
# Use this to compress the initramfs image. By default, zstd compression
# is used. Use 'cat' to create an uncompressed image.
#COMPRESSION="zstd"
#COMPRESSION="gzip"
#COMPRESSION="bzip2"
#COMPRESSION="lzma"
#COMPRESSION="xz"
#COMPRESSION="lzop"
#COMPRESSION="lz4"

# COMPRESSION_OPTIONS
# Additional options for the compressor
#COMPRESSION_OPTIONS=()

Copy public key of ed25519 key-pair into /etc/tinyssh/root_key (equivalent of ~/.ssh/authorized_keys) to allow remote ssh unlock. To generate one, you can use following command:

\$ ssh-keygen -t ed25519 -f "\${HOME}/.ssh/\${HOSTNAME}/\${HOSTNAME}_<TARGET>.key" -C "\${HOSTNAME} -> <TARGET>"

Generate tinyssh server keys:

# mkdir /etc/tinyssh/openssh_keys
# ssh-keygen -t ed25519 -f /etc/tinyssh/openssh_keys/\${HOSTNAME}.key -C "\${HOSTNAME} tinyssh key"
# tinyssh-convert /etc/tinyssh/sshkeydir < /etc/tinyssh/openssh_keys/\${HOSTNAME}.key

Generate new initramfs:

# mkinitcpio -P

Update GRUB:

# update-grub

Network configuration (optional)


Create systemd network configuration file:

# nano /etc/systemd/network/lan.network
[Match]
Name=e*

[Network]
DHCP=yes

Enable systemd-networkd:

# systemctl enable systemd-networkd
# systemctl enable systemd-resolved

Optionally also enable ssh service:

# systemctl enable sshd

Reboot


Exit chroot:

# exit

Optionally umount all in /mnt and close dm-crypt device:

# umount -R /mnt
# cryptsetup close hostname_system_luks
# reboot