Phase 3: Base System

Phase 3: Base System

pacstrap

pacstrap installs packages into the mounted filesystem at /mnt. The -K flag initializes a fresh pacman keyring in the target (instead of copying from the live ISO). This is the foundation — every package here will be available on first boot.

pacstrap -K /mnt \
    base linux linux-headers linux-lts linux-lts-headers linux-firmware \
    base-devel btrfs-progs git neovim zsh sudo \
    networkmanager openssh intel-ucode iwd wireless_tools zram-generator
Package Group Why

base

Minimal Arch system (glibc, bash, coreutils, systemd, etc.)

linux linux-headers

Main kernel + headers for DKMS modules (nvidia, etc.)

linux-lts linux-lts-headers

Fallback kernel — if a mainline update breaks NVIDIA or WiFi, boot LTS

linux-firmware

Firmware blobs for Intel WiFi, NVMe controllers, etc.

base-devel

gcc, make, etc. — needed for AUR builds and treesitter parsers

btrfs-progs

Btrfs management tools (subvolume, snapshot, scrub)

git

Required for AUR, dotfiles, lazy.nvim plugin installs

neovim

Editor — domus-nvim config cloned in Phase 7

zsh

Default shell for user account (set in useradd -s below)

sudo

Privilege escalation for the wheel group

networkmanager

Network management daemon — handles WiFi, DHCP, DNS after reboot

openssh

SSH server — required for Phase 5 remote reconnection

intel-ucode

Intel CPU microcode patches — loaded via initrd at boot

iwd wireless_tools

WiFi drivers and tools (iwd is NetworkManager’s WiFi backend)

zram-generator

Compressed swap in RAM — no swap partition needed

Use amd-ucode instead of intel-ucode for AMD CPUs.

Generate fstab

genfstab reads the currently mounted filesystems under /mnt and generates /etc/fstab — the file that tells the installed system what to mount at boot. The -U flag uses UUIDs instead of device paths (UUIDs survive disk reordering).

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

Verify fstab

cat /mnt/etc/fstab

Check for all 6 mount points:

Mount Point Filesystem Source

/

btrfs (subvol=/@)

cryptroot

/.snapshots

btrfs (subvol=/@snapshots)

cryptroot

/var/log

btrfs (subvol=/@var_log)

cryptroot

/home

btrfs (subvol=/@home)

crypthome

/boot

ext4

nvme0n1p2

/boot/efi

vfat

nvme0n1p1

# Verify all btrfs entries have the full mount options
grep btrfs /mnt/etc/fstab
Actual output (P16g fstab — all 6 mount points, all options correct)
# /dev/mapper/cryptroot LABEL=archroot
UUID=4de1f373-f999-4e5b-ae62-8fe85cdd2f17    /             btrfs         rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@    0 0

# /dev/mapper/cryptroot LABEL=archroot
UUID=4de1f373-f999-4e5b-ae62-8fe85cdd2f17    /.snapshots    btrfs         rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@snapshots    0 0

# /dev/mapper/cryptroot LABEL=archroot
UUID=4de1f373-f999-4e5b-ae62-8fe85cdd2f17    /var/log      btrfs         rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@var_log    0 0

# /dev/mapper/crypthome LABEL=archhome
UUID=4fcc4995-231f-45c3-a55a-d9f94178adca    /home         btrfs         rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@home    0 0

# /dev/nvme0n1p2
UUID=0d2665b0-4e44-4a72-8f05-831096a1037e    /boot         ext4          rw,relatime    0 2

# /dev/nvme0n1p1
UUID=F71D-2230          /boot/efi     vfat          rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro    0 2

Every btrfs line must include: noatime,compress=zstd:3,ssd,discard=async,space_cache=v2. If any option is missing, fix it now:

nvim /mnt/etc/fstab

Fix fstab BEFORE chroot. After chroot, the file is at /etc/fstab and mistakes are harder to catch. A wrong fstab means the system won’t mount filesystems correctly on boot.

Enter Chroot

arch-chroot changes the root filesystem from the live USB to your installed system at /mnt. After this, every command runs inside the installed system — the prompt changes but the disk layout is your new Arch install.

arch-chroot /mnt
Your prompt changes. You’re now "inside" the installed system. / is now what was /mnt. The live USB’s filesystem is no longer visible.

Configure crypttab (dual LUKS)

The kernel cmdline (cryptdevice=) only handles ONE LUKS volume at boot — that’s cryptroot. The second volume (crypthome) needs /etc/crypttab so systemd opens it during init.

How it works: At boot, you’ll enter the cryptroot passphrase first (from the kernel cmdline). Then systemd reads /etc/crypttab and prompts for the crypthome passphrase. Two prompts, two volumes.

# Capture the UUID into a variable — no manual copy-paste, no typo risk
# blkid reads the raw LUKS partition (nvme0n1p4), NOT the mapper (crypthome)
HOME_LUKS_UUID=$(blkid -s UUID -o value /dev/nvme0n1p4)
echo "Home LUKS UUID: $HOME_LUKS_UUID"
# Write crypttab using the variable
# NOTE: heredoc uses EOF without quotes so $HOME_LUKS_UUID expands
# With 'EOF' (quoted) the variable would be written literally as "$HOME_LUKS_UUID"
cat > /etc/crypttab << EOF
crypthome   UUID=$HOME_LUKS_UUID   none   luks,timeout=90
EOF
# Verify — should show the actual UUID, not a variable name
cat /etc/crypttab
Actual output (P16g)
[root@archiso /]# HOME_LUKS_UUID=$(blkid -s UUID -o value /dev/nvme0n1p4)
[root@archiso /]# echo "Home LUKS UUID: $HOME_LUKS_UUID"
Home LUKS UUID: 4cba46dd-8e32-43d9-bda5-62ac07e14096

