dpkgcheck - Überprüfe installierte Debian-Pakete

Winfried Mueller, www.reintechnisch.de, Start: 07.02.2007, Stand: 09.02.2007

Die Idee für dieses Werkzeug kam mir bei der Aufgabe, mehrere Debian oder Ubuntu Linux-Server paketmäßig synchron zu halten. Alles, was also auf einem Master-Server installiert wird, soll später auch auf mehreren anderen Servern genauso installiert sein.

Mit dem Werkzeug dpkgcheck kann man hierfür einen Schnappschuss der installierten Pakete des Mastersystems erzeugen. Dieser Schnappschuss kann in einer Datei gespeichert werden.

Das zu synchronisierende System kann nun mit diesem Schnappschuss verglichen werden. Hierbei wird angezeigt, welche Pakete auf diesem System fehlen und welche ein Update erfahren müssen.

Um sich die Arbeit noch zu erleichtern, kann das Ausgabeformat so eingestellt werden, dass direkt ein Bash-Skript ausgegeben wird, in dem die entsprechenden "apt-get install ..." Anweisungen drin stehen.

Gleichzeitig ist dpkgcheck aber auch universell gehalten, so dass es für eine Reihe weiterer Anwendungsfälle nutzbar sein wird. So kann man z.B. gelegentlich überprüfen, welche Pakete seit einem bestimmten Snapshot nachinstalliert wurden. Oder welche Pakete in der Zwischenzeit alle aktualisiert worden sind. Es ist damit ein Werkzeug, mit welchem man Paketveränderungen auf Systemen dokumentieren oder überwachen kann.

Einsetzbar ist das Werkzeug natürlich nur auf Systemen, die dpkg als zentrales Paketmanagment nutzen.

Das Werkzeug sollte sofort auf jedem Ruby 1.8.x System lauffähig sein. Es braucht keine speziellen Bibliotheken. Am besten spielt man mal mit den unter --help aufgeführten Beispielen, dann wird schnell klar, was man mit machen kann.

Übrigens: Genau sowas brauche ich nun auch noch für das Gem-Paketsystem von Ruby. Auch hier ist es nervig, wenn auf einem Master-System alles läuft, auf einem Zielsystem aber Ruby-Skripte nicht funktionieren und man mühsam schrittweise herausfinden muss, was denn nun alles nachinstalliert werden muss.

Quellcode

 
#!/usr/bin/ruby1.8

# dpkgcheck - Überprüfe/Erstelle dpkg-Liste
# Winfried Mueller, www.reintechnisch.de, (c) 2007, GPL
# Start: 08.02.2007, Stand: 09.02.2007
# 
# Generate List
#   dpkgcheck -l > myserver.lst
#
# Check List
#   dpkgcheck -c myserver.lst
# 

DEBUG = true
Version = "2007/02/09"


require 'optparse'
require 'ostruct'

def makelist()
  getpackages.each do |package|
    case $options.format
      when "p"
        puts package['Name']
      when "pv", nil
        puts package['Name'] + "  " + package['Version'] 
      when "inst"
        puts "apt-get install #{package['Name']}"
      else
        puts "Undefined List-Format."
    end
  end
end


def getpackages()
  l = `export COLUMNS=200;dpkg -l`
  al = l.split( "\n" )

  #Header löschen
  l = false
  10.times do
    d = al.shift
    if d =~ /^\+\+\+/
      l = true
      break
    end
  end

  raise "Kann Anfang von dpkg -l nicht finden." unless l
  packages = []
  al.each do |package|
    if package =~ /^(ii|hi)\s+(.*?)\s+(.*?)\s+/
      packages << { 'Name' => $2, 'Version' => $3 }
    end
  end  

  packages
end

def checklist( list )
  line_nr = 1
  packages = getpackages
  c_packages = Hash.new
  packages.each do |package|
    c_packages[package['Name']] = package['Version']
  end

  f_packages = Hash.new
  File.foreach( list ) do |line|
    package, version = line.split( /\s+/ )
    raise "Error in list-Format line: #{line_nr}" if !package || !version
    case $options.format
      when "all", nil
        if !c_packages[package]
          puts "#{package}: Install"
        else
          if c_packages[package] != version
            puts "#{package}: Update: #{version} => #{c_packages[package]}"
          end
        end
      when "apt"
        if !c_packages[package]
          puts "apt-get install #{package}"
        end
      when "install"
        if !c_packages[package]
          puts "#{package}: Install"
        end
      when "update"
        if (cv = c_packages[package])
          if cv != version
            puts "#{package}: Update: #{version} => #{c_packages[package]}"
          end
        end
    end
    f_packages[package] = version   
    line_nr += 1
  end

  packages.each do |package|
    case $options.format
      when "all", nil, "remove"
        if !f_packages[package['Name']]
          puts "#{package['Name']}: Remove"
        end
      when "apt"
        if !f_packages[package['Name']]
          puts "apt-get remove #{package['Name']}"
        end
    end
  end

end


# ----------------------------------------------------------------

ARGV.options do |o|
  o.banner = "Usage: dpkgcheck [options]"
  o.separator( "dpkgcheck - Generate and Compare dpkg-list" )
  o.separator( "" )

  o.on( "-l", "--mklist [FORMAT]", "Make a dpkg-list to stdout. [p|pv|apt]" ) do |fmt|
    $options.mklist = true
    $options.format = fmt
  end

  o.on( "-f", "--format FORMAT", "Output Format for -c [all|apt|install|update|remove]" ) do |fmt|
    $options.format = fmt
  end


  o.on( "-c", "--check FILELIST", "Compare installed package with dpkg-list." ) do |f|
    $options.check = f
  end

  o.on( "-v", "--verbose", "All Check results (default only different Files)." ) do
    $options.verbose = true
  end


  o.on_tail("-h", "--help", "This help." ) do
    puts o
    puts( "" )

    puts( "Examples:" )
    puts( "  dpkgcheck -l > myserver-2007-02-07.lst       # make dpkg-list snapshot" )
    puts( "  dpkgcheck -l p                               # list all installed packages" )
    puts( "  dpkgcheck -l apt                             # generate apt-get install list" )
    puts( "  dpkgcheck -c myserver-2007-02-07.lst         # View changes from snapshot" )
    puts( "  dpkgcheck -f apt -c myserver-2007-02-07.lst  # Make a package install script" )
    puts( "  dpkgcheck -f install -c myserver-2007-02-07.lst # View only packages to install" )
    puts( "  dpkgcheck -f update -c myserver-2007-02-07.lst     # View only packages to update" )
    puts( "" )
    exit
  end
  o.on_tail("--version", "Show version") do
    puts o.ver
    puts "Written by Winfried Mueller, www.reintechnisch.de"
    puts ""
    puts "Copyright (C) 2007 Winfried Mueller"
    puts "This is free software; see the source for copying conditions."
    puts "There is NO warranty; not even for MERCHANTABILITY or" 
    puts "FITNESS FOR A PARTICULAR PURPOSE."
    exit
  end
end


# ----------------------------------------------------------------
begin
  $options = OpenStruct.new
  $options.verbose = false
  err_code = 0

  ARGV.parse!

  if $options.mklist != nil
    makelist( )
  elsif $options.check != nil
    checklist( $options.check )
  else
    STDERR.puts ARGV.options.to_s
  end
rescue SystemExit
rescue Exception => exc
  STDERR.puts "E: #{exc.message}"
  #STDERR.puts ARGV.options.to_s
  raise if DEBUG
end
exit err_code