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 (asPathModified
, 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.