Skip to content

Instantly share code, notes, and snippets.

@sam-saffron-jarvis
Last active March 23, 2026 05:27
Show Gist options
  • Select an option

  • Save sam-saffron-jarvis/9f3db1da8a1857ff7b3329a5021741e5 to your computer and use it in GitHub Desktop.

Select an option

Save sam-saffron-jarvis/9f3db1da8a1857ff7b3329a5021741e5 to your computer and use it in GitHub Desktop.
Migrate Arch Linux (Encrypted LVM on LUKS) from nvme2n1 to nvme0n1 (3.64 TiB)

Migrate Arch Linux (Encrypted LVM) to New NVMe Drive

Layout

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)

Your LVM layout

LV VG Size
root vg ~1.16 TiB
swap vg 64 GiB

Device paths: /dev/vg/root, /dev/vg/swap

Prerequisites

  • 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

Step 0: Verify drives

lsblk
# Confirm:
#   nvme2n1 = 1.82 TiB (old, has Windows + Linux)
#   nvme0n1 = 3.64 TiB (new, empty)
# TRIPLE CHECK THESE. Getting them backwards is catastrophic.

Step 1: Partition the new drive

gdisk /dev/nvme0n1

Create 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

Step 2: Format and copy EFI + /boot

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}

Step 3: Clone the LUKS partition

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=progress

This 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).

Step 4: Expand LUKS + LVM

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 LVs

Now 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/root

Resize 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 /mnt

Step 5: Update fstab and crypttab

The 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 needed

Step 6: Reinstall bootloader + regenerate initramfs

arch-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

exit

Note: If os-prober doesn't detect Windows, make sure it's installed (pacman -S os-prober) and enabled in /etc/default/grub: GRUB_DISABLE_OS_PROBER=false

Step 7: Set UEFI boot entry

# 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 -v

GRUB 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.

Step 8: Unmount and reboot

umount -R /mnt
reboot

Enter BIOS and set nvme0n1 as the primary boot device.


After successful boot

Once you're booted from the new drive and everything works:

  1. Verify: lsblk should show nvme0n1 with your partitions
  2. Verify: df -h should show the expanded root filesystem
  3. The old Linux partitions on nvme2n1 (p5, p6, p7) can be deleted with gdisk to reclaim ~1.34 TiB for Windows or whatever else
  4. The 119.73 GiB of unallocated space on nvme2n1 is also freed up

Troubleshooting

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment