Here is a Ruby script that uses rdepends
to check if a package is a dependency for another installed package. That will not tell exactly what you selected, but that does not depend on your log files (which might have been rotated).
Installing packages listed as root packages will install all the packages listed in children packages. So you should end up with almost (see drawbacks below) the same packages list.
The children list will shows the packages that are dependencies of other children packages or root packages.
There are some drawbacks with this approach:
- Some packages might not be listed as root when they are dependencies of some optional, recommended package you selected. For example, on my server
apache2
is listed in children packages because I also have libapache2-mod-php
, libapache2-mpm-itk
and python-letsencrypt-apache
installed which have apache2
as a dependency.
- Cyclic dependencies (packages that directly or indirectly depends on each others) will be listed in Children packages (for example:
libapache2-mod-php
and libapache2-mod-php7.0
). There is a section listing the probable cycle (check the package ancestries for 5 generations), you should include it to have the same list of packages (unless I've overlooked something else).
#!/usr/bin/env ruby
class ListRootPackages
def run
count = manual_marked_packages.count
@dependencies ||= {}
@root_packages ||= begin
manual_marked_packages.each_with_index.map do |package, index|
STDERR.print " #{index + 1} / #{count} \r"
intersection = manual_marked_packages & reverse_dependencies(package)
if intersection.any?
@dependencies[package] = intersection
nil
else
package
end
end.compact
end
end
def potential_cyclic_dependences
run
@potential_cyclic_dependences ||= @dependencies.keys.map do |package|
package unless has_root_ancestor?(package, 5)
end.compact
end
def has_root_ancestor?(package, level=0)
return true if @root_packages.include?(package)
return false if level.zero?
@dependencies[package].any? { |parent| has_root_ancestor?(parent, level - 1) }
end
def root_packages
run
@root_packages
end
def dependencies
run
@dependencies
end
def manual_marked_packages
@manual_marked_packages ||= parse_cli_list(`apt-mark showmanual`)
end
private
def reverse_dependencies(package)
parse_cli_list(`apt-cache rdepends #{package}`)[2..-1]
end
def parse_cli_list(list)
list.split("\n").map(&:strip)
end
end
list = ListRootPackages.new
list.run
puts "===== Root Packages (#{list.root_packages.count}) ====="
puts list.root_packages.join("\n")
puts
puts "===== Children packages (#{list.dependencies.count}) ====="
puts list.dependencies.map { |package, parents| "#{package}: #{parents.join(', ')}" }.join("\n")
puts
puts "===== Potential cyclic dependencies (#{list.potential_cyclic_dependences.count}) ====="
puts list.potential_cyclic_dependences.join(", ")
if somebody wants to convert that to Bash or Python, that would be nice since Ruby is less commonly installed on servers than Bash or Python.