#! /usr/bin/perl -w
#
# /usr/sbin/dphys-setup.pl - install/upgrade packages
# authors dsbg and franklin, last modification 2003.08.01
#
# called by  cron  daily at 06:xx and by  init  at boot time
#   as first parameter ($ARGV[0]) is "cron" or "init" depending on caller
#
# controlled by /var/lib/dphys-admin/pkglist
#   # blah blah: comment, don't do anything, same for empty line
#   ? blah blah: write this text out to user
#   | do do do : execute this command by an shell
#   - pack1 pack2: delete packages
#   * pack1 pack2: delete packages with --purge
#   + pack1 pack2: install packages
#   ! pack1 pack2: install packages with --reinstall
#   pack1 pack2: install packages (old syntax, deprecated)
#
# we originally used an site package with an large Depends: list
#   unfortunately apt-get loads stuff in an unpredictaly row
#   this lead to conflicts where packages dif not find stuff they need
#   Predepends: was not reliable either, when we need something configured


# --- parameters

# by who are we being called, cron or init
my $called_by = $ARGV[0];


# --- variables for filenames

# our local packages server, you may want to change this
my $debserver_local = "debian.ethz.ch";
my $debdir_local = "pub/debian-local";

# file with our package list
my $pkgurldir = "http://$debserver_local/$debdir_local/share/";
my $pkgpathdir = "/var/lib/dphys-admin/";
my $pkgfile = "pkglist";
my $pkgurl = $pkgurldir . $pkgfile;
my $pkgpath = $pkgpathdir . $pkgfile;

# logfile for voluminous apt-get output
my $logdir = "/var/log/";
my $logfile = "dphys-admin.log";
my $logpath = $logdir . $logfile;
# logfile backup, as the daily cron overwrites it
my $logfileb = "dphys-admin.log.bak";
my $logpathb = $logdir . $logfileb;

# flagfile if we need to reboot and/or send mail
my $reboot_file = "/root/NEED-REBOOT";


# --- logging

# begin output, better for processing and recognizing trouble
print("\ndphys-setup.pl is updating the software collection ...\n");

if ($called_by eq "cron") {
  # as we run by cron, log to an separate file and mail it to root
  #   keep old logfile content forone generation/day, as backup file
  rename $logpath, $logpathb; }
else {
  # OTOH reboot/init can happen arbitrariliy often, so no mails
  #   to not lise information, here allways add to old file, no 1 gen backup
  # 2 empty lines so there is still an separator, even if last \n missing
  system("echo >>$logpath\n");
  system("echo >>$logpath\n"); }

# and now announce the start of this run
system("echo @@@ dphys-setup.pl $called_by started: `date -R` >>$logpath\n");


# --- apt-get update

# apt-get update first, so that we also can see new packages
print("updating the apt database ...\n");
system("echo @@@ apt-get update: `date -R` >>$logpath\n");
system("nice yes '' | apt-get update >>$logpath 2>>$logpath\n");


# --- package list update

# fetch package list
print("fetching package list ...\n");
system("echo @@@ wget pkgfile: `date -R` >>$logpath\n");

# CVS keeps on deleting our empty directory, so create it from here if
if (! -d "$pkgpathdir") {
  if (-e "$pkgpathdir") {
    unlink "$pkgpathdir"; }
  mkdir "$pkgpathdir", 0700; }

if (-f "/usr/bin/wget") {
  system("wget -O $pkgpath $pkgurl >>$logpath 2>>$logpath\n"); }

if (! -f $pkgpath) {
  # package list not (and so never) fetched, so we can not work
  print("no $pkgpath bailing out ...\n");
  system("echo @@@ ERROR: missing pkgfile >>$logpath\n");

  # warn the admin
  if ($called_by eq "cron") {
    # and then send the abortive logfile (contains only this run) to root
    my $mail_subject = "cron dphys-admin failled on `hostname`";
    my $echo_text = "dphys-setup.pl found no $pkgpath";
    system("echo $echo_text | mail -s \"$mail_subject\" root \n"); }

  # and bail out
  exit (1); }


# --- collect already installed stuff, to later eliminate it from installing

my %installed;
open(DPKGGETSEL, "dpkg --get-selections|");

while (<DPKGGETSEL>) {

  my $package;
  my $state;
  chomp;
  ($package, $state) = split;
  $installed{$package} = $state; }

close(DPKGGETSEL);


# --- the actual work

# evaluate package list and load/eliminate packages
print("installing or removing packages in $pkgpath ...\n");
system("echo @@@ apt-get install/remove: `date -R` >>$logpath\n");
open(PKGLIST, "<" . $pkgpath) or die "Can't open $pkgpath";

while (<PKGLIST>) {

  chomp;


  # eliminate blanks at begin and end of line
  s/^\s+(.*)s+$/$1/;

  # empty line, ignore
  if (/^$/) {
    next; }


  # get first character, which says what to do with this line
  m/^(.)(.*)$/;


  # # = comment line, ignore
  if ($1 eq "\043") {
    next; }


  # ? = tell user what is happening, print it out
  elsif ($1 eq "?") {

    # first newline to terminate line of progress log lines
    print "?\n$2\n"; }


  # | = execute (pipe) of any random command
  elsif ($1 eq "?") {

    # first newline to terminate line of progress log lines
    print "|\n";
    system("$2\n"); }


  # - = uninstall (subtract) some packages
  elsif ($1 eq "-") {

    system("nice yes '' | apt-get remove $2 >>$logpath 2>>$logpath\n");
    print "-"; }


  # * = destroy ("splat", squashed insect) some packages
  elsif ($1 eq "*") {

    system("nice yes '' | apt-get --purge remove $2 >>$logpath 2>>$logpath\n");
    print "*"; }


  # + = install (add) some packages
  # ! = overwrite ("yes", "really") some packages
  # else = also normal install

  else {

    my $packages;
    my $todo;
    if ($1 eq "+") {
      $todo = "install"; $packages = $2; }
    elsif ($1 eq "!") {
      $todo = "reinstall"; $packages = $2; }
    else {
      $todo = "install"; $packages = $_; }

    # slit list of $packages, one per line ...
    my @packages;
    @packages = split(/\s+/, $packages);
    # ... elliminate the already installed ones ...
    foreach $package (@packages) {
      if (exists $installed{$package}) {
        $package = ""; } }
    # ... and put the rest back together
    $packages = join (" ", @packages);

    # elliminate leading spaces ...
    $packages =~ s/^\s+//;
    # ..., if all packages killed, only spaces, now empty string, nothing inst
    if ($packages eq "") {
      next; }

    if ($todo eq "reinstall") {
      system("nice yes '' | " .
        "nice yes '' | apt-get --reinstall install $2 >>$logpath 2>>$logpath\n");
      print "!"; }
    else {
      system("nice yes '' | " .
        "nice yes '' | apt-get install $packages >>$logpath 2>>$logpath\n");
      print "+"; }

    if (-f $reboot_file) {
      # one of the installed packages wants a reboot

      if ($called_by eq "init") {
        # if User is booting anyway, and so not in the middle of working
        #   we can reboot immediately without any question
        system("echo @@@ reboot: `date -R` >>$logpath\n");
        unlink $reboot_file;
        system("reboot\n"); }

      else {
        # if nightly cron update, and so perhaps a user is working
        #   we are not allowed to reboot, as this would interrupt work
        #   so no reboot, and file not deleted, test and mail later
        # for now just log that it would have happend
        system("echo @@@ reboot *ignored*: `date -R` >>$logpath\n"); } } } }


# finish off all the progress log lines
print "\n";

close(PKGLIST);


# --- warn admin if some package wanted reboot and did not get it

if (-f $reboot_file) {
  # if reboot detection + delete + reboot above did not happen
  #   this will catch it and mail admin for help
  system("echo @@@ reboot *mailed*: `date -R` >>$logpath\n");
  my $mail_subject = "cron dphys-admin reboot on `hostname`";
  my $echo_text = "dphys-setup.pl has made an $reboot_file";
  system("echo $echo_text | mail -s \"$mail_subject\" root \n"); }


# --- apt-get upgrade the already installed packages

# is after install/remove/purge, so we do not upgrade "vanishing" packages
print("upgrading existing packages ...\n");
system("echo @@@ apt-get upgrade: `date -R` >>$logpath 2\n");
system("nice yes '' | apt-get upgrade >>$logpath 2>>$logpath\n");


# --- final logging

print("\ndphys-setup.pl is done with upgrading software collection\n");
# so log the end
system("echo @@@ dphys-setup.pl $called_by ended: `date -R` >>$logpath\n");

if ($called_by eq "cron") {
  # and then send the logfile (contains only this run) to root
  my $mail_subject = "cron dphys-admin has run on `hostname`";
  system("mail -s \"$mail_subject\" root < $logpath\n"); }