[root@archiso /]# cat > /etc/crypttab << EOF
> crypthome   UUID=$HOME_LUKS_UUID   none   luks,timeout=90
> EOF

[root@archiso /]# cat /etc/crypttab
crypthome   UUID=4cba46dd-8e32-43d9-bda5-62ac07e14096   none   luks,timeout=90

Time Zone & Hardware Clock

# Symlink timezone — changes what "local time" means on this machine
ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
# Sync hardware clock (RTC) to system clock
# --systohc = "system to hardware clock"
hwclock --systohc

Locale

The locale defines language, character encoding, and formatting for dates/numbers/currency. locale.gen ships with all locales commented out — you need to uncomment the one you want.

# BEFORE: see the commented line we're targeting
grep 'en_US.UTF-8' /etc/locale.gen
Expected (before)
#en_US.UTF-8 UTF-8
# DRY RUN: preview what sed will change (no -i = prints to stdout, doesn't modify file)
sed 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen | grep 'en_US.UTF-8'
Expected (dry run — uncommented)
en_US.UTF-8 UTF-8
# APPLY: -i writes the change in-place
sed -i 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
# VERIFY: confirm it's uncommented
grep 'en_US.UTF-8' /etc/locale.gen
Expected (after — no # prefix)
en_US.UTF-8 UTF-8
# Generate the locale files from locale.gen
locale-gen
# Set the system default locale
echo "LANG=en_US.UTF-8" > /etc/locale.conf
# Verify
cat /etc/locale.conf

Hostname

The hostname identifies this machine on the network and in the shell prompt. The /etc/hosts file maps the hostname to loopback addresses for local name resolution.

echo "modestus-p16g" > /etc/hostname
cat > /etc/hosts << 'EOF'
127.0.0.1   localhost
::1         localhost
127.0.1.1   modestus-p16g.inside.domusdigitalis.dev modestus-p16g
EOF
# Verify
cat /etc/hostname
cat /etc/hosts

Configure zram Swap

zram creates a compressed block device in RAM that acts as swap. No swap partition needed — the compression (zstd) typically achieves 2-3x ratio, so 32GB of RAM effectively gives 64-96GB of swap capacity.

cat > /etc/systemd/zram-generator.conf << 'EOF'
[zram0]
zram-size = ram / 2
compression-algorithm = zstd
EOF
ram / 2 means half your physical RAM. On the P16g (64GB), this creates a ~32GB zram device — same as the Razer. Verify after first boot with zramctl.

Root Password

# Set the root password for the INSTALLED system (not the live ISO)
passwd

Create User Account

# -m = create home directory (/home/evanusmodestus)
# -G wheel = add to wheel group (sudo access)
# -s /bin/zsh = default shell (zsh was installed in pacstrap)
useradd -m -G wheel -s /bin/zsh evanusmodestus
passwd evanusmodestus

Configure sudo

The wheel group convention: users in wheel can use sudo. By default the line is commented out — you need to uncomment it.

# BEFORE: see the commented line
grep '%wheel' /etc/sudoers
Expected (before)
# %wheel ALL=(ALL) ALL
# %wheel ALL=(ALL:ALL) ALL
# DRY RUN: preview what changes (no -i)
sed 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers | grep '%wheel'
# APPLY
sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers
# VALIDATE: visudo -c checks sudoers syntax without opening the editor
# This is the safety net — if sed broke something, this catches it
visudo -c
Actual output (P16g)
[root@archiso /]# visudo -c
/etc/sudoers: parsed OK
# VERIFY: confirm the line is uncommented
# The NOPASSWD line should still be commented — that's intentional (require password for sudo)
grep '%wheel' /etc/sudoers
Actual output (P16g)
%wheel ALL=(ALL:ALL) ALL
# %wheel ALL=(ALL:ALL) NOPASSWD: ALL

First line uncommented = wheel users can sudo with password.
Second line still commented = password IS required (don’t uncomment NOPASSWD unless you have a specific reason).

Option B: Interactive (manual)

EDITOR=nvim visudo
# Find the line: # %wheel ALL=(ALL:ALL) ALL
# Remove the # and save

Never edit /etc/sudoers directly with nvim /etc/sudoers. Always use visudo (interactive) or sed + visudo -c (scripted). A syntax error in sudoers locks you out of sudo entirely — and without sudo, you can’t fix it (except by booting a live USB).

Enable Services

These services start automatically on every boot. Enable them now so they’re ready on first boot.

# NetworkManager handles WiFi, DHCP, DNS — without it, no network after reboot
systemctl enable NetworkManager
# Time sync daemon — keeps the clock accurate via NTP
systemctl enable systemd-timesyncd
# Verify both are enabled
systemctl is-enabled NetworkManager systemd-timesyncd
Actual output (P16g)
[root@archiso /]# systemctl enable NetworkManager
Created symlink '.../multi-user.target.wants/NetworkManager.service' → '.../NetworkManager.service'
Created symlink '.../dbus-org.freedesktop.nm-dispatcher.service' → '.../NetworkManager-dispatcher.service'
Created symlink '.../network-online.target.wants/NetworkManager-wait-online.service' → '.../NetworkManager-wait-online.service'

[root@archiso /]# systemctl enable systemd-timesyncd
Created symlink '.../dbus-org.freedesktop.timesync1.service' → '.../systemd-timesyncd.service'
Created symlink '.../sysinit.target.wants/systemd-timesyncd.service' → '.../systemd-timesyncd.service'

[root@archiso /]# systemctl is-enabled NetworkManager systemd-timesyncd
enabled
enabled