How to change GUIDs on a cloned drive?

It is not supposed to be managed by grub2-mkconfig.

The default /boot/efi/EFI/opensuse/grub.cfg redirects to /boot/grub2/grub.cfg, but it finds it by UUID, so it must be rewritten to fix. The easiest way is to just rebuild it, but you’re right: I instead should patch the UUIDs.

OK: it all works. Procedure:

  1. Randomize drive UUID using gdisk
  2. Fetch old root partition file system UUID
  3. Randomize root partition file system UUID
  4. Fetch new root partition file system UUID
  5. Patch root partition file system UUID in /etc/fstab
  6. Patch root partition file system UUID in /boot/efi/EFI/opensuse/grub.cfg
  7. Patch root partition file system UUID in /boot/grub2/grub.cfg
  8. Fix grub environment block

Here’s the finished shell script:

#!/usr/bin/env bash
#
# For use on a cloned drive. Randomizes the drive and file system UUIDs, while leaving the
# drive bootable.
# 
# Assumptions:
#	- Recent version of openSUSE
#	- Root partition is btrfs
#	- sudo is timestamped so one password entry gives you multiple accesses
#	- (probably others)

set -o errexit
set -o pipefail

TMPDIR=''

CLEANUP=1

Cleanup() {
	if [ -n "$CLEANUP" ]; then
		if [ -n "$TMPDIR" ]; then
			rm -rf "$TMPDIR"
		fi
	else
		if [ -n "$TMPDIR" ]; then
			echo "There is debris which you should clean up here: '$TMPDIR'"
		fi
	fi
}

Bail() {
	if [ -n "$*" ]; then
		echo "ERROR: $*"
	fi
	Cleanup
	exit 1
}

Usage() {
	echo "Takes three arguments:"
	echo "    <drive device> (e.g. '/dev/sda')"
	echo "    <EFI partition number> (e.g. '1')"
	echo "    <root partition number> (e.g. '2')"
	Bail
}

GetFSType() {
	sudo lsblk -o FSTYPE "$1" 2>/dev/null | tail -n1
}

GetFSUUID() {
	sudo lsblk -o UUID "$1" 2>/dev/null | tail -n1
}

GetDriveUUID() {
	sudo fdisk -l "$1" | grep 'Disk identifier: ' | cut -c18-
}

DRIVE="$1"
EFI_PARTITION="$1$2"
ROOT_PARTITION="$1$3"

if ! sudo --validate --prompt "[sudo] Need root access, so enter root password: " ; then
	Bail "Must have root access"
fi

if [ $# -ne 3 ]; then
	Usage
elif [ ! "${DRIVE:0:5}" = "/dev/" ]; then
	echo "Bad format for drive device '$DRIVE'"
	Usage
elif [ ! -b "$DRIVE" ]; then
	echo "Can't access drive device '$DRIVE'"
	Usage
elif [ ! -b "$EFI_PARTITION" ]; then
	echo "Can't access EFI partition at ''$EFI_PARTITION''"
	Usage
elif [ ! "$(GetFSType "$EFI_PARTITION")" = "vfat" ]; then
	echo "Bad EFI partition at '$EFI_PARTITION' (expected vfat, found $(GetFSType "$EFI_PARTITION"))"
elif [ ! -b "$ROOT_PARTITION" ];  then
	echo "Can't access root partition at '$ROOT_PARTITION'"
	Usage
elif [ ! "$(GetFSType "$ROOT_PARTITION")" = "btrfs" ]; then
	echo "Bad root partition at '$ROOT_PARTITION' (must be btrfs, found $(GetFSType "$ROOT_PARTITION"))"
elif [ -n "$(findmnt "$ROOT_PARTITION")" ]; then
	Bail "Root partition '$ROOT_PARTITION' already mounted; please unmount"
elif [ -n "$(findmnt "$EFI_PARTITION")" ]; then
	Bail "EFI partition '$EFI_PARTITION' already mounted; please unmount"
fi
echo

################ Get old UUIDs ################

echo "Old drive '$DRIVE' UUID is '$(GetDriveUUID "$DRIVE")'"

ROOT_OLD_UUID="$(GetFSUUID "$ROOT_PARTITION")"
ROOT_OLD_UUID="cd4b8972-f6bc-4749-9742-a6f30e0e166c"
echo "Old root partition '$ROOT_PARTITION' file system UUID is '$ROOT_OLD_UUID'"
if [ ! "${#ROOT_OLD_UUID}" -eq 36 ]; then
	Bail "unexpected UUID length (${#ROOT_OLD_UUID}) of partition '$ROOT_PARTITION'"
fi

################ Update drive UUID ################

echo "Randomizing drive UUID"
echo "x
f
w
y" | sudo gdisk "$DRIVE" >/dev/null
if [ "$?" -ne 0 ]; then
	echo "Blew it"
	Bail
fi
echo "New drive '$DRIVE' UUID is '$(GetDriveUUID "$DRIVE")'"
echo

################ Update root partition file system UUID ################

echo "Changing root partition UUID..."
if sudo btrfstune -u -f "$ROOT_PARTITION"; then
	echo "... done."
else
	echo "... FAILED!!!!! BAILING!!!!!"
	Bail
fi

ROOT_NEW_UUID="$(GetFSUUID "$ROOT_PARTITION")"

echo "Root partition '$ROOT_PARTITION' file system UUID is now '$ROOT_NEW_UUID'"

################ Mount all necessary partitions ################

TMPDIR="$(mktemp -d)"
echo "Mounting partitions on $TMPDIR"

if ! sudo mount "$ROOT_PARTITION" "$TMPDIR"; then
	Bail "mount $ROOT_PARTITION $TMPDIR failed"
fi

if ! sudo mount "$EFI_PARTITION" "$TMPDIR/boot/efi"; then
	Bail "mount $EFI_PARTITION $TMPDIR/boot/efi failed"
fi

# Only needed to chroot grub2-editenv dummy
for dir in /dev /proc /sys /run; do
	if ! sudo mount --bind "$dir" "$TMPDIR$dir"; then
		Bail "mount $dir $TMPDIR$dir failed"
	fi
done

################ Fix fstab and grub.cfg files ################

echo "Fix fstab"
sudo sed --in-place "s/$ROOT_OLD_UUID/$ROOT_NEW_UUID/g" "$TMPDIR/etc/fstab"

# The grub.cfg files are very clearly marked "DO NOT EDIT". But, the following is the
# best way to fix them, given that we aren't changing anything else except for the
# root partition UUID.

echo "Fix /boot/efi/EFI/opensuse/grub.cfg"
sudo sed --in-place "s/$ROOT_OLD_UUID/$ROOT_NEW_UUID/g" "$TMPDIR/boot/efi/EFI/opensuse/grub.cfg"

echo "Fix /boot/grub2/grub.cfg"
sudo sed --in-place "s/$ROOT_OLD_UUID/$ROOT_NEW_UUID/g" "$TMPDIR/boot/grub2/grub.cfg"

# Editing /boot/grub2/grub.cfg breaks the grubenv file; rebuild
echo "Rebuild grub environment block"
if ! sudo chroot "$TMPDIR" grub2-editenv - unset dummy; then
	Bail "'sudo chroot $TMPDIR grub2-editenv - unset dummy' failed!"
fi


################ Unmount everything ################
# Can't really handle errors here, so don't try.

for dir in /dev /proc /sys /run; do
	sudo umount "$TMPDIR$dir"
done

sudo umount "$TMPDIR/boot/efi"

sudo umount "$TMPDIR"

Cleanup

echo "Done."

And, thanks for all the help.

Dan

Too late to edit the script, but I have one tweak. Change

DRIVE="$1"
EFI_PARTITION="$1$2"
ROOT_PARTITION="$1$3"

to

DRIVE="$1"
for PARTITION_SEPARATOR in '' 'p'; do
	if [ -b "$1$PARTITION_SEPARATOR$2" ]; then
		break
	fi
done
EFI_PARTITION="$1$PARTITION_SEPARATOR$2"
ROOT_PARTITION="$1$PARTITION_SEPARATOR$3"

This handles the cases where partition device files are just the partition numbers appended to the drive file (e.g. sda2), or have “p” between the drive and partition (e.g. nvme0n1p1).