-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feauture: automatic apt updates via unattended-upgrades
#22515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feauture: automatic apt updates via unattended-upgrades
#22515
Conversation
83b983c to
5430d60
Compare
|
Hey @mchlwbr ! Thanks for the PR, we'll have a look once you think it's ready and help you with tests if necessary. Just a quick heads up - translations are done through Fedora Weblate and are periodically synced to here. Please don't directly edit translation files here. |
|
Thanks, will do!
I will drop the commit. I added it because of the documented translation process:
|
5430d60 to
745c9be
Compare
|
Ah, my bad then, sorry! Though it may get annoying to deal with rebases every week when we sync translations. |
unattended-upgradesunattended-upgrades
8466912 to
3d3635e
Compare
|
The feature itself is ready for review. I have rebased the PR on
I agree^^ |
jelly
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much for picking this up, this has been a long awaited feature from the community. Sorry for the long delay for an initial review.
pkg/packagekit/autoupdates.jsx
Outdated
| } else { | ||
| if (timerOutput.indexOf("InactiveSec=1d\n") >= 0) | ||
| this.day = this.time = ""; | ||
| else if (this.installed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be unreachable, we return directly if it is not installed. (same issue is in the dnf5 implementation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
pkg/packagekit/autoupdates.jsx
Outdated
| } | ||
|
|
||
| // Get unattended-upgrades configuration | ||
| const aptConfigOutput = await cockpit.script('apt-config dump | grep "Unattended-Upgrade"', [], { superuser: "require", err: "message" }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, verify that this is always installed on Debian.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's correct!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it to you to resolve threads once you're satisfied, ok?
pkg/packagekit/autoupdates.jsx
Outdated
| script += ' "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";\n'; | ||
| } else { | ||
| // All updates (security + regular) | ||
| script += ' "origin=Debian,codename=${distro_codename},label=Debian";\n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? Reading the periodic updates wiki, it says the default is only security updates and that is:
Unattended-Upgrade::Origins-Pattern {
// Codename based matching:
// This will follow the migration of a release through different
// archives (e.g. from testing to stable and later oldstable).
// Software will be the latest available for the named release,
// but the Debian release itself will not be automatically upgraded.
// "origin=Debian,codename=${distro_codename}-updates";
// "origin=Debian,codename=${distro_codename}-proposed-updates";
"origin=Debian,codename=${distro_codename},label=Debian";
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
// "o=Debian Backports,n=${distro_codename}-backports,l=Debian Backports";
// Archive or Suite based matching:
// Note that this will silently match a different release after
// migration to the specified archive (e.g. testing becomes the
// new stable).
// "o=Debian,a=stable";
// "o=Debian,a=stable-updates";
// "o=Debian,a=proposed-updates";
// "o=Debian Backports,a=stable-backports,l=Debian Backports";
};
So I think if you want "all updates" you also have to enable:
"origin=Debian,codename=${distro_codename}-updates";
And we also have to handle if a user has enabled backports I suppose and enable those updates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct.
We propose to set it to "origin=*"; when the user selects all. This way, all defined apt sources get used.
For security it'll be set to the quoted default:
"origin=Debian,codename=${distro_codename},label=Debian";
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
pkg/packagekit/autoupdates.jsx
Outdated
| // Determine upgrades type based on Origins-Pattern | ||
| const regularUpgrades = aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename},label=Debian"'); | ||
| const securityUpgrades = aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename},label=Debian-Security"') || | ||
| aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are very long lines and not to readable, I have some questions about the difference between all / security updates below. Hopefully we can simplify things after that is cleared up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've suggested to set origins=* for all updates below.
I will think about how the parsing of a given config can be refactored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
pkg/packagekit/autoupdates.jsx
Outdated
| script += "systemctl enable --now apt-daily.timer; "; | ||
| } else { | ||
| script += "systemctl disable --now apt-daily-upgrade.timer; "; | ||
| script += "systemctl disable --now apt-daily.timer; "; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two timers unfortunately but not both are overridden, when I configured them to update every Friday at 6:00.
Thu 2025-11-06 20:09:51 UTC 10h Fri 2025-10-31 13:36:59 UTC - apt-daily.timer apt-daily.service
Fri 2025-11-07 06:13:11 UTC 20h Thu 2025-11-06 10:00:39 UTC 47s ago apt-daily-upgrade.timer apt-daily-upgrade.service
root@debian-trixie-127-0-0-2-2201:~# systemctl cat apt-daily-upgrade.timer
# /usr/lib/systemd/system/apt-daily-upgrade.timer
[Unit]
Description=Daily apt upgrade and clean activities
After=apt-daily.timer
[Timer]
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/apt-daily-upgrade.timer.d/time.conf
[Timer]
OnCalendar=
OnCalendar=fri 06:00
root@debian-trixie-127-0-0-2-2201:~# systemctl cat apt-daily.timer
# /usr/lib/systemd/system/apt-daily.timer
[Unit]
Description=Daily apt download activities
[Timer]
OnCalendar=*-*-* 6,18:00
RandomizedDelaySec=12h
Persistent=true
[Install]
WantedBy=timers.target
Or is this expected? And does apt-daily just download updates so this wouldn't be an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apt-daily perfoms apt update as far as I understood it: https://salsa.debian.org/apt-team/apt/-/blob/main/debian/apt-daily.service
For consistency, they should likely both be set to the defined value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
mchlwbr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your review and sorry about the delay - I was on vacation :)
I will implement your suggestions.
Can you give me the OK for setting "origin=*"; when the user selects all updates?
pkg/packagekit/autoupdates.jsx
Outdated
| script += ' "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";\n'; | ||
| } else { | ||
| // All updates (security + regular) | ||
| script += ' "origin=Debian,codename=${distro_codename},label=Debian";\n'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct.
We propose to set it to "origin=*"; when the user selects all. This way, all defined apt sources get used.
For security it'll be set to the quoted default:
"origin=Debian,codename=${distro_codename},label=Debian";
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
pkg/packagekit/autoupdates.jsx
Outdated
| } | ||
|
|
||
| // Get unattended-upgrades configuration | ||
| const aptConfigOutput = await cockpit.script('apt-config dump | grep "Unattended-Upgrade"', [], { superuser: "require", err: "message" }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkg/packagekit/autoupdates.jsx
Outdated
| script += "systemctl enable --now apt-daily.timer; "; | ||
| } else { | ||
| script += "systemctl disable --now apt-daily-upgrade.timer; "; | ||
| script += "systemctl disable --now apt-daily.timer; "; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apt-daily perfoms apt update as far as I understood it: https://salsa.debian.org/apt-team/apt/-/blob/main/debian/apt-daily.service
For consistency, they should likely both be set to the defined value.
pkg/packagekit/autoupdates.jsx
Outdated
| // Determine upgrades type based on Origins-Pattern | ||
| const regularUpgrades = aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename},label=Debian"'); | ||
| const securityUpgrades = aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename},label=Debian-Security"') || | ||
| aptConfigOutput.includes('Unattended-Upgrade::Origins-Pattern:: "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've suggested to set origins=* for all updates below.
I will think about how the parsing of a given config can be refactored.
19a3e67 to
04682ea
Compare
04682ea to
acb2e3f
Compare
jelly
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fixes!
cockpituous
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are more than 10 code coverage comments, see the full report here.
|
Hi @jelly I'll get onto that and also extend the tests. |
| @@ -1460,14 +1466,16 @@ class TestAutoUpdates(NoSubManCase): | |||
|
|
|||
| def setUp(self): | |||
| super().setUp() | |||
| # not implemented for apt yet, only dnf | |||
| self.supported_backend = "dnf" in self.backend | |||
| self.supported_backend = "dnf" in self.backend or self.backend == "apt" | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One missing converted test is testWithAvailableUpdates it now fails with NotImplementedError.
Thanks! Feel free to ask for help |
cockpituous
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are more than 10 code coverage comments, see the full report here.
| if (time !== null || day !== null) { | ||
| if (day === "" && time === "") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 added lines are not executed by any test. Details
| if (time !== null || day !== null) { | ||
| if (day === "" && time === "") { | ||
| // restore defaults | ||
| script += `rm -f ${configFile}; `; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
| if (day == null) day = this.day; | ||
| if (time == null) time = this.time; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 added lines are not executed by any test. Details
| } | ||
| } | ||
| // Configure timer | ||
| script += this.generateTimerConfigScript(timerConf, day, time); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
| } | ||
| } | ||
|
|
||
| class AptImpl extends ImplBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
pkg/packagekit/autoupdates.jsx
Outdated
| originsPatterns = { | ||
| all: '"origin=*";', | ||
| stable: '"origin=Debian,codename=${distro_codename},label=Debian";', | ||
| security: '"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";', | ||
| securityAlt: '"origin=Debian,codename=${distro_codename},label=Debian-Security";' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 5 added lines are not executed by any test. Details
| async getConfig() { | ||
| this.packageName = "unattended-upgrades"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 added lines are not executed by any test. Details
| try { | ||
| await cockpit.spawn(["dpkg", "-l", this.packageName], { err: "ignore" }); | ||
| this.installed = true; | ||
| } catch (ex) { | ||
| this.installed = false; | ||
| debug("apt getConfig: not installed:", ex); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 7 added lines are not executed by any test. Details
| return; | ||
| } | ||
|
|
||
| try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
| const timerOutput = await cockpit.script( | ||
| "set -eu; " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 added lines are not executed by any test. Details
|
Hi @jelly I have added a WIP commit for ubuntu support based on unattended-upgrades config on ubuntu: For security updates i have opted to require all patterns. Could you please test it on ubuntu, @jelly ? Once that's sorted, I'll continue updating/fixing the test suite. Edit 1: I got the timer to work as expected (I had to remove |
|
I pushed the updated tests - could you please run the test-suite again, @jelly ? I dont find the test report with the failing tests anymore :/ FYI: I'll rebase and merge both WIP commits with the others once it's running. |
This is currently based on cockpit version 337 as we (Team LinOTP) develop/run on bookworm-backports and resolves #15269.
I will happily rebase on a branch of your choice to get the feature to
bookworm-backports.#18595 seems to be stale and instead of parsing the config files on our own, we rely on
apt-config dumpdoing the heavy lifting.