sshcopyid option: exit if `ssh-add -L` fails to execute
[grml-debootstrap.git] / grml-debootstrap
index 1a44c7b..d5c3f70 100755 (executable)
@@ -2,7 +2,7 @@
 # Filename:      grml-debootstrap
 # Purpose:       wrapper around debootstrap for installing plain Debian via Grml
 # Authors:       grml-team (grml.org), (c) Michael Prokop <mika@grml.org>
-# Bug-Reports:   see http://grml.org/bugs/
+# Bug-Reports:   see https://grml.org/bugs/
 # License:       This file is licensed under the GPL v2+
 ################################################################################
 
@@ -72,7 +72,7 @@ MNTPOINT="/mnt/debootstrap.$$"
 [ -n "$POST_SCRIPTS" ] || POST_SCRIPTS='yes'
 [ -n "$PRE_SCRIPTS" ] || PRE_SCRIPTS='yes'
 [ -n "$RECONFIGURE" ] || RECONFIGURE='console-data'
-[ -n "$RELEASE" ] || RELEASE='stretch'
+[ -n "$RELEASE" ] || RELEASE='buster'
 [ -n "$RM_APTCACHE" ] || RM_APTCACHE='yes'
 [ -n "$SCRIPTS" ] || SCRIPTS='no' # deprecated, replaced by POST_SCRIPTS
 [ -n "$SECURE" ] || SECURE='yes'
@@ -103,7 +103,7 @@ Bootstrap options:
   -m, --mirror <URL>     Mirror which should be used for apt-get/aptitude.
   -i, --iso <mnt>        Mountpoint where a Debian ISO is mounted to, for use
                          instead of fetching packages from a mirror.
-  -r, --release <name>   Release of new Debian system (default: stretch).
+  -r, --release <name>   Release of new Debian system (default: buster).
   -t, --target <target>  Target partition (/dev/...) or directory where the
                          system should be installed to.
   -p, --mntpoint <mnt>   Mountpoint used for mounting the target system,
@@ -155,6 +155,7 @@ Configuration options:
       --hostname <name>    Hostname of Debian system.
       --nopassword         Do not prompt for the root password.
       --password <pwd>     Use specified password as password for user root.
+      --sshcopyid          Use locally available public keys to authorise root login on the target system.
       --bootappend <line>  Add specified appendline to kernel whilst booting.
       --chroot-scripts <d> Execute chroot scripts from specified directory.
       --pre-scripts <dir>  Execute scripts from specified directory (before chroot-scripts).
@@ -169,7 +170,7 @@ Other options:
   -V, --version            Show summary of options and exit.
 
 Usage examples can be found in the grml-debootstrap manpage.
-Send bugreports to the grml-team: bugs (at) grml.org || http://grml.org/bugs/
+Send bugreports to the grml-team: bugs (at) grml.org || https://grml.org/bugs/
 "
 }
 
