How to configure automatic offline updates with Fedora?

Hi !

I use Fedora (GNOME Workstation, n-1 version) and it’s just perfect for my professional and personal computers . I’ve also installed it on my wife’s computer, and also friends and even relatives. They are not tech and Linux enthusiasts (as I am) but for our basic use (mainly web, mail, photo, videos), Fedora does the job nicely.

There is one problem I can’t solve : automatic offline updates.

I have two conditions :

  • updates must be transparent to the user (no dnf command, no CLI, no root or sudo use)
  • root/user password must not be asked to the user.

I can achieve this goal with Gnome-Software which is not bad (offline updates, notifications, one-clic install without root password and reboot) because it doesn’t require a complex interaction with the user (clck to install) but it’s half broken after sleep/wake up (no updates notification, no offline update when poweroff, and update page is spinning endlessly…).
Once, I realized that my wife’s laptop has tons of waiting updates but she doesn’t know about it because of the missing notification. I had to end/kill the “Gnome-software” process and restart the application to get it on track again… I have to do it again every time the laptop goes in sleep mode. It’s it’s not as user friendly as promised).

I’ve tried different workaround :

  1. DNF automatic : it’s transparent for the user with a systemd service/timer but there is no offline update mode and I experienced random instabilities and crash when an updated software is running (e.g. Firefox). Moreover, the user is not warned at the end of the update process, nor invited to reboot. And, of course, I can’t force a reboot at the end of the process…

  2. PackageKit : it’s nearly perfect as it searches for updates, prepares for offline update and shows a notification. I use this command :

pkcon refresh force && pkcon update --only-download -y && pkcon offline-get-prepared && pkcon offline-trigger

But I can’t execute this command in a systemd service. With Flatpak, for example, I have system-wide systemd service and timer to get automatic updates once a week and it’s working nicely. But I can not reproduce the same thing with these PackageKit commands…

How can I execute this command line in the background and make it run once a week ?
Maybe is there an alternative solution to acheive my goal ?

Thank you for your help.

(Excuse my English, it’s not my mother tongue)

1 Like

It would be helpful to have a bug report here for the problem with GNOME Software breaking after you suspend and resume your computer. That’s not normal and is not a complaint I’ve heard before.

Thanks for your answer.
I’ve just created an account on Gnome Gitlab, and I’ll write a bug report as soon as I got logs and screenshots to document it.

By the way, any idéa on how to do these updates with PackageKit through a script or a systemd service and without Gnome Software (so it could be useful too for KDE, Xfce and other DE) ?

Have you considered using a cron job? As you need root privs for this, you should add the job to root’s crontab.

Yes, I’ve tried cron job but it’s not working (just like systemd timers) and I don’t understand why!

Just for information, the upper update command does not require to be run as root, a simple user can run it from terminal and it works.

Test 1: crontab as root :
# crontab -e
Then, I add the command to be executed every 5 minutes (for testing purpose) :
*/5 * * * * pkcon refresh force && pkcon update --only-download -y && pkcon offline-get-prepared && pkcon offline-trigger
Save and quit, but nothing happens

The log file is not very informative :
# journalctl | grep cron

févr. 29 09:47:03 XXX crontab[26087]: (root) BEGIN EDIT (root)
févr. 29 09:47:31 XXX crontab[26087]: (root) REPLACE (root)
févr. 29 09:47:31 XXX crontab[26087]: (root) END EDIT (root)

Test 2 : crontab as user
Nothing happens : there are a few updates still pending in gnome-software…

I don’t understand why it’s not working because if I execute the command in the terminal as user, it works.

cron and systemd services don’t have the full user environment so they don’t always have the full path to a command.
e.g., /usr/bin/pkcon

You will probably want to do a script of some kind to execute the commands and then point the cron job or systemd service to that.

Your original commands, saved and made executable:

#!/usr/bin/env bash 
pkcon refresh force && pkcon update --only-download -y && pkcon offline-get-prepared && pkcon offline-trigger

then you would just point the cron job or systemd service to that script using the full path.

I’m playing with doing this with a systemd user service/timer and the following script based on what I’ve seen in this thread.

#!/usr/bin/env bash
#Trap errors and print where it went boom
trap '{ echo "${0##*/} line:$LINENO -> $BASH_COMMAND" ; exit ; }' err
flatpak update -y --noninteractive
pkcon refresh force
pkcon update --only-download
pkcon offline-trigger
if pkcon offline-get-prepared; then
  systemctl reboot --when=+5m
