Since setting up my Raspberry Pi to boot from a USB SSD, I need to repurpose the SSD I used to set this up. I bought a smaller 240GB SSD to replace it and wanted to copy the existing filesystem over to it rather than reinstall.

Partitioning

First thing I did was to partition the new disk. The existing (512GB) disk has 2 partitions - ~300MB for the boot loader (mounted at /boot/firmware) and the rest as one large root partition. I did not, when I set this up, modify the default Raspbian setup to give it a swap partition which with hindsight was an error for 2 reasons:

  1. My Raspberry Pi 4 only has 2GB of memory, which most of the time is plenty (as I write this, 275MB is currently used) but a burst in need cloud push it to run out
  2. My icinga2 monitoring system expects swap and flags systems with no swap as in an error state. I could silence this but since I am adding a huge SSD with far more capacity than is required, it would be easier to just add it now.

I therefore replicated the existing boot partition, added a 10G swap partition and then created the same single large partition on the new disk. I am uncomfortable with the single large partition, splitting data between partitions has been a long established practice on Linux systems to help mitigate the impact of a filesystem being filled by runaway processes, or otherwise, accidentally on the stability (and ability to login!) of the whole system.

Launching sfdisk, I then issued the commands to create a 299M FAT32 (type c) partition (matching precisely what is on the existing disk), an 10G Linux Swap (type 82) partition and the rest of the space for Linux Data (type 83):

>>> ,299M,c
Created a new DOS disklabel with disk identifier 0xeb82491d.
Created a new partition 1 of type 'W95 FAT32 (LBA)' and of size 299 MiB.
   /dev/sdb1 :         2048       614399 (299M) W95 FAT32 (LBA)
Created a new partition 2 of type 'Linux swap / Solaris' and of size 10 GiB.
   /dev/sdb2 :       614400     21585919 (10G) Linux swap / Solaris
/dev/sdb3: ,,83
Created a new partition 3 of type 'Linux' and of size 213.3 GiB.
   /dev/sdb3 :     21585920    468862127 (213.3G) Linux
/dev/sdb4: write

New situation:
Disklabel type: dos
Disk identifier: 0xa2e77eb5

Device     Boot    Start       End   Sectors   Size Id Type
/dev/sdb1           2048    614399    612352   299M  c W95 FAT32 (LBA)
/dev/sdb2         614400  21585919  20971520    10G 82 Linux swap / Solaris
/dev/sdb3       21585920 468862127 447276208 213.3G 83 Linux

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

Copying the boot loader

As the source and destinations partitions are exactly the same size, I simply unmounted the old partition and used dd to copy one to the other:

# umount /boot/firmware
# dd if=/dev/sda1 of=/dev/sdb1 bs=1M
299+0 records in
299+0 records out
313524224 bytes (314 MB, 299 MiB) copied, 9.8722 s, 31.8 MB/s
# mount /dev/sdb1 /boot/firmware
# vim /boot/firmware/cmdline.txt  # Modify root=/dev/sda2 to root=/dev/sda3
# umount /boot/firmware

Copying the rest of the system

For the rest of the system, I choose to stop the monitoring daemons and then simply rsync the entire filesystem over. Despite my unease with it, this is actually made easier by having the whole system in a single large root partition as rather than excluding /dev, /sys, /proc, /run etc. by hand I just told rsync not to cross filesystem-boundaries.

Firstly, stopping the daemons:

# systemctl stop icinga2
# systemctl stop munin-node
# systemctl stop munin

Create and mount the new filesystem (N.B. the RASPIROOT label is important as that is what is used in FSTAB to mount the root partition):

# mkfs.ext4 -L RASPIROOT /dev/sdb3
mke2fs 1.44.5 (15-Dec-2018)
Creating filesystem with 55909526 4k blocks and 13983744 inodes
Filesystem UUID: 8f1623cc-032c-4e88-b025-c8092cfa32a2
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
	4096000, 7962624, 11239424, 20480000, 23887872

Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

# mkdir /mnt/newroot
# mount /dev/sdb3 /mnt/newroot

Do the copy:

# rsync -aHAXxvP / /mnt/newroot/

The options in use are:

  • -a - archive (equivalent to -rlptgoD)
  • -H - preserve hard-links
  • -A - copy acls (I’m not sure there are any on this system but better to be safe…)
  • -X - copy extended attributes
  • -x - do not cross filesystem boundaries (just copy the root filesystem)
  • -v - verbose
  • -P - equivalent to --partial --progress, resume partition copies (if the copy gets interrupted) and show progress during transfer

During my first attempt to copy, the Pi hung and had to be rebooted (pull and plug the power) but the 2nd time it copied everything without any drama.

I repeated the copy to ensure nothing had been missed - only log files (which I would have expected to change) were copied in this “catch up” copy.

Booting into the new system

Next I powered off the Pi, swapped the disks over and booted off the new disk.

Setting up swap

Finally, I needed to setup that new swap partition.

# mkswap -L RASPISWAP /dev/sda2
Setting up swapspace version 1, size = 10 GiB (10737414144 bytes)
LABEL=RASPISWAP, UUID=806e1fc8-9eb5-445c-b0b9-a5382238290f
# echo "LABEL=RASPISWAP none swap sw 0 0" >> /etc/fstab
# swapon -a
# cat /proc/swaps

And for good measure, rebooted to ensure the swap partition continues to be picked up afterwards (it did).