If you prefer to upgrade to newer Ubuntu releases by erasing your / partition and doing a clean install of the newer release, here are a few scripts to help gather some information about the current OS that might be useful before proceeding: like what software was added, what files were added/edited/removed. Basically listing changes made to the current OS to help avoid missing steps when setting up the new install of the new Ubuntu release. I recommend doing a full system backup before doing anything destructive to your OS, think of these scripts as pointing out partial list(s) of things you might want to be extra sure are included as part of your backup.
The latest release I’ve used these scripts on is Xubuntu 22.04. Have not had the chance to test them on 24.04 and later.
This script lists all deb packages that were manually installed by the user, i.e. did not come with the initial installation:
#!/usr/bin/env python3
import subprocess, re
def ex(*args, **kwargs):
return str(subprocess.check_output(*args, **kwargs), 'utf_8')
def getManualPkgs():
installerPkgs=ex(['zcat', '/var/log/installer/initial-status.gz']).split('\n')
manualPkgs=ex(['apt-mark', 'showmanual']).split('\n')
for raw_line in installerPkgs:
if not(raw_line.startswith('Package:')): continue
line=re.sub(r'^Package:\s*', '', raw_line, flags=re.I)
if line in manualPkgs: manualPkgs.remove(line)
return manualPkgs
if __name__ == '__main__':
print('\n'.join(getManualPkgs()))
This script lists some system files that were either modified by the user, or that were not installed by any package, and that might be useful but potentially hard to manually keep track of. It can be hard to definitively tell which files are user-modified and which are not, so it will probably list more files than you care about.
#!/usr/bin/env python3
import argparse, itertools, os, re, shutil, subprocess, sys
def msg(*args):
print(*args, file=sys.stderr)
symlink_table=None
def resolveSlashSymlink(p):
global symlink_table
if not(symlink_table):
msg('Checking for symlinks in `/` ...')
symlink_table={}
for i in os.listdir('/'):
d=os.path.join('/',i)
if os.path.isdir(d) and os.path.islink(d):
symlink_table[d+'/']=os.path.realpath(d)+'/'
msg('Found:', symlink_table)
for sd in symlink_table.keys():
if p.startswith(sd):
return os.path.join(symlink_table[sd], p[len(sd):])
# If we got here there was no symlink in `/` in this path
return p
isKnownIrrelevant=re.compile(r'/\.uuid$|^/(?:usr/(?:share/(?:mime/(?!packages)|fonts/.*(?:/msttcorefonts/|\.(?:alias|dir|scale)$)|icons/.+/icon-theme\.cache$|applications/mimeinfo\.cache$|doc/ttf-mscorefonts-installer/|info/dir|glib.*compiled$)|lib/(?:modules/|udev/hwdb\.bin$|x86_64-linux-gnu/(?:.*\.cache|vlc/plugins/plugins\.dat)$)|src/libdvd-pkg/)|etc/(?:(?:brlapi\.key|hostname|ld\.so\.cache)$|xml/|console-setup/))')
def main(outfile):
if not(shutil.which('debsums')):
print("[!] `debsums` executable not found - try: sudo apt install debsums", file=sys.stderr)
sys.exit(1)
with open(outfile, 'w') as o:
msg('Running debsums (via sudo)')
debsums=subprocess.run(['sudo', 'debsums', '-ac'], stderr=subprocess.STDOUT, \
stdout=subprocess.PIPE, encoding='utf_8')
for line in debsums.stdout.splitlines():
o.write('> '+line+'\n')
msg('Done checking debsums.')
# Separate debsums mismatches from the rest of the file
o.write('\n-------\n\n')
msg('Reading installed file lists')
with subprocess.Popen(['xargs', 'dpkg-query', '-L'], \
stdin=subprocess.PIPE, stdout=subprocess.PIPE) as xargs:
subprocess.run(['dpkg-query', '-W', '-f', r'${binary:Package}\n'], stdout=xargs.stdin, check=True)
installed_files_raw=str(xargs.communicate()[0], 'utf_8')
installed_files_lines=installed_files_raw.splitlines()
msg('Sorting...')
diversion_rx = re.compile(r'^(?:package\s+diverts\s+others|diverted\s+by\s+\S+)\s+to:\s+', re.I)
installed_files = set()
for i in installed_files_lines:
if diversion_rx.search(i):
# drop diversion statements, get just the filename
i=diversion_rx.sub('', i)
installed_files.add(resolveSlashSymlink(i))
# Skip /usr/local because we KNOW all of that stuff is not in a package.
searchdirs=['/etc', '/lib/systemd']
ls_slashusr=os.listdir('/usr')
ls_slashusr.remove('local')
searchdirs.extend(map(lambda x: os.path.join('/usr', x), ls_slashusr))
# Use sudo because regular user might not have sufficient permissions.
msg('Running find (via sudo)')
findcmd=['sudo', 'find', '-P']
findcmd.extend(searchdirs)
# Ignore .pyc files - those were all auto-generated by a package or script.
# Just the .py files will do.
findcmd.extend(['-type', 'f', '!', '-iname', '*.pyc', '-print0'])
found_files=set(map(resolveSlashSymlink, itertools.filterfalse(isKnownIrrelevant.search, subprocess.run(findcmd, stdout=subprocess.PIPE, encoding='utf_8').stdout.split('\x00'))))
msg('Sorting results')
sorted_diff=list(found_files.difference(installed_files))
sorted_diff.sort(key = lambda s: s.lower())
for fn in sorted_diff:
o.write('> '+fn+'\n')
missing_debsums=subprocess.run(['debsums', '--list-missing'], stdout=subprocess.PIPE, \
encoding='utf_8').stdout.splitlines()
if len(missing_debsums) == 0:
msg('All done, check {} for results'.format(outfile))
else:
msg("""Script done, check {0} for results
Note: Some packages could not be checked automatically.
Run the following command to download these packages for manual comparison:
apt-get download {1}""".format(outfile, ' '.join(missing_debsums)))
if __name__ == '__main__':
ap=argparse.ArgumentParser()
ap.add_argument('outfile')
ns=ap.parse_args()
main(ns.outfile)
Bonus: if you masked some systemd units, here is a bash command to list which ones:
find /etc/systemd/{system,user} -type l -lname /dev/null -exec basename '{}' \;