Capture power button press in app container (Intel NUC)

A balena user has found a clever way of intercepting the button press event in an app container that allows for any action to be triggered, while preventing the default system behaviour of powering the device down when the power button is pressed. It was tested on an Intel NUC, but the principle may apply to other platforms as well. They have kindly shared their solution and were happy for it to be posted here, so others can benefit too. Their use case was to trigger the WiFi Connect app when the power button was pressed, but anything else could be triggered instead.

The app container should have the ability to communicate with the host OS via DBUS, so it should either be privileged, or should have the “io.balena.features.dbus = true” label added to the docker-compose.yml file.

Then this shell script should be executed in the app container:

#!/usr/bin/env bash
echo 'Starting App Container'
 
# Set DBus address for Network Manager
export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
 
# Prevent power button from shutting down CPU
systemd-inhibit sleep infinity &
 
# Start ACPI service to listen for power button presses
service acpid start
# Listen for ACPI events and run script if Power Button is pressed
acpi_listen | while IFS= read -r line;
do
    if [ "$line" = "button/power PBTN 00000080 00000000" ]; then
        echo 'Power button pressed, running script'
        # run your script here
        sleep 10
        # Optional: reboot the system
        echo 'Rebooting System'
        dbus-send \
            --system \
            --print-reply \
            --dest=org.freedesktop.systemd1 \
            /org/freedesktop/systemd1 \
            org.freedesktop.systemd1.Manager.Reboot
        break
    else
        echo "$line"
    fi
done
echo 'Restarting Container'

As coded it looks like the container is restarted on every ACPI event: perhaps it would be more efficient to have a loop in the script. But the key points are:

  • systemd-inhibit sleep infinity - disables the default power off system behaviour.
  • service acpid start - starts the acpi daemon in the app container
  • acpi_listen - listen for ACPI events like the power button press
  • For the optional step of rebooting the system, what we usually recommend is using the balena supervisor API: POST /v1/reboot endpoint:
curl -X POST --header "Content-Type:application/json" \
    "$BALENA_SUPERVISOR_ADDRESS/v1/reboot?apikey=$BALENA_SUPERVISOR_API_KEY"

Where there BALENA_SUPERVISOR_ADDRESS and BALENA_SUPERVISOR_API_KEY variables are predefined in balena containers. I think the advantage of the supervisor API is that the supervisor will first try to gracefully stop the app containers. The dbus method may be more aggressive – but I thought I would keep the dbus example too.

3 Likes

Very cool, thanks to that user for sharing and @pdcastro for posting :slight_smile: