Post-install script on Yocto-built linux
Asked Answered
M

6

7

I need to run a script on a target OS built by Yocto.

This script needs to be ran as part of the install and thus must be ran only once (either after the entire OS install or on the first boot). It cannot be ran on the host system, as it depends on the hardware IO which exists only on the target.

An additional, minor, constraint is that the rootfs is mounted as read only, but I guess that can be avoided by having the script re-mount as rw and again remount as r after the execution or something along those lines.

Any help is appreciated.

Manner answered 4/11, 2016 at 21:4 Comment(0)
M
16

I ended up doing what shibley had written. Here's a detailed howto:

Create a new layer

Put the desired layer wherever your other layers are. Mine are in stuff directory, next to the build directory.

Make the following files/directories:

meta_mylayer
├── conf
│   └── layer.conf
└── recipes-core
    └── mylayer-initscript
        ├── initscript.bb
        └── files
            ├── initscript.service
            └── initscript.sh

meta_mylayer is the name of your new layer.

Let's define the layer in conf/layer.conf and tell it where to search for the recipes:

BBPATH .= ":${LAYERDIR}"
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"
BBFILE_COLLECTIONS += "meta-mylayer"
BBFILE_PATTERN_meta-mylayer := "^${LAYERDIR}/"
BBFILE_PRIORITY_meta-mylayer = "99"

The recipes are defined by the name of the .bb file. This layer only has one recipe, named initscript.

initscript.bb contains the recipe information. The following recipe will add our initscript service and put the actual install script, initscript.sh, into /usr/sbin/

SUMMARY = "Initial boot script"
DESCRIPTION = "Script to do any first boot init, started as a systemd service which removes itself once finished"
LICENSE = "CLOSED"
PR = "r3"

SRC_URI =  " \
    file://initscript.sh \
    file://initscript.service \
"

do_compile () {
}

do_install () {
    install -d ${D}/${sbindir}
    install -m 0755 ${WORKDIR}/initscript.sh ${D}/${sbindir}

    install -d ${D}${systemd_unitdir}/system/
    install -m 0644 ${WORKDIR}/initscript.service ${D}${systemd_unitdir}/system
}

NATIVE_SYSTEMD_SUPPORT = "1"
SYSTEMD_PACKAGES = "${PN}"
SYSTEMD_SERVICE_${PN} = "initscript.service"

inherit allarch systemd

install -d will create any directories needed for the specified path, while install -m 0644 will copy the specified file with 644 permissions. ${D} is the destination directory, by default it's ${WORKDIR}/image

Create the systemd service definition

I won't go into much details about how systemd works, but will rather paste the service definition:

[Unit]
Description=start initscript upon first boot

[Service]
Type=simple
ExecStart=/bin/sh -c 'sleep 5 ; /usr/sbin/initscript.sh'

Do note the script location at /usr/sbin/ - that's where it will be copied by the last line of our do_install function above.

Lastly, our initscript.sh script itself:

#!/bin/sh

logger "starting initscript"

# do some work here. Mount rootfs as rw if needed.

logger "initscript work done"

#job done, remove it from systemd services
systemctl disable initscript.service

logger "initscript disabled"

Register the layer

We need to register our new layer, so that bitbake knows it's there. Edit the build/conf/bblayers.conf file and add the following line to the BASELAYERS variable:

  ${TOPDIR}/../stuff/meta-mylayer \

Now that the bitbake recognizes our layer, we need to add our recipe to the image. Edit the build/conf/local.conf and add the initscript recipe to the IMAGE_INSTALL_append variable. Here's how it looks like when added next to the python.

IMAGE_INSTALL_append = " python initscript"

Run the build

Run the build like you usually do. For example:

bitbake angstrom-lxde-image

After you install the build and boot for the first time, your initscript.sh will be executed.

Manner answered 14/11, 2016 at 21:2 Comment(2)
it doesn't work for me, the files are transferred but the service is not executed at startup. i can execute it manually with start option.Corundum
Try adding it to autostart manually (systemctl enable foo) and see if it works and what changes. Having the service file available under /etc/systemd/system should be enough.Manner
H
3

The basic approach is to write a systemd service. The service can be enabled by default as defined in the yocto recipe systemd configuration. The script or application evoked by the service will disable the service when the script/application completes - ie. systemctl disable foo. Therefore, the service will not run in future boots.

As you mentioned, the rootfs will require mounting as rw for this to work.

Holofernes answered 4/11, 2016 at 23:10 Comment(0)
R
2

Thanks, this helped out. I needed to add

[Install] WantedBy=multi-user.target

to the initscript.service to get it working

Riyadh answered 22/12, 2017 at 0:34 Comment(1)
This is an important piece of information. Without the [Install] and WantedBy it wont be run by systemd at startup. Most other services are started by multi-user.target, too. However, If you know which service a first-boot-setup is needed for, you should also add the [Unit]\nBefore=<service>\nWantedBy=<service> so the service does not start before your first-boot-setup is done.Subterrane
G
0

A simple solution is to use a package post/install script that stops itself running at rootfs time (exit 1 if $D is set). This will result in it running at first boot. Yes, the script will need to remount the root fs.

Gherardi answered 6/11, 2016 at 10:0 Comment(3)
This sounds interesting. Could you elaborate more? I did try having a .bbappend for an existing recipe, with do_install_append script in that bbappend, but it tried executing the said script on the host system which is not what I need.Manner
yoctoproject.org/docs/2.2/dev-manual/…Gherardi
Just adding same link but into more recent doc version: docs.yoctoproject.org/dev-manual/…, btw discussed also here: https://mcmap.net/q/1475863/-debugging-bitbake-pkg_postinst_-pn-append-to-config-file-installed-by-other-recipeLycaonia
M
0

Besides, I don't know how to address the issue that rootfs is mounted as read-only, you can use pkg_postinst_ontarget_${PN}

Add this to one of your recipes:

pkg_postinst_ontarget_${PN}() {
    #!/bin/bash
    // bash script you want to run
    echo Post Install Script Test > /dev/ttyS1
}

${PN} will be replaced with the package name the recipes corresponds to.
The script will be run only once, on the first boot on the target machine as a post-install script of the package.

Mart answered 28/12, 2020 at 14:30 Comment(0)
L
0

You can also put the stuff into systemd-firstboot.service.

Lycaonia answered 23/9 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.