It is possible to create an empty image for a Raspberry Pi. Then you can copy an operating system with a boot and root partition to it and boot. To fill the empty image you can use any usable file copy method, e.g. from a tar archive, or rsync, or just cp. This way you are completely free to shrink or expand storage, or use other partition sizes than given by the original images. You can do all of it on a linux laptop, no need to use a Raspberry Pi. If you have made the image you can flash it to an SD Card and boot it in a RasPi. I have tested it by copying Raspbian Stretch Lite, Raspbian Buster Lite and Raspbian Buster with Desktop to a modified partition scheme and boot it up.
Create an empty image file
We just create a file with Null bytes of the size we want, partition it and format the partitions in the file. I will use an image with 3.9 GiB (3900 MiB) to be used with Raspbian Buster with Desktop.
This gives the empty file:
laptop ~$ sudo -Es laptop ~# dd of=raspbian.img seek=3900M bs=1 count=0
Create a default msdos partition table on the virgin image and partition it for a fat32 boot partition and an ext4 root partition:
laptop ~# parted raspbian.img mktable msdos laptop ~# parted raspbian.img mkpart primary fat32 2048s 257MiB laptop ~# parted raspbian.img mkpart primary ext4 257MiB 100%
Now we have to format the partitions with a file system but there is a problem. The formating programs need a block device file from directory /dev. Fortunately we can use loop devices to address the partitions as block devices. We will use losetup to do it:
laptop ~# losetup --find --partscan --show raspbian.img laptop ~# ls /dev/loop0* /dev/loop0 /dev/loop0p1 /dev/loop0p2 # format partitions laptop ~# mkfs.vfat -F 32 -n BOOT /dev/loop0p1 laptop ~# mkfs.ext4 -L rootfs /dev/loop0p2
Now we have a formated but empty image to which we can copy an operating system.
Copy an operating system to the empty image file
For example I will use a SD Card attached to /dev/sdb. Its content will be copied to the image, provided it all fits to the new image file. I will mount the SD Card and copy the data:
# mount the SD Card laptop ~# mkdir /mnt/sdcp2 laptop ~# mount /dev/sdb2 /mnt/sdcp2/ laptop ~# mount /dev/sdb1 /mnt/sdcp2/boot
The image partitions are still attached from the first step. Now we will mount them:
# mount the image partitions laptop ~# mkdir /mnt/imgp2 laptop ~# mount /dev/loop0p2 /mnt/imgp2/ laptop ~# mkdir /mnt/imgp2/boot laptop ~# mount /dev/loop0p1 /mnt/imgp2/boot # copy the SD Card laptop ~# cp -a /mnt/sdcp2/* /mnt/imgp2/
If you coppied Raspbian then change device name for root partition in boot/cmdline.txt and in etc/fstab to /dev/mmcblk0p1 and /dev/mmcblk0p2 because the used PARTUUID there isn't valid anymore. On other operating systems check, if they address the partitions the right way.
laptop ~# sed -i 's/root=PARTUUID=[a-z0-9]*-02/root=\/dev\/mmcblk0p2/' /mnt/imgp2/boot/cmdline.txt laptop ~# sed -i 's/^PARTUUID=[a-z0-9]*-01/\/dev\/mmcblk0p1/' /mnt/imgp2/etc/fstab laptop ~# sed -i 's/^PARTUUID=[a-z0-9]*-02/\/dev\/mmcblk0p2/' /mnt/imgp2/etc/fstab # clean up laptop ~# umount /mnt/sdcp2/boot laptop ~# umount /mnt/sdcp2 laptop ~# rmdir /mnt/sdcp2 laptop ~# umount /mnt/imgp2/boot laptop ~# umount /mnt/imgp2 laptop ~# rmdir /mnt/imgp2 laptop ~# losetup --detach-all
That's it. Flash raspbian.img to an SD Card and boot it in a RasPi.
Details
By default Raspbian uses PARTUUID to address storage and partitions on it. But this is a problem because with a new image we also have new PARTUUID. Then you have the problem that the installation does not boot. So we change the partition names to the more generic names /dev/mmcblk0p1 and /dev/mmcblk0p2.
references:
[2] Is it possible to use partition UUID for root-parameter in cmdline.txt?