#! /bin/bash -e

function error() {
    echo "**** ERROR aborted. Status of last operation: $?" 1>&2
    exit 1
}
trap error HUP INT QUIT TERM ERR

# log LEVEL "Text"
# LEVEL: 0=normal 1=verbose 2=more-verbose
function echon() {
    if test "$1" == "-n"; then
        shift
        echo -n "$*"
    else
        echo "$*"
    fi
}
function log() {
    l=$1
    shift
    if test "$1" == "-n"; then
        shift
        n="-n"
    else
        n=""
    fi
    if test $q -eq 1; then
        return
    elif test $l -eq 0 -o $l -eq 1 -a $v -eq 1 -o $l -eq 2 -a $vv -eq 1; then
        if test $l -gt 0; then
            echon $n " $l→ $*"
        else
            echon $n "$*"
        fi
    fi
}

################################################################################
# Arguments
################################################################################
q=0
v=0
vv=0
while test $# -gt 0; do
    case "$1" in
        (-q) q=1; v=0; vv=0;;
        (-v) q=0; v=1; vv=0;; 
        (-vv) q=0; v=1; vv=1;;
        (-h|--help)
            echo "SYNOPSIS: $0 [-q|-v|-vv]"
            echo " -q    quiet"
            echo " -v    verbose"
            echo " -vv   more verbose"
            exit 0;;
    esac
    shift
done
################################################################################

################################################################################
# CONFIGURATION you can overwrite any of the variables when you start the script
# e.g. HW="x86_64 noarch" VERSION=11.4 sudo susebootstrap.sh

# Setup OS sources
#
# OS: name of the OS, used for chroot pathname
OS=${OS:-"opensuse"}
log 2 "OS=$OS"
#
# VERSION: see e.g. http://download.opensuse.org/distribution/
VERSION=${VERSION:-$(
        case "$OS" in
            opensuse) echo "openSUSE-current";;
            fedora)   echo "$(wget -O- http://mirror.switch.ch/ftp/mirror/fedora/linux/releases 2>/dev/null | html2 | sed -n 's,.*/a/@href=\([0-9][0-9]*\)/,\1,gp' | uniq | tail -1)";;
            centos)   echo "$(wget -O- http://mirror.switch.ch/ftp/mirror/centos 2>/dev/null | sed -n 's,.*href="\([0-9][0-9]*\)/".*,\1,pg' | uniq | tail -1)"
        esac)}
log 2 "VERSION=$VERSION"

#
# HW: list of hardware subdirs in openSUSE, e.g. "x86_64 noarch"
HW=${HW:-$(
        case "$OS" in
            opensuse) echo "i586 noarch";;
            fedora)   echo "i686 noarch";;
            centos)   echo "x86_64 noarch";;
        esac)}
log 2 "HW=$HW"
#
# BASEARCH: Base architecture, e.g. for i686 and i566 BASEARCH is i386
BASEARCH=${BASEARCH:-$(
        case "${HW%% *}" in
            i?86) echo "i386";;
            *)    echo "${HW%% *}";;
        esac)}
log 2 "BASEARCH=$BASEARCH"
#
# URL: where to download packages from
URL=${URL:-$(
        case "$OS" in
            opensuse)
                case "$VERSION" in
                    (tumbleweed) echo "http://download.opensuse.org/tumbleweed/repo/oss/suse";;
                    (*) if wget -qO- http://download.opensuse.org/distribution/${VERSION}; then
                          echo "http://download.opensuse.org/distribution/${VERSION}/repo/oss/suse"
                        else
                          echo "http://download.opensuse.org/distribution/leap/${VERSION}/repo/oss/suse"
                        fi;;
                esac;;
            fedora)
                if test "$VERSION" -gt 20; then
                    echo "http://mirror.switch.ch/ftp/mirror/fedora/linux/releases/${VERSION}/Server/${BASEARCH}/os"
                else
                    echo "http://mirror.switch.ch/ftp/mirror/fedora/linux/releases/${VERSION}/Fedora/${BASEARCH}/os"
                fi;;
            centos)   echo "http://mirror.switch.ch/ftp/mirror/centos/${VERSION}/os/${BASEARCH}";;
        esac)}
log 2 "URL=$URL"

