#!/bin/sh
#
# 	$Source: /home/bs/src/bootcd/backupcvs/bootcd/bootcd/bootcdbackupwizard.src,v $
#       $Id: bootcdbackupwizard.src,v 1.79 2011-04-11 07:21:48 bs Exp $
#    author: Carsten Dinkelmann <din@foobar-cpa.de>
#            Bernd Schumacher <bernd.schumacher@hp.com>
#     start: 20.09.2007
#     topic: wizard for bootcddebootstrap and bootcdbackup
#
#  Download: the newest version of this wizard can be downloaded with "wget
#            ftp://bootcd.alioth.debian.org/pub/bootcd/bootcdbackupwizard"
#


# Version of debian package bootcd with this wizard included in 
# /usr/share/doc/bootcd-backup/examples/bootcdbackupwizard
VERSION=##BOOTCD_VERSION## # sources are tagged in cvs with rev=debian_version_${VERSION}

# fixe language problem
export LANG=C
export LC_ALL=C

# used basis directory under the workdir
IMGDIR=bootcd-img

# download from there
BOOTSTRAPPATH="ftp://ftp.de.debian.org/debian/pool/main/d/debootstrap/debootstrap_1.0.38_all.deb"

# needed tools
TOOLS="wget tar ./bootcddebootstrap"

# exclude from backing up (ending slash includes the dirextory itself!)
EXCLUDE_DIRS="/proc/ /sys/ /dev/"

# subdir to $workdir where the backup tar resists later
BACKUPDIR="backup"

# PATH to bootcdmk2diskconf, under $workdir
BOOTCDMK2DISKCONF="/usr/share/bootcd/bootcdmk2diskconf"

ERRLOG="/var/log/bootcdbackupwizard.log"

# If SCRIPT is not empty, Warnings would be ignored
SCRIPT=""

######################### nothing to configure behind this line ###############

SCRIPTNAME=$(basename $0)

IAHEAD="$SCRIPTNAME $VERSION"

### BOOTCD-RUN_LIB Placeholder ##############

### BOOTCDBACKUP_LIB Placeholder ##############

# function: usage
# get: $1 -- error string
usage()
{
  [ "$1" ] && echo  >&2 "$1"

  echo  "              $SCRIPTNAME Version $VERSION"
  echo  ""
  echo  " $SCRIPTNAME is the wizard for bootcddebootstrap and bootcdbackup. "
  echo  " bootcddebootstrap build a debian bootcd from scratch and bootcdbackup guess " 
  echo  " the parameters of the running system and the system itself to the" 
  echo  " created image. After that, you can backup your running system to a CD/DVD."
  echo ""
  echo  " syntax: $SCRIPTNAME [-i] [-m <mirror>] [-e <directories>] -d <directory>"
  echo  ""
  echo  "                 -i   -- do all things interactive"
  echo  "          -m <mirror> -- debian mirror for debootstrap (e.g. http://ftp.de.debian.org/debian)"
  echo  "     -e <directories> -- space seperated directories exclude from backup"
  echo  "       -c <directory> -- look for bootcd2disk.conf and bootcdwrite.conf in"
  echo  "                         this directory and use it if found."
  echo  "       -d <directory> -- create image in this directory (Space needed!)"
  echo  "                   -b -- backup only (the debian chroot already has to exist)"
  echo  ""
  echo  "                   -B -- do not delete files needed to backup again (using -b)"                 
  echo  ""
  echo  "               --star -- use star for backup"
  echo  ""
  echo  "               --cpio -- use cpio for star"
  echo  ""
  echo  "         --externdata -- do not put backupfiles on bootcd"
  echo  ""
  echo  " example: $SCRIPTNAME -e \"/home/data /home/newbie/tmp\" -d /var/spool/bootcd"
  echo  ""
 
  exit 0
}

