#!/usr/bin/perl -wT
#
# (C) 2012 D. V. Wiebe
#
#############################################################################
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this pogram; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

use strict;
use File::Copy;

# globals
my $RECONFIG = 0;
my $REMOUNT = 0;

# the version of this script
my $VERSION=20130506;

# this should change
my $ROOT_UUID = "71d80df6-c937-4ee8-90eb-07a323dfb10a";

# filenames
my $MCC_INIT = "/root/mcc_init";
my $RUN_INIT = "/run/mcc_init";
my $MCE_DSP = "/proc/mce_dsp";
my $MCC_ID = "/etc/mcc_id";
my $MCC_SERIAL = "/etc/mcc_serial";
my $HOSTS = "/etc/hosts";
my $HOSTNAMEfile = "/etc/hostname";
my $SSH_KEY = "/etc/ssh/ssh_host_rsa_key";
my $ETC_RESOLV = "/etc/resolv.conf";
my $RUN_RESOLV = "/run/resolv.conf";
my $NET_IFACE  = "/etc/network/interfaces";

my $SRC = "/home/mce/src";

# don't use this ssh fingerprint (it's the ssh fingerprint of the UBC Master)
my $BAD_FINGERPRINT = "0e:f3:91:a4:51:d1:47:7b:4a:a0:16:18:3d:d8:a1:0e";

# taint management
$ENV{PATH} = "/sbin:/bin:/usr/sbin:/usr/bin";

