How to create a Raspberry Pi golden master image?
Modify a standard Raspberry Pi image (RaspiOS) and turn it into a golden master.ΒΆ
Summary - what needs to be done to create a universal image for a RPIΒΆ
This document is a step-by-step guide to create a custom Raspberry Pi image ("golden image" or "master image") from any "official" RaspiOS distribution (light/headless, desktop/full), which can be downloaded from https://raspberrypi.org. This guide is especially useful if you want to set up many devices with the same image, but want to make as few settings manually as possible. At best, you only need to copy the https://qbee.io/ bootstrap key to the /boot partition after writing the image to the SD card. To further reduce the configuration effort, the image file can be mounted on a Raspberry Pi, i.e. to add or remove packages and make other settings. This example shows how to set up a digital signage system, so that the Raspberry Pi displays a blank desktop with a black background, no menus, no desktop icons and no decorations. The device management is handled with qbee but this guide should be useful if you use other tools as well.
Prerequisites & required hardwareΒΆ
- RaspiOS release. Here we start with the "desktop" image, but any other release ("lite/headless", "full") is also possible.
- 1 x 16 GB SD-Card
- 1 x external USB3.0 SSD
- 1 x Raspberry Pi 4, 8 GB RAM, running the raspiOS "full" release (Raspberry Pi 3 is also possible)
Image preparationΒΆ
Preparation of Raspberry Pi 4
To be able to mount the Raspberry Pi image into the system, the following packages must be installed:
sudo apt-get update sudo apt-get -y upgrade sudo apt-get install -y systemd-container zerofree
Download 2021-01-11-raspios-buster-armhf.zip from https://raspberrypi.org (any other version will do as well)
First, check sha256sum of the downloaded original compressed image
sha256sum 2021-01-11-raspios-buster-armhf.zip cb1efa778f3a4effda7bf6f622e8e8e779f5303ac77ac8c558061aece9020fe6 2021-01-11-raspios-buster-armhf.zip
Extract and mount the image. Then, do a full system upgrade
sudo losetup --show -P -f 2021-01-11-raspios-buster-armhf-full.img sudo mkdir -p /mnt/raspbian sudo mount -v /dev/loop0p2 /mnt/raspbian sudo mount -v /dev/loop0p1 /mnt/raspbian/boot sudo systemd-nspawn --directory=/mnt/raspbian apt-get update apt-get -y upgrade apt-get clean
Make the image somewhat smaller by removing some large packages not required for digital signage. Depending on the distribution (headless, desktop, full), this step will free up to 1 GB on your SD-card. If you're starting with the "desktop" release, the resulting image is approx. 3.4 GB. It'll take about 5 min writing an image of that size to an SD card.
apt-get -y purge wolfram-engine sonic-pi sonic-pi-samples sonic-pi-server scratch3 minecraft-pi thonny debian-reference-common mu-editor bluej scratch squeak-vm nodered geany greenfoot-unbundled claws-mail dillo python-games code-the-classics galculator gpicview mousepad htop qjackctl realvnc-vnc-viewer rp-bookshelf system-config-printer cups vim-common xarchiver piclone smartsim rp-prefapps qt5ct raspberrypi-ui-mods obconf rc-gui pi-package alacarte arandr lxinput lxhotkey-gtk lxtask lxrandr
Autoremove packages that are no longer required
apt-get -y autoremove
Enable ssh on boot
touch /boot/ssh
Optional: Create /boot/wpa_supplicant.conf, containing your Wi-Fi credentials (in case that you prefer wireless bootstrapping). Wi-Fi can also be set individually on a device or group level with qbee.io later on.
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=NO # <- make sure to insert the correct country code here! network={ ssid="Your network SSID" psk="Your WPA/WPA2 security key" key_mgmt=WPA-PSK }
Edit /boot/config.txt to disable bluetooth, audio and hardware serial, if desired:
dtparam=i2c_arm=off dtparam=i2s=off dtparam=spi=off dtparam=audio=off enable_uart=0 dtoverlay=vc4-fkms-v3d,audio=off dtoverlay=pi3-disable-bt
disable bluetooth service, if desired
systemctl disable hciuart.service systemctl disable bluetooth.service
Create a qbee account
Install qbee agent
wget -O /tmp/qbee-agent-installer.sh https://raw.githubusercontent.com/qbee-io/qbee-agent-installers/main/qbee-agent-installer.sh sudo bash /tmp/qbee-agent-installer.sh
The latest version for your platform (armhf, arm64, amd64) can be found here: https://www.app.qbee.io/agent-latest/armhf
Since we're working on an image, the qbee post-installation scripts may fail to enable some system-services required for bootstrapping. Therefore, we have to do this manually:
Enable qbee-agent.service
systemctl enable qbee-agent.service
In order bootstrap the device on first boot, we create and enable a systemd.service for it:
nano /lib/systemd/system/firstboot.service
Time needs to be available for later bootstrapping
It seems that some newer RPI images do not sync the time properly before bootstrapping. qbee and other ssl based services need the time to be correct to work. Therefore, we have changed the script below. Thanks to Leroy from CaptureTech for providing this fix.
[Unit] Description=FirstBoot After=time-sync.target Before=rc-local.service ConditionFileNotEmpty=/boot/firstboot.sh [Service] ExecStart=/bin/bash /boot/firstboot.sh ExecStartPost=/bin/mv /boot/firstboot.sh /boot/firstboot.sh.done Type=oneshot RemainAfterExit=no [Install] WantedBy=multi-user.target systemctl enable systemd-time-wait-sync.service
Enable firstboot.service at boot
systemctl enable firstboot.service
Create /boot/firstboot.sh
#!/bin/bash ### Configure your hostname here! Uncomment the following lines, if you don't change hostname using qbee #CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"` #NEW_HOSTNAME=`cat /boot/hostname.txt | tr -d " \t\n\r"` #echo $NEW_HOSTNAME > /etc/hostname #sed -i "s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\t$NEW_HOSTNAME/g" /etc/hosts ### bootstrap QBEE_KEY=`cat /boot/bootstrapkey.txt | tr -d " \t\n\r"` qbee-agent bootstrap -k $QBEE_KEY systemctl restart qbee-agent rm -rf /boot/bootstrapkey.txt
Copy/place your qbee bootstrap key into /boot/bootstrapkey.txt (this could be done later, i.e. after dumping the image to the SD card)
touch /boot/bootstrapkey.txt nano /boot/bootstrapkey.txt
Copy/edit the hostname in /boot/hostname.txt (this could be done later, i.e. after dumping the image to the SD card)
touch /boot/hostname.txt nano /boot/hostname.txt
Enable "wait for network on boot" (make sure that network/ internet connection is up before running /boot/firstrun.sh)
systemctl enable systemd-networkd-wait-online.service
Configuring the image for digital signageΒΆ
The following settings switch the desktop into "kiosk" mode, with a blank screen, no desktop menu, no desktop icons and decorations. You can skip these steps, if you only need automatic bootstrapping of your devices.
Create configuration directories for user "pi"
mkdir -p /home/pi/.config/lxsession/LXDE-pi mkdir -p /home/pi/.config/pcmanfm/LXDE-pi/ chown -R pi.pi /home/pi/.config
Remove LXDE-panel from desktop and disable screen blanking:
nano /home/pi/.config/lxsession/LXDE-pi/autostart
#@lxpanel --profile LXDE-pi #@pcmanfm --desktop --profile LXDE-pi #@xscreensaver -no-splash @xset s off @xset -dpms @xset s noblank @unclutter
chown pi.pi /home/pi/.config/lxsession/LXDE-pi/autostart
The same settings go into /etc/xdg/lxsession/LXDE-pi/autostart, if the file exists. User settings will override the global config file.
#@lxpanel --profile LXDE-pi #@pcmanfm --desktop --profile LXDE-pi #@xscreensaver -no-splash @xset s off @xset -dpms @xset s noblank @unclutter
In some cases, the above settings do not stop screen blanking. Therefore, we also change lightdm.conf.
nano /etc/lightdm/lightdm.conf
[Seat:*] xserver-command=X -s 0 dpms
Remove "splash" from /boot/cmdline.txt (only necessary, when starting from "full" raspiOS image) and
add "consoleblank=0" to /boot/cmdline.txt
console=tty1 root=PARTUUID=900d36fa-02 rootfstype=ext4 fsck.repair=yes consoleblank=0 rootwait
Hide the mouse pointer, if not used. Note, that the mouse pointer is not hidden on the blank desktop, but will disappear as soon as application windows are displayed (such as chromium browser).
apt-get install -y unclutter
Modify default desktop appearance (black background, no wallpaper, no desktop icons, no trashcan)
nano /home/pi/.config/pcmanfm/LXDE-pi/desktop-items-0.conf
[*] desktop_bg=#000000000000 desktop_shadow=#000000000000 desktop_fg=#e8e8e8e8e8e8 desktop_font=PibotoLt 12 wallpaper=/usr/share/rpd-wallpaper/temple.jpg wallpaper_mode=color show_documents=0 show_trash=0 show_mounts=0
chown pi.pi /home/pi/.config/pcmanfm/LXDE-pi/desktop-items-0.conf
Remove some entries of the openbox menu (optional)
rm -rf /usr/share/applications/lxde-x-www.browser.desktop rm -rf /usr/share/applications/pcmanfm-desktop-pref.desktop
Change to the /etc/xdg/autostart directory and remove the following files:
rm -rf piwiz.desktop rm -rf pprompt.desktop rm -rf xdg-user-dirs.desktop rm -rf pulseaudio.desktop
In /etc/xdg/user-dirs.conf set. This will stop auto-generation of subfolders in /home/pi, such as Documents, Music, etc.
enabled=False
Optional: enable VNC remote access (can be useful if you want to check if your content is correctly displayed). Note that the desktop wont start unless a monitor is connected or display-settings entered into /boot/config.txt.
systemctl enable vncserver-x11-serviced.service
Enhance security settingsΒΆ
Disable all USB ports. Please note that this will also disable eth0 on Raspberry Pi3. Therefore, you have to enable WiFi (see above) on Pi3 in order to allow bootstrapping.
nano /lib/systemd/system/disable_usb.service
[Unit] Description=Turn off USB chip After=basic.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/bash -c 'echo "1-1" > /sys/bus/usb/drivers/usb/unbind' [Install] WantedBy=basic.target
Enable disable_usb.service at boot
systemctl enable disable_usb.service
Disable eth0 on boot (optional)
Create disable_eth0.service in /lib/systemd/system/
[Unit] Description=Disable eth0 After=basic.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=ip link set eth0 down [Install]
Enable disable_eth0.service Important: disabling eth0 should be done only AFTER bootstrapping and with WiFi enabled. In this case you have to place a wpa_supplicant.conf file containing your WiFi credentials into /boot (see above)
systemctl enable disable_eth0.service
Disable sudo without password (password can later be set with qbee.io)
sudo visudo /etc/sudoers.d/010_pi-nopasswd pi ALL=(ALL) PASSWD: ALL
Now, logout from container, unmount and check the filesystem
logout sudo umount -v /mnt/raspbian/{boot,} sudo zerofree -v /dev/loop0p2 sudo e2fsck -f /dev/loop0p2 sudo losetup -d /dev/loop0 sync
Optional: If you want to make your image smaller, you can shrink it by using the following script: https://github.com/Drewsif/PiShrink
Optional settingsΒΆ
In order to prolong the life-span of your SD card you may want to disable system logging and swap space
Disable rsyslog
systemctl disable rsyslog.service
Disable swap
systemctl disable dphys-swapfile.service
Edit /etc/dphys-swapfile
CONF_SWAPSIZE=0
Instructions for using the golden master imageΒΆ
Check sha256sum of your master image. In our case we called it 2021-02-12-raspios-buster-armhf-qbeeio-rc5.img
0b9396fe7fdae0de02827f6bcbfc36bbc3353e7877b4b0d9f9a3b82fb40400aa
"Burn" image to SD card. DANGER: make sure to select the correct drive /dev/sd?
dd if=2021-02-12-raspios-buster-desktop-qbeeio-rc5.img of=/dev/sd? bs=4M status=progress
Mount /boot and edit/copy the following files:
Use custom bootstrap keys to onboard devices to specific groups:
With qbee you can create custom groups and bootstrap keys that associate the devices with a specific group. So if all devices in one location share a Wi-Fi key you could split devices according to Wi-Fi credentials and auto-accept them into the group delivering the specific Wi-Fi credentials through qbee file distribution (see further below).
/boot/bootstrapkey.txt /boot/wpa_supplicant.conf (optional) /boot/hostname.txt, default is: digitalsignage (optional) /boot/firstboot.sh (optional)
Insert image into Raspberry Pi4 and let it boot.
Connect to "raspberrypi" using VNC/ssh Note: All USB ports are disabled! Note: VNC does not show the desktop unless a monitor is connected!
Change default password user: pi, password: raspberry -> (can also be done later with qbee password configuration).
The hostname will change only after second boot! Default hostname after second boot, if not changed in /boot/hostname.txt, is: digitalsignage
If you connect with VNC, a right-click onto desktop allows to open a lxterminal
Accept pending device in qbee.io dashboard, if this was not set to "auto"
Reboot
qbee file distribution settingsΒΆ
Content of raspi-config-script.sh - edit to your needs. Further information about how qbee can help you configure your device can be found in the tutorial
raspi-config-script.sh
#!/usr/bin/env bash # Command control strings # receive configuration from raspi-config.conf source /usr/local/bin/raspi-config.conf sudo raspi-config nonint do_hostname $CUSTOM_HOSTNAME # sudo raspi-config nonint do_boot_wait $BOOT_WAIT # sudo raspi-config nonint do_boot_splash $BOOTSPLASH_ENABLE # sudo raspi-config nonint do_overscan $OVERSCAN # sudo raspi-config nonint do_blanking $BLANKING_ENABLE # sudo raspi-config nonint do_boot_splash $BOOT_SPLASH # sudo raspi-config nonint do_camera $CAM_ENABLE # sudo raspi-config nonint do_change_locale $LOCALE sudo raspi-config nonint do_change_timezone $TIMEZONE # sudo raspi-config nonint do_configure_keyboard $KEYBOARD sudo raspi-config nonint do_wifi_ssid_passphrase $SSID $WIFI_PASSPHRASE sudo raspi-config nonint do_ssh $SSH_ENABLE sudo raspi-config nonint do_vnc $VNC_ENABLE # sudo raspi-config nonint do_spi $SPI_ENABLE # sudo raspi-config nonint do_i2c $I2C_ENABLE # sudo raspi-config nonint do_serial $SERIAL_ENABLE # sudo raspi-config nonint do_onewire $ONEWIRE_ENABLE # sudo raspi-config nonint do_rgpio $GPIO_ENABLE # possible overclock options: None|Modest|Medium|High|Turbo # sudo raspi-config nonint do_overclock $OVERCLOCK # sudo raspi-config nonint do_memory_split $MEM_SPLIT # sudo raspi-config nonint do_resolution $RESOLUTION sudo raspi-config nonint do_wifi_country $WIFI_COUNTRY sudo systemctl enable disable_usb.service $REBOOT
Content of raspi-config.tmpl - edit to your needs
raspi-config.tmpl
# config template file for raspi-config-script.sh # deploy to /usr/local/bin/raspi-config.conf # uncomment line and expose parameter through mustache notation {{epoch}} # please remember: 0 is active/enabled and 1 is disabled # %d expects a number, %s expects a string export SSH_ENABLE={{ssh_0_yes_1_no}} export CUSTOM_HOSTNAME="{{custom_hostname}}" export VNC_ENABLE={{vnc_0_yes_1_no}} export SSID="{{ssid}}" export WIFI_PASSPHRASE="{{wifi_passphrase}}" export TIMEZONE="{{timezone}}" # use two letter country short code, example NO export WIFI_COUNTRY="{{wifi_country}}" # define as "shutdown -r now" for reboot export REBOOT="{{reboot_command}}"
Upload both files to DigitalSignage folder in the qbee file manager in the dashboard. Then, import the following json into a templated File Distribution for the devices in scope:
The json for the configuration, simply import with qbee configuration -> file distribution
{ "enabled": true, "files": [ { "label": "DigitalSignage", "templates": [ { "source": "DigitalSignage/raspi-config-script.sh", "destination": "/usr/local/bin/raspi-config-script.sh", "is_template": false }, { "source": "DigitalSignage/raspi-config.tmpl", "destination": "/usr/local/bin/raspi-config.conf", "is_template": true } ], "parameters": [ { "key": "ssh_0_yes_1_no", "value": "0" }, { "key": "custom_hostname", "value": "digitalsignage" }, { "key": "vnc_0_yes_1_no", "value": "0" }, { "key": "ssid", "value": "Your SSID goes here!" }, { "key": "wifi_passphrase", "value": "Your WIFI key goes here!" }, { "key": "timezone", "value": "Europe/Berlin" }, { "key": "wifi_country", "value": "DE" }, { "key": "reboot_command", "value": "shutdown -R now" } ], "command": "/bin/bash /usr/local/bin/raspi-config-script.sh > /dev/null 2>&1 &" } ], "version": "v1", "bundle_commit_id": "836e397274f76baf93d7ddaa76f71071c24b0e86a99738abc2a981d4a7d9864d" }
Below you will see an image how a shortened configuration looks like in file distribution. This specific configuration is applied to the group "Production".
Additional configuration settingsΒΆ
- Further configuration settings, such as display resolution can be changed in /boot/config.txt. For details, check the documentation at raspberrypi.org: https://www.raspberrypi.org/documentation/configuration/config-txt/
Please let us know if you have additional configuration needs or ideas. We have a free 30 day qbee.io trial but the techniques described here can be just as well applied without a device management solution.