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
|
|
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 |
|---|---|---|---|
|
512M |
EFI (ef00) |
EFI System Partition — bootloader, kernels, initramfs, entries, loader.conf. systemd-boot reads everything from here. |
|
2G |
Linux (8300) |
|
|
250G |
LUKS (8309) |
Encrypted root — caps system bloat, same as Razer |
|
~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 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 luksFormatwrites 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 opendecrypts 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):
-
luksFormatroot (250G): ~27s (85% CPU) -
luksFormathome (1.6T): ~20s (119% CPU) -
openroot: ~8s -
openhome: ~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 atomically —
btrfs subvolume snapshotcaptures a point-in-time copy instantly (copy-on-write, no data duplication) -
Excluded from parent snapshots — snapshotting
@won’t include@var_logcontents, 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:
|