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 |
|---|---|
|
Minimal Arch system (glibc, bash, coreutils, systemd, etc.) |
|
Main kernel + headers for DKMS modules (nvidia, etc.) |
|
Fallback kernel — if a mainline update breaks NVIDIA or WiFi, boot LTS |
|
Firmware blobs for Intel WiFi, NVMe controllers, etc. |
|
gcc, make, etc. — needed for AUR builds and treesitter parsers |
|
Btrfs management tools (subvolume, snapshot, scrub) |
|
Required for AUR, dotfiles, lazy.nvim plugin installs |
|
Editor — domus-nvim config cloned in Phase 7 |
|
Default shell for user account (set in |
|
Privilege escalation for the wheel group |
|
Network management daemon — handles WiFi, DHCP, DNS after reboot |
|
SSH server — required for Phase 5 remote reconnection |
|
Intel CPU microcode patches — loaded via initrd at boot |
|
WiFi drivers and tools (iwd is NetworkManager’s WiFi backend) |
|
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 |
|
btrfs (subvol=/@snapshots) |
cryptroot |
|
btrfs (subvol=/@var_log) |
cryptroot |
|
btrfs (subvol=/@home) |
crypthome |
|
ext4 |
nvme0n1p2 |
|
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 |
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.
Option A: sed + visudo -c (recommended — no manual editing)
# 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 |
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