Phase 2: Disk & Encryption

Phase 2: Disk & Encryption

Survey Existing Layout

Before wiping anything, inspect what’s currently on the disk. The P16g ships with Windows partitions — confirm you’re targeting the right device.

lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINTS
Example output (ThinkPad P16g factory Windows)
NAME          SIZE TYPE FSTYPE   LABEL       MOUNTPOINTS
loop0       967.1M loop squashfs             /run/archiso/airootfs
sda         119.5G disk iso9660  ARCH_202604
├─sda1        1.2G part iso9660  ARCH_202604
└─sda2        252M part vfat     ARCHISO_EFI
nvme0n1       1.9T disk
├─nvme0n1p1   450M part vfat     SYSTEM
├─nvme0n1p2    16M part
├─nvme0n1p3   1.9T part ntfs     Windows
└─nvme0n1p4     2G part ntfs     WinRE_DRV
fdisk -l /dev/nvme0n1
Example output (factory GPT table)
Disk /dev/nvme0n1: 1.86 TiB, 2048408248320 bytes, 4000797360 sectors
Disk model: SAMSUNG MZVLC2T0HBLD-00BLL
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
Disklabel type: gpt

Device              Start        End    Sectors  Size Type
/dev/nvme0n1p1       2048     923647     921600  450M EFI System
/dev/nvme0n1p2     923648     956415      32768   16M Microsoft reserved
/dev/nvme0n1p3     956416 3996700671 3995744256  1.9T Microsoft basic data
/dev/nvme0n1p4 3996700672 4000796671    4096000    2G Windows recovery environment

nvme0n1 is the 2TB internal NVMe. sda is your USB installer. Never target sda. The next command wipes the ENTIRE partition table. Everything on nvme0n1 is destroyed.

Partition (4-partition dual-LUKS layout)

This creates the same production layout as modestus-razer (verified from live lsblk and fstab). The only difference is single NVMe vs the Razer’s dual NVMe.

Partition Size Type Purpose

p1

512M

EFI (ef00)

EFI System Partition — bootloader, kernels, initramfs, entries, loader.conf. systemd-boot reads everything from here.

p2

2G

Linux (8300)

/boot (ext4) — OS-managed kernel storage. pacman installs kernels here; a pacman hook copies them to the ESP.

p3

250G

LUKS (8309)

Encrypted root — caps system bloat, same as Razer

p4

~1.7T

LUKS (8309)

Encrypted home — repos, models, data, everything personal

Why these sizes:

  • 512M EFI — systemd-boot reads EVERYTHING from here: bootloader, entries, kernels, initramfs. 512M fits main + LTS kernels (~310M used). Not oversized like the 1G many guides suggest.

  • 2G /boot ext4 outside LUKS — OS-managed kernel storage. pacman installs kernels here; a pacman hook copies them to the ESP. This partition is NOT read by systemd-boot — it’s a staging area and recovery backup.

  • 250G root cap — the Razer sits at 193G/250G after months of heavy use. 250G is the right ceiling — prevents system bloat from creeping into user space.

  • Remaining ~1.7T for /home — repos, Ollama models (70GB+), docs, dotfiles, everything grows here.

Why NOT XBOOTLDR? The Boot Loader Specification states: "the ESP and XBOOTLDR must use a file system readable by the firmware. For most systems this means VFAT." We use ext4 for /boot (OS kernel management) — the firmware can’t read it, so systemd-boot can’t find kernels there. Kernels must be copied to the ESP (VFAT) where systemd-boot reads them.

The spec also requires: "files must be located on the same partition" as the .conf entry file. Entries AND kernels must both be on the ESP.

Why dual LUKS (not single LUKS with subvolumes):

The dual-LUKS separation means you can nuke and reinstall root without losing a single file in /home. With single-LUKS + subvolumes-only, reformatting the LUKS container destroys everything. Separate LUKS volumes = independent encryption, independent lifecycle.

# Wipe partition table and create fresh GPT
# -Z = zap (destroy) both the GPT and any MBR data structures
# After this, the disk has NO partitions — completely blank
sgdisk -Z /dev/nvme0n1
# EFI System Partition (512M)
# -n 1:0:+512M = new partition 1, start at first available sector, 512M size
# -t 1:ef00 = set partition 1 type to EFI System (required by UEFI firmware)
sgdisk -n 1:0:+512M -t 1:ef00 /dev/nvme0n1
# /boot partition (2G) — Linux filesystem type (8300)
# OS-managed kernel storage. pacman installs here, pacman hook syncs to ESP.
# This is NOT a XBOOTLDR partition — systemd-boot doesn't read from it.
sgdisk -n 2:0:+2G -t 2:8300 /dev/nvme0n1
# Root partition (250G)
# -t 3:8309 = Linux LUKS type (tells the system this partition is encrypted)
# 250G cap matches the Razer — prevents system packages from consuming all space
sgdisk -n 3:0:+250G -t 3:8309 /dev/nvme0n1
# Home partition (remaining space — ~1.7T)
# -n 4:0:0 = start at next available, end at last sector (uses all remaining space)
sgdisk -n 4:0:0 -t 4:8309 /dev/nvme0n1
# Verify — should show 4 partitions with correct sizes and types
sgdisk -p /dev/nvme0n1
Actual output (ThinkPad P16g — Samsung MZVLC2T0HBLD-00BLL)
Disk /dev/nvme0n1: 4000797360 sectors, 1.9 TiB
Model: SAMSUNG MZVLC2T0HBLD-00BLL
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): CFA92F96-8455-4825-83DA-F75D8F084AEB
Partition table holds up to 128 entries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00
   2         1050624         5244927   2.0 GiB     8300
   3         5244928       529532927   250.0 GiB   8309
   4       529532928      4000797326   1.6 TiB     8309