# function: realpath -- gives the full path of file or dir
realpath()
{
  [ $# -eq 1 ] && echo "$(cd $(dirname $1); /bin/pwd)/$(basename $1)"
}


# function: in_stdout_wget -- in_stdout for wget
# get: $1 -- number
# ret: stdout string in STDOUT<number>
in_stdout_wget()
{
  local mm=$1

  in_stdout $mm "^[[:space:]]*=> .*"
  in_stdout $mm "^Resolving .*"
  in_stdout $mm "^Connecting .*"
  in_stdout $mm "^Logging .*"
  in_stdout $mm "^==> SYST .*"
  in_stdout $mm "^==> TYPE .*"
  in_stdout $mm "^==> PASV .*"
  in_stdout $mm "^Length: .*"
  in_stdout $mm "^[[:space:]]*OK.*"
  in_stdout $mm "^Length .*"
  in_stdout $mm "^$"
  in_stdout $mm "^[[:digit:]:-]* .*"  
  in_stdout $mm "^HTTP request sent, .* 200 OK[[:space:]]*$"
  in_stdout $mm "^==> SIZE "
}


### functions below split tasks in main functions 
### - to see what is going on
### - this functions use global variables


# function: prepare_debian_chroot 
# get: $1  -- interactive (optional)
# globals: debootstrapmirror
prepare_debian_chroot()
{
  local interact=""; [ $# -ge 1 ] && interact="$1"
  local TODO="" tt="" mm=0
  local IAHEAD="$IAHEAD / prepare_debian_chroot"

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm "^[[:space:]]*check for \"[^[:space:]]*\" ...ok$"
  TODO="$TODO \"check_tools $TOOLS # Check if tools are installed\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\" 
  in_stdout $mm "Directory.*"
  TODO="$TODO \"do_dir $workdir # Create work directories \""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\" 
  in_stdout_wget $mm
  TODO="$TODO \"wget -P $workdir \\\"$BOOTSTRAPPATH\\\" # Get files to create mini debian system \""

  # link debian files to work directory
  for i in $(ls *deb); do
     mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
     TODO="$TODO \"ln -s $(pwd)/$i $workdir/\""
  done

  [ "$interact" ] && interact="" || interact="-y"
  eval "interactive -h \"=== \$IAHEAD ===\" $interact -r $TODO"
}

trapfunc()
{
  # Ignore Traps
  trap "" 0 2

  # We must tell the user to unmount if necessarry
  echo >&2 "bootcdbackupwizard trapfunc: checking if filesystems are unmounted in $workdir/$IMGDIR/mnt"
  if [ -e $workdir/$IMGDIR/mnt ]; then
    if [ "$(/bin/ls $workdir/$IMGDIR/mnt/)" ]; then
      BOOTCDMP="$workdir/$IMGDIR/mnt"
      $workdir/$IMGDIR/usr/share/bootcd/bootcd2disk -onlyunmount -c /$BACKUPDIR -s -o -r $workdir/$IMGDIR
      if [ "$(/bin/ls $workdir/$IMGDIR/mnt/)" ]; then
        err "Could not unmount $workdir/$IMGDIR/mnt.
Be sure not to accidently remove $workdir/$IMGDIR before manually unmounting."
      fi
    fi
  fi

  exit 1
}

# function: do_backup
# Input:
#   -i     -- interactive (optional)
#   --cpio
#   --star
#   --externdata
do_backup()
{
  local interact=""
  local TODO=""
  local mm=0

  local BOOTSTRAP_VERSION="$(chroot $workdir/$IMGDIR sh -c "dpkg-query -W -f='\${Version}\n' bootcd")"
  local WIZARD_VERSION="$(echo "$VERSION" | sed "s/_/./")"
  local IAHEAD="$IAHEAD / do_backup"

  local externdata=""
  local use_type=""
  while [ $# -gt 0 ]; do
    case "$1" in
     "--star")
       use_type="$1"
       ;;
     "--cpio")
       use_type="$1"
       ;;
     "--externdata")
       externdata="$1"
       ;;
     "-i")
       interact="$1"
       ;;
      *) 
        echo "do_backup internal error unknown option $1" >&2
        exit 1
        ;;
    esac
    shift
  done

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm "Directory.*"
  TODO="$TODO \"do_dir $workdir/$IMGDIR/$BACKUPDIR # Create work directories\""

  if [ "$bootcd2diskconf" ]; then 
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"cp $bootcd2diskconf $workdir/$IMGDIR/$BACKUPDIR/ # copy config given on commandline\""
  else
    # check chroot and file exist
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"chroot $workdir/$IMGDIR sh -c \\\"chmod 755 $BOOTCDMK2DISKCONF\\\"\""
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_stdout $mm "^  No volume groups found$"
    TODO="$TODO \"sh -c \\\"ERRLOG=$ERRLOG $workdir/$IMGDIR/$BOOTCDMK2DISKCONF -b >$workdir/$IMGDIR/$BACKUPDIR/bootcd2disk.conf\\\" # create config from actual system\""
  fi


  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_norun $mm
  BOOTCDMP="$workdir/$IMGDIR/mnt"
  TODO="$TODO \"$workdir/$IMGDIR/usr/share/bootcd/bootcd2disk -onlymount -c /$BACKUPDIR -s -o -r $workdir/$IMGDIR\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_norun $mm
  TODO="$TODO \"chroot $workdir/$IMGDIR /usr/share/bootcd/bootcdbackup -e \\\"$workdir $extdirs $EXCLUDE_DIRS\\\" -cpiostar $use_type \\\"$IAHEAD\\\" /mnt /$BACKUPDIR\""

  if [ "$externdata" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_norun $mm
    TODO="$TODO \"mkdir $workdir/backup\""

    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_norun $mm
    TODO="$TODO \"mv $workdir/$IMGDIR/backup/data  $workdir/backup\""

    # make empty mountpoint /backup/data for sshfs or nfs mounts
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_norun $mm
    TODO="$TODO \"mkdir $workdir/$IMGDIR/backup/data\""

    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_norun $mm
    TODO="$TODO \"cp $workdir/$IMGDIR/backup/bootcd2disk.conf $workdir/backup\""
  fi

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_norun $mm
  BOOTCDMP="$workdir/$IMGDIR/mnt"
  TODO="$TODO \"$workdir/$IMGDIR/usr/share/bootcd/bootcd2disk -onlyunmount -c /$BACKUPDIR -s -o -r $workdir/$IMGDIR\""

  [ "$interact" ] && interact="" || interact="-y"
  eval "interactive -h \"=== \$IAHEAD ===\" $interact -r $TODO"

  # check if filesystems are really umounted
  if [ "$(/bin/ls $workdir/$IMGDIR/mnt/)" ]; then
    echo >&2 ""
    echo  >&2 "ATTENTION! Found files in \"$workdir/$IMGDIR/mnt\"!"
    echo >&2 "Seems that umount doesn't work, please try it manualy!"
    ask_user "Continue [y|N] " "n"
    [ $? -eq 0 ] && err "User break after umount of \"$workdir/$IMGDIR/mnt\" failed!"
  fi
}