@@ -266,7 +267,7 @@ cleanup() {
 
       # ugly, but make sure we really don't leave anything (/proc /proc and
       # /dev /dev are intended, trying to work around timing issues, see #657023)
-      for ARG in /sys /proc /proc /dev/pts /dev/pts /dev /dev ; do
+      for ARG in /run/udev /sys /proc /proc /dev/pts /dev/pts /dev /dev ; do
         [ -x "$MNTPOINT"/bin/umount ] && chroot "$MNTPOINT" umount $ARG >/dev/null 2>&1
         umount "$MNTPOINT"/$ARG >/dev/null 2>&1
       done
@@ -299,7 +300,13 @@ cleanup() {
 
   if [ -n "${ORIG_TARGET}" ] ; then
     einfo "Removing loopback mount of file ${ORIG_TARGET}."
-    kpartx -d "${ORIG_TARGET}" ; eend $?
+    kpartx -d "${ORIG_TARGET}"
+    # Workaround for a bug in kpartx which doesn't clean up properly,
+    # see Debian Bug #891077 and Github-PR grml/grml-debootstrap#112
+    if dmsetup ls | grep -q "^${LOOP_PART} "; then
+      kpartx -d "/dev/${LOOP_DISK}" >/dev/null
+    fi
+    eend $?
   fi
 }
 
@@ -329,7 +336,7 @@ stage() {
 # }}}
 
 # make sure we have what we need {{{
-check4progs debootstrap || bailout 1
+check4progs "${DEBOOTSTRAP}" || bailout 1
 # }}}
 
 # source main configuration file {{{
@@ -340,7 +347,7 @@ fi
 # }}}
 
 # cmdline handling {{{
-CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,defaultinterfaces,interactive,nodebootstrap,nointerfaces,nokernel,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,post-scripts:,pre-scripts:,debconf:,vm,vmfile,vmsize:,keep_src_list,hostname:,password:,nopassword,grmlrepos,backportrepos,bootappend:,grub:,efi:,arch:,insecure,verbose,help,version,force,debug,contrib,non-free,remove-configs
+CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,defaultinterfaces,interactive,nodebootstrap,nointerfaces,nokernel,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,post-scripts:,pre-scripts:,debconf:,vm,vmfile,vmsize:,keep_src_list,hostname:,password:,nopassword,grmlrepos,backportrepos,bootappend:,grub:,efi:,arch:,insecure,verbose,help,version,force,debug,contrib,non-free,remove-configs,sshcopyid
 
 _opt_temp=$(getopt --name grml-debootstrap -o +m:i:r:t:p:c:d:vhV --long \
   $CMDLINE_OPTS -- "$@")
@@ -454,6 +461,9 @@ while :; do
   --nopassword)        # Skip password dialog
     _opt_nopassword=T
     ;;
+  --sshcopyid)         # Use locally available public keys to authorise root login on the target system
+    _opt_sshcopyid=T
+    ;;
   --grmlrepos)         # Enable Grml repository
     _opt_grmlrepos=T
     ;;
@@ -554,6 +564,7 @@ done
 [ "$_opt_defaultinterfaces" ]   && USE_DEFAULT_INTERFACES="true"
 [ "$_opt_nointerfaces" ]        && NOINTERFACES="true"
 [ "$_opt_nokernel" ]            && NOKERNEL="true"
+[ "$_opt_sshcopyid" ]           && SSHCOPYID="true"
 [ "$_opt_bootappend" ]          && BOOT_APPEND=$_opt_bootappend
 [ "$_opt_grub" ]                && GRUB=$_opt_grub
 [ "$_opt_efi" ]                 && EFI=$_opt_efi
@@ -582,6 +593,12 @@ if [ "$_opt_grub" ] && [ "$_opt_vmfile" ] ; then
   bailout 1
 fi
 
+if [ -n "$ISO" ] && [[ "$DEBOOTSTRAP" =~ mmdebstrap$ ]] ; then
+  eerror "The ISO option is incompatible with usage of mmdebstrap for bootstrapping."
+  eerror "Either drop the --iso ... option or use plain debootstrap instead."
+  eend 1
+  bailout 1
+fi
 
 if [ "$DEBUG" = "true" ] ; then
   set -x
@@ -595,7 +612,7 @@ fi
 
 [ "$_opt_version" ] && {
   einfo "$PN - version $VERSION"
-  einfo "Send bug reports to bugs@grml.org or http://grml.org/bugs/"
+  einfo "Report bugs via https://github.com/grml/grml-debootstrap/ or https://grml.org/bugs/"
   eend 0
   exit 0
 }
@@ -753,15 +770,17 @@ prompt_for_bootmanager()
 # ask for Debian release {{{
 prompt_for_release()
 {
-  [ -n "$RELEASE" ] && DEFAULT_RELEASE="$RELEASE" || DEFAULT_RELEASE='stretch'
+  [ -n "$RELEASE" ] && DEFAULT_RELEASE="$RELEASE" || DEFAULT_RELEASE='buster'
   RELEASE="$(dialog --stdout --title "${PN}" --default-item $DEFAULT_RELEASE --menu \
             "Please enter the Debian release you would like to use for installation:" \
-            0 50 5 \
+            0 50 8 \
             lenny    Debian/5.0 \
             squeeze  Debian/6.0 \
             wheezy   Debian/7.0 \
             jessie   Debian/8.0 \
             stretch  Debian/9.0 \
+            buster   Debian/10.0 \
+            bullseye Debian/11.0 \
             sid      Debian/unstable)"
   [ $? -eq 0 ] || bailout
 }
