#!/bin/sh
#
# 	$Source: /cvsroot/bootcd/bootcd/bootcddebootstrap.src,v $
#       $Id: bootcddebootstrap.src,v 1.28 2007-12-14 06:58:07 bs Exp $
#    author: Carsten Dinkelmann <din@foobar-cpa.de>
#     start: 12.09.2007
#     topic: create a small debian cd and configured it for bootcd
#

# Version of debian package bootcd with this tools is included
# /usr/share/doc/bootcd-backup/examples/bootcddebootstrap
VERSION=##BOOTCD_VERSION## # 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="etch"

# links for debootstrap
DEBOOTLINKS="/usr/lib/debootstrap"

# needed tools
TOOLS="ar tar chroot"

# kernel
KERNEL="linux-image-2.6-486"

# bootcd packages
BOOTCDPACKAGES_APT="bootcd/unstable bootcd-mkinitramfs/unstable bootcd-backup/unstable bootcd-i386/unstable"
BOOTCDPACKAGES_LOCAL="bootcd bootcd-mkinitramfs bootcd-backup bootcd-i386"

# needed packages
PACKAGES="lvm2 grub mkisofs fdutils file dosfstools realpath syslinux"

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

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

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

SCRIPTNAME=$(basename $0)

IAHEAD="$SCRIPTNAME"

### 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  "          -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

  # we need one link that debootstrap is working
  for i in $DEBOOTLINKS; do 
     [ ! -e "${i}" ] && ln -s ${tmpdir}${i} ${i}
  done
  
  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 (etch)
deb http://ftp.debian.org/debian $DISTRI 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: 700

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
}

# 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 "^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 "^$"
}


# 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  >&2 " check mirror $i ... "
     if [ $(wget --timeout=1 -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" ] && err "No working mirror found!"
}


# function: debconf_set_console_selections
# description: prevent questions while installing: console-tools console-data console-common
# get: $1 -- workdir
debconf_set_console_selections() 
{
  # The config data was created after manual config with
  # debconf-get-selections | grep -e "^console*" |grep -v "select[[:space:]]*$"
  cat <<END | chroot $1 debconf-set-selections
console-data    console-data/keymap/qwerty/layout       select  US american
console-data    console-data/keymap/qwerty/us_american/standard/keymap  select  Standard
console-common  console-data/keymap/family      select  qwerty
console-data    console-data/keymap/family      select  qwerty
console-common  console-data/keymap/powerpcadb  boolean
console-data    console-data/keymap/powerpcadb  boolean
console-data    console-data/keymap/qwerty/us_american/variant  select  Standard
console-common  console-data/keymap/policy      select  Select keymap from arch list
console-data    console-data/keymap/policy      select  Select keymap from arch list
console-common  console-data/bootmap-md5sum     string  4bdb3d92fccb234f48640757dab09905
console-data    console-data/bootmap-md5sum     string  4bdb3d92fccb234f48640757dab09905
console-common  console-data/keymap/ignored     note
console-data    console-data/keymap/ignored     note
END
  
}

# function: install_packages
# get: $1 -- workdir
#      $2 -- useful packages switch (upack | no_upack)
#      $3 -- interactive (optional)
install_packages()
{
  local workdir=$1
  local kernel=$KERNEL
  local packages=$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"

  # only pin sources.list if we install bootcd with apt-get
  if [ ! "$DEBOOTSTRAPDIR" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    in_stdout $mm ".*"
    TODO="$TODO \"config_apt $workdir\""
  fi

  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\""

  # Don't know why, but next step seems to be needed.
  mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
  TODO="$TODO \"chroot $workdir sh -c \\\"mkdir -p /tmp/.debootstrap\\\"\""

  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.18-5-486 module-init-tools udev$"
  in_stdout $mm "^  linux-image-2.6-486 linux-image-2.6.18-5-486 module-init-tools udev$"
  in_stdout $mm "^  linux-doc-2.6.18 grub lilo$"
  in_stdout $mm "^ Hmm. The package shipped with a symbolic link /lib/modules/2.6.18-5-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-5-486/source"
  in_stdout $mm "^A chroot environment has been detected, udev not started."
  in_stdout $mm "^Running depmod."
  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."
  TODO="$TODO \"chroot $workdir sh -c \\\"apt-get -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$"
  in_stdout $mm "^  wodim cdrkit-doc grub-doc mdadm dmsetup"
  in_stdout $mm "^  mtools logrotate"
  in_stdout $mm "^  dosfstools fdutils file genisoimage grub libmagic1 lvm-common lvm2 mkisofs"
  in_stdout $mm "^  realpath"
  in_stdout $mm "^Backing up any"
  TODO="$TODO \"chroot $workdir sh -c \\\"apt-get -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 .*"
    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 \\\"apt-get -y -f install $bootcdpackages\\\"\""
  fi

  if [ "$(echo "$usepkg" | grep console-data)" ]; then
    mm=$(($mm+1)); eval local IGNORE${mm}=\"\"; eval local STDOUT${mm}=\"\"
    TODO="$TODO \"debconf_set_console_selections $workdir\""
  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:
    in_stdout $mm "^  adduser defoma fontconfig-config ifupdown iputils-ping libconsole libdrm2$"
    in_stdout $mm "^  libedit2 libexpat1 libfontconfig1 libfreetype6 libfs6 libgl1-mesa-glx$"
    in_stdout $mm "^  libgpmg1 libice6 libkrb53 libnewt0.52 libpng12-0 libpopt0 libsm6 libssl0.9.8$"
    in_stdout $mm "^  libwrap0 libx11-6 libx11-data libxau6 libxaw7 libxcursor1 libxdmcp6 libxext6$"
    in_stdout $mm "^  libwrap0 libx11-6 libx11-data libxau6 libxaw7 libxcursor1 libxdmcp6 libxext6$"
    in_stdout $mm "^  libxfixes3 libxft2 libxi6 libxkbfile1 libxmu6 libxmuu1 libxpm4 libxrandr2$"
    in_stdout $mm "^  libxrender1 libxss1 libxt6 libxtrap6 libxtst6 libxv1 libxxf86dga1$"
    in_stdout $mm "^  libxxf86vm1 openbsd-inetd openssh-client openssh-server tcpd ttf-dejavu ucf$"
    in_stdout $mm "^  update-inetd vim-common vim-runtime whiptail x11-common$"
    # Suggested packages:
    in_stdout $mm "^  unicode-data kbd-compat defoma-doc psfontmgr x-ttcidfont-conf dfontmgr$"
    in_stdout $mm "^  iproute dhcp3-client dhcp-client ppp libfreetype6-dev gpm krb5-doc krb5-user$"
    in_stdout $mm "^  ssh-askpass rssh molly-guard ctags vim-doc vim-scripts mesa-utils$"
    # Recommended packages:
    in_stdout $mm "^  libft-perl libfribidi0 debconf-utils$"
    # The following NEW packages will be installed:
    in_stdout $mm "^  adduser console-common console-data console-tools defoma fontconfig-config$"
    in_stdout $mm "^  ifupdown iputils-ping less libconsole libdrm2 libedit2 libexpat1$"
    in_stdout $mm "^  libfontconfig1 libfreetype6 libfs6 libgl1-mesa-glx libgpmg1 libice6 libkrb53$"
    in_stdout $mm "^  libnewt0.52 libpng12-0 libpopt0 libsm6 libssl0.9.8 libwrap0 libx11-6$"
    in_stdout $mm "^  libx11-data libxau6 libxaw7 libxcursor1 libxdmcp6 libxext6 libxfixes3$"
    in_stdout $mm "^  libxft2 libxi6 libxkbfile1 libxmu6 libxmuu1 libxpm4 libxrandr2 libxrender1$"
    in_stdout $mm "^  libxss1 libxt6 libxtrap6 libxtst6 libxv1 libxxf86dga1 libxxf86vm1 net-tools$"
    in_stdout $mm "^  netbase openbsd-inetd openssh-client openssh-server psmisc ssh star tcpd$"
    in_stdout $mm "^  ttf-dejavu ucf update-inetd vim vim-common vim-runtime whiptail x11-common$"
    in_stdout $mm "^  xbase-clients$"
    # 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:$"

    TODO="$TODO \"chroot $workdir sh -c \\\"apt-get -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

  # remove only links!! link that debootstrap is working
  for i in $DEBOOTLINKS; do 
     [ -L $i ] && rm -f $i
  done
}


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


TODO=""
workdir=$WORKDIR
upack="upack"
interact=""
DEBOOTSTRAP=""
DEBOOTSTRAPDIR="" 
debootstrapmirror=""
mirror=""
mm=0
while [ $# -gt 0 ]; do
  case "$1" in
    "-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_norun $mm
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: .*"
TODO="$TODO \"$TMPDIR/usr/sbin/debootstrap --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 \"install_packages $workdir $upack <-i>\"" 

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

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