LUKS Encryption

LUKS (Linux Unified Key Setup) encrypts entire partitions at the block level. Without the passphrase, the data is indistinguishable from random noise — even if someone pulls the NVMe out of the laptop.

Two separate LUKS volumes so root can be wiped/reinstalled without touching home. You’ll set a passphrase for each — use different passphrases or the same, your call.

How it works:

  • cryptsetup luksFormat writes a LUKS header to the partition and sets your passphrase. The header contains the master key (encrypted by your passphrase) and metadata (cipher, key size, hash).

  • cryptsetup open decrypts the master key using your passphrase and creates a virtual block device at /dev/mapper/<name>. Everything written to this device is transparently encrypted.

  • The raw partition (/dev/nvme0n1p3) holds ciphertext. The mapper device (/dev/mapper/cryptroot) exposes plaintext. You format and mount the mapper device, never the raw partition.

Root volume (system)

# Create encrypted container
# - Prompts for YES (uppercase) to confirm
# - Then prompts for passphrase (twice to verify)
# - Uses aes-xts-plain64, 256-bit key by default
cryptsetup luksFormat /dev/nvme0n1p3
# Open (decrypt) the container
# - Enter the passphrase you just set
# - Creates /dev/mapper/cryptroot — this is what you format and mount
cryptsetup open /dev/nvme0n1p3 cryptroot

Home volume (user data)

# Same process for home — can use a different passphrase
cryptsetup luksFormat /dev/nvme0n1p4
# Creates /dev/mapper/crypthome
cryptsetup open /dev/nvme0n1p4 crypthome
# Verify both containers are open
ls /dev/mapper/crypt*
Actual output
/dev/mapper/crypthome  /dev/mapper/cryptroot

Timing reference (P16g Samsung Gen5 NVMe):

  • luksFormat root (250G): ~27s (85% CPU)

  • luksFormat home (1.6T): ~20s (119% CPU)

  • open root: ~8s

  • open home: ~7s

Format Filesystems

Each partition gets a filesystem appropriate to its role. Notice we format the mapper devices (/dev/mapper/cryptroot), not the raw partitions (/dev/nvme0n1p3) — the mapper is the decrypted view.

# EFI — must be FAT32 per UEFI spec. The firmware reads this directly to find the bootloader.
mkfs.fat -F32 /dev/nvme0n1p1
# /boot — ext4 for OS kernel management. pacman installs kernels here.
# systemd-boot does NOT read from ext4 — a pacman hook copies kernels to the ESP.
# 2G is generous for staging; the actual boot files live on the 512M ESP.
mkfs.ext4 /dev/nvme0n1p2
# Root — btrfs on top of LUKS. The -L flag sets a human-readable label ("archroot")
# that shows up in lsblk, fstab, and mount output instead of a UUID.
mkfs.btrfs -L archroot /dev/mapper/cryptroot
# Home — same stack: btrfs on LUKS. Separate label for clarity.
mkfs.btrfs -L archhome /dev/mapper/crypthome

Why btrfs? Copy-on-write snapshots (rollback broken updates in seconds), transparent compression (zstd saves 30-50% disk), checksums (detects silent data corruption), and subvolumes (isolate /home from / without separate partitions). The trade-off: slightly more complex than ext4, but the benefits are worth it for a workstation.

Create Btrfs Subvolumes

Subvolumes are like lightweight partitions inside btrfs — they share the same pool of space but can be mounted independently, snapshotted separately, and excluded from backups. Think of them as directories with superpowers.

The @ prefix is a community convention (not a btrfs requirement). Tools like snapper, timeshift, and grub-btrfs recognize it and know how to snapshot/restore these subvolumes.

Why not just use directories? Because subvolumes can be:

  • Mounted independently — each gets its own mount options

  • Snapshotted atomicallybtrfs subvolume snapshot captures a point-in-time copy instantly (copy-on-write, no data duplication)

  • Excluded from parent snapshots — snapshotting @ won’t include @var_log contents, keeping snapshots small

Root subvolumes (on cryptroot)

