Trigger an action if a specific package was upgraded


A short entry describing a generic(-ish) solution for a specific problem that shouldn’t even exist.

Preface

I needed a system to reboot if docker was upgraded during unattended-upgrades.

In theory this should not be required - a simple docker daemon restart should bring everything up, but alas, in this case it’s intertwined with Home Assistant’s supervisor and os-agent processes and a quick reboot is the simplest method of bringing every part onto the same page.

While in this case I’m rebooting the system if a docker package was upgraded, this mechanism should be the exact same regardless of package or action - e.g. I might later modify it to only reload the related services.

While unattended-upgrades can mark the system as requiring reboot and even execute the reboot automatically, there is no mechanism for post-scripts limited to unattended-upgrades.
There is DPkg::Post-Invoke, but I don’t want my script to run EVERYTIME I (or something else) touches apt/dpkg.
There is also no mechanism for manuall marking a package as requiring reboot after an update.

Solution

So, combining this question about unattended upgrade end script, systemd path unit docs and some unattended-upgrades logs parsing, I’ve landed on this solution (maybe more of a workaround…):

File /etc/systemd/system/unattended-upgrades-post-action.path:

[Unit]
Description=Watch unattended-upgrades timestamp file and trigged post-upgrade script unit

[Path]
PathModified=/var/lib/apt/periodic/unattended-upgrades-stamp
TriggerLimitIntervalSec=5min
#Unit=override unit if you want it to trigger something else than <same-file-name>.service

[Install]
WantedBy=multi-user.target

File /etc/systemd/system/unattended-upgrades-post-action.service:

[Unit]
Description=Run post-upgrade script for unattended-upgrades

[Service]
Type=oneshot
ExecStart=/usr/sbin/unattended-upgrades-post-action

File /usr/sbin/unattended-upgrades-post-action:

#!/usr/bin/env sh

set -e

packages=$(\
	cat /var/log/unattended-upgrades/unattended-upgrades.log \
	| grep "Packages that will be upgraded" \
	| sed 's|.*will be upgraded: \(.*\)|\1|' \
)

# Upgraded package match here: modify to your needs
if echo $packages | grep -q "docker"; then
	# Action here: modify to your needs
	echo "docker was upgraded - rebooting!"
	systemctl reboot -i;
fi

Remember to adjust the script’s permissions to allow execution (chmod +x) and then enable the path unit:

sudo systemctl enable --now unattended-upgrades-post-action.path

Explanation

First, the .path file:

  • in the [Path] section it declares the file to be watched (as PathModified, which as per systemd docs triggers on simple writes without content changes);
  • in the same section it defines a trigger time limit, which should not be necessary but I’ve added it just in case unattended-upgrades spam-updates the stamp file;
  • again, in the same section it CAN define a service unit to be triggered (by default it uses it’s own filename with .path replaced by .service);
  • [Install] section just because it’s required for enabling the unit;
  • [Unit] section for description.

Then, the .service file:

  • [Unit] section for description, just like .path;
  • in [Service] section, Type=oneshot is the default but it’s here just to make it explicit;
  • in the same section, ExecStart= provides path to the script to be run.

Lastly, the main part, the unattended-upgrades-post-action script in /usr/sbin:

  • Shebang so it runs as a POSIX SH script (as it should);
  • set -e will stop the script if something inside it fails;
  • the multiline sub-shell extracts the relevant line from unattended-upgrades’ log file and removes the first part of the line, leaving only a list of upgraded packages;
  • the if simply checks for a package name (really, part of a package’s name) by grepping the list - good enough for my usecase;
  • in the else section, the desired action is run - here it’s a reboot.

Testing

To test, I highly recommend commenting out any reboot commands from the post-action script and then it’s simply touching the stamp file to trigger the systemd mechanism:

sudo touch /var/lib/apt/periodic/unattended-upgrades-stamp

This should trigger the script - it’s output will be in the .service unit’s logs, so for me:

sudo systemctl status unattended-upgrades-post-action.service

returned:

○ unattended-upgrades-post-action.service - Run post-upgrade script for unattended-upgrades
     Loaded: loaded (/etc/systemd/system/unattended-upgrades-post-action.service; static)
     Active: inactive (dead) since Sat 2024-05-11 21:47:10 CEST; 11s ago
TriggeredBy: ● unattended-upgrades-post-action.path
    Process: 10365 ExecStart=/usr/sbin/unattended-upgrades-post-action (code=exited, status=0/SUCCESS)
   Main PID: 10365 (code=exited, status=0/SUCCESS)

May 11 21:47:10 hostname systemd[1]: Starting unattended-upgrades-post-action.service - Run post-upgrade script for unattended-upgrades...
May 11 21:47:10 hostname unattended-upgrades-post-action[10365]: docker was upgraded - rebooting!
May 11 21:47:10 hostname systemd[1]: unattended-upgrades-post-action.service: Deactivated successfully.
May 11 21:47:10 hostname systemd[1]: Finished unattended-upgrades-post-action.service - Run post-upgrade script for unattended-upgrades.

Share this post: