Customizing Raspbian Images

At Bucknell we have been deploying devices built around Raspberry Pies to take on a wide range of duties including data collection duties in the field. The Raspberry Pi turns out to be a hardy device we can close in weather proof containers and it uses less energy than many PC solutions extending battery life and reducing solar panel sizes. It is however a challenge to adapt some of our contemporary Enterprise IT practices to the Pi both due to the limits of the platform and because so much Enterprise IT is geared to supporting Microsoft Windows. While we have solved a number of challenges one that I have had to solve recently was creating an image that we could have students write to SD cards en masse. This is my procedure for customizing a stock Raspbian image to enable students to rapidly deploy dozens of consistently configured Raspberry Pies.

Understanding the Raspbian Disk Image File

I think of the Raspbian operating system as a customization of Debian Linux for ARMHF (the processor family on Raspberry Pies). As such Raspbian is distributed much like other Linux distributions as a file that you write to some sort of storage media, an SD Card in the case of Raspberry Pies. Because it is intended for an SD card it is structured not like a CD image with a single ISO9660 data volume but more like an SSD or HD with Linux installed which is to say it has a partition table, FAT32 boot partition and an EXT4 root (/) partition. This means that we can not simply mount the disk image on a Mac or Windows PC and add/edit files. We can not even do it on a Linux PC without jumping through some hoops. However, the hurdle is not too high so that’s what we will do.

Mounting Partitions from Raspbian disk Image as Writeable

I will be working with Raspbian Lite 2018-11-13 for the rest of this post. If you use a different disk image as your starting point, you will need to adapt the examples accordingly.

If you simply download Raspbian Lite, unzip the download and double click the resulting .img file on a Linux PC two “Volumes” will be mounted on your system. Behind the scenes, the disk management system is creating a loopback device attached to the .img file which ends up with two slices (partitions) which are in turn mounted to /run/media/$YOUR_USERNAME}/boot and /run/media/$YOUR_USERNAME}/rootfs, at least on my Fedora Core 29 equipped laptop. Unfortunately those volumes are read only. If you try to remount them as read-write you will find that the automatic mounting of disk images, at least multi-volume disk images on contemporary Linuxes, forces read-only mounts. This is a reasonable safety measure as a disk image may have been squashed so there is no free space making it impossible to add or edit files on the filesystems mounted from the disk image even if they were writeable. We could of course create folders on our computer, copy all the files from the disk image to the folders and master disk images from the folders, but the Raspbian folks have helpfully left a bit of free disk space for us so we can edit in place if we can just mount the slices read-write. It turns out that a single volume from a disk image can be mounted as read write at a time, we need only to determine the exact boundaries of that volume in the image file first. fdisk, the standard Linux tool for working with partition tables, can be used to get the information we need:

fdisk -l ${PATH_TO_DISK_IMAGE}

For example:

fdisk -l 2018-11-13-raspbian-stretch-lite.img

Disk 2018-11-13-raspbian-stretch-lite.img: 1.8 GiB, 1866465280 bytes, 3645440 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: 0x7ee80803

Device Boot Start End Sectors Size Id Type
2018-11-13-raspbian-stretch-lite.img1 8192 98045 89854 43.9M c W95 FAT32 (LBA)
2018-11-13-raspbian-stretch-lite.img2 98304 3645439 3547136 1.7G 83 Linux

We need to do just a bit of math now as fdisk reports the start, end and sectors of partitions in multiples of ‘blocks’ but the mount tool requires offset (start) and sizelimit (sectors) in terms of bytes. Fortunately fdisk has also reported the sector size: 512 bytes so we simply multiply the start by 512 to get offset and sectors by 512 to get sizelimit in bytes e.g. start of boot partition at 8192 * 512 = 4194304 bytes.

We can now mount individual partitions of the disk image as writeable:

sudo mount -o loop,rw,sync,offset=${PARTITION_START_BYTES},sizelimit=${PARTITION_LENGTH_BYTES} ${PATH_TO_DISK_IMAGE} ${PATH_TO_MOUNT_POINT}

The mount point must exist and be an empty directory which we can insure by following the convention used by the double click ‘mount disk image’ action of your distro. For example:

sudo mkdir -p /run/media/root/boot
sudo mount -o loop,rw,sync,offset=4194304,sizelimit=46005248 /home/tom/Downloads/2018-11-13-raspbian-stretch-lite.img /run/media/root/boot

Note you can omit the “sizelimit” attribute if you are working with the last partition of the image:

sudo mkdir -p /run/media/root/rootfs
sudo mount -o loop,rw,sync,offset=50331648 /home/tom/Downloads/2018-11-13-raspbian-stretch-lite.img /run/media/root/rootfs

Also note you can only mount one partition from a disk image at a time using this method therefore if you have mounted the boot partition and want to work with a file on the rootfs partition you will need to unmount the boot partition before mounting the rootfs partition.

sudo umount /run/media/root/boot
sudo mount -o loop,rw,sync,offset=50331648 /home/tom/Downloads/2018-11-13-raspbian-stretch-lite.img /run/media/root/rootfs

Finally note that you should unmount any mounted partition before doing anything to the disk image file itself such as renaming the file or copying the file to a network share so your team of students can copy it to SD cards.

Common Customizations

Edit /boot/config.txt

At Bucknell we have attached sensors to Raspberry Pies using spi and i2c. This requires us to edit the /boot/config.txt on a running Raspbian install which we can take care of in the imaging process. Editing the config.txt file is the only change we ever make on the boot partition.

Setup Wireless Networking

Some of our Raspberry Pi projects use Pies connected to our wireless network. Therefore we add configuration to connect to our wireless network to our images. Like many if not most Universities and large enterprises, Bucknell uses WPA2-Enterprise logins to help secure our wireless network and we create a login for each of our Raspberry Pi projects that can only be used to connect to the wireless network. Following the security in depth paradigm  we still hash the password (on any Mac or Linux PC with openssl) to limit the potential for damage should someone disassemble a device containing a Raspberry Pi connecting to our wireless network:

echo -n  '${YOUR_ENTERPRISE_WIFI_PASSWORD}' | iconv -t utf16le | openssl md4

For a project using a Raspberry Pi 0 W we can then edit the existing /etc/wpa_supplicant/wpa_supplicant.conf and add /etc/network/interfaces.d/wlan0 files to the rootfs of our image. Specifically,  we add:

country=US

network={
ssid="${OUR_WIRELESS_NETWORK_NAME}"
proto=RSN
key_mgmt=WPA-EAP
pairwise=CCMP TKIP
group=CCMP TKIP
identity=”${OUR_PROJECT_ENTERPRISE_WIFI_USERNAME}”
password=hash:${OUR_HASHED_PROJECT_ENTERPRISE_WIFI_PASSWORD}
phase1="peaplabel=0"
phase2="auth=MSCHAPV2"
}

to /etc/wpa_supplicant/wpa_supplicant.conf and the contents of /etc/network/interfaces.d/wlan0 looks like:

auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp
    pre-up wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
    post-down killall -q wpa_supplicant

Set the Keymap

The Raspberry Pi project got started in the United Kingdom thus it should be no surprise that Raspbian sets the default keymap to “gb”. This keymap is frustratingly similar to the ‘us’ keymap corresponding to most keyboards at Bucknell but it is not exactly the same. Now, we do not normally connect a keyboard to the Raspberry Pies we set up using a custom image but sometimes we have to for troubleshooting purposes. Therefore, we set the keymap in /etc/default/keyboard to “us” as it can save precious time when troubleshooting a device.

Set the Hostname

The SaltStack management system we use with our Linux based IoT devices requires that devices have a unique hostname however, the stock Raspbian image configures the hostname of all Raspberry Pies as “raspberrypi”.  We can not simply edit the /etc/hostname file as it will be the same on each Raspberry Pi booted with an SD card prepared from our image. However, we can edit the shell script /etc/rc.local on the image to set a hostname for the Pi derived from the MAC address. Now we want to make the boot process as fast as possible for Raspberry Pies deployed in the field therefore in our script, we check if the hostname is the default and if and only if it is we change the /etc/hostname and /etc/hosts files then reboot the Raspberry Pi to apply the change. We thus add:

# On second boot only set the hostname to be unique on our network
_hostname=$(hostname) || true
if [ "raspberrypi" = $_hostname ]; then
  _hostname=$(echo -n '${PROJECT_NAME}' | cat - /sys/class/net/wlan0/address | sed 's/://g' -)
  printf "  Setting hostname to: %s\n" "$_hostname"
  echo $_hostname > /etc/hostname
  sed -i 's/raspberrypi/$_hostname/g' /etc/hosts
  reboot
fi

to /etc/rc.local