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
  • 104,119

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,264
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
  • 104,119