@@ -1130,7 +1149,7 @@ fi
 # Support for generic release codenames is unavailable. {{{
 if [ "$RELEASE" = "stable" ] || [ "$RELEASE" = "testing" ] ; then
    eerror "Generic release codenames (stable, testing) are unsupported. \
-Please use specific codenames such as lenny, squeeze, wheezy, jessie or stretch." ; eend 1
+Please use specific codenames such as stretch or buster." ; eend 1
    bailout 1
 fi
 # }}}
@@ -1181,6 +1200,12 @@ set_target_directory(){
 if [ -b "$TARGET" ] || [ -n "$VIRTUAL" ] ; then
     PARTITION=1
 else
+    # $TARGET was not detected as block device, but we do not want to create target directory in /dev/
+    if [[ $TARGET == "/dev/"* ]]; then
+      eerror "Error: Will not create target directory $TARGET in /dev."
+      eerror "  Please check the partition(s) of the blockdevice."; eend 1
+      bailout 1
+    fi
     set_target_directory
 fi
 # }}}
@@ -1388,6 +1413,21 @@ prepare_vm() {
      bailout 1
   fi
 
+  # make sure loop module is present and a usable loop device exists
+  modprobe -q loop
+  if ! losetup -f >/dev/null 2>&1; then
+    eerror "Error finding usable loop device" ; eend 1
+    bailout 1
+  fi
+
+  # if dm-mod isn't available then kpartx will fail with
+  # "Is device-mapper driver missing from kernel? [...]"
+  modprobe -q dm-mod
+  if ! grep -q 'device-mapper' /proc/misc >/dev/null 2>&1 ; then
+    eerror "Device-mapper support missing in kernel." ; eend 1
+    bailout 1
+  fi
+
   ORIG_TARGET="$TARGET" # store for later reuse
 
   if [ -n "$VMFILE" ]; then
@@ -1405,25 +1445,6 @@ prepare_vm() {
   parted -s "${TARGET}" 'mkpart primary ext4 2M -1'
   parted -s "${TARGET}" 'set 1 boot on'
 
-  # if dm-mod isn't available then kpartx will fail with
-  # "Is device-mapper driver missing from kernel? [...]"
-  if ! kpartx -av "$TARGET" >/dev/null 2>&1 || ! grep -q 'device-mapper' /proc/misc >/dev/null 2>&1 ; then
-    einfo "Device-mapper not ready yet, trying to load dm-mod module."
-    modprobe dm-mod ; eend $?
-  fi
-
-  # make sure loop module is present
-  if ! losetup -f >/dev/null 2>&1; then
-    einfo "Can not find a usable loop device, retrying after loading loop module."
-    modprobe loop
-    if losetup -f >/dev/null 2>&1; then
-      einfo "Found a usable loop device now, continuing."
-    else
-      eerror "Error finding usable loop device" ; eend 1
-      bailout 1
-    fi
-  fi
-
   DEVINFO=$(kpartx -asv "$TARGET") # e.g. 'add map loop0p1 (254:5): 0 20477 linear 7:0 3'
   if [ -z "${DEVINFO}" ] ; then
     eerror "Error setting up loopback device." ; eend 1
@@ -1433,6 +1454,7 @@ prepare_vm() {
   # hopefully this always works as expected
   LOOP_PART="${DEVINFO##add map }" # 'loop0p1 (254:5): 0 20477 linear 7:0 3'
   LOOP_PART="${LOOP_PART// */}"    # 'loop0p1'
+  LOOP_DISK="${LOOP_PART%p*}"      # 'loop0'
   export TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop1p1'
 
   if [ -z "$TARGET" ] ; then
@@ -1447,8 +1469,9 @@ grub_install() {
   if [ -z "${VIRTUAL}" ] ; then
      return 0
   fi
-  if [ ! "$GRUB_INSTALL" = "yes" ]; then
-     return 0
+  if [ "${GRUB_INSTALL}" != "yes" ] ; then
+    einfo "Not installing GRUB as requested via \$GRUB_INSTALL=$GRUB_INSTALL"
+    return 0
   fi
 
   if ! mount "${TARGET}" "${MNTPOINT}" ; then
@@ -1530,6 +1553,11 @@ umount_target() {
 
   umount "${MNTPOINT}"
   kpartx -d "${ORIG_TARGET}" >/dev/null
+  # Workaround for a bug in kpartx which doesn't clean up properly,
+  # see Debian Bug #891077 and Github-PR grml/grml-debootstrap#112
+  if dmsetup ls | grep -q "^${LOOP_PART} "; then
+    kpartx -d "/dev/${LOOP_DISK}" >/dev/null
+  fi
 }
 # }}}
 
@@ -1668,7 +1696,7 @@ preparechroot() {
 
   cp $VERBOSE -a -L "${CONFFILES}"/extrapackages/ "${MNTPOINT}"/etc/debootstrap/
 
-  # make sure we can access network [relevant for cdebootstrap]
+  # make sure we can access network [relevant for cdebootstrap/mmdebstrap]
   [ -f "${MNTPOINT}"/etc/resolv.conf ] || cp $VERBOSE /etc/resolv.conf "${MNTPOINT}"/etc/resolv.conf
 
   # setup default locales
@@ -1700,6 +1728,16 @@ allow-hotplug eth0
 iface eth0 inet dhcp
 "
 
+  # add dhcp setting for Predictable Network Interface Names
+  if [ -x /bin/udevadm ]; then
+    for interface in $(udevadm info -e | sed -n -e 's/E: ID_NET_NAME_PATH=\([^$*]\)/\1/p'); do
+      DEFAULT_INTERFACES="${DEFAULT_INTERFACES}
+allow-hotplug ${interface}
+iface ${interface} inet dhcp
+"
+    done
+  fi
+
   if [ -n "$NOINTERFACES" ] ; then
     einfo "Not installing /etc/network/interfaces as requested via --nointerfaces option" ; eend 0
   elif [ -n "$USE_DEFAULT_INTERFACES" ] ; then
@@ -1725,6 +1763,32 @@ iface eth0 inet dhcp
      cp /etc/network/interfaces.examples "$MNTPOINT/etc/network/interfaces.examples"
   fi
 
+  if [ -n "${SSHCOPYID}" ] ; then
+    if ssh-add -L >/dev/null 2>&1 ; then
+      einfo "Use locally available public keys to authorise root login on the target system as requested via --sshcopyid option."
+      mkdir -p "${MNTPOINT}"/root/.ssh
+      chmod 0700 "${MNTPOINT}"/root/.ssh
+      if ssh-add -L >> "${MNTPOINT}"/root/.ssh/authorized_keys ; then
+        eend 0
+      else
+        eerror "Error: executing 'ssh-add -L' failed."
+        eend 1
+        bailout 1
+      fi
+    else
+      eerror "Could not open a connection to your authentication agent or the agent has no identites."
+      eend 1
+      bailout 1
+    fi
+  fi
+
+  if [ -d /run/udev ] ; then
+    einfo "Setting up bind-mount /run/udev"
+    mkdir -p "${MNTPOINT}"/run/udev
+    mount --bind /run/udev "${MNTPOINT}"/run/udev
+    eend $?
+  fi
+
   eend 0
 }
 # }}}
@@ -1849,6 +1913,12 @@ umount_chroot() {
   fi
 
   if grep -q "$MNTPOINT" /proc/mounts ; then
+    if mountpoint "${MNTPOINT}"/run/udev &>/dev/null ; then
+      einfo "Unmounting bind-mount /run/udev"
+      umount "${MNTPOINT}"/run/udev
+      eend $?
+    fi
+
      if [ -n "$PARTITION" ] ; then
         einfo "Unmount $MNTPOINT"
         umount "$MNTPOINT"