#!/bin/sh
#
# 	$Source: /cvsroot/bootcd/bootcd/bootcddebootstrap.src,v $
#       $Id: bootcddebootstrap.src,v 1.33 2010-03-11 15:53:04 bs Exp $
#    author: Carsten Dinkelmann <din@foobar-cpa.de>
#            Bernd Schumacher <bernd.schumacher@hpe.com>
#     start: 12.09.2007
#     topic: create a small debian cd and configured it for bootcd
#

# Read supported environment variables (before set -u)
http_proxy="$http_proxy"
IAHEAD="$IAHEAD"
set -u

# Version of debian package bootcd with this tools is included
# /usr/share/doc/bootcd-backup/examples/bootcddebootstrap
VERSION=5_04 # sources are tagged in cvs with rev=debian_version_${VERSION}

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

# use for temporary files
TMPDIR=/tmp/.debootstrap

# used as basis directory
WORKDIR=/var/spool/bootcd-img

# only for help, the download path
DOWNLOADPATH="http://packages.debian.org/stable/debootstrap/all/download"

# mirrors for debootstrap
BOOTSTRAPMIRRORS="http://ftp2.de.debian.org/debian/ http://ftp.se.debian.org/debian/ http://ftp.debian.org"

# build deboostrap for this distribution
#DISTRI="squeeze"
DISTRI="wheezy"

# needed tools
TOOLS="ar tar chroot"

KERNEL="linux-image-amd64"

# bootcd packages
BOOTCDPACKAGES_APT="bootcd/unstable"
BOOTCDPACKAGES_LOCAL="bootcd"

# needed packages
PACKAGES="lvm2 genisoimage fdutils file dosfstools realpath syslinux aufs-tools"

# usefull packages
#UPACKAGES="less psmisc net-tools ssh vim netbase xbase-clients star console-common console-data console-tools wget"
# star is not available
UPACKAGES="less psmisc net-tools ssh vim netbase xauth console-common console-data console-tools wget"

# logfile
ERRLOG="/var/log/bootcddebootstrap.log"

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

SCRIPTNAME=$(basename $0)

[ "$IAHEAD" ] && IAHEAD="$IAHEAD/$SCRIPTNAME" || IAHEAD="$SCRIPTNAME"

### BOOTCD-RUN_LIB Placeholder ##############
# bootcd-run.lib
# vim: set filetype=sh :

#     version: $Version: $
#   copyright: Bernd Schumacher <bernd.schumacher@hpe.com> (2007)
#     license: GNU General Public License, version 2
#     comment: use ANSW to change the next point from external
# description: To understand what to do with this library you can download
#              the test programs with wget
#

RUN_INTERACT="no"
LANG=C
LC_ALL=C
IGNORE=""
STDOUT=""

msg()
{
  echo "--- MESSAGE ---" | tee -a $ERRLOG >&2
  echo "$@" | tee -a $ERRLOG >&2
}

set +u
if [ ! "$ERRLOG" ]; then
  ERRLOG=/var/log/bootcd.errlog
  touch "$ERRLOG" >/dev/null 2>&1
  [ $? -ne 0 ] && ERRLOG=/tmp/bootcd.errlog
  msg "ERRLOG was unset. It is set to $ERRLOG now."
fi
set -u

# function dbg prints DEBUG Messages to stderr.
# It is very fast because no if-check has to be done
# functions dbgoff, dbgon, and dbgtoggle switches dbg on or off
dbgoff()
{
   eval "dbg() { :; }"
}

if [ "$(type dbg 2>&1| grep "not found")" ]; then
  dbgoff
fi

dbgon()
{
   eval "dbg() { echo \"DEBUG \$@\" >&2; }"
}

# with option -v dbtoggle is more verbose
dbgtoggle()
{
  if [ "$(dbg 2>&1)" ]; then
    dbgoff
    [ "$1" = "-v" ] && echo "debug off" >&2
  else
    dbgon
    [ "$1" = "-v" ] && echo "debug on" >&2
  fi
}

# function run: run given command and output error
# get: $IGNORE   -- regexp for error messages, generate no output, no question
#                   (set from function ignore)
#      $STDOUT   -- regexp for error messages, only output, no question
#                   (set from function stdout)
#      $ERRORLOG -- write to this file
#
#      $RUN_INTERACT -- "yes" meens run from interactiv
# comment: by default, all errors (stdout, error) generates output and questions 
#
# Attention: do not try to set global variables in a command called by run !!!
#
run()
{
  local ERR C T A
  #echo >&2 "IGNORE $IGNORE"
  #echo >&2 "STDOUT $STDOUT"
  while :
  do

    > $ERRLOG.tmp
    (
      eval "$@" 2>&1
      ERR=$?
      if [ "$ERR" != "0" ]; then
        echo "exit=$ERR"
      fi
    ) |
    if [ "$IGNORE" != "" ]; then
      C="grep -v $IGNORE"
      tee $ERRLOG.tmp | tee -a $ERRLOG | eval "$C"
    else
      tee -a $ERRLOG.tmp | tee -a $ERRLOG
    fi

    T=`cat $ERRLOG.tmp`
    if [ "$IGNORE" != "" ]; then
      C="grep -v $IGNORE"
      T=`echo "$T" | eval "$C"`
    fi
    if [ "$STDOUT" != "" ]; then
      C="grep -v $STDOUT"
      T=`echo "$T" | eval "$C"`
    fi
  
    if [ "$T" != "" ]; then
      A=""
      while [ "$A" != "e" -a "$A" != "r" -a "$A" != "i" -a "$A" != "s" ]
      do
        echo "--- OUTPUT from <$@> ---" | tee -a $ERRLOG
        echo "$T" | tee -a $ERRLOG
        if [ "$RUN_INTERACT" = "yes" ]; then 
          echo -n "--- (e)xit (r)edo (i)gnore (s)witch interactive --- " | tee -a $ERRLOG
        else 
          echo -n "--- (e)xit (r)edo (i)gnore --- " | tee -a $ERRLOG
        fi
        read A 
        # Next line handles EOF from a pipe as "(e)xit"
        [ $? -ne 0 ] && A="e"
        echo "$A" >> $ERRLOG
      done
      if [ "$A" = "e" ]; then
        exit 1
      elif [ "$A" = "i" ]; then
        break
      # switch to interactive mode
      elif [ "$A" = "s" -a "$RUN_INTERACT" = "yes" ]; then
        echo
        echo "switching to interactive mode to point ($ntrctv_ANSW) which failed"
        echo
	ntrctv_ANSW=$(($ntrctv_ANSW-1))
        ntrctv_y=""
        break
      fi
    else
      break
    fi
  
  done
  IGNORE=""
  STDOUT=""
}

