Cloning and Migrating the Primary Hard Drive on an OpenBSD Install
My OpenBSD router has an old spinning disk in it and I’d like to move that disk to a new SSD. I want to keep downtime to a minimum since my home internet doesn’t work when this machine is powered down, so I’m going to try to do the copy live using OpenBSD’s dump and restore.
I ended up spending way more time on this doing things partition by partition than if I had just taken the machine down and dd’ed everything. In the future I probably wouldn’t go this route, but if you’re looking for a way to minimize downtime while migrating to a new drive these steps might work for you.
I took a couple practice runs at this with a KVM install of OpenBSD 7.6 because it made it easier (and less stressful) to test out ideas. If you’re going to follow these steps I would recommend doing the same, since messing up any of these steps could lead to data loss. (Related: Setting up an OpenBSD KVM)
NOTE WELL: Mixing up the drive name in any of these commands could lead to permanent data loss. Use these commands at your own risk- make sure you understand them before running them, and always double check the drive name that you’re using!
Find the New Drive
I bought a new SSD the same size as my old disk and plugged it in to a USB SATA adapter. I had to find the name of the newly connected drive- dmesg
seems to be the best way, and it updates with info about devices connected after boot.
> dmesg | grep -i sd
OpenBSD 7.6 (GENERIC.MP) #338: Mon Sep 30 08:55:35 MDT 2024
...
sd2 at scsibus5 targ 1 lun 0: <CT500BX5, 00SSD1, 0114> serial.152d356912345678960F
sd2: 476940MB, 512 bytes/sector, 976773168 sectors
That looks like my drive, but to make sure I can get more info with disklabel
.
> doas disklabel -h sd2
# /dev/rsd2c:
type: SCSI
disk: SCSI disk
label: 00SSD1
duid: 0000000000000000
flags:
bytes/sector: 512
sectors/track: 63
tracks/cylinder: 255
sectors/cylinder: 16065
cylinders: 60801
total sectors: 976773168 # total bytes: 465.8G
boundstart: 0
boundend: 976773168
16 partitions:
# size offset fstype [fsize bsize cpg]
c: 465.8G 0 unused
The OpenBSD Disk Setup FAQ was helpful for these next couple of steps.
Prepare the New Drive
The first thing I need to do is prepare the new drive so that it can boot. My machine has UEFI, so I need to set a MBR on the drive.
> doas fdisk -iy sd2
Writing MBR at offset 0.
> doas fdisk sd2
Disk: sd2 geometry: 60801/255/63 [976773168 Sectors]
Offset: 0 Signature: 0xAA55
Starting Ending LBA Info:
#: id C H S - C H S [ start: size ]
-------------------------------------------------------------------------------
0: 00 0 0 0 - 0 0 0 [ 0: 0 ] Unused
1: 00 0 0 0 - 0 0 0 [ 0: 0 ] Unused
2: 00 0 0 0 - 0 0 0 [ 0: 0 ] Unused
*3: A6 0 1 2 - 60801 80 63 [ 64: 976773104 ] OpenBSD
That will handle getting things bootstraped from EFI, but I’ll also need to set up a partition boot record (PBR) so that OpenBSD can boot from there. I’ll do that with installboot once I’ve created the partitions and copied the data over to the root partition.
My primary drive has 16 partitions because I just went with the recommended OpenBSD partition scheme.
> doas disklabel sd0
# /dev/rsd0c:
type: SCSI
disk: SCSI disk
label: TOSHIBA DT01ACA0
duid: 79faa6d3c580e2c2
flags:
bytes/sector: 512
sectors/track: 63
tracks/cylinder: 255
sectors/cylinder: 16065
cylinders: 60801
total sectors: 976773168
boundstart: 64
boundend: 976768065
16 partitions:
# size offset fstype [fsize bsize cpg]
a: 2097152 64 4.2BSD 2048 16384 12960 # /
b: 8500056 2097216 swap # none
c: 976773168 0 unused
d: 8388576 10597280 4.2BSD 2048 16384 12960 # /tmp
e: 24340128 18985856 4.2BSD 2048 16384 12960 # /var
f: 12582912 43325984 4.2BSD 2048 16384 12960 # /usr
g: 2097152 55908896 4.2BSD 2048 16384 12960 # /usr/X11R6
h: 41943040 58006048 4.2BSD 2048 16384 12960 # /usr/local
i: 4194304 99949088 4.2BSD 2048 16384 12960 # /usr/src
j: 12582912 104143392 4.2BSD 2048 16384 12960 # /usr/obj
k: 629145536 116726336 4.2BSD 4096 32768 26062 # /home
Create and Format the New Partitions
Re-creating all of these by hand on the new drive would be a pain. Luckily, disklabel supports dumping and restoring the partition information from a file:
> doas disklabel sd0 > /tmp/layout
> doas disklabel -R sd2 /tmp/layout
My new disk now magically has a matching partition layout!
> doas disklabel sd2
...
16 partitions:
# size offset fstype [fsize bsize cpg]
a: 2097152 64 4.2BSD 2048 16384 12960
b: 8500056 2097216 swap
c: 976773168 0 unused
d: 8388576 10597280 4.2BSD 2048 16384 12960
e: 24340128 18985856 4.2BSD 2048 16384 12960
f: 12582912 43325984 4.2BSD 2048 16384 12960
g: 2097152 55908896 4.2BSD 2048 16384 12960
h: 41943040 58006048 4.2BSD 2048 16384 12960
i: 4194304 99949088 4.2BSD 2048 16384 12960
j: 12582912 104143392 4.2BSD 2048 16384 12960
k: 629145536 116726336 4.2BSD 4096 32768 26062
Note that disklabel won’t show mount points in the rightmost column like it does for sd0
because it uses /etc/fstab
to look those up. Our current /etc/fstab
is using the old drive’s duid, so disklabel
can’t match up any of the mount pooints. When you reboot on the new drive you’ll see the labels again.
Now I need to init all of these partitions one by one. It’s not necessary to init the swap partition, so sd2b can be skipped. You also want to be sure to skip sd2c since the “c” partition on OpenBSD represents the entire disk.
> doas newfs sd2a
/dev/rsd2a: 1024.0MB in 2097152 sectors of 512 bytes
6 cylinder groups of 202.50MB, 12960 blocks, 25920 inodes each
super-block backups (for fsck -b #) at:
160, 414880, 829600, 1244320, 1659040, 2073760,
> doas newfs sd2d
/dev/rsd2d: 4096.0MB in 8388576 sectors of 512 bytes
21 cylinder groups of 202.50MB, 12960 blocks, 25920 inodes each
super-block backups (for fsck -b #) at:
160, 414880, 829600, 1244320, 1659040, 2073760, 2488480, 2903200, 3317920, 3732640, 4147360, 4562080, 4976800, 5391520, 5806240, 6220960,
6635680, 7050400, 7465120, 7879840, 8294560,
> doas newfs sd2e
/dev/rsd2e: 11884.8MB in 24340128 sectors of 512 bytes
59 cylinder groups of 202.50MB, 12960 blocks, 25920 inodes each
...
Copy Data to the New Drive
The new drive should be ready for data. Let’s start by mounting the new root (a) partition:
> doas mount /dev/sd2a /mnt/newfs
> df -h
Filesystem Size Used Avail Capacity Mounted on
/dev/sd0a 986M 241M 695M 26% /
...
/dev/sd2a 986M 2.0K 937M 1% /mnt/newfs
Dump the Root Partition
Now we can copy data over onto the new partition. It’s easier to do this as root than trying to sprinkle doas in all the right places:
> doas su
# whoami
root
# cd / && dump 0f - . | (cd /mnt/newfs && restore -rf - )
DUMP: Dumping sub files/directories from /
DUMP: Dumping file/directory .
DUMP: Date of this level 0 dump: Thu Dec 5 23:18:12 2024
DUMP: Date of last level 0 dump: the epoch
DUMP: Dumping /dev/rsd0a (/) to standard output
DUMP: mapping (Pass I) [regular files]
DUMP: mapping (Pass II) [directories]
DUMP: estimated 258422 tape blocks.
DUMP: Volume 1 started at: Thu Dec 5 23:18:13 2024
DUMP: dumping (Pass III) [directories]
DUMP: dumping (Pass IV) [regular files]
./home: (inode 51840) not found on tape
./tmp: (inode 77760) not found on tape
./usr: (inode 25920) not found on tape
./var: (inode 103680) not found on tape
./backup: (inode 104090) not found on tape
./mnt/newfs: (inode 104431) not found on tape
DUMP: 250851 tape blocks
DUMP: Date of this level 0 dump: Thu Dec 5 23:18:12 2024
DUMP: Volume 1 completed at: Thu Dec 5 23:18:25 2024
DUMP: Volume 1 took 0:00:12
DUMP: Volume 1 transfer rate: 20904 KB/s
DUMP: Date this dump completed: Thu Dec 5 23:18:25 2024
DUMP: Average transfer rate: 20904 KB/s
DUMP: DUMP IS DONE
And to verify that it worked:
# df -h
Filesystem Size Used Avail Capacity Mounted on
/dev/sd0a 986M 241M 695M 26% /
...
/dev/sd2a 986M 242M 694M 26% /mnt/newfs
We need to re-create all of the mount points, since dump
/restore
won’t make them for us.
> cd /mnt/newfs
> doas mkdir home
> doas mkdir tmp
> doas mkdir usr/X11R6
> doas mkdir usr/local
> doas mkdir usr/obj
> doas mkdir usr/src
> doas mkdir var
Fix /etc/fstab
The disks are identified with their duid in /etc/fstab
. We need to update those to point at the new drive in the fstab on the new root partition:
> cat /mnt/newfs/etc/fstab
79faa6d3c580e2c2.b none swap sw
79faa6d3c580e2c2.a / ffs rw 1 1
79faa6d3c580e2c2.k /home ffs rw,nodev,nosuid 1 2
79faa6d3c580e2c2.d /tmp ffs rw,nodev,nosuid 1 2
79faa6d3c580e2c2.f /usr ffs rw,nodev 1 2
79faa6d3c580e2c2.g /usr/X11R6 ffs rw,nodev 1 2
79faa6d3c580e2c2.h /usr/local ffs rw,wxallowed,nodev 1 2
79faa6d3c580e2c2.j /usr/obj ffs rw,nodev,nosuid 1 2
79faa6d3c580e2c2.i /usr/src ffs rw,nodev,nosuid 1 2
79faa6d3c580e2c2.e /var ffs rw,nodev,nosuid 1 2
Find the duid of our new drive:
> doas disklabel sd2
...
duid: 468597c66d719060
...
Update the fstab on the new drive:
> doas sed -i 's/79faa6d3c580e2c2/468597c66d719060/g' /mnt/newfs/etc/fstab
Dump the Rest of the Partitions
Now I have to repeat that dump command for the rest of the partitions. There’s no need to copy /tmp
so I skipped sd2d
.
# mount /dev/sd2e /mnt/newfs/var
# cd /var && dump 0f - . | (cd /mnt/newfs/var && restore -rf - )
DUMP: Dumping sub files/directories from /var
DUMP: Dumping file/directory .
...
# mount /dev/sd2f /mnt/newfs/usr
# cd /var && dump 0f - . | (cd /mnt/newfs/usr && restore -rf - )
Don’t forget to create the mount points under /usr
as well
> doas mkdir /mnt/newfs/usr/X11R6
> doas mkdir /mnt/newfs/usr/local
> doas mkdir /mnt/newfs/usr/obj
> doas mkdir /mnt/newfs/usr/src
Check Your Data
Your new filesystem should be mounted under /mnt/newfs/
. Take a moment to look through there and make sure it has everything you expect to see.
Make the drive bootable with installboot
First we need to prepare the filesystem for the bootloader on our root drive:
> doas installboot -pv sd2
>
I was stuck for a long time on this error:
> doas installboot -nv -r /mnt/newfs sd2
Using /mnt/newfs as root
installboot: realpath: No such file or directory
I finally had to pull the OpenBSD source to find out what installboot is trying top do here. Since I didn’t give it a value for stage1
it used the default, which is /usr/mdec/
. Since I just mounted the root partition and not /usr
that path couldn’t be found and installboot
errored out. The solution is to mount the new /usr
on top of my new root partition.
> doas mkdir /mnt/newfs/usr
> doas mount /dev/sd2f /mnt/newfs/usr
Now the installboot
command works:
> doas installboot -v -r /mnt/newfs/ sd2
Using /mnt/newfs as root
would install bootstrap on /dev/rsd2c
using first-stage /mnt/newfs/usr/mdec/biosboot, second-stage /mnt/newfs/usr/mdec/boot
would copy /mnt/newfs/usr/mdec/boot to /mnt/newfs/boot
looking for superblock at 65536
found valid ffs2 superblock
/mnt/newfs/boot is 6 blocks x 16384 bytes
fs block shift 2; part offset 64; inode block 72, offset 13936
expecting 64-bit fs blocks (incr 4)
master boot record (MBR) at sector 0
partition 3: type 0xA6 offset 64 size 976773104
/mnt/newfs/usr/mdec/biosboot will be written at sector 64
Unmount everything and get ready for a reboot
> doas umount /mnt/newfs/usr/X11R6
> doas umount /mnt/newfs/usr/local
> doas umount /mnt/newfs/usr/src
> doas umount /mnt/newfs/usr/obj
> doas umount /mnt/newfs/usr
> doas umount /mnt/newfs/var
> doas umount /mnt/newfs/home
> doas umount /mnt/newfs
Power down, swap the drive cables, cross your fingers, and reboot. Good luck!