Skip to content

Instantly share code, notes, and snippets.

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

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

Select an option

Save sam-saffron-jarvis/9914a562daf742425e9be8bb867ccca7 to your computer and use it in GitHub Desktop.
Migrate Arch Linux (LUKS+LVM) from dual-boot NVMe to a new empty NVMe drive

Migrate Arch Linux (LUKS+LVM) to a New NVMe Drive

Moving only the Linux side of a dual-boot nvme2n1 → empty nvme0n1. Windows partitions (p1–p4) stay on the old drive untouched.

What we're moving

Old (nvme2n1) What Size
p5 LUKS → LVM (VG vg) 1.22 TiB
p6 EFI (fat32, label EFI-LINUX) 100 MiB
p7 /boot (ext4) 500 MiB

Target: nvme0n1 — 3.64 TiB, currently empty.

Prerequisites

  • Arch Linux live USB (latest ISO: https://archlinux.org/download/)
  • Both NVMe drives installed in the machine
  • Know your LVM layout — run lvs on your live system before starting

Phase 0: Gather info (do this BEFORE rebooting)

Run these on the live system and save the output (photo, write it down, whatever):

# LVM layout — need to know LV names and sizes
sudo lvs -o lv_name,vg_name,lv_size,lv_path

# Current fstab for reference
cat /etc/fstab

# Current crypttab
cat /etc/crypttab

# Check bootloader type
# systemd-boot:
bootctl status
# GRUB:
# grub-install --version

# Kernel cmdline (shows root= and cryptdevice= params)
cat /proc/cmdline

# Current UUIDs for reference
sudo blkid /dev/nvme2n1p5 /dev/nvme2n1p6 /dev/nvme2n1p7

Write down or photograph ALL of this output. You'll need it.


Phase 1: Boot the live USB

  1. Write the Arch ISO to a USB stick
  2. Boot from it (F12 / boot menu)
  3. Connect to the internet (iwctl for wifi, or just plug in ethernet)
  4. Install rsync if not present: pacman -Sy rsync

Verify both drives are visible:

lsblk
# Should see nvme0n1 (3.64 TiB, empty) and nvme2n1 (1.82 TiB, with partitions)

Phase 2: Partition the new drive

gdisk /dev/nvme0n1

Create this layout:

# Size Type code Purpose
1 512 MiB EF00 EFI System Partition
2 1 GiB 8300 /boot
3 (rest) 8309 LUKS

In gdisk:

o        ← new GPT table (confirm)
n        ← new partition
  1      ← partition number
  enter  ← default first sector
  +512M  ← size
  EF00   ← EFI type

n
  2
  enter
  +1G
  8300   ← Linux filesystem

n
  3
  enter
  enter  ← use all remaining space
  8309   ← Linux LUKS

w        ← write and exit

Phase 3: Format EFI and /boot

mkfs.fat -F32 -n EFI-LINUX /dev/nvme0n1p1
mkfs.ext4 -L boot /dev/nvme0n1p2

Phase 4: Set up LUKS + LVM on the new drive

# Create LUKS container (you'll set a new passphrase)
cryptsetup luksFormat /dev/nvme0n1p3

# Open it
cryptsetup open /dev/nvme0n1p3 cryptnew

# Create LVM physical volume
pvcreate /dev/mapper/cryptnew

# Create volume group — use the SAME name as your old VG
# (no conflict since old drive's VG isn't activated)
vgcreate vg /dev/mapper/cryptnew

Now create your LVs. Adapt these to match your actual layout from Phase 0.

Example (adjust sizes — you have ~3.6 TiB to play with now):

# If you have swap + root:
lvcreate -L 32G vg -n swap
lvcreate -l 100%FREE vg -n root

# If you have swap + root + home:
# lvcreate -L 32G vg -n swap
# lvcreate -L 500G vg -n root
# lvcreate -l 100%FREE vg -n home

Format the logical volumes:

mkfs.ext4 /dev/vg/root
mkswap /dev/vg/swap

# If you have a separate /home:
# mkfs.ext4 /dev/vg/home

Phase 5: Mount old and new, copy with rsync

Open the old LUKS

cryptsetup open /dev/nvme2n1p5 cryptold

Important: If the old VG is also named vg, LVM will see duplicate VG names. Rename the old one temporarily:

# Check what LVM found
vgscan
pvs

# If both VGs are named "vg", rename the OLD one:
# Find the old VG's UUID
vgdisplay
# Rename by UUID:
vgrename <old-vg-uuid> vgold
vgchange -ay

If the VG names are already different, just activate:

vgchange -ay

Mount everything

# New drive
mount /dev/vg/root /mnt
mkdir -p /mnt/boot /mnt/efi

# If separate /home:
# mkdir -p /mnt/home
# mount /dev/vg/home /mnt/home

mount /dev/nvme0n1p2 /mnt/boot
mount /dev/nvme0n1p1 /mnt/efi

# Old drive
mkdir -p /mnt2
mount /dev/vgold/root /mnt2
mkdir -p /mnt2/boot /mnt2/efi
mount /dev/nvme2n1p7 /mnt2/boot
mount /dev/nvme2n1p6 /mnt2/efi

# If old had separate /home:
# mkdir -p /mnt2/home
# mount /dev/vgold/home /mnt2/home

rsync

# Root filesystem
rsync -aAXHv --info=progress2 /mnt2/ /mnt/ \
  --exclude={'/boot/*','/efi/*','/home/*'}

# Boot
rsync -aAXHv --info=progress2 /mnt2/boot/ /mnt/boot/

# EFI
rsync -aAXHv --info=progress2 /mnt2/efi/ /mnt/efi/

# Home (if separate LV)
# rsync -aAXHv --info=progress2 /mnt2/home/ /mnt/home/

Note: The root rsync will take the longest — depends on how much data is on the 1.22 TiB LV. NVMe-to-NVMe should be fast. Expect ~10–30 min for a typical install.


Phase 6: Fix configuration on the new system

Get new UUIDs

blkid /dev/nvme0n1p1 /dev/nvme0n1p2 /dev/nvme0n1p3
blkid /dev/vg/root /dev/vg/swap
# If separate /home:
# blkid /dev/vg/home

Write these down. You need:

  • nvme0n1p1 UUID → fstab (EFI)
  • nvme0n1p2 UUID → fstab (/boot)
  • nvme0n1p3 UUID → crypttab (LUKS partition)
  • vg/root UUID → fstab (/)
  • vg/swap UUID → fstab (swap)

Update fstab

nano /mnt/etc/fstab

Replace all old UUIDs with the new ones. It should look something like:

# <device>                                 <mount>  <type>  <options>        <dump> <pass>
UUID=<vg-root-uuid>                        /        ext4    rw,relatime      0      1
UUID=<nvme0n1p2-uuid>                      /boot    ext4    rw,relatime      0      2
UUID=<nvme0n1p1-uuid>                      /efi     vfat    rw,relatime,...  0      2
UUID=<vg-swap-uuid>                        none     swap    defaults         0      0

Update crypttab

nano /mnt/etc/crypttab

Update to point to the new LUKS partition:

cryptlvm    UUID=<nvme0n1p3-uuid>    none    luks

Match the mapped name (cryptlvm or whatever your old config used) — check what your kernel cmdline referenced.


Phase 7: Reinstall bootloader + regenerate initramfs

Unmount old, chroot into new

umount -R /mnt2

# Make sure new drive is mounted correctly
# (should already be from Phase 5)
arch-chroot /mnt

For systemd-boot (most likely)

bootctl install

Edit the loader entry:

nano /efi/loader/entries/arch.conf
# (or wherever your entry file is — check /efi/loader/loader.conf for the default)

It should reference the new LUKS partition. Example:

title   Arch Linux
linux   /vmlinuz-linux
initrd  /intel-ucode.img
initrd  /initramfs-linux.img
options cryptdevice=UUID=<nvme0n1p3-uuid>:cryptlvm root=/dev/vg/root rw

Use the LUKS partition UUID (the raw nvme0n1p3), not the LV UUID. Adjust intel-ucodeamd-ucode if on AMD. Match cryptlvm to whatever mapped name your crypttab uses.

For GRUB (if that's what you use instead)

grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

Regenerate initramfs

mkinitcpio -P

This is critical — it rebuilds initramfs with the new crypttab/fstab references.


Phase 8: Boot into the new drive

exit          # leave chroot
umount -R /mnt
reboot
  1. Enter BIOS/UEFI firmware settings (usually F2 or Del)
  2. Set nvme0n1 as first boot device
  3. Boot — you should get the LUKS passphrase prompt
  4. Enter your (new) passphrase
  5. Should boot normally into your Arch install

Phase 9: Post-migration cleanup

Once booted and verified:

# Verify everything mounted correctly
lsblk
mount | grep nvme0n1
swapon --show

# If you renamed the old VG to vgold temporarily,
# it doesn't matter — the old drive isn't activated.

# Optional: reclaim the old Linux partitions on nvme2n1
# (p5, p6, p7) — can delete them in gdisk/GParted
# to free ~1.32 TiB on the old drive for Windows use

Troubleshooting

Can't boot / drops to emergency shell:

  • Boot the live USB again
  • Mount the new root, chroot in
  • Check /etc/fstab UUIDs match blkid output
  • Check /etc/crypttab UUID matches the LUKS partition
  • Check bootloader entry has correct cryptdevice= UUID
  • Run mkinitcpio -P again

LUKS passphrase prompt doesn't appear:

  • The initramfs doesn't have the encrypt hook
  • In chroot: check /etc/mkinitcpio.conf has encrypt and lvm2 in HOOKS
  • Typical order: ... block encrypt lvm2 filesystems ...
  • Regenerate: mkinitcpio -P

VG name collision in live environment:

  • Use vgrename with the VG UUID (from vgdisplay) to disambiguate
  • Or: only open ONE LUKS container at a time

Appendix: Fixing GRUB after migration (the most common failure)

If the data copied fine but GRUB won't boot, this is almost always one of three things:

  1. cryptdevice= in GRUB config still has the old drive's UUID
  2. grub-install was never re-run against the new EFI partition
  3. grub.cfg was never regenerated with the new UUIDs

Fix procedure (from live USB)

# 1. Open the new LUKS + activate LVM
cryptsetup open /dev/nvme0n1p3 cryptlvm
vgchange -ay

# 2. Mount the new system
mount /dev/vg/root /mnt
mount /dev/nvme0n1p2 /mnt/boot
mount /dev/nvme0n1p1 /mnt/efi    # adjust if your ESP mounts at /boot/efi

# 3. Chroot in
arch-chroot /mnt

# 4. Get the new LUKS partition UUID
blkid /dev/nvme0n1p3
# Note the UUID= value (NOT PARTUUID)

# 5. Update /etc/default/grub with the new UUID
nano /etc/default/grub

The critical line — make sure it references the new LUKS partition:

GRUB_CMDLINE_LINUX="cryptdevice=UUID=<nvme0n1p3-uuid>:cryptlvm root=/dev/vg/root"

Also verify these modules are listed (needed for LUKS + LVM):

GRUB_PRELOAD_MODULES="part_gpt part_msdos cryptodisk luks lvm"

Then reinstall and regenerate everything:

# 6. Reinstall GRUB to the new EFI partition
grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB

# 7. Regenerate grub.cfg (picks up the new cryptdevice UUID)
grub-mkconfig -o /boot/grub/grub.cfg

# 8. Verify mkinitcpio.conf has the right hooks
grep HOOKS /etc/mkinitcpio.conf
# Must contain: ... block encrypt lvm2 filesystems ...
# If missing encrypt or lvm2, add them and save

# 9. Rebuild initramfs
mkinitcpio -P

# 10. Also verify /etc/crypttab and /etc/fstab have new UUIDs
cat /etc/crypttab    # should reference nvme0n1p3 UUID
cat /etc/fstab       # should reference new partition/LV UUIDs

# 11. Exit and reboot
exit
umount -R /mnt
reboot

LUKS2 warning

If you created the new LUKS container with cryptsetup luksFormat (defaults to LUKS2 on modern systems) and your /boot is inside the encrypted volume, GRUB cannot decrypt it — GRUB's LUKS2 support is limited.

This shouldn't affect you if /boot is on a separate unencrypted partition (nvme0n1p2), which is the layout in this guide. But if you're hitting error: disk 'lvmid/...' not found, check whether GRUB is trying to read from inside the LUKS container before it's unlocked.

Quick sanity checks after fix

Once booted successfully:

# Verify you're on the new drive
lsblk
mount | grep nvme0n1
cat /proc/cmdline    # should show cryptdevice=UUID=<new-uuid>

# Verify LUKS is the new partition
sudo cryptsetup status cryptlvm

References

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