Firstrun.sh on Raspian Bullseye not working

I saw in this post that firstrun.sh support was added to the latest Raspbian Bullseye Images. So, I thought I would give it a try on my Le Potato, but ran into several issues.

I may be naïve, but I had thought at least one of the reasons for adding this was to make it easier for new users and people moving from Raspberry Pi to get started. Anyway, I grabbed the “2023-05-03-raspbian-bullseye-arm64+aml-s905x-cc.img” from the official Libre download server and used the Raspberry Pi Imager Tool to write it to my SD card. I used the advanced settings in the Imager Tool to enable SSH, set my username and password, set my hostname, and configure wifi.

First problem - the firstrun.sh file did not get created by the imager. I thought this was strange because I knew that it was created when I used an earlier image (2022-09-22-raspbian-bullseye-arm64+aml-s905x-cc.img). I also noticed that the new 2023 image was much “cleaner” and had a lot fewer files in the Boot partition. It looks like Libre removed a lot of the files from the latest image that were only useful to Raspberry Pi boards.

Since the code for the Raspberry Pi Imager is posted on Github, I took a look at it to see if I could figure out why it was not generating the firstrun.sh file with this new image. I found there was some code here: https://github.com/raspberrypi/rpi-imager/blob/b49408781a3c347bd6f6c057c68bb34d6c06ad10/src/downloadthread.cpp#L949 that checked for a file called issue.txt and looked for it to contain the text “pi-gen”. If the file isn’t there, it assumes that this is some distribution that uses cloudinit and does not create firstrun.sh. This file was present in the older Libre images, but not the latest one.

            else if (issue.contains("pi-gen"))
            {
                /* If issue.txt mentions pi-gen, and there is no user-data file assume
                 * it is a RPI OS flavor, and use the old systemd unit firstrun script stuff */
                _initFormat = "systemd";
                qDebug() << "using firstrun script invoked by systemd customization method";
            }

I found I could use the Raspberry Pi Imager to generate the firstrun.sh file by using the older 2022 Bullseye image. Then I copied it to my laptop. Then I could burn the 2023 Bullseye image to SD card and copy the firstrun.sh file back.

Can Libre include the issue.txt file on their images, to solve this issue?

Second Issue - Once I had the firstrun.sh file on the SD card, I found that it did, in fact execute and it successfully set my username and password, enabled SSH, set my hostname, and configured the wifi settings. However, the script is supposed to delete itself before it finishes, and this was not happening. Here’s an example of the firstrun.sh file generated by the Raspberry Pi Imager (I replaced any sensitive text)

#!/bin/bash

set +e

CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"`
if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom set_hostname __MY_HOSTNAME__
else
   echo __MY_HOSTNAME__ >/etc/hostname
   sed -i "s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\t__MY_HOSTNAME__/g" /etc/hosts
fi
FIRSTUSER=`getent passwd 1000 | cut -d: -f1`
FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`
if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom enable_ssh
else
   systemctl enable ssh
fi
if [ -f /usr/lib/userconf-pi/userconf ]; then
   /usr/lib/userconf-pi/userconf '__MY_USERNAME__' '__MY_HASHED_PASSWORD__'
else
   echo "$FIRSTUSER:"'__MY_HASHED_PASSWORD__' | chpasswd -e
   if [ "$FIRSTUSER" != "__MY_USERNAME__" ]; then
      usermod -l "__MY_USERNAME__" "$FIRSTUSER"
      usermod -m -d "/home/__MY_USERNAME__" "__MY_USERNAME__"
      groupmod -n "__MY_USERNAME__" "$FIRSTUSER"
      if grep -q "^autologin-user=" /etc/lightdm/lightdm.conf ; then
         sed /etc/lightdm/lightdm.conf -i -e "s/^autologin-user=.*/autologin-user=__MY_USERNAME__/"
      fi
      if [ -f /etc/systemd/system/getty@tty1.service.d/autologin.conf ]; then
         sed /etc/systemd/system/getty@tty1.service.d/autologin.conf -i -e "s/$FIRSTUSER/__MY_USERNAME__/"
      fi
      if [ -f /etc/sudoers.d/010_pi-nopasswd ]; then
         sed -i "s/^$FIRSTUSER /__MY_USERNAME__ /" /etc/sudoers.d/010_pi-nopasswd
      fi
   fi
fi
if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom set_wlan '__MY_SSID__' '__MY_HASHED_WPA_PASSWORD__' 'US'
else
cat >/etc/wpa_supplicant/wpa_supplicant.conf <<'WPAEOF'
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

update_config=1
network={
	ssid="__MY_SSID__"
	psk=__MY_HASHED_WPA_PASSWORD__
}

WPAEOF
   chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
   rfkill unblock wifi
   for filename in /var/lib/systemd/rfkill/*:wlan ; do
       echo 0 > $filename
   done
fi
if [ -f /usr/lib/raspberrypi-sys-mods/imager_custom ]; then
   /usr/lib/raspberrypi-sys-mods/imager_custom set_keymap 'us'
   /usr/lib/raspberrypi-sys-mods/imager_custom set_timezone 'America/New_York'
else
   rm -f /etc/localtime
   echo "America/New_York" >/etc/timezone
   dpkg-reconfigure -f noninteractive tzdata
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
   dpkg-reconfigure -f noninteractive keyboard-configuration
fi
rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0

The line to remove firstrun.sh does not work because the path is different/incorrect:
rm -f /boot/firstrun.sh
fails because the path needs to be /boot/efi/, like this:
rm -f /boot/efi/firstrun.sh

I found if I edited the firstrun.sh file to fix this path before I booted the SD card for the first time, the script would correctly self-delete after executing.

I don’t see any good way for Libre to address this issue, other than to warn users to check their firstrun.sh file and manually edit it if the path is wrong, or go back and delete it after they have booted for the first time. Leaving this file in place could be a security issue since it contains sensitive data (at least the passwords are encrypted and not just plain text).

Third Issue - I found that if I booted the image for the first time with a monitor and keyboard connected, and with this modified firstrun.sh file added to the boot partition, the Raspberry Pi first run wizard (piwiz) still attempted to execute, but would hang. You would see the background graphic for the wizard, but it would not display the prompts to set your username and password. I could not find anyway to exit this screen, nor could I find a way to prevent this wizard from attempting to run. But, I could log in through the UART Serial console, or through SSH (using the credentials established in firstrun.sh). And from there I could issue a command to shutdown or reboot. From that point forward, the desktop would work as normal.

This issue is probably not a big deal, since most people who would want to use the firstrun.sh to set things up probably are going to want to work headless anyway. And, if you do happen to connect a monitor and keyboard as I did and run into this issue, it can be resolved by simply re-booting. But, still it isn’t really desirable and can be offputting/confusing for new users when their system appears to lock-up the first time they attempt to boot.

I just wanted to document my experience in case others run into it, and in case Libre or others have ideas for solutions to make this function more smoothly.

I think the ability to execute a firstrun.sh script could still be useful for advanced users (you could do all sorts of other custom things with this script besides just setting up login stuff), but unfortunately it is still not a very smooth experience for those trying to take advantage of the Raspberry Pi Imager advanced settings.

1 Like

Thank you for taking the time to report the issues you are having.

  1. We will correct this on the next release by adding pi-gen in issue.txt file.
  2. We recommend always coding in a directory agnostic way.
$ echo $(dirname ${BASH_SOURCE[0]}) # relative path to script
.
$ echo $(readlink -f $(dirname ${BASH_SOURCE[0]})) # absolute path to script
/home/user
$ cd $(dirname ${BASH_SOURCE[0]})

Raspberry Pi is not known for their coding standards. A lot of the code and scripts are poorly done and there’s no much we can control there. We can do a sed on the firstrun script.

The Debian releases targeted for the end of the month will help with this since they will do a lot of stuff in a much cleaner way.

  1. We will take a look into this for the next release as well.