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!