sub Usage {
  die <<'EOF';
Copyright (C) 2012 D. V. Wiebe

Usage
  cd / && mcc_init config [OPTION]...
or
  cd / && mcc_init reconfig [OPTION]...

This program performs the basic configuration required to initialise a new MCE
Control Computer (MCC).  It can also be used to reconfigure an existing system.

The first argument to this function must be either "config" (if configuring a
new system) or "reconfig" (if reconfiguring a previously initialised system.
(This is simply a mechanism to deter inadvertant execution: only one of these is
appropriate in any situation; choosing the wrong one will result in the script
doing nothing.

Other options available:

  installmas         Install MAS and MCE script.  This will also check out and
                     build them, if necessary.  Done automatically in config
                     mode and whenever /data0 is remastered.

  hostname=HOSTNAME  Set the hostname to HOSTNAME.  In config mode, if not
                     specified the default is used.  In reconfig mode, this
                     will rename the host.

  id=N               Set the MCC system id to N.  In config mode, you will be
                     prompted for this if not specified here.  In reconfig mode,
                     this is ignored.

  newkey             Create a new SSH key.  This is done automatically if the
                     default key is detected.

  remaster=0         Reinitialise /data0.  Doing this will delete everything on
                     the partition (including /home).

  remaster=1         Reinitialise /data1.  Doing this will delete everything on
                     the partition.

  remaster=0,1       Reinitialise both /data0 and /data1.  This is done
                     automatically in config mode.

  duplicate          Make /data1 a duplicate of /data0.  This is useful if you
                     wish to replace the drive containing /data0 without losing
                     the current /home.  This will delete everything on /data1.
                     Also note: the contents of /data0/mce (ie. acquired data)
                     are not copied over to the new drive.  This option is
                     mutually incompatible with the 'remaster' option.  Ignored
                     in config mode.

  noupdate           Don't try to self-update before operation.

WARNING: cavalier use of this program may result in data loss or render the
         system unusable.  Use with caution.

Please send reports of bugs and other communication to:
         D. V. Wiebe <dvw@phas.ubc.ca>

This program comes with no warranty, not even for merchantability or fitness
for a particular purpose.  See the GNU General Public License for more details.
EOF
}

# partition a data device via fdisk ... sfdisk would be easier to use, but
# doesn't do the right thing.
sub Fdisk {
  my $dev = shift;
  print "  ... running fdisk to partition $dev\n";

  open FDISK, "|fdisk $dev >/dev/null"
    or die "FATAL! Couldn't spawn fdisk: $!\n";

  local $SIG{PIPE} = sub { die "FATAL: Unexpected close from fdisk!" };

  # the following is ten fdisk inputs:
  # "d\n1\n -> delete partition 1
  # "d\n2\n -> delete partition 2
  # "d\n3\n -> delete partition 3
  # "d\n4\n -> delete partition 4
  # "n\n" -> create a new partition
  # "\n" -> primary partition (default)
  # "\n" -> partition number 1 (default)
  # "\n" -> first sector 2048 (default)
  # "\n" -> last sector (default is use entire device)
  # "w\n" -> write and exit
  print FDISK "d\n1\nd\n2\nd\n3\nd\n4\nn\n\n\n\n\nw\n";

  close FDISK or die "FATAL! Bad spool on fdisk: $!";
}

# run a system command and make sure it was successful
sub ForkExec {
  my ($cmd, $exit, $message) = @_;

  my $return = qx/$cmd/;

  if ($? == -1) {
    die "Unexpectedly unable to execute `$cmd`: $!\n";
  } elsif ($? & 127) {
    die "Command `$cmd` unexpectedly died with signal ", ($? & 127), "\n";
  } elsif ($?) {
    if ($message) {
      print "$message: error code ", ($? >> 8), "\n";
    } else {
      print "FATAL! " if ($exit);
      print "Command `$cmd` exited abnormally: error code ", ($? >> 8), "\n";
    }
    exit 1 if ($exit);
  }

  chomp $return;
  $return
}

# try to unmount a device
sub Umount {
  my ($dev, $mount) = @_;
  $dev = "/dev/$dev";

  # is the mountpoint busy?
  my $lsof = qx/lsof $mount/;
  my $busy = not ($? >> 8);

  print "Trying to unmount $dev from $mount:\n";

  if ($mount eq "/data0") {
    # stop the usual suspects
    print "  ... stopping cron\n";
    ForkExec("/usr/sbin/service cron stop >/dev/null");
    print "  ... stopping syslog\n";
    ForkExec("/usr/sbin/service rsyslog stop >/dev/null");
  }

  if ($busy) {

    # collect pids
    my @pids;
    for (split /\n/, $lsof) {
      if (/^[^ ]* *([0-9]+)/) {
        push @pids, $1;
      }
    }
    if ($#pids > -1) {
      print "  ... HUPing all processes using $mount\n";
      kill "HUP", @pids;
      sleep 1;
      print "  ... TERMing all processes using $mount\n";
      kill "TERM", @pids;
      sleep 1;
      print "  ... KILLing all processes using $mount\n";
      kill "KILL", @pids;
    }
  }

  print "  ... running umount\n";
  ForkExec("umount $dev", 1);
  print "\n";
}

# remount read/write
sub ReMount {
  return if $REMOUNT;

  print "  ... Remounting / read-write\n";
  ForkExec("mount / -o remount,rw", 1, "FATAL! Couldn't remount / read-write");
  $REMOUNT = 1;
}

# create/overwrite a file containing something
sub CatFile {
  my ($name, $data, $quiet) = @_;
  print "  ... Recreating $name\n" unless $quiet;
  open OUT, ">$name";
  print OUT $data;
  close OUT
}

# create subdirs
sub MakeSubDirs {
  my $parent = shift;

  for (@_) {
    my $dir = "$parent/$_";
    my $mode = ($_ eq "tmp") ? 01777 : 0755;
    mkdir $dir, $mode or die "  ERROR: Couldn't create $dir: $!\n";
    if ($_ =~ /^(mce|mas)/) {
      chown 1000, 1000, $dir or die "  ERROR: Couldn't chown $dir: $!";
    }
  }
}

#check dotted quads
sub CheckInetAddr {
  my $addr = shift;
  my ($a, $b, $c, $d) = $addr =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/;
  Usage() unless defined $d;

  Usage() if ($a > 255 or $b > 255 or $c > 255 or $d > 255);

  $addr
}

# do a substitution on a file
sub SedFile {
  my ($name, $old, $new) = @_;
  local $/; # slurp

  print "  ... Modifying $name\n";
  #read in
  open IN, "<$name";
  my $file = <IN>;
  close IN;
  
  #substitute
  $file =~ s/$old/$new/g;

  #write back
  CatFile($name, $file, 1);
}

# Ensure we're root
if ($> != 0) {
  print "This program must be run as root.\n";
  exit 1;
}

# salutations
print "MCC Init version $VERSION\n";

# foolproofing
Usage() if ($#ARGV < 0);

if ($ARGV[0] eq "reconfig") {
  $RECONFIG = 1
} elsif ($ARGV[0] ne "config") {
  Usage();
}
shift @ARGV;

# collect options
my ($HOSTNAME, $ID, $NEWKEY, $DUPLICATE, $NOUPDATE, $INSTALLMAS);
my ($DHCP, $INET_ADDR, $NETMASK, $GATEWAY, $DNS_SERVER);
my $REMASTER = "";
for (@ARGV) {
  # strip leading dashes
  s/^-+//;

  if (/^hostname=([0-9A-Za-z-]+)$/) {
    $HOSTNAME = $1;
  } elsif (/^net=dhcp$/) {
    $DHCP = 1;
  } elsif (/^net=([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+)/) {
    $DHCP = undef;
    $INET_ADDR = CheckInetAddr($1);
    $NETMASK = CheckInetAddr($2);
    $GATEWAY = CheckInetAddr($3);
    $DNS_SERVER = CheckInetAddr($4);
  } elsif (/^id=([0-9]+)$/) {
    $ID = $1;
  } elsif (/^remaster=([01]+)$/) {
    $REMASTER .= $1;
  } elsif (/^duplicate$/) {
    $DUPLICATE = 1;
  } elsif (/^newkey$/) {
    $NEWKEY = 1;
  } elsif (/^installmas$/) {
    $INSTALLMAS = 1;
  } elsif (/^noupdate$/) {
    $NOUPDATE = 1;
  } else {
    Usage();
  }
}
print "\n";

die "ERROR: You may not specify both 'duplicate' and 'remaster'.\n" if (
  $REMASTER ne "" and $DUPLICATE);

# check running environment
my $CWD = `pwd`;
die "ERROR: This program must be run from /.  Try 'cd /' first.\n" unless (`pwd`
  eq "/\n");

# don't get tripped up by umask
umask 0;

# configure network
if ($DHCP) {
  print "Configuring network:\n";
  ReMount();
  print "  ... removing any previous lease\n";
  ForkExec("dhclient -r eth0", 1);
  print "  ... requesting DHCP lease\n";
  ForkExec("dhclient eth0", 1);
  if (not -l $ETC_RESOLV) {
    if (-e $ETC_RESOLV) {
      unlink $ETC_RESOLV or die "FATAL! Couldn't mess with $ETC_RESOLV: $!\n";
    }
    symlink $RUN_RESOLV, $ETC_RESOLV
      or die "FATAL! Couldn't create symlink: $!\n";
  }
  CatFile($NET_IFACE, <<EOF
auto lo eth0
iface lo inet loopback
iface eth0 inet dhcp
EOF
  );
  print "\n";
} elsif (defined $INET_ADDR) {
  print "Configuring network:\n";
  ReMount();
  print "  ... setting IP4 address\n";
  ForkExec("ifconfig eth0 $INET_ADDR netmask $NETMASK", 1);
  print "  ... routing\n";
  ForkExec("route del default", 0);
  ForkExec("route add default gateway $GATEWAY", 1);
  if (-l $ETC_RESOLV) {
    print "  ... removing resolv.conf symlink\n";
    unlink $ETC_RESOLV or die "FATAL! Couldn't mess with $ETC_RESOLV: $!\n";
  }
  CatFile($ETC_RESOLV, "nameserver $DNS_SERVER\n");
  CatFile($NET_IFACE, <<EOF
auto lo eth0
iface lo inet loopback
iface eth0 inet static
  address $INET_ADDR
  netmask $NETMASK
  gateway $GATEWAY
EOF
  );
  print "\n";
}

# self-update
unless ($NOUPDATE) {
  print "Self-updating:\n";
  print "  ... checking server for update\n";
  my ($latest, $server_md5) = ForkExec("wget --quiet -O- " . 
    "http://e-mode.phas.ubc.ca/mce/mcc/LATEST_IS") =~ /^([0-9]*) (.*)$/;
  if ($latest) {
    chomp $latest;
    print "  ... latest program version available: $latest\n";

    if ($latest > $VERSION) {
      $RUN_INIT .= "_$latest";
      my $bad_dl = -1;
      do {
        print "  ... fetching update\n";
        ForkExec("wget --quiet -O$RUN_INIT " . 
          "http://e-mode.phas.ubc.ca/mce/mcc/mcc_init_$latest");
        print "  ... verifying update\n";
        my ($local_md5) = ForkExec("md5sum $RUN_INIT", 0,
          "  ERROR: couldn't download program update.  Skipped.") =~ /^([^ ]*)/;
        if (not defined $local_md5) {
          print "  ERROR: couldn't download update.\n";
          $bad_dl = -1;
          last;
        } elsif (lc $local_md5 eq lc $server_md5) {
          $bad_dl = 0;
        } else {
          print "  ERROR: Verification failed.  Trying again.\n";
          $bad_dl = ($bad_dl == -1) ? 1 : $bad_dl + 1;
        }
      } until ($bad_dl == 0 or $bad_dl > 3);

      if ($bad_dl) {
        print "ERROR: Problems encountered while trying to download update. " .
        "Skipped.\n";
      } else {
        print "  ... moving update into place\n";
        # save
        copy($MCC_INIT, $MCC_INIT . "-") or die "FATAL! Back-up failed: $!\n";
        # move new script into place
        move($RUN_INIT, $MCC_INIT) or die "FATAL! Copying new script into " .
        "place failed: $!\n";
        # re-exec
        print "  ... re-starting\n\n";
        my @args = ($RECONFIG ? "reconfig" : "config", "noupdate");
        push @args, "duplicate" if ($DUPLICATE);
        push @args, "hostname=$HOSTNAME" if ($HOSTNAME);
        push @args, "id=$ID" if (defined $ID);
        push @args, "installmas" if ($INSTALLMAS);
        push @args, "newkey" if ($NEWKEY);
        push @args, "remaster=$REMASTER" unless ($REMASTER eq "");
        chmod 0700, $MCC_INIT;
        exec $MCC_INIT, @args;
      }
    }
  } else {
    print "  ERROR: Can't talk to server, self-update skipped.\n";
  }
}
print "\n";

# hardware detection
print "Hardware detection commencing:\n";

# figure out how many fibre cards we have.
$_ = ForkExec("cat $MCE_DSP", 1);
my $ncards = 1 + scalar(/^quiet_RP/mg);
print "  ... found $ncards PCI fibre card" . (($ncards == 1) ? "" : "s") . "\n";
if ($ncards < 1 or $ncards > 2) {
  die "FATAL! Bizarre number of PCI fibre cards found.  Check your hardware.\n";
}

# which device is the CF card?
$_ = readlink "/dev/disk/by-uuid/$ROOT_UUID"
  or die "FATAL! Couldn't determine root partition: $!\n";

my ($rootdev) = /(sd[a-z])1/;
print "  ... found CF card on device $rootdev\n";

# find mounted devices
my @datas;
for (split '\n', ForkExec("mount 2> /dev/null")) {
  if (m#/dev/([a-zA-Z0-9_/-]+) on /data([01])#) {
    $datas[$2] = $1;
  }
}

print "  ... /dev/$datas[0] is mounted on /data0\n" if ($datas[0]);
print "  ... /dev/$datas[1] is mounted on /data1\n" if ($datas[1]);

# find hard drives
my ($ndrive, $nextra, $nsuppl) = (0, 0, 0);
my @devs;
for(split '\n', ForkExec('fdisk -l 2> /dev/null')) {
  if (m#^Disk /dev/(sd[a-z]): .*, ([0-9]*) bytes#) {
    my $dev = $1;
    next if ($dev eq $rootdev);

    my $capacity = $2;
    my $repcap;
    if ($capacity >= 1e13) {
      $repcap = sprintf "(%.2f TB)", $capacity / 1e9;
    } elsif ($capacity >= 1e10) {
      $repcap = sprintf "(%.2f GB)", $capacity / 1e9;
    } elsif ($capacity >= 1e7) {
      $repcap = sprintf "(%.2f MB)", $capacity / 1e6;
    } elsif ($capacity >= 1e4) {
      $repcap = sprintf "(%.2f kB)", $capacity / 1e3;
    } else {
      $repcap = sprintf "(%.2f  B)", $capacity;
    }

    if ($dev gt $rootdev) {
      print "  ... found supplemental (unconfigurable) device: $dev $repcap\n";
      $nsuppl++;
    } else {
      if ($ndrive == 2) {
        print "  ... found unexpected additional (unconfigurable) device: $dev "
        . $repcap . "\n";
        $nextra++;
      } else {
        printf "  ... found data device (/data$ndrive): $dev $repcap\n";
        $devs[$ndrive] = $dev;
        $ndrive++;
      }
    }
  }
}

print "\n";

# check whether we've been configured before.
my $CHECK_ID = ForkExec("cat $MCC_ID");
if ($CHECK_ID =~ /[^0-9]/) {
  # new system
  die "FATAL! can't reconfigure an unconfigured system.\n" if $RECONFIG;

  # default operation
  $REMASTER = ($ndrive == 2) ? "01" : "0";
  $DUPLICATE=undef;

  # ask for ID if not given.
  until ($ID) {
    print "MCC ID: ";
    $_ = <>;
    
    #detaint
    if (defined $_ and /^([0-9]+)$/) {
      $ID = $1;
      print "\n";
    }
  }

  # generate hostname if necessary
  $HOSTNAME = sprintf("mcc%02i", $ID) unless $HOSTNAME;
} else {
  # already configured
  die "FATAL! can't configure a configured system.\n" unless $RECONFIG;
  $ID = $CHECK_ID;
}

$INSTALLMAS = 1 if ($REMASTER =~ /0/);

# check whether the hardware jibes with the requested operation
die "FATAL! can't duplicate devices: not enough devices\n" if ($DUPLICATE and
  $ndrive < 2);
die "FATAL! can't remaster /data1: no available device\n" if ($REMASTER =~
  /1/ and $ndrive < 2);
die "FATAL! can't remaster /data0: no available device\n" if ($REMASTER =~
  /0/ and $ndrive < 1);

# grab some interesting numbers early

# MCC Image id
my ($IMAGE) = ForkExec("cat $MCC_SERIAL", 1) =~ /^([0-9]+)$/;
die "FATAL! Bad system image: $IMAGE" unless ($IMAGE);

# MAC address
my ($MAC) = ForkExec("ifconfig eth0") =~ /HWaddr ([0-9A-Fa-f:]*)/;

# SSH fingerprint
my ($SSH) =
  ForkExec("ssh-keygen -lf $SSH_KEY.pub") =~ /^[0-9]* ([0-9A-Fa-f:]+)/;

# check for the bad key
$NEWKEY = 1 if (lc $SSH eq $BAD_FINGERPRINT);

# remember the old hostname
my $OLD_HOSTNAME = ForkExec("hostname");

# what do we need to unmount?
my @need_umount;
if ($DUPLICATE or $REMASTER ne "") {
  # unmount stuff as necessary
  $need_umount[0] = 1 if (defined $datas[0] and ($REMASTER =~ /0/ or
      (defined $devs[1] and $datas[0] eq $devs[1] and $REMASTER =~ /1/)));
  $need_umount[1] = 1 if (defined $datas[1] and ($DUPLICATE or $REMASTER =~ /0/
        or (defined $devs[0] and $datas[1] eq $devs[0] and $REMASTER =~ /1/)));
}

# it's a bad idea to get interrupted after this point
$SIG{INT} = $SIG{TERM} = sub {
  my $sig = shift;
  print "\nInterrupting this program now is not a good idea.\n",
  "But we won't stop you if you try the same thing again.\n";
  $SIG{$sig} = 'DEFAULT';
};

#unmount stuff!
Umount($datas[0], "/data0") if ($need_umount[0]);
Umount($datas[1], "/data1") if ($need_umount[1]);

# record ID, if appropriate
if (not $RECONFIG) {
  print "Recording configuration:\n";
  ReMount();
  CatFile($MCC_ID, $ID . "\n");
  print "\n";
}

# set the hostname if needed
$HOSTNAME ||= $OLD_HOSTNAME;
if ($OLD_HOSTNAME ne $HOSTNAME) {
  print "Setting hostname to \"$HOSTNAME\":\n";
  ReMount();
  CatFile($HOSTNAMEfile, $HOSTNAME . "\n");
  SedFile($HOSTS, "\\b$OLD_HOSTNAME\\b", $HOSTNAME);
  print "  ... running hostname\n";
  ForkExec("hostname $HOSTNAME");
  print "\n";
}

# generate a new ssh key, if needed
if ($NEWKEY) {
  print "Generating a new SSH key:\n";
  ReMount();
  print "  ... running ssh-keygen\n";
  ForkExec("yes | ssh-keygen -t rsa -f $SSH_KEY -P \"\"");

  # capture the new fingerprint
  ($SSH) = ForkExec("ssh-keygen -lf $SSH_KEY.pub") =~ /^[0-9]* ([0-9A-Fa-f:]+)/;
  print "\n";
}

# Remaster /data0
if ($REMASTER =~ /0/) {
  my $dev = "/dev/$devs[0]";
  print "Remastering /data0:\n";
  Fdisk($dev);
  $dev .= "1";
  print "  ... running mkfs.ext4 to create a filesystem on $dev\n";
  ForkExec("mkfs.ext4 -q $dev >/dev/null", 1);
  print "  ... mounting $dev on /data0\n";
  ForkExec("mount $dev >/dev/null", 1);
  print "  ... creating filesystem skeleton\n";
  MakeSubDirs("/data0", qw(mas mce tmp));
  symlink "mce", "/data0/mce0" or die "FATAL! Couldn't create symlink: $!\n";
  if ($ndrive == 1) {
    MakeSubDirs("/data0", qw(mce1));
  } else {
    symlink "/data1/mce", "/data0/mce1"
      or die "FATAL! Couldn't create symlink: $!\n";
  }
  chdir "/data0" or die "Couldn't chdir to /data0: $!\n";
  print "  ... replicating /data0/home\n";
  ForkExec("wget -O- --quiet " .
    "http://e-mode.phas.ubc.ca/mce/mcc/mcc_home_$IMAGE.txz | tar -Jxv", 1);
  print "  ... replicating /data0/var\n";
  ForkExec("wget -O- --quiet " .
    "http://e-mode.phas.ubc.ca/mce/mcc/mcc_var_$IMAGE.txz | tar -Jxv", 1);
  chdir "/" or die "Couldn't chdir to /: $!\n";
  print "\n";
}

# Remaster /data1
if ($REMASTER =~ /1/) {
  my $dev = "/dev/$devs[1]";
  print "Remastering /data1:\n";
  Fdisk($dev);
  $dev .= "1";
  print "  ... running mkfs.ext4 to create a filesystem on $dev\n";
  ForkExec("mkfs.ext4 -q $dev >/dev/null", 1);
  print "  ... mounting $dev on /data1\n";
  ForkExec("mount $dev >/dev/null", 1);
  print "  ... creating filesystem skeleton\n";
  MakeSubDirs("/data1", "mce");
  print "\n";
} elsif ($DUPLICATE) {
  # Duplicate /data0
  my $dev = "/dev/$devs[1]";
  print "Duplicating /data0 on $dev:\n";
  Fdisk($dev);
  $dev .= "1";
  print "  ... running mkfs.ext4 to create a filesystem on $dev\n";
  ForkExec("mkfs.ext4 -q $dev >/dev/null", 1);
  print "  ... mounting $dev on /data1\n";
  ForkExec("mount $dev >/dev/null", 1);
  print "  ... creating filesystem skeleton\n";
  symlink "mce", "/data1/mce0" or die "FATAL! Couldn't create symlink: $!\n";
  if (-l "/data0/mce1") {
    symlink "/data1/mce", "/data1/mce1"
      or die "FATAL! Couldn't create symlink: $!\n";
  } else {
    MakeSubDirs("/data1", qw(mce1));
  }
  MakeSubDirs("/data1", qw(mas mce tmp));
  print "  ... duplicating /data0/home\n";
  ForkExec("rsync -a /data0/home /data1", 1);
  print "  ... duplicating /data0/mas\n";
  ForkExec("rsync -a /data0/mas /data1", 1);
  print "  ... duplicating /data0/var\n";
  ForkExec("rsync -a /data0/var /data1", 1);

  print "\n";
}

# clean up
my $TIDY = 0;
if ($REMOUNT) {
  print "Tidying up:\n";
  $TIDY = 1;
  print "  ... remounting / read-only.\n";
  ForkExec("mount / -o remount,ro", 0,
    "ERROR! Couldn't remount / read-only");
}

if ($REMASTER =~ /0/) {
  print "Tidying up:\n" unless $TIDY;
  $TIDY = 1;
  print "  ... starting syslog\n";
  ForkExec("/usr/sbin/service rsyslog start >/dev/null");
  print "  ... starting cron\n";
  ForkExec("/usr/sbin/service cron start >/dev/null");
}

print "\n" if $TIDY;

# we do this late, since it's not a privileged operation
if ($INSTALLMAS) {
  my $SKIP_OK = 1;
  $( = $) = 1000; # sg to mce
  $< = $> = 1000; # su to mce
  umask 022; # use a sane umask
  print "Installing MAS:\n";
  chdir $SRC or die "FATAL! Couldn't chdir to $SRC: $!\n";

  unless (-e "mas/INSTALL" ) {
    print "  ... checking-out MAS\n";
    ForkExec("svn --username=mceanon co svn://e-mode.phas.ubc.ca/mas/trunk " .
      "mas >/dev/null", 1);
    $SKIP_OK = 0; 
  }
  chdir "mas" or die "FATAL! Couldn't chdir to $SRC/mas: $!\n";
  unless ($SKIP_OK and -e "configure" ) {
    print "  ... bootstrapping MAS\n";
    ForkExec("autoreconf -ifs >/dev/null", 1);
    $SKIP_OK = 0;
  }
  unless ($SKIP_OK and -e "Makefile") {
    print "  ... configuring MAS\n";
    ForkExec("./configure" .
      " --prefix=/data/mas" .
      " --enable-multicard" .
      " --disable-driver" .
      " --disable-sys-links" .
      " --disable-init-script" .
      " --with-etc-dir=/data/mas/etc" .
      " --with-data-root=/data0/mce,/data1/mce" .
      " >/dev/null", 1);
    $SKIP_OK = 0;
  }
  # "configure"
  unless ($SKIP_OK and -e "config2/mce0.cin" and -e "config2/mce1.cin") {
    copy "config2/templates/mce_v2.cin", "config2/mce0.cin"
      or die "FATAL! Couldn't configure MAS/mce0: $!";
    copy "config2/templates/mce_v2.cin", "config2/mce1.cin"
      or die "FATAL! Couldn't configure MAS/mce1: $!";
  }
  print "  ... rebuilding MAS\n";
  ForkExec("make -j2 >/dev/null", 1);

  print "  ... installing MAS\n";
  ForkExec("make install >/dev/null");

  print "\n";

  # now MCE script
  $SKIP_OK = 1;
  print "Installing MCE Script:\n";
  chdir $SRC or die "FATAL! Couldn't chdir to $SRC: $!\n";

  unless (-e "mce_script/INSTALL" ) {
    print "  ... checking-out MCE Script\n";
    ForkExec("svn --username=mceanon co " .
      "svn://e-mode.phas.ubc.ca/mce_script/trunk " .
      "mce_script >/dev/null", 1);
    $SKIP_OK = 0; 
  }

  $ENV{'MAS_VAR'} = "/data/mas/bin/mas_var";
  chdir "mce_script" or die "FATAL! Couldn't chdir to $SRC/mce_script: $!\n";
  print "  ... rebuilding MCE Script\n";
  ForkExec("make >/dev/null", 1);

  print "  ... installing MCE Script\n";
  if (not -e "/data/mas/mce_script") {
    mkdir "/data/mas/mce_script" or die "Can't install mce_script: $!\n";
  }
  ForkExec("make install >/dev/null");

  print "\n";
}

# summarise, I guess
print "Success! (maybe)\n";
print "Here are some things you might want to write down:\n";
print "  MCC ID:               $ID\n";
print "  System Image Version: $IMAGE\n"; 
print "  MAC Address:          $MAC\n";
if ($DHCP) {
  print "  INET Address:         dhcp\n";
} elsif (defined $INET_ADDR) {
  print "  INET Address:         $INET_ADDR\n";
}
print "  Hostname:             $HOSTNAME\n";
print "  SSH Fingerprint:      $SSH\n";
print "\nHave fun!\n";

print "\n(PS. A 'telinit 6' may be required after remastering /data0.)\n" if (
  $REMASTER =~ /0/);