# function: build stdout for interactive
# get: $1 -- number
#      $2 -- stdout string
in_stdout()
{
  local nr=$1
  shift 

  eval STDOUT${nr}=\"\$STDOUT${nr} -e \\\"\$@\\\"\"
}

# function: build ignore for interactive
stdout()
{
  STDOUT="$STDOUT -e \"$@\""
}

# function: build ignore for interactive
# get: $1 -- number
#      $2 -- ignore string
in_ignore()
{
  local nr=$1
  shift 

  eval IGNORE${nr}=\"\$IGNORE${nr} -e \\\"\$@\\\"\"
}

## example
#mm=0
#mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
#in_ignore $mm "error bla"
#in_ignore $mm "^bluber sumsel .*"
#in_stdout $mm "no error but but a useful comment"
#mm=$(($mm+1)); eval IGNORE${mm}=\"\"
#in_norun $mm
#echo $IGNORE1 -- $IGNORE2
#exit

# function: run interactiv -r without run for this menu point
# get: $1 -- number
# comment: please update norunstring also in interactive!
in_norun()
{
  local norunstring="interactive_without_run"
  local nr=$1

  eval IGNORE${nr}=\"$norunstring\"
  eval STDOUT${nr}=\"$norunstring\"
}


ignore()
{
  IGNORE="$IGNORE -e \"$@\""
}

