Cloning/Migrating SSD/HDD on MacOS

Here are full steps that worked for me, verified, explained & enhanced with GPT-5 Thinking.

Umbrel: clone 1 TB → 2 TB on macOS

:warning: The destination disk will be overwritten. Triple-check identifiers by size.
:warning: Don’t boot the Pi with both old and new SSDs at the same time (duplicate UUIDs).

0) Clean shutdown of Umbrel (Raspberry Pi)

ssh umbrel@umbrel.local
sudo shutdown -h now

Wait until the Pi powers off. Unplug the old 1 TB SSD from the Pi.


1) Connect both SSDs to your Mac

  • Plug in old 1 TB (source) and new 2 TB (destination).

Identify just the externals:

diskutil list external

Example mapping (adjust to your output):

❯ diskutil list external
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *2.0 TB     disk4
   1:       Microsoft Basic Data T7                      2.0 TB     disk4s1

/dev/disk5 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk5
   1:           Linux Filesystem                         1.0 TB     disk5s1
  • /dev/disk5old 1 TBSOURCE → use /dev/rdisk5

  • /dev/disk4new 2 TBDEST → use /dev/rdisk4

Tip: Re-run diskutil list external if you unplug/replug; numbers can change.


2) Unmount the whole disks (do NOT eject)

diskutil unmountDisk /dev/disk4
diskutil unmountDisk /dev/disk5

Unmounting frees the devices for raw cloning; “eject” would remove them from the system.


3) Keep the Mac awake

Run this in new Terminal tab/window.

caffeinate -dimsu

Leave this running while cloning. You’ll stop it later with Ctrl-C (Step 7).

  • -d prevent display sleep

  • -i prevent idle sleep

  • -m prevent disk sleep

  • -s prevent system sleep

  • -u simulate user activity

Alternatively, use an app like Amphetamine.


4) Check which dd you have installed

GNU with live progress vs. stock BSD

which gdd
which dd
dd --version   # GNU dd prints a version; BSD dd will complain about --version

  • If which gdd prints something like /opt/homebrew/bin/gdd, you have GNU dd → you’ll get live progress.

  • If not installed, you can add it: brew install coreutils (command is gdd), or skip to step 5-alt)

Example output

/opt/homebrew/bin/gdd
/bin/dd
dd: unknown operand --version

5) Clone using gdd (GNU dd) — with live progress

sudo gdd if=/dev/rdisk5 of=/dev/rdisk4 bs=4M status=progress conv=fsync

Flags

  • if=/dev/rdisk5input file: the raw source device (old 1 TB).

  • of=/dev/rdisk4output file: the raw destination device (new 2 TB).

  • rdisk* vs disk*rdisk is the raw device; it bypasses extra buffering for faster I/O on macOS.

  • bs=4Mblock size 4 MiB: reduces syscall overhead and usually maxes out USB-SSD throughput.

  • status=progresslive progress (bytes copied, speed, elapsed). GNU gdd feature only.

  • conv=fsyncflushes all write buffers at the end, ensuring data is physically on disk when the command exits.

Example output

Mine took 1hr 34min

❯ sudo gdd if=/dev/rdisk5 of=/dev/rdisk4 bs=4M status=progress conv=fsync

245903654912 bytes (246 GB, 229 GiB) copied, 890 s, 276 MB/s

Optional resilience
slower; only if you suspect read errors on the old SSD:

sudo gdd if=/dev/rdisk5 of=/dev/rdisk4 bs=1M status=progress conv=noerror,sync,fsync

  • noerror → don’t stop on read errors; continue.

  • sync → zero-pad unreadable blocks so offsets stay aligned.

  • This protects against a flaky source but costs throughput.

5-alt) Clone using stock macOS dd (BSD) — no live progress

sudo dd if=/dev/rdisk5 of=/dev/rdisk4 bs=4m conv=fsync

  • Progress: press Ctrl-T in that Terminal to print a status snapshot (BSD feature).

  • Note: bs=4m is lowercase m with BSD dd.


6) When the clone finishes

Eject both disks:

diskutil eject /dev/disk4
diskutil eject /dev/disk5

STOP CAFFEINATE: go to the window where it’s running and press Ctrl-C (or just close that window). This returns your Mac to normal sleep behavior.


7) Move the new 2 TB SSD to the Pi and boot

  • Plug only the new 2 TB SSD into the Pi.

  • Power on the Pi. Don’t connect the old SSD at the same time.


8) Expand the partition to use the full 2 TB (on the Pi)

Install the tool that provides growpart:

sudo apt update
sudo apt install -y cloud-guest-utils

Why install this?
The clone copied a 1 TB partition layout onto a 2 TB disk. You now need to:

  1. Grow the partition boundary to fill the disk (growpart).

  2. Resize the filesystem (ext4) to use that bigger partition (resize2fs).

Identify the disk/partition:

lsblk -f
# Typically you'll see the SSD as /dev/sda with the main ext4 partition /dev/sda1

Grow the partition, then the filesystem:

sudo growpart /dev/sda 1
sudo resize2fs /dev/sda1

  • growpart /dev/sda 1 – “stretch” partition 1 on /dev/sda to fill the remaining unallocated space.

  • resize2fs /dev/sda1 – expand the ext4 filesystem inside that partition to the new size.

Verify and reboot:

df -h
sudo reboot

  • df -h should now show ~2 TB available for your Umbrel data.

Sanity checklist

  • Pi boots normally with just the new SSD.

  • df -h shows the main partition near 2 TB size.

  • Umbrel services behave the same as before.


Quick safety notes

  • Re-check diskutil list external every time you reconnect drives; identifiers can change.

  • unmountDisk (Step 2) is required so the raw devices aren’t in use; don’t skip it.

  • gdd is optional but recommended for a real progress bar; stock dd works fine with Ctrl-T.