# Setup installation pathes
#
# BASE: Base path to the chroots on your system
BASE=${BASE:-/var/chroot}
log 2 "BASE=$BASE"
#
# SCHROOTCONF: Configurationfile for schroot
SCHROOTCONF=${SCHROOTCONF:-${OS}-${VERSION}_${BASEARCH}}
log 2 "SCHROOTCONF=$SCHROOTCONF"
#
# ROOT: Full path to the chroot directory
ROOT=${ROOT:-${BASE}/${SCHROOTCONF}}
log 2 "ROOT=$ROOT"
#
# PACKAGES: Where to store package lists path+filename-prefix
PACKAGES=${PACKAGES:-${ROOT}/var/tmp/packagelist}
log 2 "PACKAGES=$PACKAGES"
#
# PKGDIR: Where to store downloaded packages, relative to ${ROOT}
PKGDIR=${PKGDIR:-/var/tmp/packages}
log 2 "PKGDIR=$PKGDIR"
#
# SCHROOTUSER: Name of user or list of user that will use schroot
SCHROOTUSER=${SCHROOTUSER:-${USER}}
log 2 "SCHROOTUSER=$SCHROOTUSER"
#
# CHROOT: Command for starting chroot, more possibilities, see below
CHROOT="schroot -c ${SCHROOTCONF} -d / -u root"
log 2 "CHROOT=$CHROOT"
#
# YUM: Set Fedora / CentOS repository tool
if test "$OS" = "fedora" && test "${VERSION}" -gt "21"; then
    YUM=${YUM:-"dnf"}
else
    YUM=${YUM:-"yum"}
fi
log 2 "YUM=$YUM"
#
# PKGS: Base packages to install for minimal system
PKGS=${PKGS:-$(
        case "$OS" in
            opensuse) echo "bash rpm zypper";;
            fedora)
                echo "bash rpm  fedora-release"
                if test "${VERSION}" -gt "21"; then
                    echo "dnf"
                else
                    echo "yum"
                fi
                ;;
            centos)
                echo "bash rpm yum centos-release"
                ;;
        esac)}
log 2 "PKGS=$PKGS"
#
#
if [ -z "${CHROOT}" ]; then
    echo "**** ERROR: Variable CHROOT must be set." 1>&2
    exit 1
fi
# ADDITIONAL_DEFINITIONS: Add additional definitions to /etc/schroot/chroot.d
# Example: ADDITIONAL_DEFINITIONS="setup.fstab=jenkins/fstab"
ADDITIONAL_DEFINITIONS=${ADDITIONAL_DEFINITIONS:-}
log 2 "ADDITIONAL_DEFINITIONS=$ADDITIONAL_DEFINITIONS"
################################################################################

################################################################################
# Main Part
################################################################################
# 1. Download basic packages
# 2. Unpack them into a chroot-path
# 3. Tweak the minimal system
# 4. Setup basic /dev
# 5. Properly install all the packages within chroot
# 6. configure basic system
# 7. Done, now chroot to the path and install whatever you need
################################################################################

log 0 "Install ${OS} ${VERSION} for ${BASEARCH} in ${ROOT}"

################################################################################
# Preparation of path for chroot and packages
test -d ${ROOT}${PKGDIR} || mkdir -p ${ROOT}${PKGDIR}
################################################################################

################################################################################
# 0. Setup schroot configuration
cat > /etc/schroot/chroot.d/${SCHROOTCONF} <<EOF
[${SCHROOTCONF}]
description=${OS}-${VERSION} ${BASEARCH}
directory=${ROOT}
users=${SCHROOTUSER}
root-groups=root
root-users=${SCHROOTUSER}
type=directory
${ADDITIONAL_DEFINITIONS}
EOF
################################################################################

################################################################################
# 0. Search for dependencies to install
# requires: rpm xml2 schroot
log 0 "   ... read repository meta data"
test -d ${PACKAGES} || mkdir -p ${PACKAGES}
test -f ${PACKAGES}/repomd.xml || \
    wget -q -O ${PACKAGES}/repomd.xml ${URL}/repodata/repomd.xml
datapath=$(xml2 < ${PACKAGES}/repomd.xml \
    | awk -F= 'BEGIN                                     {x=0} 
               x==1 && $1=="/repomd/data/location/@href" {print $2; exit} 
               $1=="/repomd/data/@type" && $2=="primary" {x=1}')
dataname=${PACKAGES}/${datapath##*/}
log 2 "dataname=${dataname} from ${URL}/${datapath}"
log 0 "   ... read repository details"
test -f ${dataname} || \
    wget -q -O ${dataname} ${URL}/${datapath}

log 0 "   ... recursively extract dependencies"
oldPKGS=""
if [ ! -f ${PACKAGES}/packages ]; then 
    test ! -f ${PACKAGES}/install || rm  ${PACKAGES}/install
    while [ "$PKGS" != "$oldPKGS" ]; do
	oldPKGS=$PKGS
	log 0 "     ... extract dependencies: scan $(echo "$PKGS" | wc -w)" \
            "packages"
	log 0 "     ... extract dependencies: find requirements"
	PKGS=${PKGS% }
	chkinstpkgs=$(echo $PKGS | sed -e 's/[^-A-Za-z0-9_ ]/./g' | tr ' ' '|')
	deps=$(
	    (
		echo "$PKGS" | tr ' ' '\n';
		zcat $dataname | xml2 | awk -F= '
                BEGIN {
                    x=0
                }
                x==1 &&
                ( $1=="/metadata/package/format/rpm:requires/rpm:entry/@name" ||
                  ( $1=="/metadata/package/format/rpm:provides/rpm:entry/@name" &&
                    $2~/^('${chkinstpkgs}')$/ ) ) {
                    print $2
                }
                $1=="/metadata/package/name" && $2~/^('${chkinstpkgs}')$/ {
                    x=1
                }
                $1=="/metadata/package/name" && $2!~/^('${chkinstpkgs}')$/ {
                    x=0
                }
                $1=="/metadata/package/arch" && $2!~/^('${HW// /|}')$/ {
                    x=0
                }'
	    ) | \
		sed -e 's/[^-A-Za-z0-9_]/./g' | \
		sort | uniq | tr '\n ' '||' | \
		sed -e 's/^|//' -e 's/|$//'
	)
	log 0 "     ... extract dependencies: extend packagelist by providers"
	PKGS=$(
            zcat $dataname | xml2 | awk -F= '
                BEGIN {
                    matcher="^('${deps}')$" #(|\\(.*\\))$"
                }
                $1=="/metadata/package/name" && $2~matcher {
                    name=$2
                    tmp=$2
                    gsub(/[^-A-Za-z0-9_]/, ".", tmp)
                    sub("\\|" tmp "\\|", "|", matcher)
                    sub("^" tmp ")\\|", "", matcher)
                    sub("\\|" tmp "$", "", matcher)
                    print name
                }
                $1=="/metadata/package/name" && $2!~matcher {
                    name=$2
                }
                ( $1=="/metadata/package/format/rpm:provides/rpm:entry/@name" ||
                  $1=="/metadata/package/format/file" ) &&
                $2~matcher {
                    tmp=$2
                    gsub(/[^-A-Za-z0-9_]/, ".", tmp)
                    sub("\\|" tmp "\\|", "|", matcher)
                    sub("^" tmp ")\\|", "", matcher)
                    sub("\\|" tmp "$", "", matcher)
                    print name
                }
                END {
                }' | sort | uniq | tr '\n' ' '
	)
	((++pkgcnt)) ###
	log 2 "temp packages:     $PKGS"
	log 2 "temp dependencies: $deps"
    done
else
    log 2 "packages already evaluated"
    PKGS=$(<${PACKAGES}/packages)
fi

PKGS=${PKGS% }
echo ${PKGS} > ${PACKAGES}/packages
log 1 "final packages:     $PKGS"
log 0 "   ... extract dependencies: find files to download and install"
if [ -f ${PACKAGES}/install ]; then
    log 2 "installations already evaluated"
    INSTALL=$(<${PACKAGES}/install)
else
    chkinstpkgs=$(echo $PKGS | sed -e 's/[^-A-Za-z0-9_ ]/./g' | tr ' ' '|')
    INSTALL=$(
        zcat $dataname | xml2 | awk -F= '
            BEGIN {
                x=0
            }
            x==1 && $1=="/metadata/package/location/@href" {
                print $2
            }
            $1=="/metadata/package/name" && $2~/^('${chkinstpkgs}')$/ {
                x=1
            }
            $1=="/metadata/package/name" && $2!~/^('${chkinstpkgs}')$/ {
                x=0
            }
            $1=="/metadata/package/arch" && $2!~/^('${HW// /|}')$/ {
                x=0
            }' | sort | uniq | tr '\n' ' '
    )
    echo $INSTALL > ${PACKAGES}/install
fi
log 1 "installations: ${INSTALL}"
log 0 "   ... download and install $(echo ${INSTALL} | wc -w) files"
################################################################################

################################################################################
# 1. Download basic packages
# We know the logical package names, find matching package files
# on server
# INST: Will be filled with a list of package files to download and
# install

for p in $INSTALL; do # for all packages to install
    log 0 "   ... download $p"
    test -f ${ROOT}${PKGDIR}/${p##*/} || \
        wget -q -O ${ROOT}${PKGDIR}/${p##*/} ${URL}/$p
    INST="$INST ${p##*/}"
done
################################################################################

################################################################################
# 2. Unpack them into a chroot-path
# Extract all downloaded packages in the chroot directory
cd ${ROOT}
for p in $INST; do # for all downloaded packages
    # just extract the file structure withing the package without running
    # pre-/post-install scripts
    # this is necessary for a minimal basic system to chroot in
    log 0 "   ... unpack $p"
    rpm2cpio ${ROOT}${PKGDIR}/$p | cpio -dim --quiet 2> /dev/null
done
cd - > /dev/null
################################################################################

################################################################################
# 3. Tweak the minimal system
log 0 "   ... setup system"
#
# Do some system setup tweaks
#
# Create minimal /etc/passwd and /etc/group with user "root"
test -e ${ROOT}/etc/passwd || \
    echo 'root:x:0:0:root:/root:/bin/bash' > ${ROOT}/etc/passwd
test -e ${ROOT}/etc/group || \
        ( \
            echo 'root:x:0:'; \
            echo 'utmp:x:0:' \
        ) > ${ROOT}/etc/group
test -e ${ROOT}/etc/shadow || touch ${ROOT}/etc/shadow
test -e ${ROOT}/etc/gshadow || touch ${ROOT}/etc/gshadow
test -e ${ROOT}/etc/fstab || touch ${ROOT}/etc/fstab
#
# copy /etc/resolv.conf into chroot to be able to access internet
test -e ${ROOT}/etc/resolv.conf || \
    cp /etc/resolv.conf ${ROOT}/etc/

################################################################################
# 4. Setup basic /dev
log 0 "   ... setup prepare minimal /dev with /dev/null and /dev/zero"
if [ -n "${CHROOT//*schroot*/}" ]; then
    # only if chroot is not schroot, schroot creates /dev
    test -d ${ROOT}/dev || ${CHROOT} mkdir ${ROOT}/dev
    test -e ${ROOT}/dev/null || \
	( \
            ${CHROOT} mknod -m 666 /dev/null c 1 3 && \
            ${CHROOT} chown root:root /dev/null
        )
    test -e ${ROOT}/dev/zero || \
	( \
            ${CHROOT} mknod -m 666 /dev/zero c 1 5 && \
            ${CHROOT} chown root:root /dev/zero \
	)
fi
################################################################################

################################################################################
# 5. Properly install all the package packages within chroot
# now chroot into the new system and properly install all downloaded packages,
# this executes triggers and maintains package database
log 0 "   ... install all packages"
all_success=1
if ! ${CHROOT} -- rpm -i --nodeps --force ${PKGDIR}/*.rpm > /dev/null 2> /dev/null; then
    for p in ${ROOT}${PKGDIR}/*.rpm; do
	log 0 -n "     ... install ${p#${ROOT}}"
        if ${CHROOT} -- rpm -i --nodeps --force ${p#${ROOT}} \
             > /dev/null 2> /dev/null; then
            log 0 " success"
        else
            all_success=0
            log 0 " error"
        fi
    done
fi
################################################################################

################################################################################
# System Dependent Part
################################################################################
case "$OS" in
    (opensuse)
        ########################################################################
        # 6. Add an archive source to package manager
        # add installation source as repository
        log 0 "   ... setup package manager"
        ${CHROOT} -- zypper -q ar ${URL} repo-oss
        #
        # setup hardware architecture in zypper, necessary different from host
        ${CHROOT} -- perl -pi \
            -e 's#^\#? *arch = .*$#arch = '${HW%% *}'#g' \
            /etc/zypp/zypp.conf
        # 
        # basic initialisation, first update - bug with key in 12.2
        log 0 "   ... basic initialisation of package manager"
        ${CHROOT} -- zypper -q -n --gpg-auto-import-keys update || \
            ${CHROOT} -- zypper -q -n --no-gpg-checks update
        ${CHROOT} -- zypper -q -n --gpg-auto-import-keys dist-upgrade || \
            ${CHROOT} -- zypper -q -n --no-gpg-checks dist-upgrade
        #
        # install some basic packages
        log 0 "   ... install more basic packages"
        ${CHROOT} -- zypper -q -n --gpg-auto-import-keys \
            install openSUSE-release
        ########################################################################
        ;;
    (fedora)
        ########################################################################
        # 6. Install basic packages
        log 0 "   ... setup package manager using $YUM"
        test -d ${ROOT}/etc/rpm || mkdir -p ${ROOT}/etc/rpm
        sed -i 's,$basearch,'${BASEARCH}',g' \
           $(grep -lr '$basearch' ${ROOT}/etc/yum.repos.d)
        echo "${HW%% *}-${OS}-linux" > ${ROOT}/etc/rpm/platform
        if test "${VERSION}" -gt "16"; then # hack for fedora 17 and above
            log 0 "   ... convert fedora 17+ filesystem"
            ${0%/*}/convertfs-fedora-17.sh ${ROOT}
        fi
        log 0 "   ... upgrade base system"
        ${CHROOT} -- ${YUM} -y -q upgrade || \
            ( ${CHROOT} -- bash -c 'sed -i "s|^#baseurl|baseurl| ; s|^mirrorlist|#mirrorlist|" /etc/yum.repos.d/*' && \
              ${CHROOT} -- ${YUM} -y -q upgrade )
        log 0 "   ... install more basic packages"
        for g in "Base" "Minimal Install"; do
            if ${CHROOT} -- ${YUM} -y -q grouplist | grep -q "$g"; then
                log 2 "install $g"
                case "$VERSION" in
                    (*-Fedora)
                        ${CHROOT} -- ${YUM} -y -q groups mark install "$g"
                        ;;
                    (*)
                        ${CHROOT} -- ${YUM} -y -q groupupdate "$g"
                        ;;
                esac
            fi
        done
        ########################################################################
        ;;
    (centos)
        ########################################################################
        # 6. Install basic packages
        log 0 "   ... setup package manager using $YUM"
        ${CHROOT} -- ln -s /sbin/chkconfig /usr/sbin/chkconfig
        test -d ${ROOT}/etc/rpm || mkdir -p ${ROOT}/etc/rpm
        echo "${HW%% *}-${OS}-linux" > ${ROOT}/etc/rpm/platform
        if test "${VERSION}" -gt "6"; then # hack for fedora 17 and above
            log 0 "   ... convert fedora 17+ filesystem"
            ${0%/*}/convertfs-fedora-17.sh ${ROOT}
        fi
        log 0 "   ... upgrade base system"
        ${CHROOT} -- ${YUM} -y -q upgrade || \
            ( ${CHROOT} -- bash -c 'sed -i "s|^#baseurl|baseurl| ; s|^mirrorlist|#mirrorlist|" /etc/yum.repos.d/*' && \
              ${CHROOT} -- ${YUM} -y -q upgrade )
        log 0 "   ... install more basic packages"
        for g in "Base" "Minimal Install"; do
            if ${CHROOT} -- ${YUM} -y -q grouplist | grep -q "$g"; then
                log 2 "install $g"
                ${CHROOT} -- ${YUM} -y -q groups mark install "$g"
                ${CHROOT} -- ${YUM} -y -q upgrade
                log 2 "update $g"
                ${CHROOT} -- ${YUM} -y -q groupupdate "$g"
            fi
        done
        ########################################################################
        ;;
esac
################################################################################

################################################################################
# 7. Done - use your chroot to work or install more packages
log 0 "**** SUCCESS done."
log 0
log 0 "########################################################################"
log 0 "Use your new chroot:"
log 0 " > ${CHROOT} -- bash"
log 0 "   > [... work in your chroot ...]"
log 0 "   > exit"
log 0 "########################################################################"
log 0 "   ... cleanup chroot"
################################################################################