warn()
{
  if [ $# -gt 0 ]; then
    A=""
    while [ "$A" != "e" -a "$A" != "i" ]
    do
      echo "--- WARNING ---" | tee -a $ERRLOG
      echo "$@" | tee -a $ERRLOG
      echo -n "--- (e)xit (i)gnore --- " | tee -a $ERRLOG
      if [ "$SCRIPT" ]; then
        A="i"
	echo "$A (SCRIPT)"
        echo "$A (SCRIPT)" >> $ERRLOG
        echo
      else
        read A 
        # Next line handles EOF from a pipe as "(e)xit"
        [ $? -ne 0 ] && A="e"
        echo "$A" >> $ERRLOG
      fi
    done
    if [ "$A" = "e" ]; then
      exit 1
    fi
  fi
}

err()
{
  (
  echo "--- FATAL ERROR ---" 
  echo "$@"
  ) |
  if [ "$(set | grep ^ERRLOG=)" ]; then
    tee -a $ERRLOG >&2
  else
    cat >&2
  fi
  exit 1
}

# create_host_keys <DIR>
#   if ssh_host_key, ssh_host_rsa or ... exists in <DIR>
#   then recreate it.
create_host_keys() 
{
  # Now only ssh versions which support Option -t are supported

  F=ssh_host_key
  if [ -f $1/$F ]; then  
    echo "Creating OpenSSH_2 $F" >>$ERRLOG
    run "rm -f $1/$F $1/$F.pub"
    run "ssh-keygen -b 1024 -f $1/$F -N '' -t rsa1 >>$ERRLOG 2>&1"
  fi

  F=ssh_host_rsa_key
  if [ -f $1/$F ]; then  
    echo "Creating OpenSSH_2 $F" >>$ERRLOG
    run "rm -f $1/$F $1/$F.pub"
    run "ssh-keygen -b 1024 -f $1/$F -N '' -t rsa >>$ERRLOG 2>&1"
  fi

  F=ssh_host_dsa_key
  if [ -f $1/$F ]; then  
    echo "Creating OpenSSH_2 $F" >>$ERRLOG
    run "rm -f $1/$F $1/$F.pub"
    run "ssh-keygen -b 1024 -f $1/$F -N '' -t dsa >>$ERRLOG 2>&1"
  fi
}

#      syntax: interactive [-y] [-e] [-h <head>] <functioncall with params> ...
# description: interactive evaluates the listed functioncalls. If the first 
#              argument is not -y an interactive menu will be given. This menu
#              can get a header <head> with -h. An extra evaluation of <head> 
#              and <functioncall with params> can be forced with option -e.
#              The <functioncall with params> can contain the special 
#              character sequences "<-i>" and "<h>". "<-i>" will be translated
#              either to "-i" or to "". This can be toggled with the key "i".
#              
#              -y        --   run without interaction
#              -e        --   evaluate each function call one times more
#              -h <head> --   print <head> as menu head
#              -r        --   run command with "run"
#
# helper functions for "run" (-r given to interactive):
#            in_stdout <number> <string> -- ignore stdout+stderr
#            in_ignore <number> <string> -- ignore stdout+stderr, don't print
#            in_norun <number>           -- run menupoint without run!
#
#
interactive()
{
  local ntrctv_ANSW ntrctv_A="" ntrctv_i ntrctv_y="" ntrctv_t="-i " ntrctv_head="" ntrctv_e="" ntrctv_run="" ntrctv_c
  local ntrctv_cmd
  local norunstring="interactive_without_run"

  # get all options
  while [ $# -ge 1 ]; do
    if [ "$1" = "-y" ]; then
      ntrctv_y="$1"
      ntrctv_t=""
      shift
    elif [ "$1" = "-e" ]; then
      ntrctv_e="$1"
      shift
    elif [ "$1" = "-h" -a $# -gt 2 ]; then
      ntrctv_head="$2"
      shift 2
    elif [ "$1" = "-r" ]; then
      ntrctv_run="yes"
      shift 1
    else
      break
    fi
  done
  
  # read the function calls
  if [ $# -gt 0 ]; then
    ntrctv_ANSW="1"
    while :; do
      if [ ! "$ntrctv_y" ]; then
        # print head 
        if [ "$ntrctv_head" ]; then 
	  ntrctv_c="$ntrctv_head"
          # evaluate the head
          [ "$ntrctv_e" ] && ntrctv_c="$(eval echo "$ntrctv_c")"
          echo "$ntrctv_c"
        fi
        ntrctv_i=1
        # build menu
        while [ $ntrctv_i -le $# ]; do
          ntrctv_c=$(echo "$ntrctv_i $(eval echo "\${$ntrctv_i}")" | sed "s/<-i>[[:space:]]*/$ntrctv_t/g")
          [ "$ntrctv_e" ] && ntrctv_c="$(eval echo "$ntrctv_c")"
          echo "$ntrctv_c"
          ntrctv_i=$(($ntrctv_i+1))
        done
        
        # dbg exists, print flag (do not use declare to be posix konform)
        [ "$(type dbg 2>&1| grep "not found")" ] || echo "d toggle dbg() function on and off"
        # i flag can be toggled 
        [ "$(echo "$*"|grep "<-i>")" ] && echo "i toggle -i flag"
        # menu point "continue"
        [ "$ntrctv_ANSW" != "q" ] && [ $ntrctv_ANSW -lt $# ] && echo "c continue without questions"
        echo "q quit"
        echo -n "? [$ntrctv_ANSW]"
        read ntrctv_A
        # Next line handles EOF from a pipe as "q"
        [ $? -ne 0 ] && ntrctv_A="q"
        [ "$ntrctv_A" -a "$ntrctv_A" != "c" -a "$ntrctv_A" != "i" -a "$ntrctv_A" != "d" ] && ntrctv_ANSW="$ntrctv_A"
      fi
      if [ "$ntrctv_ANSW" = "q" ]; then
        return
      elif [ "$ntrctv_A" = "d" ]; then
        dbgtoggle -v
      elif [ "$ntrctv_A" = "i" ]; then
        [ "$ntrctv_t" ] && ntrctv_t="" || ntrctv_t="-i "
      elif [ "$(echo "$ntrctv_ANSW" | sed 's/[0-9]//g')" ]; then
        ntrctv_ANSW="?"
      elif [ "$ntrctv_ANSW" -ge 1 -a "$ntrctv_ANSW" -le $# ]; then
        [ "$ntrctv_A" = "c" ] && ntrctv_y="-y"
        # build the command and set -i, if needed
        ntrctv_cmd="$(echo "$(eval echo "\${${ntrctv_ANSW}}")" | sed -e "s/<-i>/$ntrctv_t/g")"
        # eval function call again
        [ "$ntrctv_e" ] && ntrctv_cmd="$(eval echo "$ntrctv_cmd")"

        # call function
        if [ "$ntrctv_run" ]; then
          local IGNORE="" STDOUT=""
	  [ "$(set | grep "^IGNORE${ntrctv_ANSW}=")" ] && eval IGNORE=\"\$IGNORE${ntrctv_ANSW}\"
	  [ "$(set | grep "^STDOUT${ntrctv_ANSW}=")" ] && eval STDOUT=\"\$STDOUT${ntrctv_ANSW}\"
          # run is on but run this menu point without run!
          if [ "$IGNORE" = "$STDOUT" -a "$STDOUT" = "$norunstring" ]; then
            eval "$ntrctv_cmd"
          else
            [ "$ntrctv_y" ] && local RUN_INTERACT="yes" || local RUN_INTERACT="no"
            run "$ntrctv_cmd"
          fi
        else
          eval "$ntrctv_cmd"
        fi
        ntrctv_ANSW=$(($ntrctv_ANSW+1))
        [ $ntrctv_ANSW -gt $# ] && ntrctv_ANSW=q
      fi
    done
  fi
}
## examples ... comment out and try it
#
#a() { A="called: a"; [ $# -ge 1 ] && A="$A <$1>"; [ $# -ge 2 ] && A="$A <$2>"; [ $# -ge 3 ] && A="$A <$3>"; [ $# -ge 4 ] && A="$A <$4>"; echo "$A" ;}
#interactive a "a -x # with arg and comment" "a -x \"one two\" # 2 args, one with spaces"
#exit 0
#
#interactive -y a "a -x # with arg and comment" "a -x \"one two\" # 2 args, one with spaces"
#exit 0
#
#interactive "a A" "a B" "a C" "a D" "a E" "a F" "a G" "a H" "a I" "a J" "a K"
#exit 0
#
#f="a \"a -x # with arg and comment\" \"a -x \\\"one two\\\" # 2 args, one with spaces\""
#eval "interactive $f"
#exit 0
#
#interactive # should return without doing anything
#exit 0
#
#interactive "a <-i> A" "a B" "a <-i> C"
#exit 0
#
#f1() { IANAME="$IANAME/f1" interactive -e -h "=== \$IANAME \(opts=-e\) n=\<\$n\> ===" "n=$((n+1))" f1; }
#n=""; IANAME="main" interactive -e -h "=== \$IANAME \(opts=-e\) n=\<\$n\> ===" "n=$(($n+1)" f1
#exit 0
#
#interactive -r "sh -c \"exit 0\"" "echo \"error auf stdout\"" "echo \"error auf stderr\" >&2" "sh -c \"exit 1\""
#exit 0
#
#interactive -y -r "sh -c \"exit 0\"" "echo \"error auf stdout\"" "echo \"error auf stderr\" >&2" "sh -c \"exit 1\""
#exit 0
#


# function: ask_user -- ask user
# get: question = string
#      default  = y|n
# ret: 1 == yes | 0 == no
ask_user()
{
  local quest=$1
  [ "$2" = "y" -o "$2" = "n" ] && local default=$2 || local default=""

  while [ 1 ]; do
    echo  "$quest"
    read a
    # Next line handles EOF from a pipe as "default"
    [ $? -ne 0 ] && a=""
    [ "$a" = "" ] && a=$default
    if [ "$a" = "Y" -o "$a" = "y" -o "$a" = "J" -o "$a" = "j" ]; then
      # echo "yes"
      return 1;
    fi
    if [ "$a" = "N" -o "$a" = "n" ]; then
      # echo "no"
      return 0;
    fi
    echo >&2 "Error: \"$a\" is not a allowed answer!"
  done
}


# function: do_dir -- create directory
# get: $1 -- directory
#      $2 -- permissions (optional)
do_dir()
{
  local dir=$1
  local perm=""; [ $# -ge 2 ] && perm=$2

  if [ -d "$dir" ]; then
    ask_user "Directory \"$dir\" exist's, remove it [Y|n] " "y"
    [ $? = 0 ] && err "Can't work without directory \"$dir\"."
    rm -rf $dir
  fi
  mkdir -p $dir
  [ "$perm" ] && chmod $perm $dir
  echo ""
}


# function: check_tools -- check if tools exist
#           use ./ to search in local directory
# get: $1..$n -- tools
check_tools()
{
  while [ $# -gt 0 ]; do 
    echo -n >&2 " check for \"$1\" ..."
    if [ "$(echo $1 |cut -c1-2)" = "./" ]; then
      [ ! -f "$1" ] && err "Can't find file \"$1\"!"
    else
      [ ! "$(which $1)" ] && err "Can't find tool \"$1\" in PATH!"
    fi
    echo >&2 "ok"
    shift
  done
}

# function: prepare_chroot <workdir> [-i]
# globals: IAHEAD
prepare_chroot()
{
  local workdir="$1"
  local interact=""; [ $# -ge 2 ] && interact="$2"
  local TODO="" mm=0
  if [ "$(env | grep "^IAHEAD=")" ]; then 
    local IAHEAD="$IAHEAD / prepare_chroot"
  else
    local IAHEAD="prepare_chroot"
  fi

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"mount --bind /dev $workdir/dev\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"mount -t proc none $workdir/proc\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"mount -t sysfs none $workdir/sys\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"mount -t devpts none $workdir/dev/pts\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"chroot $workdir sh -c \\\"[ -f /etc/mtab ] && mv /etc/mtab /etc/mtab.bootcd || rm -f /etc/mtab.bootcd\\\"\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"chroot $workdir sh -c \\\"grep -v -e \\\\\\\"^sunrpc\>\\\\\\\" -e \\\\\\\"^rootfs\>\\\\\\\" /proc/mounts >/etc/mtab\\\"\""

  # we need selinux mounted to install openssh_server without
  # error "chage: Permission denied."
  if [ "$(cat /proc/mounts | grep selinux)" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"mount -t selinuxfs none $workdir/selinux\""
  fi

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

# unprepare_chroot <workdir> [-i]
# globals IAHEAD
unprepare_chroot()
{
  local workdir="$1"
  local interact=""; [ $# -ge 2 ] && interact="$2"
  local TODO="" mm=0
  if [ "$(env | grep "^IAHEAD=")" ]; then 
    local IAHEAD="$IAHEAD / unprepare_chroot"
  else
    local IAHEAD="unprepare_chroot"
  fi

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"chroot $workdir sh -c \\\"[ -f /etc/mtab.bootcd ] && mv /etc/mtab.bootcd /etc/mtab || rm -f /etc/mtab\\\"\""

  if [ "$(cat /proc/mounts | grep selinux)" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"umount $workdir/selinux\""
  fi

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"umount $workdir/dev/pts\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"umount $workdir/sys\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"umount $workdir/proc\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"umount $workdir/dev\""

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

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

  echo ""
  echo  " $SCRIPTNAME creates a debian bootcd image in a chroot-enviroment."
  echo  " After that, you can backup your running system to a CD/DVD."
  echo  ""
  echo  " syntax: $SCRIPTNAME [-i] [-m <mirror>] [-d <directory>] <debootstrap path|directory>"
  echo  ""
  echo  "                 -i   -- do all things interactive"
  echo  "       -d <directory> -- create image in this directory (Space needed!)"
  echo  "                         default: \"$WORKDIR\""
  echo  "        --grub-legacy -- use old GRUB instead of new GRUB"
  echo  "          -m <mirror> -- debian mirror for debootstrap (e.g. http://ftp.de.debian.org/debian)"
  echo  "       -nu            -- no usefull packages (don't install network, lvm ... in bootcdimage)"
  echo  "   <debootstrap path> -- path to debootstrap debian package, use bootcd packages from"
  echo  "                         \"apt\" source server"
  echo  "   <debootstrap directory> -- directory with debootstrap and bootcd debian packages"
  echo  ""                         
  echo  " download debootstrap: \"$DOWNLOADPATH\" "
  echo  ""
  echo  " example: $SCRIPTNAME /tmp/debootstrap_0.3.3.2etch1_all.deb"
  echo  ""
 
  exit 0
}


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


# function: extract_debootstrab -- extract debootstrap
# get: $1 -- tmpdir
#      $2 -- path to debfile
extract_debootstrap()
{
  local tmpdir=$1
  local debfile=$2

  echo >&2 "extract debootstrap from debian package"

  (cd $tmpdir; ar -x $debfile data.tar.gz)

  tar -xz -C$tmpdir -f /$tmpdir/data.tar.gz

  echo
}

# function: config_apt -- configure apt (pinning) to get unstable/testing 
#                         packages for bootcd
# get: $1 -- workdir
config_apt()
{
  local workdir=$1

  echo >&2 "write sources.list"
  cat >$workdir/etc/apt/sources.list <<END
# stable
deb http://ftp.debian.org/debian $DISTRI main

# testing
deb http://ftp.debian.org/debian testing main

# unstable
deb http://ftp.debian.org/debian unstable main
END

  echo >&2 "write apt_preferences"
  cat >$workdir/etc/apt/preferences <<END
Package: *
Pin: release a=$DISTRI
Pin-Priority: 750

Package: *
Pin: release a=stable
Pin-Priority: 700

Package: *
Pin: release a=testing
Pin-Priority: 650

Package: *
Pin: release a=unstable
Pin-Priority: 600
END

echo >&2 "Configure Cache-Limit in apt.conf"
  cat >>$workdir/etc/apt/apt.conf <<END
APT::Cache-Limit "125000000";
END

  if [ "$http_proxy" ]; then
    echo >&2 "add http_proxy to apt.conf"
  cat >>$workdir/etc/apt/apt.conf <<END
Acquire::http::Proxy "$http_proxy";
END
  fi
}

# function: in_stdout_aptget -- generic in_stdout for apt
# get: $1 -- number
# ret: stdout string in STDOUT<number>
in_stdout_aptget()
{
  local mm=$1
  in_stdout $mm "^Reading package lists..."
  in_stdout $mm "^Reading state information..."
  in_stdout $mm "^After this operation, .* of additional disk space "
  in_stdout $mm "^Building dependency tree..."
  in_stdout $mm "^The following extra packages.*"
  in_stdout $mm "^Suggested packages:"
  in_stdout $mm "^Recommended packages:"
  in_stdout $mm "^The following NEW packages will be installed:"
  in_stdout $mm "^[[:digit:]]* upgraded, .*"
  in_stdout $mm "^Need to get .*"
  in_stdout $mm "^After unpacking .*"
  in_stdout $mm "^Get:"
  in_stdout $mm "^debconf: .*"
  in_stdout $mm "^Fetched .*"
  in_stdout $mm "^Unpacking .*"
  in_stdout $mm "^Selecting .*"
  in_stdout $mm "^(Reading database..."  
  in_stdout $mm "^[[:digit:]]* files and directories .*"  
  in_stdout $mm "^[dD]one.$"  
  in_stdout $mm "^Setting up .*"
  in_stdout $mm "^$"
  in_stdout $mm "^\.$"
  in_stdout $mm " is already the newest version.$"
  in_stdout $mm "^Adding .diversion of"
}


# function: config_kernel -- configure kernel install
# get: $1 -- workdir
config_kernel()
{
  local file=$1//etc/kernel-img.conf

  echo >&2 "write kernel-img.conf"

  for i in do_symlinks do_initrd; do
     [ ! "$(grep "[[:space:]]*${i}[[:space:]]*=" $file 2>/dev/null)" ] && echo "$i = Yes" >>$file
     sed -i -e "s|\([[:space:]]*${i}[[:space:]]*=[[:space:]]*\).*|\1Yes|" $file
  done

}

# get: $1 -- workdir
config_motd()
{
  local file=$1/etc/motd

  echo >&2 "--- configuring motd ---"
  echo "to change keyboard, use \"loadkeys /usr/share/keymaps/.....kmap.gz\"" >>$file
}


# function: get_debootstrapmirror
# globals: mirror (from cmdline), BOOTSTRAPMIRRORS
# set:      debootstrapmirror
get_debootstrapmirror()
{
  local i

  echo >&2 "check mirrors"
  # if mirror set by user, use it else try all with ping
  for i in $mirror $BOOTSTRAPMIRRORS; do
     echo -n >&2 " check mirror $i ... "
     if [ $(wget --timeout=10 -t 1 -O /dev/null $i >/dev/null 2>&1; echo $?) -eq 0 ]; then
       echo >&2 "ok"
       debootstrapmirror=$i;
       break;
     else
       echo >&2 "doesn't work!"
     fi
  done

  [ ! "$debootstrapmirror" ] && echo "No working mirror found!" || :
}

# function: install_packages
# get: $1 -- workdir
#      $2 -- useful packages switch (upack | no_upack)
#      $3 -- interactive (optional)
install_packages()
{
  local workdir=$1
  local kernel=$KERNEL
  # $grub is "grub" or "grub-legacy". (we need the old grub-legacy for RH6.1)
  local packages="$grub $PACKAGES"
  [ "$DEBOOTSTRAPDIR" ] && local bootcdpackages="$BOOTCDPACKAGES_LOCAL" || local bootcdpackages="$BOOTCDPACKAGES_APT"
  [ "$2" = "upack" ] && local usepkg="$UPACKAGES" || local usepkg=""
  local interact=""; [ $# -ge 3 ] && interact="$3"
  local TODO=""
  local mm=0
  local IAHEAD="$IAHEAD / install_packages"

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm ".*"
  TODO="$TODO \"config_apt $workdir\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm "^Get:.*"
  in_stdout $mm "^Hit .*"
  in_stdout $mm "^Ign .*"
  in_stdout $mm "^Fetched .*"
  in_stdout $mm "^Reading package lists....*"
  TODO="$TODO \"chroot $workdir apt-get update\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm "write kernel-img.conf"
  TODO="$TODO \"config_kernel $workdir\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout_aptget $mm
  in_stdout $mm "^/sys/class/net/ is not available, persistent interface names not saved."
  in_stdout $mm "^  busybox initramfs-tools klibc-utils libklibc libvolume-id0$"
  in_stdout $mm "^  linux-image-2.6."
  in_stdout $mm "^  linux-image-2.6-486 linux-image-2.6.18-.-486 module-init-tools udev$"
  in_stdout $mm "^  linux-doc-"
  in_stdout $mm "^ Hmm. The package shipped with a symbolic link /lib/modules/2.6.18-.-486/source"
  in_stdout $mm "^ However, I can not read the target: No such file or directory"
  in_stdout $mm "^ Therefore, I am deleting /lib/modules/2.6.18-.-486/source"
  in_stdout $mm "^A chroot environment has been detected, udev not started."
  in_stdout $mm "^Running "
  in_stdout $mm "^Processing "
  in_stdout $mm "^Finding valid ramdisk creators."
  in_stdout $mm "^Using mkinitramfs-kpkg to build the ramdisk."
  in_stdout $mm "^linux-image.*is already the newest version."
  in_stdout $mm "^After this operation, "
  in_stdout $mm "^update-initramfs: deferring update"
  in_stdout $mm "^update-initramfs: Generating"
  in_stdout $mm "^run_command: calling: test$"
  in_stdout $mm "^udevadm_test:"
  in_stdout $mm "^parse_file: reading "
  in_stdout $mm "^udev_rules_new:"
  in_stdout $mm "^udev_device_new_from_syspath:"
  in_stdout $mm "^udev_device_read_db:"
  in_stdout $mm "^udev_device_update_db:"
  in_stdout $mm "^udev_rules_apply_to_event:"
  in_stdout $mm "^util_run_program:"
  in_stdout $mm "^update-alternatives: using"
  in_stdout $mm "^df: Warning: cannot read table of mounted file systems: No such file or directory$"
  in_stdout $mm "^Examining /etc/kernel/postinst.d"
  in_stdout $mm "^run-parts: executing /etc/kernel/postinst.d/"
  in_stdout $mm "^add_matching_files: unable to open '/dev/.udev/rules.d': No such file or directory$"
  in_stdout $mm "^Setting kernel variables"
  TODO="$TODO \"chroot $workdir sh -c \\\"DEBIAN_FRONTEND=noninteractive apt-get -qq -y -f install $kernel\\\"\""

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout_aptget $mm
  in_stdout $mm "^  \(\<\(\
genisoimage\|libmagic1\|lvm-common\|wodim\|cdrkit-doc\|grub-doc\|mdadm\|dmsetup\|\
mtools\|logrotate\|dosfstools\|fdutils\|file\|genisoimage\|grub\|libmagic1\|\
lvm-common\|lvm2\|mkisofs\|realpath\|syslinux\|grub-common\|syslinux-common\|\
cdrkit-doc\|grub-legacy-doc\|multiboot-doc\|floppyd\
\)\>[[:space:]]*\)*$"
  in_stdout $mm "^Backing up any"
  in_stdout $mm "^  Found volume group"
  in_stdout $mm " logical volume(s) in volume group .* now active"
  in_stdout $mm "^df: Warning: cannot read table of mounted file systems: No such file or directory$"
  in_stdout $mm "^install-info: "
  in_stdout $mm "^update-initramfs: "
  in_stdout $mm "^Processing triggers for initramfs-tools "
  in_stdout $mm "^update-initramfs: Generating "
  in_stdout $mm "^Moving old data out of the way"
  in_stdout $mm "^Creating config file /etc/default/grub with new version"
  in_stdout $mm "^  No volume groups found"
  in_stdout $mm "^  Reading all physical volumes."
  in_stdout $mm "^  No volume groups found"
  in_stdout $mm "^Creating /etc/network/interfaces"
  in_stdout $mm "^Looking for keymap to install:"
  in_stdout $mm "^NONE$"
  in_stdout $mm "^Creating SSH2.*key"
  in_stdout $mm "^Processing /usr/share/vim/addons"
  TODO="$TODO \"chroot $workdir sh -c \\\"DEBIAN_FRONTEND=noninteractive apt-get -qq -y -f install $packages\\\"\""

  # use packages from configured directory
  if [ "$DEBOOTSTRAPDIR" ]; then
    local inst=""
    for i in $bootcdpackages; do
       mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
       local file=$(ls $DEBOOTSTRAPDIR/${i}_*.deb |head -1)
       TODO="$TODO \"cp $file $workdir/tmp/\""
       [ "$inst" ] && inst="$inst /tmp/$(basename $file)" || inst="/tmp/$(basename $file)"
    done  
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_stdout $mm "^(Reading database ... .*"
    in_stdout $mm "^Selecting .*" 
    in_stdout $mm "^Unpacking .*"
    in_stdout $mm "^Setting up .*"
    in_stdout $mm "^update-initramfs: Generating.*"
    in_stdout $mm "^[[:digit:]]* files and directories .*"
    in_stdout $mm "^update-initramfs: deferring update"
    in_stdout $mm "^Processing triggers"
    in_stdout $mm "^df: Warning: cannot read table of mounted file systems: No such file or directory$"
    TODO="$TODO \"chroot $workdir sh -c \\\"dpkg -i $inst \\\"\""
  else 
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_stdout_aptget $mm
    in_stdout $mm ".*"
    TODO="$TODO \"chroot $workdir sh -c \\\"DEBIAN_FRONTEND=noninteractive apt-get -qq -y -f install $bootcdpackages\\\"\""
  fi

  if [ "$usepkg" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_stdout_aptget $mm
    # The following extra packages will be installed:
    # Suggested packages:
    # Recommended packages:
    # The following NEW packages will be installed:
    in_stdout $mm "^  \(\<\(\
adduser\|defoma\|fontconfig-config\|ifupdown\|iputils-ping\|libconsole\|libdrm2\|\
libedit2\|libexpat1\|libfontconfig1\|libfreetype6\|libfs6\|libgl1-mesa-glx\|\
libgpmg1\|libice6\|libkrb53\|libnewt0.52\|libpng12-0\|libpopt0\|libsm6\|libssl0.9.8\|\
libwrap0\|libx11-6\|libx11-data\|libxau6\|libxaw7\|libxcursor1\|libxdmcp6\|libxext6\|\
libxfixes3\|libxft2\|libxi6\|libxkbfile1\|libxmu6\|libxmuu1\|libxpm4\|libxrandr2\|\
libxrender1\|libxss1\|libxt6\|libxtrap6\|libxtst6\|libxv1\|libxxf86dga1\|\
libxxf86vm1\|openbsd-inetd\|openssh-blacklist\|openssh-client\|openssh-server\|tcpd\|\
ttf-dejavu\|ucf\|unicode-data\|\
update-inetd\|vim-common\|vim-runtime\|whiptail\|x11-common\|\
kbd-compat\|defoma-doc\|psfontmgr\|x-ttcidfont-conf\|dfontmgr\|\
iproute\|dhcp3-client\|dhcp-client\|ppp\|libfreetype6-dev\|gpm\|krb5-doc\|krb5-user\|\
ssh-askpass\|rssh\|molly-guard\|ctags\|vim-doc\|vim-scripts\|mesa-utils\|\
libft-perl\|libfribidi0\|debconf-utils\|\
console-common\|console-data\|console-tools\|fontconfig-config\|\
ifupdown\|iputils-ping\|less\|libconsole\|libdrm2\|libedit2\|libexpat1\|\
libfontconfig1\|libfreetype6\|libfs6\|libgl1-mesa-glx\|libgpmg1\|libice6\|libkrb53\|\
libnewt0.52\|libpng12-0\|libpopt0\|libsm6\|libssl0.9.8\|libwrap0\|libx11-6\|\
libx11-data\|libxau6\|libxaw7\|libxcursor1\|libxdmcp6\|libxext6\|libxfixes3\|\
libxft2\|libxi6\|libxkbfile1\|libxmu6\|libxmuu1\|libxpm4\|libxrandr2\|libxrender1\|\
libxss1\|libxt6\|libxtrap6\|libxtst6\|libxv1\|libxxf86dga1\|libxxf86vm1\|net-tools\|\
netbase\|openbsd-inetd\|openssh-client\|openssh-server\|psmisc\|ssh\|star\|tcpd\|\
ttf-dejavu\|ucf\|update-inetd\|vim\|vim-common\|vim-runtime\|whiptail\|x11-common\|\
xbase-clients\|libfontenc1\|libkeyutils1\|libxdamage1\|libxinerama1\|libxxf86misc1\|\
openssh-blacklist-extra\|ttf-dejavu-core\|ttf-dejavu-extra\|x11-apps\|\
x11-session-utils\|x11-utils\|x11-xfs-utils\|x11-xkb-utils\|x11-xserver-utils\|\
xauth\|xinit\|libpam-ssh\|keychain\|libgpm2\|libxcb-xlib0\|libxcb1\
\)\>[[:space:]]*\)*$"
    # special from packages
    in_stdout $mm "^ifupdown.postinst: .*"
    in_stdout $mm "^Moving old data out of the way"
    in_stdout $mm "^Stopping internet superserver: .*"
    in_stdout $mm "Not starting internet superserver:"
    in_stdout $mm "^Creating SSH2 .* key; this may take some time ...$"
    in_stdout $mm "^Moving old data out of the way"
    in_stdout $mm "^Processing /usr/share/vim/addons/doc$"
    in_stdout $mm "^\.$"
    in_stdout $mm "^Restarting OpenBSD Secure Shell server:.*"
    in_stdout $mm "^Looking for keymap to install:$"
    in_stdout $mm "^NONE$"
    in_stdout $mm "^Setting console screen modes and fonts.$"
    in_stdout $mm "^Looking for keymap to install:$"
    in_stdout $mm "^update-alternatives: using"
    in_stdout $mm "^Setting kernel variables"
    in_stdout $mm "^df: Warning: cannot read table of mounted file systems: No such file or directory$"
    in_stdout $mm "^setterm: .TERM is not defined."
    in_stdout $mm "^Creating /etc/network/interfaces.$"
    in_stdout $mm "^Looking for keymap to install:$"
    in_stdout $mm "^NONE$"
    in_stdout $mm "^Setting console screen modes and fonts.$"
    in_stdout $mm "^setterm: cannot (un)set powersave mode: Invalid argument$"
    in_stdout $mm "^Setting up console-common (0.7.87) ...$"
    in_stdout $mm "^Looking for keymap to install:$"
    in_stdout $mm "^NONE$"
    in_stdout $mm "^Creating SSH2 RSA key; this may take some time ...$"
    in_stdout $mm "^Creating SSH2 DSA key; this may take some time ...$"
    in_stdout $mm "^Creating SSH2 ECDSA key; this may take some time ...$"
    in_stdout $mm "^Processing /usr/share/vim/addons/doc$"
    TODO="$TODO \"chroot $workdir sh -c \\\"DEBIAN_FRONTEND=noninteractive apt-get -qq -y -f install $usepkg\\\"\""
  fi

  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  in_stdout $mm "^--- configuring motd ---$"
  TODO="$TODO \"config_motd $workdir\""

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

# function: do_clean -- clean all things
# get: $1 -- tmpdir
do_clean()
{
  local tmpdir=$1

  rm -rf $tmpdir

  return 0
}


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


TODO=""
workdir=$WORKDIR
upack="upack"
interact=""
DEBOOTSTRAP=""
DEBOOTSTRAPDIR="" 
debootstrapmirror=""
mirror=""
grub="grub"
mm=0
while [ $# -gt 0 ]; do
  case "$1" in
    "--grub-legacy")
      grub="grub-legacy"
      ;;
    "-d")
      shift
      workdir="$1"
      ;;
    "-nu")
      upack="no_upack"
      ;;
   "-h")
     shift
     IAHEAD="$1 / $IAHEAD"
     ;;
   "-m")
     shift
     mirror="$1"
     ;;
   "-i")
     interact="$1"
     ;;
    --help|-h) 
      usage
      ;;
    *) 
      if [ ! "$DEBOOTSTRAP" -a ! "$DEBOOTSTRAPDIR" ]; then
        [ -d "$1" ] && DEBOOTSTRAPDIR=$1 || DEBOOTSTRAP=$1
      else
        usage "parameter \"$1\" unknown!"
      fi
      ;;
  esac
  shift
done

[ ! "$DEBOOTSTRAP" -a ! "$DEBOOTSTRAPDIR" ] && usage "Mandatory parameter \"debootstrap path|directory\" not found!"

# if DEBOOTSTRAPDIR is set, the user has set DIR 
#    (DEBOOTSTRAP is automatically computed!)
if [ "$DEBOOTSTRAPDIR" ]; then
  DEBOOTSTRAPDIR="$(realpath $DEBOOTSTRAPDIR)"
  # check for debootstrap and bootcd packages 
  DEBOOTSTRAP="$(ls $DEBOOTSTRAPDIR/debootstrap_*.deb |head -1)"
  echo "use debootstrap package: $DEBOOTSTRAP"
  # search local bootcd packages
  for i in $BOOTCDPACKAGES_LOCAL; do
     [ ! -f "$(ls $DEBOOTSTRAPDIR/${i}_*.deb |head -1)" ] && err "debian package of $i not found in \"$DEBOOTSTRAPDIR\"!"
  done
fi

DEBOOTSTRAP="$(realpath $DEBOOTSTRAP)"
[ ! -f "$DEBOOTSTRAP" ] && err "debootstrap file \"$DEBOOTSTRAP\" not found!"


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

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_stdout $mm "Directory.*"
TODO="$TODO \"do_dir $workdir 700\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_stdout $mm "Directory.*"
TODO="$TODO \"do_dir $TMPDIR 700\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_stdout $mm "check mirror"
in_stdout $mm "^--"
in_stdout $mm "=> ./dev/null."
in_stdout $mm "^Resolving"
in_stdout $mm "^Connecting"
in_stdout $mm "^Giving up."
in_stdout $mm "^doesn't work"
in_stdout $mm "^$"
in_stdout $mm "^HTTP request sent"
in_stdout $mm "^Length:"
in_stdout $mm "^[[:space:]]*0K .."
in_stdout $mm "/dev/null. saved"
in_stdout $mm "^ok$"
in_stdout $mm "^Proxy request sent"
TODO="$TODO \"get_debootstrapmirror\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_stdout $mm "^extract debootstrap from debian package$"
TODO="$TODO \"extract_debootstrap $TMPDIR $DEBOOTSTRAP\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_stdout $mm "^I: .*"
in_stdout $mm "^W: Failure .* This will be re-atempted"
TODO="$TODO \"DEBOOTSTRAP_DIR=$TMPDIR/usr/share/debootstrap $TMPDIR/usr/sbin/debootstrap --no-check-gpg --arch i386 --variant=buildd $DISTRI $workdir \\\$debootstrapmirror\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_norun $mm # do not start functions that itself use interactive with run
TODO="$TODO \"prepare_chroot $workdir <-i>\"" 

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_norun $mm # do not start functions that itself use interactive with run
TODO="$TODO \"[ -f $workdir/selinux/enforce ] && echo 0 > $workdir/selinux/enforce\"" 

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_norun $mm # do not start functions that itself use interactive with run
TODO="$TODO \"install_packages $workdir $upack <-i>\"" 

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
TODO="$TODO \"do_clean $TMPDIR\""

mm=$(($mm+1)); eval IGNORE${mm}=\"\"; eval STDOUT${mm}=\"\"
in_norun $mm # do not start functions that itself use interactive with run
TODO="$TODO \"unprepare_chroot $workdir <-i>\"" 

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