# Temporary mount to create subvolumes (we'll remount with proper options later)
mount /dev/mapper/cryptroot /mnt
# @ = the actual root filesystem (/)
# @snapshots = system snapshots — used by snapper/timeshift for rollback
# @var_log = isolated logs — if a service floods logs, it won't fill root
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@snapshots
btrfs subvolume create /mnt/@var_log
# Verify — should show 3 subvolumes
btrfs subvolume list /mnt
Actual output (P16g)
ID 256 gen 9 top level 5 path @
ID 257 gen 9 top level 5 path @snapshots
ID 258 gen 9 top level 5 path @var_log

Btrfs details (btrfs-progs v6.19.1):

mkfs.btrfs output (archroot — 250G)
Label:              archroot
UUID:               4de1f373-f999-4e5b-ae62-8fe85cdd2f17
Node size:          16384
Sector size:        4096
Filesystem size:    249.98GiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP               1.00GiB
  System:           DUP               8.00MiB
SSD detected:       yes
Features:           extref, skinny-metadata, no-holes, free-space-tree, block-group-tree
Checksum:           crc32c
mkfs.btrfs output (archhome — 1.62T)
Label:              archhome
UUID:               4fcc4995-231f-45c3-a55a-d9f94178adca
Node size:          16384
Sector size:        4096
Filesystem size:    1.62TiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP               1.00GiB
  System:           DUP               8.00MiB
SSD detected:       yes
Features:           extref, skinny-metadata, no-holes, free-space-tree, block-group-tree
Checksum:           crc32c
umount /mnt

Home subvolume (on crypthome)

mount /dev/mapper/crypthome /mnt
btrfs subvolume create /mnt/@home
# Verify — should show 1 subvolume
btrfs subvolume list /mnt
Expected output
ID 256 gen 8 top level 5 path @home
umount /mnt

Mount Everything

Mount order matters — root (/) must be mounted first because all other mount points are directories inside root. If you try to mount /mnt/boot before /mnt exists, it fails. If you mount /mnt/boot/efi before /mnt/boot, same problem.

The sequence: root → create directories → subvolumes → home → boot → efi.

These mount options match the Razer’s production config and are applied to every btrfs mount.

# Mount root subvolume with production btrfs options
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=@ /dev/mapper/cryptroot /mnt
# Create mount points on the root btrfs subvolume
# NOTE: Do NOT create boot/efi here — it must be created AFTER /boot is mounted
mkdir -p /mnt/{boot,home,.snapshots,var/log}
# Mount remaining root subvolumes (snapshots + logs)
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@snapshots /dev/mapper/cryptroot /mnt/.snapshots
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@var_log /dev/mapper/cryptroot /mnt/var/log
# Mount home from the separate LUKS volume
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@home /dev/mapper/crypthome /mnt/home
# Mount /boot (ext4, outside LUKS)
mount /dev/nvme0n1p2 /mnt/boot
# Create efi directory AFTER /boot is mounted — if created before, the ext4
# mount overlays the btrfs layer and the efi/ directory disappears
mkdir -p /mnt/boot/efi
# Mount EFI
mount /dev/nvme0n1p1 /mnt/boot/efi

Verify

# Should show all 6 mount points: /, /.snapshots, /var/log, /home, /boot, /boot/efi
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS /dev/nvme0n1
Actual output (P16g — all 6 mount points confirmed)
NAME           SIZE TYPE  FSTYPE      MOUNTPOINTS
nvme0n1        1.9T disk
├─nvme0n1p1    512M part  vfat        /mnt/boot/efi
├─nvme0n1p2      2G part  ext4        /mnt/boot
├─nvme0n1p3    250G part  crypto_LUKS
│ └─cryptroot  250G crypt btrfs       /mnt/var/log
│                                     /mnt/.snapshots
│                                     /mnt
└─nvme0n1p4    1.6T part  crypto_LUKS
  └─crypthome  1.6T crypt btrfs       /mnt/home
# Detailed mount options — verify btrfs options are correct
findmnt -t btrfs,vfat,ext4 --output TARGET,SOURCE,FSTYPE,OPTIONS -n
Actual output (P16g — all btrfs options confirmed)
/mnt              /dev/mapper/cryptroot[/@]          btrfs rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/@
├─/mnt/.snapshots /dev/mapper/cryptroot[/@snapshots] btrfs rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/@snapshots
├─/mnt/var/log    /dev/mapper/cryptroot[/@var_log]   btrfs rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/@var_log
├─/mnt/home       /dev/mapper/crypthome[/@home]      btrfs rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/@home
└─/mnt/boot       /dev/nvme0n1p2                     ext4  rw,relatime
  └─/mnt/boot/efi /dev/nvme0n1p1                     vfat  rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro

Mount options explained:

  • noatime — don’t update access timestamps (reduces writes, improves performance)

  • compress=zstd:3 — zstd compression level 3 (balanced speed/ratio, typically 2-3x)

  • ssd — SSD-aware allocation patterns (NVMe/SSD only)

  • discard=async — batched TRIM (better than synchronous discard)

  • space_cache=v2 — improved free space tracking (faster allocations)