#!/bin/bash
# -------------------------------------------------------------------
# Copyright (C) 2008-2025
# by Intevation GmbH
# Author(s):
# Sascha Wilde <wilde@intevation.de>
# Thomas Arendsen Hein <thomas@intevation.de>

# This program is free software under the GNU GPL (>=v2)
# Read the file COPYING coming with the software for details.
# -------------------------------------------------------------------

# WARNING  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING
#
# Seems to work quite well...  Anyway:
#
# DONT USE IT IF YOU DON'T KNOW HOW IT WORKS!!!
#
# WARNING  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING

# Sanitize environment
export LC_ALL=C
export LANG=C

ME=`basename "$0"`
REVISION=`echo "$Revision$" | sed 's/.* \([a-z0-9]\+\).*/\1/'`
VERSION="0.13beta Rev: $REVISION"

XENMAKEGUEST_LIBDIR=${XENMAKEGUEST_LIBDIR:-/usr/local/lib/xen-make-guest}
TEMPLATE_VARS="MEM CPUS NAME SHORTNAME APTSRV SECSRV DISTRO DISTRO_SECURITY IP VG VIFEXTRA DISK KERNELVERSION KERNELDIR"

ROOTSIZE=10G
SWAPSIZE=512M
FSTYPE=ext4
VG=vg0
PV=""
VIFEXTRA=""
MEM=1024
CPUS=1
DISTRO=trixie
DISTRO_SECURITY="$DISTRO-security"
ARCH=amd64
APTSRV="http://ftp.de.debian.org/debian/"
SECSRV="http://security.debian.org/"
USERLOGIN=intevation
USERGECOS="Intevation Common Account"
KERNELVERSION="error_retrieving_kernel_version"
KERNELDIR="/etc/xen/kernel"

declare EXTENSION

MNTOPTIONS="defaults"

INSTALL=1
VERBOSE=0
TEST=0

