18

Many questions have been asked about how to find my Pi on my network. Others - including myself - have time consuming issues while trying to deploy a batch of fresh Pi's.

While the creation of custom images could be a solution for these issues, I'm wondering if there are other solutions.

With (only) the /boot directory open for access on regular machines (Win/OSX), would it be possible to use /boot/cmdline.txt to pipe text to a bash script, run it and delete it afterwards?

1

6 Answers 6

10

I created a lightly modified version of Raspberian-light that addresses this need - it executes your custom /boot/firstboot.sh script on first boot:

https://github.com/nmcclain/raspberian-firstboot

1
  • Thanks! 4 years after OP there's finally a good solution. Not particularly rocket science to build it yourself, but you're in for many hours every time there's a new release. IMHO this should be added to the main firmware.
    – EDP
    Aug 27, 2019 at 8:05
11

For those who prefer a solution involving only scripts dropped into the FAT32 boot partition, here is how to do that. [Edit: The files are now available in a project pi-boot-script.]

As mentioned in other answers, it involves the command-line arguments with which the Linux kernel is started. Those arguments are in /boot/cmdline.txt.

I have tested this on Raspbian Buster (v10.1) 2019-09-26. It works on a newly flashed SD card or on the downloaded .img disk image, which you can then flash to any number of SD cards.

1. Edit the kernel arguments

Open the text file /boot/cmdline.txt, remove any init= part from it, and add this at the end of the line:

init=/bin/bash -c "mount -t proc proc /proc; mount -t sysfs sys /sys; mount /boot; source /boot/unattended"

The last word on this line is the name of a script to be run by the kernel as the first process (PID=1) instead of /sbin/init. The kernel-arguments help page says only arguments without . get passed to the init executable, so you can't call the script unattended.sh or things like that.

2. Put the script on the boot partition

Save the following to the boot partition as /unattended (the name you put in the command line):

# 1. MAKING THE SYSTEM WORK. DO NOT REMOVE
mount -t tmpfs tmp /run
mkdir -p /run/systemd
mount / -o remount,rw
sed -i 's| init=.*||' /boot/cmdline.txt

# 2. THE USEFUL PART OF THE SCRIPT
# Example:
[[ -d /boot/payload/home/pi ]] && sudo -u pi cp --preserve=timestamps -r\
 /boot/payload/home/pi /home/ && rm -rf /boot/payload/home/pi              # A
[[ -d /boot/payload ]] && cp --preserve=timestamps -r /boot/payload/* /\
 && rm -rf /boot/payload                                                   # B
ln -s /lib/systemd/system/one-time-script.service\
 /etc/systemd/system/multi-user.target.wants/                              # C

# 3. CLEANING UP AND REBOOTING
sync
umount /boot
mount / -o remount,ro
sync
echo 1 > /proc/sys/kernel/sysrq
echo b > /proc/sysrq-trigger
sleep 5

This script does some necessary preparation (chapter 1), then anything you want to do (2) and then cleanup and re-booting (3). Replace the stuff under 2 with the commands you want to run.

For some configuration tasks you probably need a normal boot to bring up networking and other services, so the example in this version (explained below) only prepares for a proper script to run when the Pi reboots.

3. Put any other files your script needs on the boot partition

...obviously.

Example

Together with my script, I put a folder payload/ on the boot partition, which holds the files I want to move to the Linux partition. In the script unattended above,

  • line A moves files into the pi-user's directory. E.g. payload/home/pi/.bashrc is moved into the root filesystem as /home/pi/.bashrc;
  • line B moves root-owned files into the Linux partition, including payload/usr/local/bin/one-time-script.sh which becomes /usr/local/bin/one-time-script.sh, and similar for payload/lib/systemd/system/one-time-script.service;
  • line C then creates a symlink to that last file, so my configuration script one-time-script.sh is run at the next boot.

That script does various customizations I like: it creates and formats another FAT32 partition and adds it to /etc/fstab so the pi user can write to it (for application logs etc.); resizes the ext4 partition & filesystem to the remainder of the SD card; changes the locale, timezone, hostname (based on CPU serial number), WiFi country; sets WiFi network and passphrase; switches SSH on; fixes a language-settings problem for SSH sessions; configures booting into a console without auto-login; writes some data about the system to a file on the boot partition; and of course it removes that symlink so it won't run again at boot.

Most users will find this unnecessary and prefer to use PiBakery, pi-init2, or a custom ext4 image, which are great solutions. I prefer this because I can fully understand it and I don't have to run other software. And it also works: with the .img file I've put my scripts in, all of flashing an SD card + putting it in a Pi + letting it run to configure itself takes 6 minutes.

Source I found the idea of a script as the init= kernel argument, and the mount commands necessary to make it work, in the init_resize.sh script that runs by default to resize the Linux partition.

3
  • A couple of things: 1. Arguments after -- are passed to init, so can contain a ".". 2. The only thing you need to mount from cmdline.txt is /boot; everything else can be mounted in the script. 3. You can exec /sbin/init at the end of the script to continue boot.
    – tc.
    Jul 12, 2021 at 17:22
  • Have you actually tested claim 2? When I wrote this I tested various forms to find the minimum command, but as I recall the script wouldn't start without /sys and /proc. With regard to 3, I need a normal boot with the standard mounts and I think this method mounts things a little differently, so rebooting is safer.
    – Jim Danner
    Jul 14, 2021 at 8:23
  • My bad: I was using mount -t vfat /dev/mmcblk0p1 /boot which works, but just mount /boot does not because (I think parsing PARTUUID=... uses libblkid which requires /proc/partitions and /sys/dev/block/...). I am a little wary of echo b > /proc/sysrq-trigger, but I guess it's fine on Linux where sync waits for the data to actually be flushed.
    – tc.
    Jul 18, 2021 at 23:55
3

You CAN cause code to be executed by messing with the kernel command line. The most obvious method is to replace init with something else. The most common application of this is to launch a shell very early in the boot process, usually because you need to fix something or because everything else is very badly broken, eg:

init=/bin/bash

Keep in mind that at this point in the boot process, the filesystems are all still mounted read-only. In addition, there is a whole bunch of things that just won't work correctly. Because you don't have a real init running, shutdown and reboot won't work. You have to manually remount the root filesystem read-only and call reboot -f to reboot, for example.

I have no idea if you can pass arguments to bash in this manner. I have never tried. In theory, if you can pass -c to bash, you can tell that bash process to do anything. But it might turn into a fairly long argument, and I don't know if the kernel would allow such things.

Second thing you can do. You can copy an initial ramfs (initramfs) to the filesystem, and configure the bootloader to use it in config.txt. There are several ways to get scripts into an initramfs to do special things. You'll have to prepare a special initramfs for this purpose though (see initramfs-tools(8)), so I am not sure if this is a better solution than a custom image.

You could include the script in /boot (I laughed at your suggestion about "regular" machines, but this would be the bit you can access from those machines) and attempt to launch that using the kernel init line, but files on dos filesystems aren't executable unless you make it so for the entire filesystem.

If it was me, I'd make a custom image that uses dhcp to configure the network, and that contains a custom script that runs at boot. This script checks for a specific file which acts as a flag. If the file exists, do nothing. If not, configure things, then create the flag file.

Your config script could even pull the real thing from an http server. This means you don't have to make a new image if you have to tweak something.

That should be the least stressful solution.

One final possibility, but you'll have to do this on a "non-regular" machine :-) You can mount the ext4 filesystem to a loop device and copy files to it without writing it to sdcard first. For a standard Raspbian Jessie image, it would be something like this:

sudo losetup /dev/loop0 /tmp/gw.img -o 62914560
sudo mount /dev/loop0 /mnt
sudo cp /my/superduper/script.sh /mnt
sudo umount /dev/loop0
sudo fsck -f /dev/loop0 # This is optional
sudo losetup -d /dev/loop0

I like to do a forced fsck on my filesystems before making images. Sets the mount count to zero on first boot :-)

EDIT: After many months and more experience. You want to look at u-boot. Replace the boot-loader with u-boot. This can be done from a "regular machine". Once you have u-boot in place, you can either network-boot a distribution from which you can easily flash the sd-card, or you could in theory flash the card directly, though I have no idea how hard that would be.

Essentially u-boot brings network-boot to the Raspberry Pi, something it does not support on its own.

3
  • Ok, file system is readonly at that stage. What about init=script & init? The script would run in background while init starts up normally. The script would need some condition checking at the beginning and e.g. continue when init has finished its work. Aug 31, 2016 at 20:28
  • 1
    Using & to background something is a shell thing. Unless you tell the kernel to run a specific command in a shell (eg: bash -c "some command & another command") that won't work, and I already think this is a bad idea. But I did extend my answer and added the u-boot option, something I discovered recently.
    – izak
    Aug 31, 2016 at 22:06
  • 1
    Just finished trying ;-) No, it really does not work Aug 31, 2016 at 22:08
1

Arguably, if you're OK with modifying the image to auto-run a script at the first boot, you could simply modify the image in the way your script would, and then save that SD card to an image file and use it to flash SD cards you're going to use with new RPis. For instance, if you want all your RPis to have a certain entry in /etc/fstab, you could simply modify /etc/fstab itself instead of writing a script which does the modification.

If you absolutely need scripted actions (e.g. if each image should be modified in a different way), you could move your /etc/rc.local to /etc/rc.bak and put a script in /etc/rc.local which replaces itself with /etc/rc.bak in the last command. That script could perform the first boot actions itself, or it could call a particular script from the /boot partition if you prefer.

It's possible to make an auto-run by touching only the /boot partition, by supplying a special boot ramdisk image to the kernel as described here. That image would contain the scripts to modify the root partition and then self-delete from config.txt. I'm not sure if it's worth the trouble though.

0

I wouldn't recommend touching anything in the boot area (other than config.txt) unless you have a detailed understanding of what that stuff is doing. cmdline.txt isn't designed to run things when the RPi starts. It's used to pass parameters to the Linux kernel on boot.

I would suggest doing this all through SSH. A script on your desktop could push a bash/python/java/c/whatever program to the RPi, execute it, and then delete it when it's finished. Add threading to the script on your desktop and you can send it out to as many devices as you want all at the same time.

5
  • 1
    That's how we do it now but I'm looking for a more easy solution.
    – EDP
    Jan 30, 2016 at 14:57
  • 1
    Read: run the setup client-initiated rather than server-initiated
    – EDP
    Jan 30, 2016 at 15:15
  • @EDP: there's no way to get what you want simply by editing the boot position. You could write a script that pulls the file from a server on the RPi's first boot, and then have that program remove the startup script. That would require you to use a custom image.
    – Jacobm001
    Jan 31, 2016 at 1:13
  • 1
    "A script on your desktop" - Which script on my desktop? At the first boot, there's no script on the Desktop. "Doing this all through SSH" - at the first boot the Pi might not have the correct Ethernet or WLAN settings. Aug 31, 2016 at 20:25
  • You don't even need threading check out the fabric project. IIRC its only rewuirement is SSH Sep 1, 2016 at 17:15
-2

You may want to look at my project Nard which has a solution for your problem:

1) Each SD-card can be assigned an unique ID with a regular Windows PC as described here:
http://www.arbetsmyra.dyndns.org/nard/#devsettingsid

2) Power on all your Pis

3) Disable the PC firewall if possible

4) Open a DOS prompt window and ping the subnet broadcast address

5) List the ARP table with Windows command "arp -a". In the list you will find the MAC and IP addresses of all nearby Raspberry Pi.

6) Connect to each device with telnet (usually available in Windows too). The welcome phrase will display the ID assigned in step 1.

2
  • Perhaps my initial description was not clear enough. I'm not particularly looking for a way of identifying the Pi's. I already have that covered. What I'm looking for is to run one or more bash commands by altering the /boot/cmdline.txt file. This all without the need of logging in via ssh - even once.
    – EDP
    Aug 3, 2015 at 4:57
  • You probably won't be able to 'ping the subnet broadcast address', Smurf Attacks are usually prevented. Oct 13, 2015 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.