There are many guides to upgrading Debian and Debian derived systems. This one is mine.
As with painting a house the quality of the result is mostly on the quality of the preparation for painting. Preparing a system for upgrade is the same. It is the preparation that most influences the quality of the result.
At this time I am working through an upgrade of Trisquel 9 to Triquel 10 and am using that as the working example for the documentation here.
The first thing to do is to read the release notes for the new system. If there are special handling instructions then those must be followed. Historically while most upgrades have been generic there have been upgrades that required stepping the Linux kernel and udev forward in a particular ordering. And in the changing of init systems there have been requirements about the ordering of particular package upgrades. Always read the release notes and pay attention to any needed special handling.
The etckeeper package is a utility to keep changes of /etc files in a version control system such as git. Using git is most popular but others can also be used. Then all changes to /etc can be seen in the version history. This means that one can "purge" packages without fear of the loss of any particular file. Since the changes will all be in version control. One can also rm any of those /etc files too. Since the changes will all be in version control. It's a great safety net.
The git version control system is most popular. However git wants to know something about the user making commits. In this case the user is the root user. Therefore let's set up the root user for git first before even installing either git or etckeeper. Write this file to the root .gitconfig file.
cat >/root/.gitconfig <<EOF
[user]
name = root
email = root@localhost
EOF
Alternatively there is now a git command to make git configuration modifications.
git config --global user.name root
git config --global user.email root@localhost
Since root is actually a pseudo-user I prefer to use generic
information for the generic user. This is the default information
that is logged in the commit log when no other overriding author
information is provided. Admins that wish to override this may use
the GIT_AUTHOR_NAME
and GIT_AUTHOR_EMAIL
and related env
variables. Since that is then set up ensure that git is installed and
then install etckeeper.
apt-get install git
apt-get install etckeeper
At that point upkeep is automated. One can forget that it is there for the most part. All commits are then automated. To examine the commit log use normal git commands.
cd /etc
git log
This is very useful. I strongly recommend it. It creates a safety net such that then one can do cleanup and other modifications without fear of losing track of those files and changes.
The dpkg package manager tracks files in /etc as "conffiles". These are tracked such that if dpkg believes the file has NOT been locally modified that it automatically upgrade the file on package upgrade. However if dpkg believes the file has been modified locally then it will present a question as to how to handle it. Does the admin want to keep the current file? If so then the new file will be installed with a ".dpkg-new" suffix. Does the admin want to upgrade to the new package maintainer's file? If so then the new file will be installed and the old file will be moved to a ".dpkg-old" suffix. This makes it easy in either case for the admin to locate and further take action about these files.
Additionally some packages in the maintainer scripts will use the name convention ".dpkg-dist" and add custom management. An example package that does this is the bind9 package. The use of ".dpkg-dist" is similar to ".dpkg-new" but outside the control of dpkg since it is custom handling added by the package maintainer.
Some people did not like the handling provided by dpkg. They wanted more features in the upgrade. Also some packages wanted to do their own handling of /etc files which by Debian Policy needed to have local modifications preserved. The "ucf" utility was created for this purpose. See the "man ucf" page for documentation. Enough to say that ucf is like dpkg but different. Here we only need to know that it uses ".ucf-new" and ".ucf-old" suffixes instead of ones using the ".dpkg-*" string. The cleanup of these is the same.
find /etc -name '*.dpkg-*'
find /etc -name '*.ucf-*'
...I usually type in the simple form above...
find /etc \( -name '*.dpkg-*' -o -name '*.ucf-*' \) -print
...but one can combine the find into one command...
That will print the list of these files that exist. Review the list. Handle them all. Which means that if features should be merged then merge them. Or if files should be removed then simply remove them.
Most of the ".dpkg-old" and ".ucf-old" files should simply be removed as part of the cleanup task. Those are older versions of the configuration files that were not used and are not being used. The upcoming upgrade will create more of these files. We want to clean the system of these files from previous upgrades so that we don't confuse what has happened a decade ago with something that has just now happened as part of the current upgrade.
find /etc \( -name '*.dpkg-old' -o -name '*.ucf-old' \) -print
If that list is okay then remove them.
find /etc \( -name '*.dpkg-old' -o -name '*.ucf-old' \) -delete
Look at new version of the file that are left.
find /etc \( -name '*.dpkg-*' -o -name '*.ucf-*' \) -print
New versions of these files need attention. These are new package maintainer's versions of the configuration files that upon upgrade the admin said to retain the old version of the file. Therefore the incoming new file was placed with a ".dpkg-new" or ".dpkg-dist" name. If one were installing a new pristine system then these are the new versions of the file that would have been installed. Since we want to leave the upgraded system looking as closely to a newly installed system as possible then we need to install these versions of these files. This usually means merging local changes into the new version of the file and then replacing the current file with the merged file.
Example. In a previous version of the sudo package the sudoers file
did not include the secure_path
option. Then the binary executable
changed to require it and the package maintainer's version started
including it. At that upgrade admins were presented with the option
to keep their previous sudoers file or switch to the new sudoers file.
I daresay that many admins said to keep their existing file. And were
snagged by now missing the secure_path option! The result was that
when they ran sudo that they no longer had /usr/sbin:/sbin in PATH.
The fix for this was to merge in their local changes into the new
version of the sudoers configuration file.
That example is a good reason why nice packages with configuration
files provide a way to add local configuration in a different file
that is not tracked. A file specifically for the local admin to
modify. That way it will not need to be merged in any future package
upgrade. In this case sudo includes all files in /etc/sudoers.d/*
making that the best place for local configuration. I suggest using a
name with "local" in the name so that we can easily tell that it is a
local downstream configuration. /etc/sudoers.d/sudoers-local
for
example. Then that file will never need merging. The nicer packages
all make this type of configuration available. Use it.
The /etc directory should be cleaned of all '.dpkg-' files before upgrading. And always checked for required merges after upgrading.
Debian's dpkg package manager has an interesting and useful feature that removing a package leaves the /etc configuration files behind. This allows installing the package again and immediately having the same configuration files as before. This means that if testing two incompatible programs installing one may push out the other one and allows re-installing it again and immediately being back to the same state as before. Those /etc configuration files that are managed by the dpkg package manager have the special name "conffile". This is useful. But in order for the package manager to continue to manage those conffiles even after the package has been removed it means that removing the package does not actually remove EVERYTHING about the package. The package remains installed but in the removed-config state or "rc" state where the package has been removed but conffiles remain installed.
You can list packages on the system that are in this "rc" state.
dpkg -l | grep ^rc
Another way to list these packages using the grep-status command.
Install the dctrl-tools
package to get this useful utility.
grep-status -sPackage -n -FStatus 'deinstall ok config-files'
Especially if a system has been operating for a long time and upgraded there may be quite a few packages in the rc state. These create obstacles for the upgrade process because in addition to the current packages and current package dependencies these older packages and older package dependencies also must all be considered when performing an upgrade.
These packages need to be purged in order to fully remove them from the system. Debian's dpkg differentiates the actions "remove" and "purge" for this. With dpkg "remove" will remove the non-conffile files, the files not in /etc, but will leave the conffiles in /etc on the system and will leave the package otherwise listed as installed in order to manage those conffiles. The "purge" action will remove those conffiles and remove the package from the listing. And of course that also opens up any packages in the Depends relation for removal too.
grep-status -sPackage -n -FStatus 'deinstall ok config-files'
Review the list of removed-config packages and if okay then purge them all using this command.
dpkg --purge $(grep-status -sPackage -n -FStatus 'deinstall ok config-files')
After these have been purged then other packages may now become candidates for autoremoval. If a package was installed as a dependency of another package then it will be an automatically installed package. Automatically installed packages that no longer have a dependency keeping them installed are now candidates for autoremoval. And when we autoremove we want to use the --purge option so that packages are purged and we avoid creating more packages in the "rc" state and needing to clean those out again.
apt-get autoremove --purge
Example.
root@frontend1:~# apt-get autoremove --purge
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
fonts-dejavu* fonts-dejavu-extra* g++-5* gcc-6-base* imagemagick-6.q16*
libapache2-mod-php7.0* libassuan-dev* libconfig-file-perl* libgpg-error-dev*
libpython3.5-minimal* libpython3.5-stdlib* libstdc++-5-dev* libxtables11*
linux-image-unsigned-4.4.0-197-generic* linux-modules-4.4.0-197-generic*
lua-lpeg* php7.0* php7.0-cli* php7.0-common* php7.0-fpm* php7.0-gd*
php7.0-json* php7.0-mysql* php7.0-opcache* php7.0-readline* python-chardet*
python-debian* python-debianbts* python-httplib2* python-pkg-resources*
python-pycurl* python-pysimplesoap* python-reportbug* python-setuptools*
python-six* python3.5* python3.5-minimal*
0 upgraded, 0 newly installed, 37 to remove and 0 not upgraded.
After this operation, 156 MB disk space will be freed.
Do you want to continue? [Y/n]
(Reading database ... 72633 files and directories currently installed.)
Removing fonts-dejavu (2.37-1) ...
Removing fonts-dejavu-extra (2.37-1) ...
Removing g++-5 (5.5.0-12ubuntu1) ...
Removing gcc-6-base:amd64 (6.5.0-2ubuntu1~18.04) ...
Removing imagemagick-6.q16 (8:6.9.7.4+dfsg-16ubuntu6.13) ...
Removing php7.0 (7.0.33-0ubuntu0.16.04.16) ...
Removing libapache2-mod-php7.0 (7.0.33-0ubuntu0.16.04.16) ...
apache2_invoke php7.0 prerm: No action required
Removing libassuan-dev (2.5.1-2) ...
Removing libconfig-file-perl (1.50-3) ...
Removing libgpg-error-dev (1.27-6) ...
Removing python3.5 (3.5.2-2ubuntu0~16.04.13) ...
Removing libpython3.5-stdlib:amd64 (3.5.2-2ubuntu0~16.04.13) ...
Removing python3.5-minimal (3.5.2-2ubuntu0~16.04.13) ...
Unlinking and removing bytecode for runtime python3.5
Removing libpython3.5-minimal:amd64 (3.5.2-2ubuntu0~16.04.13) ...
Removing libstdc++-5-dev:amd64 (5.5.0-12ubuntu1) ...
Removing libxtables11:amd64 (1.6.0-2ubuntu3) ...
Removing linux-image-unsigned-4.4.0-197-generic (4.4.0-197.229+8.0trisquel3) ...
/etc/kernel-img.conf:5: W: ignoring unknown parameter do_boot_enable
/etc/kernel/postrm.d/initramfs-tools:
update-initramfs: Deleting /boot/initrd.img-4.4.0-197-generic
Removing linux-modules-4.4.0-197-generic (4.4.0-197.229+8.0trisquel3) ...
Removing lua-lpeg:amd64 (1.0.0-2ubuntu0.18.04.1) ...
Removing php7.0-fpm (7.0.33-0ubuntu0.16.04.16) ...
apache2_invoke php7.0-fpm prerm: No action required
Removing php7.0-cli (7.0.33-0ubuntu0.16.04.16) ...
Removing php7.0-mysql (7.0.33-0ubuntu0.16.04.16) ...
Removing php7.0-json (7.0.33-0ubuntu0.16.04.16) ...
Removing php7.0-gd (7.0.33-0ubuntu0.16.04.16) ...
Removing php7.0-opcache (7.0.33-0ubuntu0.16.04.16) ...
Removing php7.0-readline (7.0.33-0ubuntu0.16.04.16) ...
Removing python-reportbug (6.6.6ubuntu1) ...
Removing python-debian (0.1.32) ...
Removing python-chardet (3.0.4-1+ubuntu16.04.1+certbot+2) ...
Removing python-debianbts (2.7.2) ...
Removing python-pysimplesoap (1.16-2) ...
Removing python-httplib2 (0.9.2+dfsg-1ubuntu0.3) ...
Removing python-setuptools (39.0.1-2) ...
Removing python-pkg-resources (39.0.1-2) ...
Removing python-pycurl (7.43.0.1-0.2) ...
Removing python-six (1.11.0-2) ...
Removing php7.0-common (7.0.33-0ubuntu0.16.04.16) ...
Processing triggers for install-info (6.5.0.dfsg.1-2) ...
Processing triggers for libc-bin (2.27-3ubuntu1.6) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for hicolor-icon-theme (0.17-2) ...
Processing triggers for fontconfig (2.12.6-0ubuntu2) ...
Processing triggers for mime-support (3.60ubuntu1) ...
(Reading database ... 69300 files and directories currently installed.)
Purging configuration files for php7.0-mysql (7.0.33-0ubuntu0.16.04.16) ...
Purging configuration files for php7.0-readline (7.0.33-0ubuntu0.16.04.16) ...
Purging configuration files for linux-modules-4.4.0-197-generic (4.4.0-197.229+8.0trisquel3) ...
dpkg: warning: while removing linux-modules-4.4.0-197-generic, directory '/lib/modules/4.4.0-197-generic' not empty so not removed
Purging configuration files for php7.0-opcache (7.0.33-0ubuntu0.16.04.16) ...
Purging configuration files for libapache2-mod-php7.0 (7.0.33-0ubuntu0.16.04.16) ...
apache2_invoke postrm: Purging state for php7.0
dpkg: warning: while removing libapache2-mod-php7.0, directory '/etc/php/7.0/apache2/conf.d' not empty so not removed
Purging configuration files for php7.0-gd (7.0.33-0ubuntu0.16.04.16) ...
Purging configuration files for php7.0-cli (7.0.33-0ubuntu0.16.04.16) ...
dpkg: warning: while removing php7.0-cli, directory '/etc/php/7.0/cli/conf.d' not empty so not removed
Purging configuration files for php7.0-fpm (7.0.33-0ubuntu0.16.04.16) ...
apache2_invoke php7.0-fpm postrm: No action required
dpkg: warning: while removing php7.0-fpm, directory '/etc/php/7.0/fpm/conf.d' not empty so not removed
Purging configuration files for python3.5-minimal (3.5.2-2ubuntu0~16.04.13) ...
Purging configuration files for php7.0-json (7.0.33-0ubuntu0.16.04.16) ...
Purging configuration files for linux-image-unsigned-4.4.0-197-generic (4.4.0-197.229+8.0trisquel3) ...
/etc/kernel-img.conf:5: W: ignoring unknown parameter do_boot_enable
Purging configuration files for php7.0-common (7.0.33-0ubuntu0.16.04.16) ...
dpkg: warning: while removing php7.0-common, directory '/etc/php/7.0' not empty so not removed
Purging configuration files for libpython3.5-minimal:amd64 (3.5.2-2ubuntu0~16.04.13) ...
Processing triggers for systemd (237-3ubuntu10.43) ...
Processing triggers for ureadahead (0.100.0-21) ...
In addition to autoremove I find using "deborphan" to be another good tool for more cleaning. Install deborphan. Run it. Use it to do more cleaning. The deborphan tool is an old tool. It's utility has mostly been replaced by autoremove. But it is still useful and still almost always finds more packages that are good candidates for purging.
deborphan
dpkg --purge $(deborphan)
Repeat the above sequence until there are no more packages listed from deborphan. After purging some things listed by deborphan it will then open up additional packages as candidates. Keep repeating until the list is reduced to zero packages being listed as orphans. In some cases you may need to preserve one of the listed packages if it is an orphan but just the same one that you want to preserve.
Some packages have gone away over time. They just don't exist anymore. They will never be upgraded again. These create obstacles to the system since if they don't ever get upgraded the shared libraries that they use are also stuck. These packages need to be removed in order to allow the upgrade to occur without entanglements with ancient libraries and other dependencies.
Some packages have encoded version numbers. The emacs packages are examples of ones with an included version number such as emacs21 and so on from there. These will never get upgraded and their presence can cause problems. For example emacs shares some of the files in a common package to be shared across versions. But really old versions then cause failures due to the use of newer features. These packages need to be removed in order to allow the upgrade to occur without entanglements with ancient libraries and other dependencies.
The easiest way to list these is with apt-show-versions
. Install
that utility and use it to list packages without an installation
source. Most packages will show as "uptodate" and so filtering those
out will show all of the rest of the packages that are interesting.
Specifically listing out packages without an install source are the
ones we want to know about.
apt-show-versions | grep -v -e uptodate
apt-show-versions | awk '/No available version in archive/{print$1}'
That will also list a few packages that we want to keep for a bit longer. Older Linux kernels that are installed for example. Those don't get upgraded but are useful to provide a safety net for rebooting back to if needed. Therefore some intelligence and filtering needs to be mentally applied to this list. Also any locally created packages will be listed if they have been installed as a package but do not have an associated repository. Review. Purge packages that these identify and that you agree should be cleaned up from the system.
Example.
root@frontend1:~# apt-show-versions | grep -v -e uptodate
emacs24-bin-common:amd64 24.5+1-6ubuntu1.1 installed: No available version in archive
emacs24-common:all 24.5+1-6ubuntu1.1 installed: No available version in archive
emacs24-common-non-dfsg:all 24.4+1-2+8.0trisquel2 installed: No available version in archive
emacs24-el:all 24.5+1-6ubuntu1.1 installed: No available version in archive
emacs24-nox:amd64 24.5+1-6ubuntu1.1 installed: No available version in archive
gcc-4.9-base:amd64 4.9.3-13ubuntu2 installed: No available version in archive
initscripts:amd64 2.88dsf-59.3ubuntu2 installed: No available version in archive
insserv:amd64 1.14.0-5ubuntu3 installed: No available version in archive
libcgmanager0:amd64 0.39-2ubuntu5 installed: No available version in archive
libreadline6:amd64 6.3-8ubuntu2 installed: No available version in archive
libreadline6-dev:amd64 6.3-8ubuntu2 installed: No available version in archive
libtasn1-6:amd64 4.13-2+8.0trisquel1 newer than version in archive
linux-image-unsigned-4.4.0-210-generic:amd64 4.4.0-210.242+8.0trisquel4 installed: No available version in archive
linux-modules-4.4.0-210-generic:amd64 4.4.0-210.242+8.0trisquel4 installed: No available version in archive
mountall:amd64 2.54ubuntu1 installed: No available version in archive
python-gpgme:amd64 0.3-1.1 installed: No available version in archive
sysv-rc:all 2.88dsf-59.3ubuntu2 installed: No available version in archive
In this case all of those packages are okay candidates for removal. If a new system were installed then none of those would be installed. But this makes a good example of a problem that needs manually handling. Because look at libtasn1-6! It is listed as "newer than version in archive". WAT?! How can that happen?
This is an odd Trisquel problem.
root@frontend1:~# apt-show-versions | grep libtasn1-6
libtasn1-6:amd64 4.13-2+8.0trisquel1 newer than version in archive
root@frontend1:~# apt-cache policy libtasn1-6
libtasn1-6:
Installed: 4.13-2+8.0trisquel1
Candidate: 4.13-2+8.0trisquel1
Version table:
*** 4.13-2+8.0trisquel1 100
100 /var/lib/dpkg/status
4.13-2 500
500 http://mirror.fsf.org/trisquel etiona/main amd64 Packages
It looks like it went forward and then backward in the upstream Trisquel repository? This is an unusual Trisquel specfic case. Let's manually walk it back to something that matches what is in the repository.
root@frontend1:~# apt-get install libtasn1-6=4.13-2
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be DOWNGRADED:
libtasn1-6
0 upgraded, 0 newly installed, 1 downgraded, 0 to remove and 0 not upgraded.
Need to get 36.2 kB of archives.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n]
Get:1 http://mirror.fsf.org/trisquel etiona/main amd64 libtasn1-6 amd64 4.13-2 [36.2 kB]
Fetched 36.2 kB in 0s (165 kB/s)
dpkg: warning: downgrading libtasn1-6:amd64 from 4.13-2+8.0trisquel1 to 4.13-2
(Reading database ... 60111 files and directories currently installed.)
Preparing to unpack .../libtasn1-6_4.13-2_amd64.deb ...
Unpacking libtasn1-6:amd64 (4.13-2) over (4.13-2+8.0trisquel1) ...
Setting up libtasn1-6:amd64 (4.13-2) ...
Processing triggers for libc-bin (2.27-3ubuntu1.6) ...
Now that problem is handled we return to the issue of removing things that are now no longer in the repository.
root@frontend1:~# apt-show-versions | awk -F: '!/uptodate/{print$1}'
emacs24-bin-common
emacs24-common
emacs24-common-non-dfsg
emacs24-el
emacs24-nox
gcc-4.9-base
initscripts
insserv
libcgmanager0
libreadline6
libreadline6-dev
linux-image-unsigned-4.4.0-210-generic
linux-modules-4.4.0-210-generic
mountall
python-gpgme
sysv-rc
root@frontend1:~# apt-get purge $(apt-show-versions | awk -F: '!/uptodate/{print$1}')
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
...
root@frontend1:~# apt-show-versions | awk -F: '!/uptodate/{print$1}'
root@frontend1:~#
And now it is clean for those types of files.
This is one of the more confusing aspects of packages and managing packages. This is due to buggy maintenance of a package leaving behind "obsolete conffiles".
An obsolete file is one that was owned by a previous version of the package, removed from a later version of the package, and then left behind when upgraded. These files are marked by dpkg as obsolete files in the package file listing. They are obsolete because if you were to install a pristine system with the new version of the package that conffile would not be installed. But when doing an upgrade if a previous version of the package contained the conffile and if the package does not handle the removal of it properly then this conffile is left behind as lint.
Here is some mailing list discussion for reference.
These commands will list out these files.
dpkg-query -W -f='${Conffiles}\n' | grep obsolete
dpkg-query -W -f='${Package} ${Conffiles}\n' | less +/obsolete
That latter one is most useful because it lists out both the package as well as the file that is marked as obsolete. Browse that list. Unfortunately as Paul noted in that message from 2013(!) there are still quite a few packges with these bugs in them. And here it is years and years later and we are still fighting with the exact same bugs and problems!
Browsing this list will show the files remaining behind that were included with a previous version of a package but are now no longer included. These fall into a couple of different cases that are useful to treat differently.
Moved /etc Files
These files are harder to deal with because they were removed from one package and added to a different package. Simply removing them solves the problem of the obsolete conffile but creates a problem of a missing conffile for the other package that now includes it. Therefore one cannot simply remove it. Instead one must temporarily move it out of the way to a save location, then reinstall the package claiming it is obsolete, then move the file back into place.
I must talk about moved files first. Because otherwise we don't know if the file was moved and we remove it incorrectly. But in order for a file to be in both it will need to be listed as both obsolete for one package and not obsolete for another package.
apt-show-versions /etc/apt/apt.conf.d/20apt-show-versions e02489f6adb594c2609841ba130345fe
/etc/cron.daily/apt-show-versions ceccc6292ad7c2e762bd04f0bee3bf98
/etc/bash_completion.d/apt-show-versions db61f00b9796596527fb9510bacc681c obsolete
root@frontend1:~# dpkg -S /etc/bash_completion.d/apt-show-versions
apt-show-versions: /etc/bash_completion.d/apt-show-versions
That file only belongs to the apt-show-versions package. Therefore it is not moved. It has only been removed. This is the simpler case. Just remove it and reinstall the package.
root@frontend1:~# rm -f /etc/bash_completion.d/apt-show-versions
root@frontend1:~# apt-get install --reinstall apt-show-versions
If the file is owned by multiple packages however then we have to not remove it but instead move it to a saved location, reinstall the package marking it as obsolete, then move the file back to continue to be part of the other package. I apologize for not capturing a live sample but the strategy looks like this.
dpkg -S /etc/someobsoletefile
foo: /etc/someobsoletefile
bar: /etc/someobsoletefile
In this case the file was incorrectly moved from one package to another package. The new location is okay. But the old location is marked as obsolete.
There are two ways to fix this problem. Previously I used the method
as described in the mailing list messages cited above. But now I use
an alternate method which is easier. In this case even trivial. In
this case we only need to remove the line describing the file as
obsolete and leave the line in place for the new entry in the
/var/lib/dpkg/status
file. That will be enough to complete the
migration of the file from one package to the other successfully.
sed --in-place '@^ /etc/someobsoletefile .*obsolete$@d' /var/lib/dpkg/status
That will look for the the entry in the status file. The entry is always offset by a single space. It will be followed by a hash that we skip using ".*". It will be marked with an "obsolete" at the end of line. Delete this line. It will leave the other entry NOT marked with "obsolete" in place.
Removed Files
Init files are most common examples. With the migration to systemd
(love it or hate it) many package maintainers have summarily removed
the /etc/init.d/*
files from the package without handling the
removal properly. This leaves those init scripts behind. This also
includes init scripts from "upstart" which is used in Ubuntu. If you
are also drinking the Kool-Aid of systemd then you will not be using
any of these init scripts again. In which case removing these files
is always the correct answer. A pristine installation would not
include them. However if you are counting on that init script then
move it out of the way, reinstall the package, and move the init
script back. It is something the local sysadmin would know.
Here is a simple example with an upstart init script.
xinetd
...
/etc/init/xinetd.conf 8b8bc68acf27ae09755d71b09d3ddab4 obsolete
Mitigation.
root@frontend1:~# dpkg -S /etc/init/xinetd.conf
xinetd: /etc/init/xinetd.conf
Only one package owns this file.
root@frontend1:~# rm -f /etc/init/xinetd.conf
root@frontend1:~# apt-get install --reinstall xinetd
Reading package lists... Done
Building dependency tree
Reading state information... Done
0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 0 not upgraded.
Need to get 108 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://mirror.fsf.org/trisquel etiona/main amd64 xinetd amd64 1:2.3.15.3-1 [108 kB]
Fetched 108 kB in 1s (106 kB/s)
[master 00aad4c] saving uncommitted changes in /etc prior to apt run
2 files changed, 54 deletions(-)
delete mode 100644 init/xinetd.conf
(Reading database ... 72637 files and directories currently installed.)
Preparing to unpack .../xinetd_1%3a2.3.15.3-1_amd64.deb ...
Unpacking xinetd (1:2.3.15.3-1) over (1:2.3.15.3-1) ...
Setting up xinetd (1:2.3.15.3-1) ...
Processing triggers for systemd (237-3ubuntu10.43) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for ureadahead (0.100.0-21) ...
Alternatively one can use the line deletion mitigation described for the moved file. As you can see this is faster and perhaps simpler. But the install --reinstall is also not complicated. Choose whichever mitigation recipe you prefer here.
root@frontend1:~# rm -f /etc/init/xinetd.conf
root@frontend1:~# sed --in-place '@^ /etc/init/xinetd.conf .*obsolete$@d' /var/lib/dpkg/status
Random /etc files. These are all unique one-off cases that need to be looked at individually. There are a lot of files and very likely they are not being used. But it is possible that you are using one of those files, perhaps even customized one of those files. If so then removing the file would remove part of your needed customization.
Review the file. Understand if you need it. Determine if it was moved from one package to another and left behind in the old one.
Here is a simple example.
base-files
/etc/update-motd.d/50-motd-news 1008a5c52c15ce6f484da4ff9c36d294 obsolete
Mitigation. Here is the official fix process.
root@frontend1:~# dpkg -S /etc/update-motd.d/50-motd-news
base-files: /etc/update-motd.d/50-motd-news
It is associated with only one package. It has not been incorrectly moved from one package to another. It was simply removed incorrectly. The fix is to remove the file and then reinstall it.
root@frontend1:~# rm -f /etc/update-motd.d/50-motd-news
root@frontend1:~# apt-get install --reinstall base-files
Reading package lists... Done
Building dependency tree
Reading state information... Done
0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 0 not upgraded.
Need to get 58.4 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://mirror.fsf.org/trisquel etiona-updates/main amd64 base-files amd64 1:10.1ubuntu2.11+9.0trisquel3 [58.4 kB]
Fetched 58.4 kB in 0s (2,398 kB/s)
[master 8f9d49a] saving uncommitted changes in /etc prior to apt run
2 files changed, 147 deletions(-)
delete mode 100755 update-motd.d/50-motd-news
(Reading database ... 72636 files and directories currently installed.)
Preparing to unpack .../base-files_1%3a10.1ubuntu2.11+9.0trisquel3_amd64.deb ...
Unpacking base-files (1:10.1ubuntu2.11+9.0trisquel3) over (1:10.1ubuntu2.11+9.0trisquel3) ...
Setting up base-files (1:10.1ubuntu2.11+9.0trisquel3) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for install-info (6.5.0.dfsg.1-2) ...
Then look for another file. Remove it. Reinstall. Repeat until all of the obsolete conffiles have been worked through. This manual handling is rather tedious. There may be quite a few files.
Finding out how many of these files need to be fixed.
root@frontend1:~# dpkg-query -W -f='${Package} ${Conffiles}\n' | grep -c obsolete
26
Ugh! Well let's grind through those and get done with them.
Look for packages named as transitional or dummy packages. These are cases where the names of the package has been changed. Sometimes packages have been reorganized.
dpkg -l | grep -e transition -e dummy
Review the list. Remove the transitional and dummy packages. But be aware that the grep might match on other packages that should not be removed. Also in some cases an OS release is made which is buggy and even though these packages are transitional packages other packages may depend upon them. In which case they cannot be removed without also removing the other packages that we want installed.
ii libncurses5-dev:amd64 6.1+20181013-2+deb10u2 amd64 transitional package for libncurses-dev
apt-get purge libncurses5-dev
This part is of historical interest on systemd systems. On non-systemd systems however this is still an important step.
Look for files in /etc/init.d/
that are not part of any package but
may not have LSB headers. If not then the upgrade to insserv will
fail. Find those and enumerate them first.
dpkg -S $(find /etc/init.d -type f -perm /a+x) 2>&1 >/dev/null | awk '/not found/{print$2}'
Each of those files are probably some locally installed file that is not part of any package. Ensure that they contain LSB headers so that the "insserv" program can work with them without an error.
All of the above steps are important preparation for an upgrade. Whew! If you made it here then you have made it through a long preparation and cleaning process! Congratulations!
This process basically follows as outlined in the release notes. Read them again. Then proceed!
However a generic upgrade process can be outlined and most of the time this is the process we use.
List any held packages.
apt-mark showhold
That will list any packages that are explicitly held to prevent upgrades. Examine that list. Can they be released? At the least be aware of these. At the most unhold them and let them upgrade.
For an example the mercurial package is currently held due to incompatible changes in a security patch upgrade. The issues of the security issues did not apply to our case therefore not taking the upgrade was not a problem. But the mitigation for the issue was completely showstopping breaking. We still do not know what we are going to do about this long term.
apt-mark unhold foopackage
List package names that were manually installed.
apt-mark showmanual
apt-mark showmanual | wc -l
509
apt-mark showmanual | less
That might be a fairly long list. Sometimes people manually install something in order to fix an installation issue. Or for other reasons. And sometimes they don't really want it to be manually installed. These can be changed to be marked as automatic installed packages which are dependencies of other packages. Most of those will be wanted. But library packages are usually not manually installed. So these are good candidates for cleaning.
apt-mark showmanual | grep ^lib | grep -v -e -progs -e -bin -e -perl -e -dev | wc -l
56
apt-mark showmanual | grep ^lib | grep -v -e -progs -e -bin -e -perl -e -dev | less
Those are likely possibilities for marking as automatically installed which will make them candidates for autoremove if nothing depends upon them. The above avoids packages like libc-bin and liblockfile-bin which include desired utilities that are likely installed manually. And the perl packages are also likely used for many scripts and also likely manually installed. But the rest are probably not intentionally manually installed. Review. And perhaps mark them all as auto
apt-mark auto libyaml-0-2
Or perhaps set the entire set all at once. But only upon review.
apt-mark auto $(apt-mark showmanual | grep ^lib | grep -v -e -progs -bin -e -perl -e -dev)
Then test what would be candidates for autoremove.
apt-get autoremove --purge
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
libapache2-mod-rpaf* libncurses5*
0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded.
After this operation, 388 kB disk space will be freed.
Do you want to continue? [Y/n]
I am definitely not using libapache2-mod-rpaf so that one can go. And I am not using libncurses5 anymore since everything has rolled to libncurses6 so that one can go. I say yes to the above and let those autoremove. However if there was something that got swept in that I don't want to remove then I can either mark it as manual, or install it again which marks it as manual.
dpkg -L libjpeg-turbo-progs | grep bin/
/usr/bin/cjpeg
/usr/bin/djpeg
/usr/bin/exifautotran
/usr/bin/jpegexiforient
/usr/bin/jpegtran
/usr/bin/rdjpgcom
/usr/bin/tjbench
/usr/bin/wrjpgcom
Oh, yes, I want those installed. Let's mark them as manual. (Also installing something again marks them manual.)
apt-mark manual libjpeg-turbo-progs
This file controls starting of services by packages. Notably it does NOT control starting of packages at boot time by init services. It's a specific restriction targeting use of packages and handling of upgrades in a chroot container. But this file has been found accidentally existing in systems outside of containers. Since it does not control boot time init actions it was apparently not noticed when the system was provisioned and delivered.
ls -ld /usr/sbin/policy-rc.d
Remove and/or update any stale preferences files that may have been installed and configured.
ls -l /etc/apt/preferences /etc/apt/preferences.d/*
If the apt-listchanges package is installed then it is very noisy during a full system release upgrade. And what is one going to do? If one were installing a pristine system then we would get the new versions of the packages. This is regardless of whether apt-listchanges would warn about possible problems with packages. If it is installation time of a pristine system then these will get installed regardless. Therefore if apt-listchanges is installed it is better to purge it for the upgrade. If you want to install it back again after the upgrade then that at least avoids the problems it causes during the upgrade.
apt-get purge apt-listchanges
If the system hasn't been getting cleaned regularly then there may be
gigabytes and gigabytes of old deb package files hanging around in
/var/cache/apt/archives
directory. Since we are upgrading we will
be leaving those old versions behind never to be used again. Remove
them to free up space which can then be used for new packages.
apt-get clean
The way I handle upgrades is a little different. I prefer to do as much of the upgrade non-interactively as possible. It's really deferring the interactive actions. Then interactively handling the parts that needed interaction after the non-interactive part of the upgrade has completed.
export DEBIAN_FRONTEND=noninteractive
These variables control the action of dpkg. It selects the non-interactive frontend. This is the part that asks the user whether they want to keep the old file or install the new file or show differences.
export DEBCONF_ADMIN_EMAIL=""
When noninteractive is selected dpkg will then decide to email the admin some information instead of presenting it interactively on the screen. Since I am upgrading many systems this would all be repeat information every time it was done. After I have seen the information once I don't need to see it every time after then.
export UCF_FORCE_CONFFNEW=1
The ucf program similar to dpkg manages conffiles and will ask if the new file should be used or the old file. This pre-selects using the new conffile. We will do the same for dpkg but dpkg does not use environment variables for this but command line options instead.
export UCF_FORCE_CONFFMISS=1
If the conffile has been removed do not treat this as a local modification to be preserved but install the package maintainer's version of the file the same as if it were a pristine installation. (Treating removal as a local modification was successfully argued by a pedantic Debian user. It wasn't always that way.)
Review the apt sources.list files. Sometimes configuration files will
be found in /etc/apt/sources.list.d
that were not known about.
These most often contain 3rd party repositories.
ls -l /etc/apt/sources.list.d /etc/apt/sources.list
What to do about 3rd party repositories? It is best to never use 3rd party repositories. And if you do then it is best to never upgrade the system. Instead create a new system, install new stuff there, and then throw away the old system. Because 3rd parties most often do not handle upgrades well. They expect pristine installation only.
My advice if 3rd party software has been installed is to purge it
before the upgrade. Then if it is still required install it again
after the upgrade. That's safest. This was probably already found
during the apt-show-versions
part of the cleaning. But maybe not.
Remove the 3rd party sources.list entries and then look at what is
left. Purge off those packages.
apt-show-versions | grep -v -e uptodate
If everything is ready for the upgrade then change the release name in the sources.list file to the new release. Update to download the new Packages index files.
sed --in-place 's/etiona/nabia/g' /etc/apt/sources.list
apt-get update
Download all of the packages first to the local machine to remove the dependency upon the network needing to be alive during the installation. If network-manager is in use it has a notorious reputation for dropping the network during the upgrade and not bringing it back online again. Unfortunately network-manager is now a default and pulled in by gnome during some upgrades. In any case it is much safer to download every package to the cache before starting the upgrade. The -d option downloads but does not install.
apt-get upgrade -d -q -y
apt-get upgrade --with-new-pkgs -d -q -y
apt-get dist-upgrade -d -q -y
All of the above has been the preparation. Now we are finally ready to perform the actual upgrade. This is the final place where it is critically important to have read the release notes and to be aware of any specific upgrade needs. Most often needed for udev and kernel related upgrades.
It is good to run the upgrade under screen
or tmux
which are both
terminal multiplexers. Especially if the upgrade is on a remote
system. That way if the network glitches and is disconnected that the
session is not killed and may be connected to again. I am using tmux
but screen has been around forever.
tmux
It is good to run the upgrade under script
which makes a typescript
of everything written to the terminal. The output is saved to a file
"typescript" which can then be reviewed for content. The GNU system
uses the util-linux
packaging of script and requires the -f, --flush
option to be provided to ensure that data is written up to the
reboot. (Other OS versions do not require this option nor provide it.)
script -f
At this point it is possible to run Ubuntu's do-release-upgrade
command and perform an interactive upgrade. That is what the upstream
documentation will recommend at this point. If you want to do an
interactive upgrade then this is an okay thing to do. However I don't
do this myself because it will run for unpredictable length of time
grinding through the upgrade and then will stop at unpredictable times
to ask an interactive question. This cycle will repeat many times.
I prefer to upgrade as non-interactively as possible. Batch mode. Then react to things needing interactive action afterward. These are the commands. I will repeat the env variables again to reinforce their association though the rationale was explained above.
export DEBIAN_FRONTEND=noninteractive DEBCONF_ADMIN_EMAIL="" UCF_FORCE_CONFFNEW=1 UCF_FORCE_CONFFMISS=1
apt-get -o DPkg::Options::=--force-confnew -o DPkg::Options::=--force-confmiss upgrade -q -y < /dev/null
apt-get -o DPkg::Options::=--force-confnew -o DPkg::Options::=--force-confmiss upgrade --with-new-pkgs -q -y < /dev/null
apt-get -o DPkg::Options::=--force-confnew -o DPkg::Options::=--force-confmiss dist-upgrade -q
We pass in the dpkg options to force confnew so that if a conffile has been removed, which is almost certainly an accident, then it will be installed using the package maintainer's version. This is always what we want. (It's only the other way by default because of a pedantic user who successfully argued that removing a required conffile constituted a deliberate local admin change and must by Debian Policy be preserved. I disagree strongly.)
We pass in the dpkg options to select the new conffile to always replace the old conffile. The old conffile will be renamed to a name with ".dpkg-old" or ".ucf-old" suffix. After the upgrade we will look for those files and perform any merges needed.
The upgrade first uses "upgrade" which is a one-for-one package upgrade. No new package names are installed. No old package names are removed. But if packages can be upgraded in place then they are upgraded in place. This handles the majority of packages using an algorithm that cannot be in error.
Then use upgrade --with-new-pkgs
which allows new package names to
be installed due to being pulled in by package dependencies. This
handles most of the rest of the upgrade topology. No packages are
allowed to be removed making this another safe upgrade algorithm.
Then use dist-upgrade
to perform the final bits of the upgrade that
require removing packages. This is not a safe action and normally
requires careful review. But what's a person to do? We are most of
the way there. Therefore we can only do our best and barge forward.
I removed the -y yes option from the recipe above to require the admin
to review and confirm the action. But honestly in my own scripts I
keep the -y yes option there and barge forward.
Unfortunately sometimes there are errors. These are always package bugs. But they happen just the same. In the upgrade from Trisqual 9 to 10 I experienced an abort. Usually the best thing is to get as much of the collection configured as possible. Which simply means restarting the configuration step.
export DEBIAN_FRONTEND=noninteractive DEBCONF_ADMIN_EMAIL="" UCF_FORCE_CONFFNEW=1 UCF_FORCE_CONFFMISS=1
dpkg --force-confnew --force-confmiss --configure -a
If an error in a postinst script causes dpkg to abort the installation then this command will restart dpkg configuring all unconfigured packages, with the same options as before to select the package maintainer's version of conffiles and install missing conffiles. Very often the bug that aborted the upgrade won't be seen on a retry. This is often due to a missing dependencies where a package depends upon another package, does not state it, and the dependency does not happen to be ready when it is needed. In the next retry it might be available due to having unpacked and configured more packages before getting to it again. Seeing an error on cryptsetup I restarted and did not see that error again therefore it was one of those types.
Example. I have seen fail2ban have problems during the upgrade. It is possible that it will take "forever" to perform some command. I am sure that if there were visibility into what it is doing that it would make sense that it is doing some exponential time something. But that is not an uncommon package that will get stuck, will need to be interrupted, will need to be forcibly dragged kicking and screaming through the upgrade. Perhaps by removing it (not purging, keep the config files), completing the upgrade without it, then installing it again.
Also the /var/lib/fail2ban/fail2ban.sqlite3
file has been a problem.
It has become gigabytes in size with seemingly nothing reducing the
size of it. Purging fail2ban, removing that file, re-installing
fail2ban, verifying the new configuration, is sometimes needed at
major OS upgrade.
Example. The /boot
partition filled up on an Ubuntu upgrade. It
seems that Ubuntu uses /boot
for temporary space and needs a
significant amount of free space there. (Debian seems to use the
larger /var/tmp
directory for this same purpose and avoids the
problem.) The problem was that one extra older Linux 4 kernel was
left on the system as a backup kernel and not removed. That used just
enough more space that the upgrade failed. As I was using Ubuntu's
do-release-upgrade
script that caused it to fail out of that script.
And then having failed there was no way to restart the script.
After purging the extra Linux 4 linux-image-4* kernel packages to make
space restarting the do-release-upgrade
simply had it exit
immediately. Which meant I had to walk through the usual upgrade
steps described above manually. I did that and completed the
upgrade. Starting with dpkg --configure -a
recovery and all of the
rest. But that then proceeded through correctly.
WARNING! Do not reboot immediately! There are a few items that need to be fixed up as soon as possible! Definitely required before rebooting.
Since we have instructed dpkg to install the new conffiles and save the locally modifed ones to ".dpkg-old" everywhere the first thing we need to do is to merge in changes. And specifically to the most critical services. Critical services such as ssh which we are undoubtedly using for the upgrade.
ls -l /etc/ssh/sshd_config*
-rw-r--r-- 1 root root 3207 Dec 29 2021 /etc/ssh/sshd_config
-rw-r--r-- 1 root root 3207 Dec 29 2021 /etc/ssh/sshd_config.dpkg-old
emacs /etc/ssh/sshd_config*
...merge local customizations from the old file to the new...
service ssh restart || systemctl restart ssh.service
Examine the old sshd_config.dpkg-old
file. Merge in any local
changes from there to the new sshd_config file. Restart ssh. Verify
that login using it is still possible by logging in from a different
terminal. This is not as scary as I make it sound now. The sshd
defaults are back to something less scary. For a while it was
PermitRootLogin=no which might lock you out from logging in but now
the default is PermitRootLogin=prohibit-password which allows ssh
keys. That's standard operating procedure for most systems. So not
so critical now as it was for at least one release. In any case there
likely will be other configuration needed to be merged.
Examine the syslog for sshd messages. Over time this command has changed supported options. Here is an example where after an upgrade the old version of the file contained now deprecated options. The merge required removing these from the file.
Jun 24 17:06:10 frontend1 sshd[32160]: rexec line 16: Deprecated option UsePrivilegeSeparation
Jun 24 17:06:10 frontend1 sshd[32160]: rexec line 19: Deprecated option KeyRegenerationInterval
Jun 24 17:06:10 frontend1 sshd[32160]: rexec line 20: Deprecated option ServerKeyBits
Jun 24 17:06:10 frontend1 sshd[32160]: rexec line 31: Deprecated option RSAAuthentication
Jun 24 17:06:10 frontend1 sshd[32160]: rexec line 38: Deprecated option RhostsRSAAuthentication
A specific item that is needed for the FSF VMs for an upgrade from Trisquel 9 to Trisquel 10 is this package cryptsetup-initramfs which needs to be installed.
cryptsetup-initramfs
Originally the required files were in cryptsetup but that package is
now split and the new version does not "Depends" upon the
cryptsetup-initramfs
package likely leaving the system upgraded
without it installed. The new package cryptsetup-initramfs is a
"Recommends" only and isn't guaranteed to get installed. It's needed
with the current VM setup (isn't needed on the older VMs) in order to
boot. If upgrading one must ensure that cryptsetup-initramfs
gets
installed.
And less important is that for whatever reason the kernel symlinks did not track to the new kernel package. That's quite odd. I don't know why. These links.
lrwxrwxrwx 1 root root 34 Dec 7 2021 initrd.img -> boot/initrd.img-4.15.0-161-generic
lrwxrwxrwx 1 root root 31 Dec 7 2021 vmlinuz -> boot/vmlinuz-4.15.0-161-generic
They were still pointing to the old, in this case version 4, kernels. Ian ran this manually to fix them up. This is an FSF VM specific issue.
linux-update-symlinks install 5.4.0-121-generic
/boot/vmlinuz-5.4.0-121-generic
lrwxrwxrwx 1 root root 33 Jul 8 18:34 initrd.img -> boot/initrd.img-5.4.0-121-generic
lrwxrwxrwx 1 root root 30 Jul 8 18:34 vmlinuz -> boot/vmlinuz-5.4.0-121-generic
I'll watch for the next kernel upgrade and verify that they update at that time or not. The current config file says this.
root@frontend1:~# cat /etc/kernel-img.conf
do_symlinks = yes
do_initrd = Yes
silent_modules=yes
clobber_modules=yes
link_in_boot = no
Again this is an FSF VM specific issue. They boot to a private GRUB with a configuration that requires those boot symlinks. On my other systems I prefer not to have these symlinks at all. And to boot the guest system GRUB. Then all of the configuration is simply normal.
find /etc \( -name '*.dpkg-*' -o -name '*.ucf-*' \) -print
Review and merge each of those files. Some of those services will be non-functional until that step is done.
The /etc/profile
is not upgraded automatically. The base-files
package installs one if it is not there. That handles the initial
provisioning and setup. But then it is up to the local admin to
maintain after that point.
Since we do not customize /etc/profile
and simply use the base-files
copy directly we can simply copy it into place. Let's diff it for
differences so we know and if there are differences and those
differences make sense then copy it into place. And of course if
there are no differences then there is no need to copy anything.
diff -u /usr/share/base-files/profile /etc/profile
cp /usr/share/base-files/profile /etc/profile
Review the other files there for upgrades too. Some files such as motd message-of-the-day sometimes change. And others are for root's environment files which would not be normally upgraded. Nothing too exciting but it's something to review, find nothing of interest, and then move on to other things.
ls -l /usr/share/base-files/
Repeat all of the same pre-upgrade cleaning all over again. We have upgraded almost every package on the system. There will be new conffiles that need to be merged. There will be ".dpkg-" files to be handled. There will be packages no in the rc state. There will be new transition packages installed needing removal. Go back to the start of the cleaning and repeat all of the cleaning steps again.
Along the way we will find many packages that did not handle the upgrade correctly. They will have obsolete conffiles. They will have other problems. These are all upgrade bugs. File an appropriate bug report for the problems found.
The adequate
package.
If you are a go-getter and want to help the project out then the
adequate
package is there to make finding these problems easier.
This is something to install before going through other installation
and upgrades. The adequate package will examine each install and
upgrade and run checks against the package. It will complain,
interactively requiring an OK to continue, about problems in the
packages it finds. These might be obsolete conffiles, might be
dangling symlinks, might be unresolved library symbols, or might be
other things. These are all packaging bugs and in a perfect world a
bug report would get filed for each of them and each of these bugs
would get fixed. Because adequate is interactive this is not
appropriate for fully automated installs and upgrades. But it is
useful for interactive upgrades to find and notify of packaging
problems.
apt-cache show adequate
Homepage: https://salsa.debian.org/debian/adequate
Description-en: Debian package quality testing tool
adequate checks packages installed on the system and reports bugs and policy
violations.
.
The following checks are currently implemented:
* broken symlinks;
* missing copyright file;
* obsolete conffiles;
* Python modules not byte-compiled;
* /bin and /sbin binaries requiring /usr/lib libraries;
* missing libraries, undefined symbols, symbol size mismatches;
* license conflicts;
* program name collisions;
* missing alternatives;
* missing binfmt interpreters and detectors;
* missing pkg-config dependencies.
This is a good developer helper tool to identify package problems. This is not needed for everyone. This is an OPTIONAL additional helper for those who like to dig into the details.
This is the final step. Hold your breath. Reboot. The system needs to boot in order to run the newly installed kernel. After rebooting run the system regression test suite. Any failures? Fix failures.
If you have made it to here then you have completed the full upgrade process from one end to the other.
Copyright (C) 2022 Bob Proulx
Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty.