Note to Self

Install Ubuntu on a Raspberry Pi headless

The download and installation instructions will tell you that you need an HDMI cable or a serial cable. With a bit of preparation, that is actually not necessary.

I am assuming that you have a Linux machine with a Micro SD card reader/writer available. In my case, this also runs Ubuntu. I am also assuming you want to connect your Raspberry Pi to your wired network.

Download Ubuntu

Get the image you want. The link to the download page is above. I have tried this with the 32 bit image of Ubuntu 20.04 LTS "Focal Fossa", trying to revive an old Raspberry PI 2B I had lying around.

Prepare the image

Uncompress the image to a work directory. Put your Micro SD card into the reader. Find out how big your SD card is ( dmesg is your friend, or use lsblk -b ). In your work directory, create a file that is the size of your card, like in this example (I already knew that the card would be /dev/sdg , your mileage may vary):

% lsblk -b /dev/sdg
NAME MAJ:MIN RM        SIZE RO TYPE MOUNTPOINT
sdg    8:96   1 15931539456  0 disk
% fallocate -l 15931539456 my_sd_card_image

Now overwrite the beginning of the file with the uncompressed image you downloaded. The special form of output redirection prevents the truncation of the file.

% cat diskimage 1<>my_sd_card_image

Next, create a loop device so you can work with the image. The -f parameter looks for the first free loop device, and the -P parameter forces the kernel to read the partition table and create the appropriate devices. You will need to do a lot of things as root now, so you can get a root shell at this point.

% sudo su
# losetup -fP --show my_sd_card_image
/dev/loop3
# ls /dev/loop3* 
/dev/loop3  /dev/loop3p1  /dev/loop3p2

First, make the data partition bigger so that it spans the whole image. Currently, most of the image is actually not allocated. GPartEd is my preferred tool for this. Start it on the loop device for the whole image, and re-size the second partition to its maximum size. You can use fdisk and resize2fs if you want; it isn't difficult either.

# mount /dev/loop3p2 /mnt

Now let's edit some files. Make sure you don't edit the files on your machine under /etc but the files on the image under /mnt/etc !

Add a user for yourself. You do this by adding a line to /mnt/etc/passwd :

martini:x:1001:1001:Martin Ibert:/home/martini:/bin/bash

And this to /mnt/etc/shadow . The number 18192 should be replaced by the days since the epoch ( expr $(date +%s) / 86400 ).

martini:*:18192:0:99999:7:::

Now edit /mnt/etc/group and add yourself to the following groups: adm sudo . Also add the following line:

martini:x:1001:

Edit /mnt/etc/sudoers so that the middle section reads as follows. The file is read-only, so you need to write it out with :x! .

# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL

# Members of the adm group may gain root privileges
%adm ALL=(ALL) NOPASSWD: ALL

Finally, give yourself a rudimentary home directory (obviously, use the SSH key you want, or use your regular authorized_keys file):

# mkdir -p /mnt/home/martini/.ssh
# cp ~martini/.ssh/id_rsa_np.pub /mnt/home/martini/.ssh/authorized_keys
# chmod 700 /mnt/home/martini/.ssh
# chmod 600 /mnt/home/martini/.ssh/authorized_keys
# chown -R 1001:1001 /mnt/home/martini

Unmount the image.

# umount /mnt

Write the image to the card

If you write the image with dd , it will take forever for a big Micro SD card. So we will be doing it differently.

First, we'll put the partition table on the card. That is in the first megabyte of the image:

# dd if=/dev/loop3 of=/dev/sdg count=2048
# ls /dev/sdg*
/dev/sdg  /dev/sdg1  /dev/sdg2

You now need partclone . Install it if you don't have it yet. We use it to quickly put just the data that means something onto the card, rather than dump a ton of blocks that have no useful data in them. (Output not shown.) The boot partition can be dumped normally as it is not very big. Make sure it is actually the size shown.

# dd if=/dev/loop3p1 of=/dev/sdg1 bs=256M
# sync
# partclone.ext4 -b -s /dev/loop3p2 -o /dev/sdg2

Don't panic if it seems to hang after saying Syncing... . If your machine has ample RAM, this is when the actual writing to the card will take place. Everything before that happens in memory if there is enough of it.

Put the card in the Raspberry PI and boot it

In goes the card, and then connect your Raspberry PI to your LAN and power it up. Watch your DHCP server to find out its address (in my case, I knew the MAC beforehand and could configure DHCP ahead of time, but if you don't, you need to find the latest device to get an address). It still takes a few seconds for the SSH server to come up after the address is assigned. All my Respberry Pis have MAC addresses beginning with b8:27:eb . The internet says that dc:a6:32 is also a possibility.

When you power up the Raspberry Pi, pay attention to the yellow LED. It should light up briefly. After that, nothing appears to be happening for a while. Be patient.

You should now be able to ssh into your Raspberry Pi and sudo from there to complete the configuration of the system.

Don't forget to destroy the loop device.

# losetup -d /dev/loop3