| Drive | What |
|---|---|
/dev/nvme2n1 (1.82 TiB) |
Old drive — Windows (p1-p4) + Linux (p5-p7). Linux is leaving. |
/dev/nvme0n1 (3.64 TiB) |
New drive — empty. Linux goes here. |
Old Linux partitions being migrated:
nvme2n1p5— Encrypted LVM2 PV (1.22 TiB) — root, swap, etc.nvme2n1p6— EFI-LINUX (fat32, 100 MiB, mounted at/efi)nvme2n1p7— /boot (ext4, 500 MiB)
| LV | VG | Size |
|---|---|---|
| root | vg | ~1.16 TiB |
| swap | vg | 64 GiB |
Device paths: /dev/vg/root, /dev/vg/swap
- Arch install USB (has all needed tools:
dd,cryptsetup,lvm2,gdisk,rsync,arch-chroot) - Both NVMe drives installed in the machine
- Boot from the USB
lsblk
# Confirm:
# nvme2n1 = 1.82 TiB (old, has Windows + Linux)
# nvme0n1 = 3.64 TiB (new, empty)
# TRIPLE CHECK THESE. Getting them backwards is catastrophic.gdisk /dev/nvme0n1Create this layout:
| # | Type | Size | Purpose |
|---|---|---|---|
| 1 | EFI System (ef00) | 512 MiB | /efi |
| 2 | Linux filesystem (8300) | 1 GiB | /boot |
| 3 | Linux filesystem (8300) | Remainder (~3.63 TiB) | LUKS container |
In gdisk:
o # new GPT table
n # new partition
1 # partition 1
<enter> # default first sector
+512M # size
ef00 # EFI System type
n # new partition
2 # partition 2
<enter> # default first sector
+1G # size
8300 # Linux filesystem
n # new partition
3 # partition 3
<enter> # default first sector
<enter> # use all remaining space
8300 # Linux filesystem
w # write and exit
mkfs.fat -F32 /dev/nvme0n1p1
mkfs.ext4 /dev/nvme0n1p2
# Mount old and new
mkdir -p /mnt/{efi-old,efi-new,boot-old,boot-new}
mount /dev/nvme2n1p6 /mnt/efi-old
mount /dev/nvme0n1p1 /mnt/efi-new
mount /dev/nvme2n1p7 /mnt/boot-old
mount /dev/nvme0n1p2 /mnt/boot-new
# Copy
rsync -aAX /mnt/efi-old/ /mnt/efi-new/
rsync -aAX /mnt/boot-old/ /mnt/boot-new/
# Unmount
umount /mnt/{efi-old,efi-new,boot-old,boot-new}This is the big one. Copies the entire encrypted container byte-for-byte. Preserves the LUKS UUID and everything inside (LVM PV, VG, LVs, filesystems).
dd if=/dev/nvme2n1p5 of=/dev/nvme0n1p3 bs=64M status=progressThis copies 1.22 TiB. Both drives are NVMe so expect ~10-20 minutes.
Do NOT interrupt this. If power fails or you ctrl-c, the destination is corrupt and you start over (the source is untouched).
After dd, the LUKS container on nvme0n1p3 is still 1.22 TiB inside a ~3.63 TiB partition. Time to grow everything.
# Open the LUKS container on the NEW drive
cryptsetup open /dev/nvme0n1p3 cryptroot
# (enter your passphrase)
# Resize LUKS to fill the partition
cryptsetup resize cryptroot
# Resize the LVM physical volume
pvresize /dev/mapper/cryptroot
# Check what you have
vgs # see free space in VG
lvs # see existing LVsNow extend whichever LV(s) you want. Examples:
# Give all free space to root:
lvextend -l +100%FREE /dev/vg/root
# Or split it — e.g. give root 2 TiB more, keep the rest free:
# lvextend -L +2T /dev/vg/rootResize the filesystem on root:
# ext4:
e2fsck -f /dev/vg/root
resize2fs /dev/vg/root
# btrfs:
# mount /dev/vg/root /mnt && btrfs filesystem resize max /mnt
# xfs:
# mount /dev/vg/root /mnt && xfs_growfs /mntThe LUKS UUID is preserved (dd copied the header), so anything referencing it stays correct. But the EFI and /boot partitions are new and have new UUIDs.
# Mount the root filesystem
mount /dev/vg/root /mnt
mount /dev/nvme0n1p2 /mnt/boot
mount /dev/nvme0n1p1 /mnt/efi # or /mnt/boot/efi — check your old fstab
# Get UUIDs
blkid /dev/nvme0n1p1 # new EFI UUID
blkid /dev/nvme0n1p2 # new /boot UUID
# Edit fstab — update the UUID for /efi and /boot entries
nano /mnt/etc/fstab
# Check crypttab — if it references the LUKS device by UUID, it's fine
# (the LUKS UUID was preserved by dd). If it uses a device path like
# /dev/nvme2n1p5, change it to /dev/nvme0n1p3 or better yet, use
# UUID=<luks-uuid> instead.
cat /mnt/etc/crypttab
nano /mnt/etc/crypttab # if neededarch-chroot /mnt
# Reinstall GRUB to the new drive's EFI partition
grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB
# Regenerate GRUB config — this will also auto-detect Windows on nvme2n1
# (os-prober finds it and adds a menu entry)
grub-mkconfig -o /boot/grub/grub.cfg
# Verify the cryptdevice= line in grub.cfg still references the correct
# LUKS UUID (it should — dd preserved it). Check with:
grep cryptdevice /boot/grub/grub.cfg
# Regenerate initramfs
mkinitcpio -P
exitNote: If
os-proberdoesn't detect Windows, make sure it's installed (pacman -S os-prober) and enabled in/etc/default/grub:GRUB_DISABLE_OS_PROBER=false
# Create a boot entry pointing to the new drive's EFI partition
efibootmgr -c -d /dev/nvme0n1 -p 1 -L "Arch Linux" \
-l '\EFI\GRUB\grubx64.efi'
# Verify
efibootmgr -vGRUB on the new drive will show both Arch and Windows in its menu. Windows boots from its own ESP on nvme2n1 — no need to copy anything.
umount -R /mnt
rebootEnter BIOS and set nvme0n1 as the primary boot device.
Once you're booted from the new drive and everything works:
- Verify:
lsblkshould show nvme0n1 with your partitions - Verify:
df -hshould show the expanded root filesystem - The old Linux partitions on nvme2n1 (p5, p6, p7) can be deleted with gdisk to reclaim ~1.34 TiB for Windows or whatever else
- The 119.73 GiB of unallocated space on nvme2n1 is also freed up
Can't unlock LUKS on new drive: The dd preserved the LUKS header. Same
passphrase works. If it doesn't, something went wrong with the dd (check
cryptsetup luksDump /dev/nvme0n1p3 — it should show valid LUKS metadata).
VG name conflict: If both drives are connected and both LUKS containers are
open, LVM will see duplicate VG names. Only open the NEW drive's LUKS. If you
accidentally opened both: vgchange -an, close the old one with
cryptsetup close, then vgchange -ay.
Boot entry not found: Make sure you ran bootctl install from inside the
chroot with /efi (or /boot) mounted. Check efibootmgr -v to see what the
firmware knows about.
Wrong root mounted: If the system boots but mounts the old drive's root,
your crypttab or boot entry still references the old partition. Check
/etc/crypttab and /boot/loader/entries/*.conf for stale device paths.