# function: add_info -- add informations to first page
# get: $1    -- bootcd2disk.conf - file
add_info()
{
  local ctrlL=""
  local white="0f"
  local grey="07"
  local green="0a"
  local yellow="0e"
  local bootcdconf=$1
  local file=/etc/bootcd/bootcdbackupwizard.txt

  echo -n "${green}" >>$workdir/$IMGDIR/$file
  cat >>$workdir/$IMGDIR/$file <<-'END'
  _                 _          _
 | |__   ___   ___ | |_ ___ __| |
 | '_ \ / _ \ / _ \| __/ __/ _` |_____
 | |_) | (_) | (_) | || (_| (_| |_____|
 |_.__/ \___/ \___/ \__\___\__,_|
     _                _                        _                  _
    | |__   __ _  ___| | ___   _ _ ____      _(_)______ _ _ __ __| |
    | '_ \ / _` |/ __| |/ / | | | '_ \ \ /\ / / |_  / _` | '__/ _` |
    | |_) | (_| | (__|   <| |_| | |_) \ V  V /| |/ / (_| | | | (_| |
    |_.__/ \__,_|\___|_|\_\\__,_| .__/ \_/\_/ |_/___\__,_|_|  \__,_|
                                |_|
END
# '

cat >>$workdir/$IMGDIR/$file <<END

 ${grey}Backup of host ${white}$(hostname) ${grey}from ${white}$(date "+%d. %B %Y, %X")
 ${white}restore with: ${yellow}bootcd2disk -c /backup

 ${grey}To select a cdrom specify "hda", "hdb", "hdc", "hdd", "sr0" or "sr1".
END

  sed -ie "s|^\(DISPLAY=\"\)[^\"]*\(\"\)|\1${file}\2|" $bootcdconf

} 


# function: run_bootcdwrite
# get: $1     -- interactive (optional)
run_bootcdwrite()
{
  local interact=""; [ $# -ge 1 ] && interact="$1"
  local TODO=""
  local mm=0
  local i

  local IAHEAD="$IAHEAD / run_bootcdwrite"

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm ".*"
  TODO="$TODO \"chroot $workdir/$IMGDIR bootcdwrite -s # create cd iso image\""

  for i in var/spool/bootcd/cdimage.iso var/log/bootcdwrite.log var/log/bootcdbackup.log; do
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"mv $workdir/$IMGDIR/$i $workdir\""
  done

  if [ ! "$backuponly" ]; then
    for i in var/log/bootstrap.log; do
      mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
      TODO="$TODO \"mv $workdir/$IMGDIR/$i $workdir\""
    done
  fi

  [ "$interact" ] && interact="" || interact="-y"
  eval "interactive -h \"=== \$IAHEAD ===\" $interact -r $TODO"
}

# function: check_chroot_timezone
# get: $1    -- chroot directory
check_chroot_timezone()
{
  local chrootdir=$1
  if [ "$(date "+%z")" != "$(chroot $chrootdir date "+%z")" ]; then
    echo "timezone in chroot is different !!! (possible fix: cp /etc/localtime)"
    return 1
  fi
  return 0
}

# function: fixchrootfuture
# get: $1  -- chroot directory
fixchrootfuture()
{
  local chrootdir=$1
  chroot $chrootdir sh -c "touch /tmp/timefile ;find / -xdev -newer /tmp/timefile -print0 | xargs -0r touch; rm /tmp/timefile"
}

#################### the real things #########################################


TODO=""
workdir=""
interact=""
extdirs=""
ownconfdir=""
bootcd2diskconf=""
bootcdwriteconf=""
debootstrapmirror=""
backuponly=""
backuponlynexttime=""
script=""
use_type=""
externdata=""
while [ $# -gt 0 ]; do
  case "$1" in
   "-d")
     shift
     workdir="$1"
     ;;
   "-i")
     interact="$1"
     ;;
   "-e")
     shift
     extdirs="$1"
     ;;
   "-c")
     shift
     ownconfdir="$1"
     ;;
   "-m")
     shift
     debootstrapmirror="$1"
     ;;
   "-b")
     backuponly="$1"
     ;;
   "-B")
     backuponlynexttime="$1"
     ;;
   "--star")
     use_type="$1"
     ;;
   "--cpio")
     use_type="$1"
     ;;
   "--externdata")
     externdata="$1"
     ;;
   "-s")
     script="$1"
     ;;
    --help|-h) 
      usage
      ;;
    *) 
      usage "parameter \"$1\" unknown!"
      ;;
  esac
  shift
