3

I've recently started working as an IT person in a all Linux company, and noticed some of the tasks we do can be 'easily' automated. Today's task to automate is installing software and configuring /etc/apt/apt.config.d/50unattended-upgrades on new computers. I've already written the software-installing script, but now I'm stuck on uncommenting the desired unattended-upgrades lines.

I'll give you an example, this:

// Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
        "${distro_id}:${distro_codename}";
        "${distro_id}:${distro_codename}-security";
        // Extended Security Maintenance; doesn't necessarily exist for
        // every release and this system may not have it installed, but if
        // available, the policy for updates is such that unattended-upgrades
        // should also install from here by default.
        "${distro_id}ESMApps:${distro_codename}-apps-security";
        "${distro_id}ESM:${distro_codename}-infra-security";
//      "${distro_id}:${distro_codename}-updates";                             
//      "${distro_id}:${distro_codename}-proposed";
//      "${distro_id}:${distro_codename}-backports";
};

should look like this:

// Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
        "${distro_id}:${distro_codename}";
        "${distro_id}:${distro_codename}-security";
        // Extended Security Maintenance; doesn't necessarily exist for
        // every release and this system may not have it installed, but if
        // available, the policy for updates is such that unattended-upgrades
        // should also install from here by default.
        "${distro_id}ESMApps:${distro_codename}-apps-security";
        "${distro_id}ESM:${distro_codename}-infra-security";
        "${distro_id}:${distro_codename}-updates";                             
//      "${distro_id}:${distro_codename}-proposed";
//      "${distro_id}:${distro_codename}-backports";
};

(the updates line should be uncommented)

I've tried to do it with sed, but it just isn't working, probably because I'm a total n00b. Here is my clumsy sed line so someone can, hopefully, explain to me what I am doing wrong!

#!/bin/bash
sudo sed -i 's@//      "${distro_id}:${distro_codename}-updates"@        "${distro_id}:${distro_codename}-updates"@' /etc/apt/apt.conf.d/50unattended-upgrades

Any help would be greatly apreciated! Have a nice day!

terdon
  • 100,812
  • How is it failing? That sed command can certainly be improved, yes, but it should work. – terdon May 06 '22 at 15:01
  • Thank you for taking the time to answer and editing my question! It just won't change the file! I execute the script with sudo privileges and i get no output (as expected) but then i nano /etc/apt/apt.config.d/50unattended-upgrades and the line is still commented! – Marcos Delfino May 06 '22 at 15:18
  • 1
    The approach to the problem seems contrary to how apt config files are designed. Apt config is designed to that you can supersede any option with your own later-running config file. You shouldn't need to programmatically edit apt config files. You can edit the existing files if you like, but it seems more headache than is worthwhile. – user535733 May 06 '22 at 15:20
  • @MarcosDelfino I don't know, I can't reproduce that, it works fine on your example. You probably have one more space than expected or something like that. – terdon May 06 '22 at 15:24

2 Answers2

9

Why not just create another file /etc/apt/apt.config.d/51my-unattended-upgrades with the content:

Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-updates";
};

The options are merged together. Check it with apt-config dump.

Additionally it is easier to remove your changes by just removing the single file. And any upgrade changing /etc/apt/apt.conf.d/50unattended-upgrades will not break your changes.

Better never change config files of others if not needed.

Try of an explanation:

From the python source of unattended-upgrade you can see, that it does not parse the config files in /etc/apt/.... Instead it uses the python-apt-api. It is similar to use apt-config in the shell (which you should always do instead of reading the config files).

In this special case, the python-apt-api does the merging of all the files and returns a list of Origins-Pattern and the unattended-upgrade script loops over all of them.

I don't know of any good docu about this. Best you can do is looking into the source.

Marco
  • 1,188
  • @Marco thank you so much, this actually solves everything, although i'm not sure of what's happening in this solution, how did apt interpreted these were instructions for unattended-upgrades if the name is different from the original config file? Can you please tell me where can i read more about it or how to search it? Thank you a lot! – Marcos Delfino May 06 '22 at 15:34
  • 2
    It shockingly simple: Apt loads each config file in sequence. YOU control the sequence -- that's what the number in the filename does. Newer settings, if they exist, overwrite older settings. So file 50xxx says A=0, then file 51xxx overwrites that with A='sheep', then file 99xxx runs last and overwrites with A='purple' – user535733 May 06 '22 at 18:05
5

Please see Marco's answer, that's The Right Way To Do It®, I am focusing only on the sed and regular expression issues here.


The first rule of working with regular expressions is "less is more". Don't try to match the entire line, instead use the smallest possible regex that catches what you need to catch. Here, for instance, you don't care about the empty spaces at the beginning of the line, so don't bother trying to match them. Plus, the number of spaces is very likely to change and you don't want that to break your script, so ignore them!

As far as I can tell, all you really ant here is to remove a leading // from any lines that contain the string -updates. If so, all you need is:

sed '/-updates/{s@^\s*//@@}' /etc/apt/apt.conf.d/50unattended-upgrades

This means "if this line matches -updates, then replace 0 or more leading whitespace characters (the comment doesn't have to be the first character of the line) followed by // with nothing". So this will remove the leading // from lines matching -updates.

As you can see, this is much easier to write and to understand. That said, your original command works as expected with your example file, it is just unnecessarily complicated and fragile (it breaks if whitespace is added):

$ sed 's@//      "${distro_id}:${distro_codename}-updates"@        "${distro_id}:${distro_codename}-updates"@' file 
/ Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
        "${distro_id}:${distro_codename}";
        "${distro_id}:${distro_codename}-security";
        // Extended Security Maintenance; doesn't necessarily exist for
        // every release and this system may not have it installed, but if
        // available, the policy for updates is such that unattended-upgrades
        // should also install from here by default.
        "${distro_id}ESMApps:${distro_codename}-apps-security";
        "${distro_id}ESM:${distro_codename}-infra-security";
        "${distro_id}:${distro_codename}-updates";                             
//      "${distro_id}:${distro_codename}-proposed";
//      "${distro_id}:${distro_codename}-backports";
};

Finally, a couple of general observations:

  • never use sed -i on importan files without taking a backup. The -i option allows you to specify a backup suffix, so if you run sed -i.bak 's/a/b/' file, that will copy the original as file.bak. So you should do something like sudo sed -i.bak ... in your case, so you can always undo things.
  • It is very rarely a good idea to have sudo inside your script. Instead, write the script without sudo and then call it with sudo with sudo script.sh.
terdon
  • 100,812