fi

Here are the service/timer files
doupdates.service

[Unit]
Description="Update flatpaks and rpm packages"

[Service]
ExecStart=%h/bin/doupdates.bash

doupdates.timer

[Unit]
Description="Run updates daily"

[Timer]
OnCalendar=Mon..Sun *-*-* 5:30:00

[Install]
WantedBy=default.target

I have the script in a bin directory in my home directory … (/home/grumpey/bin)
The service and timer for a user service go under ~/.config/systemd/user/
To enable the timer.
systemctl --user enable --now doupdates.timer

More on systemd timers

Reference for the OnCalendar portion.
https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html#Calendar%20Events

Sorry I forgot this part,
If you want this to run when you are not logged in you will need to enable linger.
https://wiki.archlinux.org/title/Systemd/User#Automatic_start-up_of_systemd_user_instances

Thanks for your well documented answer.

  1. Is better to create a user service or a system service for this kind of job ?
    On my computer there are 3 users, most of the time there is only one session opened, but at some moment of the day, 2 users can be connected. I assume a system service is more convinient in that case, isn’t it ? Or is it better to configure 3 scripts in each user session ?

  2. About the location of the different files:

For a user script, I should store:

  • the script location in /home/user/bin
  • the systemd service and timer in /home/.config/systemd/user/

For a system-wide script, I should store:

  • the script location in /usr/local/bin/
  • the systemd service and timer in /etc/systemd/system/

Are these locations correct ?

I’m using a user service because I’m in the wheel group and I’m the only user of the computer. I’ve also enabled linger for that user.

It is probably more straight forward to use a system service in this case or just execute the script with a cron job.
If you did it as a user service you would just need one user setup who is in the wheel group and linger enabled for that user.

For the system scripts.
/usr/local/bin/ is appropriate.

systemd system services can be in /etc/systemd/system/
The full list of paths is for system and user is here

For user scripts either
~/.local/bin or ~/bin
I use ~/bin

Remember you need to use the full path for the service or for a cron job.
Since I set my updates to be early in the morning I’m not sure how much of a notification users get.

One of my favorite things about Linux is the number of different ways a problem can be solved.

Many thanks John Walker for your help !

Everything seems to work.
Here is my proposed solution, step by step, for those who are interested:

1. Script side

A. Create the script (update-script.bash)

# nano /usr/local/bin/update-script.bash

B. Add the commands (update routine) to run:

#!/usr/bin/env bash
sleep 60
dnf clean all
fwupdmgr get-updates
fwupdmgr update
flatpak repair
flatpak --system update -y --noninteractive
pkcon refresh force
pkcon update --only-download
pkcon offline-get-prepared
pkcon offline-trigger

C. make the script executable:

# chmod +x /usr/local/bin/update-script.bash

2. Systemd service

A. Create the “update-script.service” :

# nano /etc/systemd/system/update-script.service

B. Add the following content:

[Unit]
Description=Service to run update script
Wants=network-online.target
ConditionACPower=true

[Service]
Type=oneshot
ExecStart=/usr/local/bin/update-script.bash
  • Wants=network-online.target : waits for network to be available.

  • ConditionACPower=true : ensures power cable is plugged in before (you may want to comment it if it’s not necessary)

3. Systemd timer

A. Create the “update-script.timer

# nano /etc/systemd/system/update-script.timer

B. Add the following content:

[Unit]
Description=Timer for the custom update script service

[Timer]
OnCalendar=Tue,Fri *-*-* 18:00
RandomizedDelaySec=15m
WakeSystem=true
Persistent=true

[Install]
WantedBy=default.target
  • RandomizedDelaySec=5m : run the script randomly between 18:00 and 18:15, it’s useful when several computers share a weak connection.

  • WakeSystem=true : wake the computer from sleep to make it run the service.

  • Persistent=true : if the script cannot be launched (because the computer was off, for exemple) it will be relaunched during the next boot.

C. Enable the timer:

# systemctl enable --now update-script.timer
# systemctl daemon-reload

You can check the last/next run of the timer with:
# systemctl list-timers

If there is any problem, logs of the service/timer are stored in journald :
# journalctl -u update-script.service

Edit : But I’m not really sure of these options:

  • the “Type=oneshot” in the .service
  • the “WantedBy=default.target” in the .timer

Good solutions are already there, I just highly recommend the atomic variants, so much less troubles, especially when installing on other peoples PCs.