usage()
{
  cat <<EOF
Usage: $ME [OPTION]... NAME [IP]
Create new domU \`NAME'.
The virtual network interface will be bound to \`IP'.
If no IP is provided it will be looked up on DNS.

Options:
  -r, --rootsize=SIZE    use SIZE for root partition, suffixes are
                         used by lvcreate (default ${ROOTSIZE})
  -s, --swapsize=SIZE    use SIZE for swap partition, suffixes are
                         used by lvcreate (default ${SWAPSIZE})
  -f, --fstype=TYPE      use filesystem type TYPE for root (default ${FSTYPE})
  -S, --shortname=SNAME  use SNAME as short name, (default is NAME
                         with everything from the first dot on stripped)
  -D, --domain=DOMAIN    use DOMAIN as domain name, (default is everything
                         after the first dot of NAME)
  -u, --userlogin=LOGIN  set LOGIN for the created user account (default ${USERLOGIN})
  -U, --usergecos=GECOS  set GECOS for the created user account (default ${USERGECOS})
  -V, --vg=VG            use volume group VG for new logical volumes
                         (default $VG)
      --pv=PV            use physical volume PV for new logical volumes
                         (may be specified multiple times)
  -m, --mem=MEM          allocate MEM MiB of memory for the new DomU.
                         (default $MEM)
  -c, --cpus=CPUS        number of virtual CPUs for the new DomU.
                         (default $CPUS)
  -b, --bridge           bridge to which the virtual interface will be attached.
      --vif-script       override vif script set in xl.conf (vif-bridge or vif-route)
  -M, --mac              MAC address for the virtual interface.
  -d, --distro=DISTRO    Debian distribution to use (default ${DISTRO})
  -a, --arch=ARCH        Target Architecture (default ${ARCH})
  -e, --extension=EXNAME Extension with name EXNAME will be used for creating the guest.
  -A, --aptserver        URL to use for bootstrapping and apt sources
                         (default ${APTSRV})
  -Z, --secserver        URL to use for security updates in apt sources
                         (default ${SECSRV})
  -G, --gw               Gateway, defaults to IP with the last byte set to 254.
  -N, --netmask          Netmask, defaults to 255.255.255.0
      --no-install       Skip installation, just create Xen config and logical volumes
      --test             Don't do anything, but print all actions to STDOUT.
  -v, --verbose          be verbose
      --help             display this help and exit
      --version          output version information and exit
EOF
  exit $1
}

version()
{
  cat <<EOF
$ME $VERSION
Copyright (C) 2008-2025 Intevation GmbH
This is free software.  You may redistribute copies of it under the terms of
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.

Written by Sascha Wilde and Thomas Arendsen Hein
EOF
  exit 0
}

verbose()
{
  [ $VERBOSE -eq 1 ] && echo "$1"
}

error()
{
  echo "$1" >/dev/stderr
  [ $TEST -eq 0 ] && exit 23
}

runcmd()
{
  cmdstr=`for word in "$@" ; do printf "%q " "$word"; done`
  if [ $TEST -eq 0 ] ; then
    "$@" || \
      error "FATAL: Execution of \`$cmdstr' failed."
  else
    echo "$cmdstr"
  fi
}

distro_name2version()
# $1 :: Debian distro name
# returns Debian version number
{
  # Note: we don't support distros older than etch.
  local -A name2version=(
    ["etch"]=4
    ["lenny"]=5
    ["squeeze"]=6
    ["wheezy"]=7
    ["jessie"]=8
    ["stretch"]=9
    ["buster"]=10
    ["bullseye"]=11
    ["bookworm"]=12
    ["trixie"]=13
    ["forkie"]=14
  )
  if [ -v 'name2version[$1]' ]; then
    echo -n "${name2version[$1]}"
  else
    return 1
  fi
}

min_dist()
# $1 :: Debian distro name
# returns True if selected DISTRO is >= the given one
{
  [[ `distro_name2version "$DISTRO"` -ge `distro_name2version "$1"` ]]
}

add_log_item()
{
  # $logitems is a global variable where we collect items which are
  # later dumped as an new entry to the logbuch by calling
  # write_log_items.
  if [ ! "$logitems" ] ; then
    logitems="$*"
  else
    logitems="$logitems
* $*"
  fi
}

write_log_items()
{
  if [ $TEST -eq 0 ] ; then
    chroot ${DOMUMNT} bash -c "`printf 'LOGBUCH_BATCHMODE=yes log %q' "$logitems"`" || \
      error "FATAL: Could not create /etc/logbuch.txt for guest."
  else
    echo "LOG: Writing to new systems /etc/logbuch.txt:"
    echo "- BEGIN -------------------------------------------------------------------"
    echo "$logitems"
    echo "- END ---------------------------------------------------------------------"
  fi
  logitems=""
}

create_file()
{
  if [ $TEST -eq 0 ] ; then
    echo >"${DOMUMNT}/$1" "$2" || \
      error "FATAL: Could not create ${DOMUMNT}/$1"
  else
    echo "CREATING FILE ${DOMUMNT}/$1:"
    echo "- BEGIN -------------------------------------------------------------------"
    echo "$2"
    echo "- END ---------------------------------------------------------------------"
  fi
  add_log_item "$1:
$(echo "$2" | sed 's/^/    /')"
}

add_to_file()
{
  if [ $TEST -eq 0 ] ; then
    echo >>"${DOMUMNT}/$1" "$2"
  else
    echo "ADDING TO FILE ${DOMUMNT}/$1:"
    echo "- BEGIN -------------------------------------------------------------------"
    echo "$2"
    echo "- END ---------------------------------------------------------------------"
  fi
  add_log_item "$1:
  # ...
$(echo "$2" | sed 's/^/    /')"
}

create_file_from_template()
# $1 FILE_TO_CREATE
# $2 TEMPLATE_FILE
# $3 WHERE_TO_CREATE (empty or "guest" for the created DomU,
#                     "host" for the Dom0 system.)
{
  if [ "$3" != "host" ] ; then
    dst="${DOMUMNT}/$1"
  else
    dst="$1"
  fi
  substexp=""
  for var in $TEMPLATE_VARS ; do
    substexp="${substexp}s/@$var@/\${$var//\//\\\\/}/g;"
  done
  substexp="\"${substexp}\""
  content=$( eval sed $substexp "\"$XENMAKEGUEST_LIBDIR/$2\"" )
  if [ $TEST -eq 0 ] ; then
    echo "$content" > "${dst}" || \
      error "FATAL: Could not create ${dst}"
  else
    echo "CREATING FILE ${dst} FROM TEMPLATE $XENMAKEGUEST_LIBDIR/$2:"
    echo "- BEGIN -------------------------------------------------------------------"
    echo "$content"
    echo "- END ---------------------------------------------------------------------"
  fi
  if [ "$3" != "host" ] ; then
    add_log_item "$1:
$(echo "$content" | sed 's/^/    /')"
  fi
}

is_ipv6()
{
  # $1 IP
  # return 0 if IP looks like an IPv6 address
  # and 1 otherwise
  echo "$1" | grep -q '^[^.]*:'
}

check_cmd_available()
{
  # $1 COMMAND_TO_CHECK_FOR
  # $2 OPTIONAL_HINT_WHEN_MISSING
  verbose "Checking for $1"
  if which "$1" >/dev/null ; then
    return 0
  else
    error "FATAL: Required command \"$1\" not found.  $2"
  fi
}

check_prerequisites()
{
  verbose "Checking prerequisites..."
  distro_name2version "$DISTRO" >/dev/null || error "FATAL: unsupported distro '$DISTRO'"
  check_cmd_available "lvcreate" "Install: lvm2"
  check_cmd_available "debootstrap" "Install: debootstrap"
  check_cmd_available "pwgen" "Install: pwgen"
  vgs "$VG" >/dev/null 2>&1 || \
    error "FATAL: Volume Group $VG not found."
  if [ ! -r "/usr/share/debootstrap/scripts/$DISTRO" ]; then
    error "FATAL: debootstrap does not support distro '$DISTRO', maybe you need a backport?"
  fi
}

create_kerneldir()
{
  verbose "Making sure that the kernel is available in Dom0..."
  [ -d "$KERNELDIR" ] || runcmd mkdir -p "$KERNELDIR"
}

create_vols()
{
  verbose "Creating logical volumes..."
  runcmd lvcreate -n v-${NAME} -L${ROOTSIZE} $VG$PV
  runcmd lvcreate -n v-${NAME}-swap -L${SWAPSIZE} $VG$PV
  verbose "Creating filesystems..."
  runcmd mkfs.${FSTYPE} /dev/${VG}/v-${NAME}
  runcmd mkswap -f /dev/${VG}/v-${NAME}-swap
}

bootstrap()
{
  if [ $TEST -eq 0 ] ; then
    verbose "Mounting new root..."
    [ -d ${DOMUMNT} ] || runcmd mkdir ${DOMUMNT}
    runcmd mount -o $MNTOPTIONS /dev/${VG}/v-${NAME} ${DOMUMNT}
    verbose "Bootstrapping debian..."
    runcmd debootstrap --arch ${ARCH} "$DISTRO" ${DOMUMNT} "$APTSRV"
  else
    echo "BOOTSTRAPING $DISTRO TO /dev/${VG}/v-${NAME}"
  fi
}


basic_setup()
{
  verbose "Installing logbuch.txt and logbuch tools..."
  runcmd cp /usr/local/bin/log ${DOMUMNT}/root/logbuch-installer
  runcmd chroot ${DOMUMNT} bash -c "LOGBUCH_BATCHMODE=\"yes\" LOGBUCH_HOST=$NAME bash ~/logbuch-installer"
  verbose "Creating fstab..."
  create_file /etc/fstab "# /etc/fstab: static file system information.
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults        0       0
/dev/${DISK}1      /               $FSTYPE    $MNTOPTIONS        0       1
/dev/${DISK}2      none            swap    sw              0       0"
  verbose "Creating network config..."
  if is_ipv6 "$IP" ; then
      create_file /etc/network/interfaces "auto lo
iface lo inet loopback
auto $NETDEV
iface $NETDEV inet6 static
	address $IP
	netmask $NETMASK
	gateway $GATEWAY"
  else
    if [ "${GATEWAY%.*}" = "${IP%.*}" ] || min_dist "stretch" ; then
	create_file /etc/network/interfaces "auto lo
iface lo inet loopback
auto $NETDEV
iface $NETDEV inet static
	address $IP
	netmask $NETMASK
	gateway $GATEWAY"
    else
      create_file /etc/network/interfaces "auto lo
iface lo inet loopback
auto $NETDEV
iface $NETDEV inet static
	address $IP
	netmask $NETMASK
	post-up route add -host $GATEWAY $NETDEV
	post-up route add default gw $GATEWAY"
    fi
  fi
  verbose "Setting hostname..."
  create_file /etc/hostname $SHORTNAME
  verbose "Setting domain in /etc/resolv.conf ..."
  runcmd sed -i "s/^search .*/search $DOMAIN/" ${DOMUMNT}/etc/resolv.conf
  add_log_item "/etc/resolv.conf:
    search $DOMAIN"
  verbose "Installing hosts file..."
  create_file /etc/hosts "127.0.0.1       localhost
$IP	$NAME $SHORTNAME

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts"
  if [ "$DISTRO" = "lenny" -o "$DISTRO" = "squeeze" -o "$DISTRO" = "wheezy" ] ; then
    verbose "Set device for main console to hvc0..."
    runcmd sed -i "/^1:2345:/c\1:2345:respawn:/sbin/getty 38400 hvc0" ${DOMUMNT}/etc/inittab
    add_log_item "/etc/inittab:
    1:2345:respawn:/sbin/getty 38400 hvc0"
  fi
  verbose "Installing apt sources..."
  create_file_from_template /etc/apt/sources.list apt-sources.list
  verbose "Setting root password..."
  root_passwd=`pwgen -scn 20`
  root_passwd_salt="\$6\$`pwgen -scn 16`"
  root_passwd_hash=$( pw-crypt - $root_passwd_salt <<< $root_passwd )
  runcmd chroot ${DOMUMNT} usermod -p $root_passwd_hash root
  verbose "Adding user '$USERLOGIN' ..."
  user_passwd=`pwgen -scn 20`
  user_passwd_salt="\$6\$`pwgen -scn 16`"
  user_passwd_hash=$( pw-crypt - $user_passwd_salt <<< $user_passwd )
  runcmd chroot ${DOMUMNT} useradd -s /bin/bash -c "$USERGECOS" -m -p $user_passwd_hash $USERLOGIN
  create_file /root/.screenrc 'startup_message off
defscrollback 5000
bind c screen 1
bind ^c screen 1
bind 0 select 10
screen 1
bind h
sorendition "=s cb"
hardstatus off
hardstatus alwayslastline
hardstatus string "%{.1099} %-w%{.cb}%40L>%n %t%{-}%+w %=%H %0c%<"'
create_file /etc/vim/vimrc.local '" load defaults for users, even if they have their own vimrc
try
  source $VIMRUNTIME/defaults.vim
  let g:skip_defaults_vim = 1
catch /^Vim\%((\a\+)\)\=:E484/
  " ignore E484: Can'"'"'t open file .../defaults.vim
endtry

" defaults.vim in vim8 sets mouse=a, this causes problems with copy&paste
if has('"'mouse'"')
  set mouse=
endif

" our terminal emulators usually have a dark background
set background=dark'
  write_log_items
}

install_debs()
{
  verbose "Mounting /proc and /sys..."
  runcmd chroot ${DOMUMNT} mount -t proc proc /proc
  runcmd chroot ${DOMUMNT} mount -t sysfs sysfs /sys
  verbose "Installing various essential packages..."
  case "$ARCH" in
  "i386")
      libc="libc6-xen"
      kernarch="686"
  ;;
  "amd64")
      libc=""
      kernarch="amd64"
  ;;
  *)
      libc=""
      kernarch=""
  esac
  if min_dist "wheezy" ; then
    [ $kernarch == "686" ] && kernarch="686-pae"
    kernpkgver="${kernarch}"
  else
    kernpkgver="2.6-xen-${kernarch}"
  fi
  KERNELPKG="linux-image-${kernpkgver}"

  if min_dist "bullseye" ; then
    PYTHONPKG="python3 python-is-python3"
  else
    PYTHONPKG="python"
  fi

  EXTRAPKGS=""
  if min_dist "trixie" ; then
    EXTRAPKGS="$EXTRAPKGS wtmpdb"
  fi

  runcmd chroot ${DOMUMNT} bash -c "LOGBUCH_BATCHMODE=yes DEBIAN_FRONTEND=noninteractive \
                            DEBIAN_PRIORITY=critical apt-install -y --purge ${libc} \
                            ${KERNELPKG} ssh rsync locales atop \
                            build-essential chrony bzip2 less man-db screen tmux \
                            curl nullmailer nano- vim-nox psmisc \
                            bash-completion ${PYTHONPKG} wget \
                            unattended-upgrades needrestart ${EXTRAPKGS}"
  KERNELVERSION=`chroot ${DOMUMNT} dpkg -s "$KERNELPKG" | grep ^Depends: | sed 's/.*linux-image-\([^, ]\+\).*/\1/'`

  if find "${DOMUMNT}/etc/udev/rules.d/" -name "*persistent-net*"|grep .; then
    verbose "Removing persistent network configuration..."
    case "$DISTRO" in
      "squeeze"|"wheezy"|"jessie")
        runcmd ln -sf /dev/null ${DOMUMNT}/etc/udev/rules.d/70-persistent-cd.rules
        runcmd ln -sf /dev/null ${DOMUMNT}/etc/udev/rules.d/70-persistent-net.rules
        add_log_item "ln -sf /dev/null /etc/udev/rules.d/70-persistent-cd.rules
    ln -sf /dev/null /etc/udev/rules.d/70-persistent-net.rules"
        ;;
      *)
        runcmd rm ${DOMUMNT}/etc/udev/rules.d/*persistent-net*
        add_log_item "rm /etc/udev/rules.d/*persistent-net*"
        ;;
    esac
  fi

  verbose "Configuring sshd..."
  if [ -d "${DOMUMNT}/etc/ssh/sshd_config.d" ]; then
    create_file /etc/ssh/sshd_config.d/50AcceptEnvEmail.conf "AcceptEnv EMAIL"
  else
    add_to_file /etc/ssh/sshd_config "AcceptEnv EMAIL"
  fi
  ssh_rsa_fingerprint="`ssh_fingerprint ${DOMUMNT}/etc/ssh/ssh_host_rsa_key`"
  ssh_ecdsa_fingerprint="`ssh_fingerprint ${DOMUMNT}/etc/ssh/ssh_host_ecdsa_key`"
  ssh_ed25519_fingerprint="`ssh_fingerprint ${DOMUMNT}/etc/ssh/ssh_host_ed25519_key`"
  verbose "Configuring ssh..."
  if [ -d "${DOMUMNT}/etc/ssh/ssh_config.d" ]; then
    create_file /etc/ssh/ssh_config.d/50HashKnownHostsNo.conf "HashKnownHosts no"
    create_file /etc/ssh/ssh_config.d/50SendEnvEmail.conf "SendEnv EMAIL"
  else
    runcmd sed -i 's/^\( *HashKnownHosts\) .*/\1 no/' ${DOMUMNT}/etc/ssh/ssh_config
    runcmd sed -i 's/^ *SendEnv .*/& EMAIL/' ${DOMUMNT}/etc/ssh/ssh_config
    add_log_item '/etc/ssh/ssh_config: change "HashKnownHosts" to "no" and add "EMAIL" to "SendEnv"'
  fi
  verbose "Installing root authorized_keys..."
  if [ ! -d "${DOMUMNT}/root/.ssh" ]; then
    runcmd mkdir ${DOMUMNT}/root/.ssh
  fi
  if [ -r "$XENMAKEGUEST_LIBDIR/authorized_keys.in" ] ;then
    verbose "found template:"
    create_file_from_template /root/.ssh/authorized_keys authorized_keys.in
  else
    verbose "no template, using host root authorized_keys:"
    runcmd cp /root/.ssh/authorized_keys ${DOMUMNT}/root/.ssh/authorized_keys
  fi
  verbose "Configuring locales..."
  runcmd chroot ${DOMUMNT} debconf-communicate locales <<<\
         "set locales/locales_to_be_generated de_DE.UTF-8 UTF-8, \
                                              en_IE.UTF-8 UTF-8, \
                                              en_US.UTF-8 UTF-8"
  runcmd chroot ${DOMUMNT} debconf-communicate locales <<<\
         "set locales/default_environment_locale C"
  runcmd chroot ${DOMUMNT} rm -f /etc/locale.gen /etc/default/locale
  runcmd chroot ${DOMUMNT} dpkg-reconfigure -fnoninteractive locales
  add_log_item "dpkg-reconfigure locales
    # de_DE.UTF-8
    # en_IE.UTF-8
    # en_US.UTF-8
    # Default: C.UTF-8"
  verbose "Configuring nullmailer..."
  create_file /etc/mailname "$MAILNAME"
  verbose "Configuring time zone..."
  case "$DISTRO" in
    "etch")
      runcmd chroot ${DOMUMNT} bash -c 'echo -e "y\n8\nBerlin\n" |tzconfig'
      add_log_item "tzconfig  # Europe/Berlin"
      ;;
    "lenny"|"squeeze"|"wheezy")
      create_file /etc/timezone "Europe/Berlin"
      runcmd chroot ${DOMUMNT} dpkg-reconfigure -fnoninteractive tzdata
      add_log_item "dpkg-reconfigure -fnoninteractive tzdata"
      ;;
    *)
      # This es equivalent to
      # "timedatectl set-timezone Europe/Berlin",
      # but it works in a chroot:
      runcmd chroot ${DOMUMNT} ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime
      add_log_item "ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime"
      ;;
  esac
  if grep -Fxq "CONFIG_PARAVIRT_CLOCK=y" "${DOMUMNT}/boot/config-${KERNELVERSION}" ; then
    # TODO: what should be done here?
    verbose "WARNING: xen independent wallclock not available"
  else
    verbose "Configuring xen independent wallclock..."
    add_to_file /etc/sysctl.conf "xen.independent_wallclock=1"
  fi
  verbose "Configuring chrony..."
  runcmd sed -i 's/^\(\(server\|pool\) .*\)/#\1/' ${DOMUMNT}/etc/chrony/chrony.conf
  runcmd sed -i '1iserver ntp.intevation.de\ninitstepslew 30 ntp.intevation.de\n' ${DOMUMNT}/etc/chrony/chrony.conf
  add_log_item "/etc/chrony/chrony.conf: # disable existing server/pool and add:
    server ntp.intevation.de
    initstepslew 30 ntp.intevation.de"
  logrotate_chrony="${DOMUMNT}/etc/logrotate.d/chrony"
  if [ -f "$logrotate_chrony" ] && grep -q '^\s\+EOF$' "$logrotate_chrony"; then
    runcmd sed -i 's/^\t*EOF$/EOF/' "$logrotate_chrony"
    add_log_item "/etc/logrotate.d/chrony:
    Removed tabs in front of EOF to fix https://bugs.debian.org/763542"
  fi
  verbose "Configuring unattended-upgrades..."
  create_file /etc/apt/apt.conf.d/20auto-upgrades 'APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";'
  create_file /etc/apt/apt.conf.d/51unattended-upgrades-allorigins 'Unattended-Upgrade::Origins-Pattern { "origin=*"; };'
  create_file /etc/needrestart/conf.d/10-local.conf "\$nrconf{restart} = 'a';"
  add_log_item 'mkdir /etc/systemd/system/apt-daily-upgrade.timer.d 2>/dev/null'
  mkdir "${DOMUMNT}/etc/systemd/system/apt-daily-upgrade.timer.d" 2>/dev/null
  create_file /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf '[Timer]
OnCalendar=
OnCalendar=*-*-* 5:00'
  create_file /etc/initramfs-tools/conf.d/resume '# workaround for https://bugs.debian.org/860403
# (Boot delay waiting for nonexistent suspend/resume device):
RESUME=none'
  # "update-initramfs -u" would be needed for the changed resume setting,
  # but the bug does not seem to occur on new VMs and initramfs will be
  # regenerated by some updates. So skip it for now to save time.
  write_log_items
  for basename in vmlinuz initrd.img; do
    runcmd cp "${DOMUMNT}/boot/${basename}-${KERNELVERSION}" "${KERNELDIR}/${basename}-${SHORTNAME}"
  done
}

check_exist_extension()
{
  if [ -z ${EXTENSION+x} ] ; then
  # https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash
  # ${EXTENSION+x} is a parameter expansion which evaluates to nothing if $EXTENSION is unset, and substitutes the string x otherwise.
    verbose "Skipping, no extension is given..."
  else
    verbose "Checking if the $EXTENSION extension exists..."
    if [ -z $EXTENSION ] ; then
      error "FATAL: Extension option is set but empty."
    else
      if [ -f "$XENMAKEGUEST_LIBDIR/$EXTENSION.xmgext" ] ; then
        verbose "Extension $EXTENSION exists, continuing..."
      else
        error "FATAL: Extension $EXTENSION does not exist."
      fi
    fi
  fi
}

load_extension()
{
  if [ -z ${EXTENSION+x} ] ; then
    verbose "Skipping loading of extension..."
  else
    verbose "Using $EXTENSION extension..."
    verbose "- $EXTENSION.xmgext BEGIN -------------------------------------------------"
    source $XENMAKEGUEST_LIBDIR/$EXTENSION.xmgext
    verbose "- $EXTENSION.xmgext END ---------------------------------------------------"
  fi
}

cleanup()
{
  if [ $TEST -eq 0 ] ; then
    verbose "Stopping services in chroot..."
    chroot ${DOMUMNT} /etc/init.d/ssh stop
    chroot ${DOMUMNT} /etc/init.d/chrony stop
    chroot ${DOMUMNT} /etc/init.d/nullmailer stop
    if [ "$DISTRO" == "jessie" ] ; then
      chroot ${DOMUMNT} /etc/init.d/irqbalance stop
    fi
    verbose "Un-mounting /proc and /sys..."
    chroot ${DOMUMNT} umount /proc
    chroot ${DOMUMNT} umount /sys
    verbose "Un-mounting new root..."
    umount ${DOMUMNT}
    rmdir ${DOMUMNT} || echo "Can't remove temporary mountpoint ${DOMUMNT}"
  else
    echo "CLEANING UP"
  fi
}

config_xen()
{
  verbose "Creating XEN config file for $NAME..."
  create_file_from_template /etc/xen/$SHORTNAME domU-conf.in host
}

ssh_fingerprint()
{
  FILE=$1; shift
  { ssh-keygen -lf "$FILE" "$@" 2>/dev/null || echo " unavailable"; } | tail -n 1 | cut -d' ' -f2
}

OPTS=`getopt \
      -l help,version,verbose,no-install,test,shortname:,domain:,userlogin:,usergecos:,rootsize:,swapsize:,fstype:,vg:,pv:,mem:,cpus:,bridge:,vif-script:,mac:,distro:,arch:,aptserver:,secserver:,gw:,netmask:,extension: \
      -o vS:r:s:D:u:U:f:V:m:c:b:M:d:a:A:Z:G:N:e: -n "$ME" -- "$@"`
[ $? -eq 0 ] || usage 23

eval set -- "$OPTS"

while true ; do
  case "$1" in
    --rootsize|-r)
      ROOTSIZE="$2"
      shift 2
      ;;
    --swapsize|-s)
      SWAPSIZE="$2"
      shift 2
      ;;
    --fstype|-f)
      FSTYPE="$2"
      shift 2
      ;;
    --vg|-V)
      VG="$2"
      shift 2
      ;;
    --pv)
      PV="$PV $2"
      shift 2
      ;;
    --shortname|-S)
      SHORTNAME="$2"
      shift 2
      ;;
    --domain|-D)
      DOMAIN="$2"
      shift 2
      ;;
    --userlogin|-u)
      USERLOGIN="$2"
      shift 2
      ;;
    --usergecos|-U)
      USERGECOS="$2"
      shift 2
      ;;
    --mem|-m)
      MEM="$2"
      shift 2
      ;;
    --cpus|-c)
      CPUS="$2"
      shift 2
      ;;
    --bridge|-b)
      VIFEXTRA+=", bridge=$2"
      shift 2
      ;;
    --vif-script)
      VIFEXTRA+=", script=$2"
      shift 2
      ;;
    --mac|-M)
      VIFEXTRA+=", mac=$2"
      shift 2
      ;;
    --distro|-d)
      DISTRO="$2"
      if min_dist "bullseye"; then
        DISTRO_SECURITY="$DISTRO-security"
      else
        DISTRO_SECURITY="$DISTRO/updates"
      fi
      shift 2
      ;;
    --arch|-a)
      ARCH="$2"
      shift 2
      ;;
    --extension|-e)
      EXTENSION="$2"
      shift 2
      ;;
    --aptserver|-A)
      APTSRV="$2"
      shift 2
      ;;
    --secserver|-Z)
      SECSRV="$2"
      shift 2
      ;;
    --gw|-G)
      GATEWAY="$2"
      shift 2
      ;;
    --netmask|-N)
      NETMASK="$2"
      shift 2
      ;;
    --no-install)
      INSTALL=0
      shift
      ;;
    --test)
      TEST=1
      shift
      ;;
    --verbose|-v)
      VERBOSE=1
      shift
      ;;
    --help|-h)
      usage 0
      ;;
    --version)
      version
      ;;
    --)
      shift
      break
      ;;
  esac
done

[ $# -eq 1 -o $# -eq 2 ] || usage 23

NAME=$1
[ "$SHORTNAME" ] || SHORTNAME=${NAME%%.*}
if [ "$NAME" = "$SHORTNAME" ] ; then
  MAILNAME="$SHORTNAME.hq.intevation.de"
else
  MAILNAME="$NAME"
fi
[ "$DOMAIN" ] || DOMAIN=${NAME#*.}
if [ "$NAME" = "$DOMAIN" ] ; then
  DOMAIN="$NAME.hq.intevation.de"
fi
IP=$2
# FIXME: we should be able to lookup IPv6, too.
#        Maybe we need a switch to elect whether v4 or v6 is
#        prefered.
[ "$IP" ] || IP=`host "$NAME" | sed -n 's/.* \([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+\)/\1/p'`
[ "$IP" ] || error "Failed to look up IP on DNS!"

if is_ipv6 ["$IP"] ; then
    # Link local router address is easily predictable with xen:
    [ "$GATEWAY" ] || GATEWAY='fe80::fcff:ffff:feff:ffff'
    [ "$NETMASK" ] || NETMASK=64
else
  # good old intevation *.254 is the gateway for the network...
  [ "$GATEWAY" ] || GATEWAY=${IP%.*}.254
  [ "$NETMASK" ] || NETMASK=255.255.255.0
fi

if min_dist "bookworm" ; then
  NETDEV="enX0"
else
  NETDEV="eth0"
fi

if min_dist "squeeze" ; then
  DISK="xvda"
else
  DISK="hda"
fi

verbose "Generating new domU \`$NAME':"
verbose "installing Debian $DISTRO base system for $ARCH"
verbose "$ROOTSIZE root partition on $FSTYPE filesystem"
verbose "$SWAPSIZE swap partition"
verbose "$MEM MiB of RAM"
verbose "$CPUS virtual CPUs"
verbose "will be created as logical volumes on $VG"
verbose "short name: $SHORTNAME"
verbose "domain: $DOMAIN"
verbose "IP $IP"
verbose "Gateway $GATEWAY"
verbose "mailname: $MAILNAME"
verbose "virtual hard disk: $DISK"

check_prerequisites
check_exist_extension
create_kerneldir
create_vols
if [ "$INSTALL" -eq 1 ]; then
  if [ $TEST -eq 0 ] ; then
    DOMUMNT=`mktemp -d /mnt/domumnt.XXXXXXXXXX`
    trap 'test -d "$DOMUMNT" && rmdir -- "$DOMUMNT"' EXIT
  fi

  bootstrap
  basic_setup
  install_debs
  load_extension
  cleanup
else
  verbose "Skipping installation ..."
  for basename in vmlinuz initrd.img; do
    # make it easier to use correct file names:
    runcmd touch "${KERNELDIR}/${basename}-${SHORTNAME}"
  done
fi
config_xen

cat <<EOF
New DomU $SHORTNAME is ready!

$NAME ($IP)
EOF

if [ "$INSTALL" -eq 1 ]; then
  grep -v "key fingerprint is unavailable" <<EOF
RSA key fingerprint is $ssh_rsa_fingerprint.
ECDSA key fingerprint is $ssh_ecdsa_fingerprint.
ED25519 key fingerprint is $ssh_ed25519_fingerprint.
  root: $root_passwd
  $USERLOGIN: $user_passwd
EOF
fi

[ $TEST -eq 0 ] || cat <<EOF

END OF TEST RUN
No permanent changes have been made.
On the next run other passwords and keys will be generated!
EOF
