Technical notes: convert a partition image to a bootable disk image

I decided I will blog short technical guides when I do something undocumented. These are probably of zero interest to the blog followers and are just meant for Google. If they annoy, tell me and I'll get a wiki or something.

I am moving a machine off Linode (old style Xen) to my Proxmox hypervisor. I love Linode and will miss their control panel, but this dedicated server costs me just twice the Linode.

Getting the image out was easy: first I booted the Linode in rescue mode with Finnix, then from my hypervisor

# ssh [email protected] dd if=/dev/xvda bs=1M | pv -s 51002736640 > linode.img

However this won't boot. Turns out this is just a ext4 partition, since Linode/Xen does not need or support loading a MBR (at least not without pv-grub).

# file linode.img
linode.img: Linux rev 1.0 ext4 filesystem data (extents) (large files)  

So we need to add a MBR, a partition table and a boot loader to make it into a proper disk image. Let's start by making space for it.

As the always useful ArchWiki tells us in the GRUB page, we need some space between the MBR and the first partition. I'll cheat and just look how much space there is on another Ubuntu I have.

Disk /dev/sda: 42.9 GB, 42949672960 bytes  
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors  
Units = sectors of 1 * 512 = 512 bytes  
Sector size (logical/physical): 512 bytes / 512 bytes  
I/O size (minimum/optimal): 512 bytes / 512 bytes  
Disk identifier: 0x0002442c

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    79693823    39845888   83  Linux

2048 units * 512 bytes = 1MiB

# dd if=/dev/zero of=new.img count=1 bs=1MiB
1+0 records in  
1+0 records out  
1048576 bytes (1.0 MB) copied, 0.00177852 s, 590 MB/s  
# pv linode.img >> new.img
47.5GiB 0:11:55 [  68MiB/s] [=====================================================>] 100%  

Now let's create a partition table, with a bootable partition at the right place and of the right type.

# fdisk new.img

Welcome to fdisk (util-linux 2.25.2).  
Changes will remain in memory only, until you decide to write them.  
Be careful before using the write command.

Device does not contain a recognized partition table.  
Created a new DOS disklabel with disk identifier 0xd2df1593.

Command (m for help): n  
Partition type  
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p  
Partition number (1-4, default 1): 1  
First sector (2048-99616767, default 2048): 2048  
Last sector, +sectors or +size{K,M,G,T,P} (2048-99616767, default 99616767):

Created a new partition 1 of type 'Linux' and of size 47.5 GiB.

Command (m for help): a  
Selected partition 1  
The bootable flag on partition 1 is enabled now.

Command (m for help): p  
Disk new.img: 47.5 GiB, 51003785216 bytes, 99616768 sectors  
Units: sectors of 1 * 512 = 512 bytes  
Sector size (logical/physical): 512 bytes / 512 bytes  
I/O size (minimum/optimal): 512 bytes / 512 bytes  
Disklabel type: dos  
Disk identifier: 0xd2df1593

Device     Boot Start      End  Sectors  Size Id Type  
new.img1   *     2048 99616767 99614720 47.5G 83 Linux

Command (m for help): w  
The partition table has been altered.  
Syncing disks.

# file new.img
new.img: DOS/MBR boot sector; partition 1 : ID=0x83, active, start-CHS (0x0,32,33), end-CHS (0x38,218,34), startsector 2048, 99614720 sectors  

Nice. Now we need a bootloader. Again to the ArchWiki GRUB page (that wiki is a gold mine). We can just follow the Install to external USB stick instructions, mounting the image with kpartx instead of the stick.

# kpartx -a -v new.img
add map loop1p1 (252:0): 0 99614720 linear /dev/loop1 2048  
# mkdir linode
# mount /dev/mapper/loop1p1 linode
# grub-install --target=i386-pc --recheck --debug --boot-directory=linode/boot/ /dev/loop1
Installing for i386-pc platform.  
Installation finished. No error reported.  

This installed GRUB2, easy. Now we have to configure it. This is harder because grub-mkconfig relies on config files present on the running system, while we want to point it at our mounted system. Here the Gentoo wiki page comes handy, and I was about to just create grub.cfg manually when I realized I don't have any kernel installed at all. I'll need to chroot in and install it.

# mount -t proc proc linode/proc/
# mount -t sysfs sys linode/sys/
# mount -o bind /dev linode/dev/
# chroot linode/
# joe /etc/resolv.conf # set some non-linode name servers
# apt-get install --no-install-recommends linux-generic

At this point we might as well configure grub2 from inside.

# apt-get install grub2-common
# grub-mkconfig -o boot/grub/grub.cfg
Generating grub configuration file ...  
Found linux image: /boot/vmlinuz-3.13.0-61-generic  
Found initrd image: /boot/initrd.img-3.13.0-61-generic  

Last step: fix fstab. We don't want to boot successfully just to have the kernel not find its root, so we replace /dev/xvda with /dev/sda1. Finally fix network (in my case assigning the static "Failover" IP that is routed to this machine's MAC via the hypervisor bridge), clean up, and try it!

# joe /etc/fstab
# joe /etc/network/interfaces
# exit
# umount linode/proc linode/sys linode/dev linode
# kpartx -d -v new.img
del devmap : loop1p1  
loop deleted : /dev/loop1  
# rmdir linode
# mv new.img /var/lib/vz/images/104/vm-104-disk-1.raw