done

[ ! "$workdir" ] && usage "Mandatory parameter \"workdir\" not found!"
[ -d "$workdir" ] || usage "workdir <$workdir> does not exist!"

workdir=$(realpath $workdir)

if [ "$ownconfdir" ]; then
  ownconfdir=$(realpath $ownconfdir)
  [ -e "$ownconfdir/bootcd2disk.conf" ] && bootcd2diskconf="$ownconfdir/bootcd2disk.conf"
  [ -e "$ownconfdir/bootcdwrite.conf" ] && bootcdwriteconf="$ownconfdir/bootcdwrite.conf"
fi

trap trapfunc 0 2

if [ ! "$backuponly" ]; then
  TODO="$TODO \"prepare_debian_chroot <-i> # using $(basename $BOOTSTRAPPATH)\""
  tt=""
  [ "$debootstrapmirror" ] && tt="-m $debootstrapmirror"

  # We try to use the same grub (by installing grub or grub-legacy debian 
  # package) on debian bootcd, as used by system we want to backup.
  grubopt=""
  [ -x /sbin/grub ] && [ "$(/sbin/grub --version | grep "\<0.97\>")" ] && grubopt="--grub-legacy"

  TODO="$TODO \"./bootcddebootstrap <-i> -h \\\"$IAHEAD\\\" $tt $grubopt -d $workdir/$IMGDIR $workdir\""
fi

TODO="$TODO \"prepare_chroot $workdir/$IMGDIR <-i>\""
TODO="$TODO \"check_chroot_timezone $workdir/$IMGDIR\""

TODO="$TODO \"do_backup <-i> $use_type $externdata # including filesystem layout\""

if [ "$bootcdwriteconf" ]; then 
  TODO="$TODO \"cp $bootcdwriteconf $workdir/$IMGDIR/etc/bootcd/ # use bootcd config from commandline\""
else
  TODO="$TODO \"add_info $workdir/$IMGDIR/etc/bootcd/bootcdwrite.conf\""
fi

TODO="$TODO \"fixchrootfuture $workdir/$IMGDIR\""

# Allow login without passwd for bootcd root (for ssh we would also need 
# "PermitEmptyPasswords yes" in /etc/sshd_config)
TODO="$TODO \"sed -i \\\"s/^root:x:/root::/\\\" $workdir/$IMGDIR/etc/passwd\""

TODO="$TODO \"run_bootcdwrite <-i> # to create the iso image\""
TODO="$TODO \"unprepare_chroot $workdir/$IMGDIR <-i>\""

if [ ! "$backuponly" ]; then
  if [ ! "$backuponlynexttime" ]; then
    TODO="$TODO \"rm -rf $workdir/$IMGDIR # cleanup\""
  fi
  TODO="$TODO \"rm -f $workdir/bootcddebootstrap $workdir/$(basename $BOOTSTRAPPATH) # cleanup\""
fi

TODO="$TODO \"mv $ERRLOG $workdir\""
if [ ! "$backuponly" ]; then
  TODO="$TODO \"mv /var/log/bootcddebootstrap.log $workdir\""
fi

[ "$interact" ] && interact="" || interact="-y"
eval "interactive -h \"=== \$IAHEAD\" $interact $TODO"

echo "The result cdimage.iso plus logfiles are in $workdir"
