diff options
-rw-r--r-- | preseed-cfg | 115 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/changelog | 5 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/compat | 1 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/control | 11 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/copyright | 15 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/install | 1 | ||||
-rwxr-xr-x | tdf-postinst-udeb/debian/rules | 4 | ||||
-rw-r--r-- | tdf-postinst-udeb/debian/templates | 9 | ||||
-rwxr-xr-x | tdf-postinst-udeb/finish-install.d/07tdf-postinst | 115 | ||||
-rwxr-xr-x | tdfvm-install | 279 |
10 files changed, 555 insertions, 0 deletions
diff --git a/preseed-cfg b/preseed-cfg new file mode 100644 index 0000000..0076e63 --- /dev/null +++ b/preseed-cfg @@ -0,0 +1,115 @@ +cat <<- EOF + # Network + d-i netcfg/choose_interface select auto + d-i netcfg/get_hostname string ${VM_NAME-debian} + d-i netcfg/get_domain string documentfoundation.org + + # Localization + d-i debian-installer/locale string en_US.UTF-8 + d-i keyboard-configuration/xkb-keymap string + + # Clock and time zone setup + d-i clock-setup/utc boolean true + d-i clock-setup/ntp boolean true + d-i clock-setup/ntp-server string 0.de.pool.ntp.org + d-i time/zone string Europe/Berlin + + # Partitioning, see + # https://anonscm.debian.org/cgit/d-i/debian-installer.git/tree/doc/devel/partman-auto-recipe.txt + d-i partman-auto/method string regular + d-i partman-partitioning/default_label string gpt + d-i partman-partitioning/confirm_write_new_label boolean true + d-i partman-auto/expert_recipe string root :: \\ + 1 1 1 free \\ + \$iflabel{ gpt } \$primary{ } \\ + method{ biosgrub } \$bios_boot{ } \\ + . \\ + 500 10000 -1 ext4 \\ + \$primary{ } \\ + method{ format } format{ } \\ + use_filesystem{ } filesystem{ ext4 } \\ + mountpoint{ / } \\ + . \\ + 4096 1 4096 linux-swap \\ + \$primary{ } \\ + method{ swap } format{ } \\ + . + d-i partman/choose_partition select finish + d-i partman/confirm boolean true + d-i partman/confirm_nooverwrite boolean true + + # Debian archive mirror + d-i mirror/country string manual + d-i mirror/http/hostname string ftp.de.debian.org + d-i mirror/http/directory string /debian + d-i mirror/http/proxy string + popularity-contest popularity-contest/participate boolean false + + # Use the latest kernel + bootstrap-base base-installer/kernel/image select + + # The subset of 'standard' tasksel that's interesting, see output of + # 'tasksel --task-packages standard' + tasksel tasksel/first string + d-i pkgsel/include string dbus git etckeeper \\ + haveged salt-minion openssh-server \\ + bash bash-completion \\ + haveged qemu-guest-agent \\ + bind9-host curl \\ + screen tmux \\ + emacs-nox vim-nox + d-i pkgsel/upgrade select safe-upgrade + + d-i preseed/early_command string anna-install tdf-postinst-udeb + d-i preseed/late_command string in-target update-alternatives --set editor /usr/bin/vim.nox + + # Install GRUB to the master boot record + grub-installer grub-installer/bootdev string default + grub-installer grub-installer/only_debian boolean true + + # Avoid that last message about the install being complete + finish-install finish-install/reboot_in_progress note + + # Poweroff the machine when finished (dont' reboot into the + # installed system) + d-i debian-installer/exit/poweroff boolean true + + # Hostname or ipv4 of the Salt master + d-i tdf-postinst/salt_master string floyd.documentfoundation.org + + # Fingerprint of the master public key to validate the identity of + # the Salt master before the initial key exchange + d-i tdf-postinst/salt_master_fingerprint string 33:49:0d:84:98:5a:9d:93:89:a9:d1:c8:47:36:e8:83 +EOF + +if [ ${VM_NAME+x} ] && [ "${VM_NAME#vm}" != "$VM_NAME" ] && + [ "${VM_NAME#vm}" -gt 129 -a "${VM_NAME#vm}" -lt 256 ]; then + cat <<- EOF + d-i netcfg/disable_autoconfig boolean true + d-i netcfg/get_ipaddress string 89.238.68.${VM_NAME#vm} + d-i netcfg/get_netmask string 255.255.255.128 + d-i netcfg/get_gateway string 89.238.68.129 + d-i netcfg/get_nameservers string 217.11.48.200 217.11.49.200 + d-i netcfg/confirm_static boolean true + EOF +fi + +# Account setup +echo 'user-setup-udeb passwd/shadow boolean true' +if [ "$USER_NAME" = root ]; then + cat <<-EOF + user-setup-udeb passwd/root-login boolean true + user-setup-udeb passwd/root-password-crypted password $PASSWORD_CRYPTED + user-setup-udeb passwd/make-user boolean false + EOF +else + cat <<-EOF + user-setup-udeb passwd/root-login boolean false + user-setup-udeb passwd/make-user boolean true + user-setup-udeb passwd/user-fullname string + user-setup-udeb passwd/username string $USER_NAME + user-setup-udeb passwd/user-password-crypted password $PASSWORD_CRYPTED + EOF +fi + +# vim: set filetype=sh : diff --git a/tdf-postinst-udeb/debian/changelog b/tdf-postinst-udeb/debian/changelog new file mode 100644 index 0000000..41eb167 --- /dev/null +++ b/tdf-postinst-udeb/debian/changelog @@ -0,0 +1,5 @@ +tdf-postinst-udeb (0.1) unstable; urgency=low + + * Initial release. + + -- Guilhem Moulin <guilhem@libreoffice.org> Tue, 18 Oct 2016 19:23:23 +0200 diff --git a/tdf-postinst-udeb/debian/compat b/tdf-postinst-udeb/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/tdf-postinst-udeb/debian/compat @@ -0,0 +1 @@ +9 diff --git a/tdf-postinst-udeb/debian/control b/tdf-postinst-udeb/debian/control new file mode 100644 index 0000000..2b1ff24 --- /dev/null +++ b/tdf-postinst-udeb/debian/control @@ -0,0 +1,11 @@ +Source: tdf-postinst-udeb +Section: debian-installer +Priority: optional +Maintainer: Guilhem Moulin <guilhem@libreoffice.org> +Build-Depends: debhelper (>= 9) + +Package: tdf-postinst-udeb +XC-Package-Type: udeb +Architecture: all +Depends: ${misc:Depends} +Description: Postinstall hook for TDF VMs setup diff --git a/tdf-postinst-udeb/debian/copyright b/tdf-postinst-udeb/debian/copyright new file mode 100644 index 0000000..409a651 --- /dev/null +++ b/tdf-postinst-udeb/debian/copyright @@ -0,0 +1,15 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Source: native package + +Files: * +Copyright: © 2016 The Document Foundation <hostmaster@documentfoundation.org> +License: GPL-3+ + +License: GPL-3+ + This package is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 3 of the License, or (at your + option) any later version. + . + On Debian systems, the complete text of the GNU General Public License + version 3 can be found in file "/usr/share/common-licenses/GPL-3". diff --git a/tdf-postinst-udeb/debian/install b/tdf-postinst-udeb/debian/install new file mode 100644 index 0000000..d477454 --- /dev/null +++ b/tdf-postinst-udeb/debian/install @@ -0,0 +1 @@ +finish-install.d/* /usr/lib/finish-install.d diff --git a/tdf-postinst-udeb/debian/rules b/tdf-postinst-udeb/debian/rules new file mode 100755 index 0000000..2d33f6a --- /dev/null +++ b/tdf-postinst-udeb/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh $@ diff --git a/tdf-postinst-udeb/debian/templates b/tdf-postinst-udeb/debian/templates new file mode 100644 index 0000000..e56a68a --- /dev/null +++ b/tdf-postinst-udeb/debian/templates @@ -0,0 +1,9 @@ +Template: tdf-postinst/salt_master +Type: text +Description: Hostname or ipv4 of the Salt master + +Template: tdf-postinst/salt_master_fingerprint +Type: text +Description: Salt master fingerprint + Fingerprint of the master public key to validate the identity of + the Salt master before the initial key exchange diff --git a/tdf-postinst-udeb/finish-install.d/07tdf-postinst b/tdf-postinst-udeb/finish-install.d/07tdf-postinst new file mode 100755 index 0000000..3b92d76 --- /dev/null +++ b/tdf-postinst-udeb/finish-install.d/07tdf-postinst @@ -0,0 +1,115 @@ +#!/bin/sh +set -e + +. /usr/share/debconf/confmodule || true + +in-target modprobe 9pnet_virtio || true +in-target modprobe 9p || true + +virtfs="$(mktemp -d)" +mount -t 9p -o trans=virtio,version=9p2000.L virtfs "$virtfs" || true +trap 'umount "$virtfs"; rmdir "$virtfs"' EXIT TERM INT + + +####################################################################### +# Configuration SSHd + +if [ -d /target/etc/ssh ]; then + in-target find /etc/ssh -maxdepth 1 -type f -a \ + \( -name "ssh_host_*_key" -o -name "ssh_host_*_key.pub" \) \ + -delete + in-target ssh-keygen -b 4096 -t rsa -N '' -C /etc/ssh/ssh_host_rsa_key -f /etc/ssh/ssh_host_rsa_key + in-target ssh-keygen -t ed25519 -N '' -C /etc/ssh/ssh_host_ed25519_key -f /etc/ssh/ssh_host_ed25519_key + for pk in $(find /target/etc/ssh -maxdepth 1 -type f -name "ssh_host_*_key.pub"); do + cp -f "$pk" "$virtfs" + done + + cat >/target/etc/ssh/sshd_config <<- EOF + # What ports, IPs and protocols we listen for + Port 22 + # Use these options to restrict which interfaces/protocols sshd will + # bind to + #ListenAddress :: + #ListenAddress 0.0.0.0 + Protocol 2 + # HostKeys for protocol version 2 + HostKey /etc/ssh/ssh_host_rsa_key + HostKey /etc/ssh/ssh_host_ed25519_key + #Privilege Separation is turned on for security + UsePrivilegeSeparation yes + + # Logging + SyslogFacility AUTH + LogLevel INFO + + # Authentication: + LoginGraceTime 120 + PermitRootLogin without-password + StrictModes yes + + PubkeyAuthentication yes + #AuthorizedKeysFile %h/.ssh/authorized_keys + + # Change to yes to enable challenge-response passwords (beware issues + # with + # some PAM modules and threads) + ChallengeResponseAuthentication no + + # Change to no to disable tunnelled clear text passwords + PasswordAuthentication no + + X11Forwarding no + PrintMotd no + PrintLastLog yes + TCPKeepAlive yes + + # Allow client to pass locale environment variables + AcceptEnv LANG LC_* + + Subsystem sftp /usr/lib/openssh/sftp-server + EOF + + if [ -f "/cdrom/authorized_keys" ]; then + authorized_keys="$(mktemp -p "/target/tmp")" + cat /cdrom/authorized_keys >"$authorized_keys" + authorized_keys="${authorized_keys#/target}" + if db_get passwd/username && [ "$RET" ]; then + username="$RET" + else + username="root" + fi + in-target sh -c " + install -m0700 -o $username -g $username --directory ~$username/.ssh + install -m0600 -o $username -g $username $authorized_keys ~$username/.ssh/authorized_keys + " + fi +fi + + +####################################################################### +# Configure salt-minion + +if [ -d /target/etc/salt ]; then + in-target sh -c ' + pkidir="/etc/salt/pki/minion" + mkdir -p -m0700 "$pkidir" + + install -m0400 /dev/null "$pkidir/minion.pem" + openssl genrsa -rand /dev/urandom -f4 4096 >"$pkidir/minion.pem" + + install -m0644 /dev/null "$pkidir/minion.pub" + openssl pkey -pubout <"$pkidir/minion.pem" >"$pkidir/minion.pub" + + mkdir -p /etc/salt/minion.d + install -m0644 /dev/null /etc/salt/minion.d/999user.conf + ' + if db_get tdf-postinst/salt_master && [ "$RET" ]; then + echo "master: $RET" >>/target/etc/salt/minion.d/999user.conf + fi + if db_get tdf-postinst/salt_master_fingerprint && [ "$RET" ]; then + echo "master_finger: '$RET'" >>/target/etc/salt/minion.d/999user.conf + fi + echo "id: $(hostname).documentfoundation.org" >>/target/etc/salt/minion.d/999user.conf + + cp /target/etc/salt/pki/minion/minion.pub "$virtfs" +fi diff --git a/tdfvm-install b/tdfvm-install new file mode 100755 index 0000000..ed02fba --- /dev/null +++ b/tdfvm-install @@ -0,0 +1,279 @@ +#!/bin/bash + +set -ue + +error() { + echo "Error:" "$@" >&2 + exit 1 +} +usage() { + [ ${1+x} ] && echo "Unknown option '$1'" >&2 + echo "Usage: $0 [OPTIONS] NAME" >&2 + echo " $0 --help" >&2 + exit 1 +} + + +ARCH=$(dpkg-architecture -qDEB_TARGET_ARCH) +unset ISO +USER_NAME=root +PROMPT_PASSWORD=n +unset AUTHORIZED_KEYS +FORCE=n +unset OUTPUT + +CPU="host" +unset VCPUS +GRAPHICS="none" +NETWORK="none" +unset MEMORY +unset DISK +TRANSIENT= + +HELP_MESSAGE="$(cat <<-EOF + Install a new VM in an unattended fashion + Usage $0 [OPTIONS] NAME + --arch=ARCH target architecture (default: "$ARCH") + --iso=FILENAME path to the installation ISO image to preseed (required) + -u,--username=USERNAME user account to create (default: root) + -p,--password prompt for USERNAME's password (password login are disabled by default) + --authorized-keys=FILENAME pass to USERNAME's authorized_keys(5) file + -f,--force imediately the domain NAME if it exists, and remove existing configuration + -o,--output=DIRNAME directory where to export the XML definition and key material from the guest + + virt-install(1) options + --cpu=MODEL,... CPU model and CPU features exposed to the guest (default: "$CPU") + --vcpus=STRING number of virtual cpus to configure for the guest + --graphics=STRING graphical display configuration (default: "$GRAPHICS") + --network=STRING network configuration (default: "$NETWORK") + --memory=STRING memory to allocate for the guest, in MiB (required) + --disk=STRING media to use as storage for the guest (required) + --transient create a transient libvirt VM +EOF +)" + +while [ $# -gt 0 ]; do + case "$1" in + --arch) ARCH="$2"; shift;; + --arch=*) ARCH="${1#--arch=}";; + --iso) ISO="$2"; shift;; + --iso=*) ISO="${1#--iso=}";; + -u|--username) USER_NAME="$2"; shift;; + -u*) USER_NAME="${1#-u}";; + --username=*) USER_NAME="${1#--username=}";; + -p|--password) PROMPT_PASSWORD=y;; + --authorized-keys) AUTHORIZED_KEYS="$2"; shift;; + --authorized-keys=*) AUTHORIZED_KEYS="${1#--authorized-keys=}";; + -f|--force) FORCE=y;; + -o|--output) OUTPUT="$2"; shift;; + -o*) OUTPUT="${1#-o}";; + --output=*) OUTPUT="${1#--output=}";; + + --vcpus) VCPUS="$2"; shift;; + --vcpus=*) VCPUS="${1#--vcpus=}";; + --graphics) GRAPHICS="$2"; shift;; + --graphics=*) GRAPHICS="${1#--graphics=}";; + --network) NETWORK="$2"; shift;; + --network=*) NETWORK="${1#--network=}";; + --memory) MEMORY="$2"; shift;; + --memory=*) MEMORY="${1#--memory=}";; + --disk) DISK="$2"; shift;; + --disk=*) DISK="${1#--disk=}";; + --transient) TRANSIENT='--transient';; + + --help|-\?) printf '%s\n' "$HELP_MESSAGE"; exit;; + -*) usage "$1";; + *) break;; + esac + shift +done +[ $# -eq 1 ] || usage +VM_NAME="$1" + + +for x in ISO MEMORY DISK; do + if ! eval [ "\${$x+x}" ]; then + echo "Error missing non-optional argument --$(echo "$x" | tr 'A-Z' 'a-z')" >&2 + exit 1 + fi +done + +for prog in fuseiso fusermount rsync md5sum xorriso find xmlstarlet; do + which "$prog" >/dev/null || error "Missing $prog" +done + +ISOHDPFX=/usr/lib/ISOLINUX/isohdpfx.bin +[ -f "$ISOHDPFX" ] || error "Missing $ISOHDPFX. Is the 'isolinux' package installed?" + +[ $(id -u) -eq 0 ] || error "This script needs to run as root" + +if [ "$PROMPT_PASSWORD" = n ]; then + PASSWORD_CRYPTED='*' +else + PASSWORD_CRYPTED="$( + [ "$USER_NAME" = root ] && prompt="Enter root password" || prompt="Enter password for $USER_NAME" + read -rs -p "$prompt (leave blank to auto-generate): " pw + + if [ "$pw" ]; then + printf '\n' >/dev/tty + read -rs -p "Re-enter password to confirm: " pw2 + printf '\n' >/dev/tty + if [ "$pw" != "$pw2" ]; then + echo "Password do not match, aborting" >&2 + exit 1 + fi + else + pw="$(pwgen -syn 32 1 | sed 's/\s$//')" + printf '%s\n' "$pw" >/dev/tty + fi + printf '%s' "$pw" | mkpasswd --stdin --method=SHA-512 + )" +fi + + +####################################################################### +# Presseed the ISO image +# + +VMTMPDIR="$(mktemp --tmpdir=/var/lib/libvirt/images --directory "$VM_NAME.XXXXXX" )" +trap 'rm -rf "$VMTMPDIR"' EXIT TERM INT +chmod a+x "$VMTMPDIR" +install -o libvirt-qemu -m 0400 /dev/null "$VMTMPDIR/install.iso" +install -o libvirt-qemu -m 0700 --directory "$VMTMPDIR/virtfs" + +( + mountdir="$(mktemp --tmpdir --directory)" + fuseiso "$ISO" "$mountdir" + + isoeditdir="$(mktemp --tmpdir --directory)" + trap 'rm -rf "$isoeditdir"' EXIT TERM INT + + rsync -aH --exclude=TRANS.TBL --chmod=u+w "$mountdir/" "$isoeditdir/" + + fusermount -u "$mountdir" + rmdir "$mountdir" + + . ./preseed-cfg >"$isoeditdir/preseed.cfg" + [ ! "${AUTHORIZED_KEYS+x}" ] || cat "$AUTHORIZED_KEYS" >"$isoeditdir/authorized_keys" + + ( + builddir="$(mktemp --tmpdir --directory)" + trap 'rm -rf "$builddir"' EXIT TERM INT + + cp -a ./tdf-postinst-udeb "$builddir" + find "$builddir" -mindepth 2 -maxdepth 2 -name debian -type d \ + -execdir dpkg-buildpackage -us -uc -b -a "$ARCH" \; + mkdir "$isoeditdir/pool-extra" + find "$builddir" -maxdepth 1 -type f -name '*.udeb' -print0 | \ + xargs -r0 cp -vlt "$isoeditdir/pool-extra" + + cd "$isoeditdir" + find ./dists -type f \ + | grep -P "^\./dists/[^\/]+/main/debian-installer/binary-\Q$ARCH\E/Packages(\.gz)?$" \ + | while read packages; do + [ "${packages%.gz}" = "$packages" ] || gunzip -f "$packages" + dpkg-scanpackages -tudeb -a"$ARCH" ./pool-extra >>"${packages%.gz}" + [ "${packages%.gz}" = "$packages" ] || gzip -f "${packages%.gz}" + done + find ./pool-extra -maxdepth 1 -type f -name '*.udeb' -print0 | \ + xargs -r0 md5sum >>./md5sum.txt + ) + + cd "$isoeditdir" + md5sums=$(mktemp --tmpdir="$isoeditdir" md5sum.txt-XXXXXX) + while read sum file; do + if [ "${file%/main/debian-installer/binary-$ARCH/Packages}" != "$file" ] || + [ "${file%/main/debian-installer/binary-$ARCH/Packages.gz}" != "$file" ]; then + md5sum "$file" + else + echo "$sum $file" + fi + done <./md5sum.txt >"$md5sums" + mv -f "$md5sums" ./md5sum.txt + md5sum ./preseed.cfg >>./md5sum.txt + + kernel="$(sed -rn '/^\s+kernel\s+/ {s///p; q}' ./isolinux/txt.cfg)" + initrd="$(sed -rn '/^\s+append\s(.*\s)?initrd=(\S+)(\s.*)?$/ {s//\2/p;q}' ./isolinux/txt.cfg)" + cat >./isolinux/isolinux.cfg <<-EOF + default install + label install + kernel $kernel + append initrd=$initrd preseed/file=/cdrom/preseed.cfg auto=true --- fb=false + EOF + + xorriso -as mkisofs -r \ + -checksum_algorithm_iso all \ + -isohybrid-mbr "$ISOHDPFX" \ + -b isolinux/isolinux.bin -c isolinux/boot.cat \ + -partition_offset 16 \ + -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot \ + --efi-boot boot/grub/efi.img -append_partition 2 0x01 ./boot/grub/efi.img \ + -o "$VMTMPDIR/install.iso" ./ +) + + +####################################################################### +# +grep -q '^kvm\s' /proc/modules || echo 'WARN: KVM not available!' >&2 +[ "$NETWORK" = none ] || NETWORK="$NETWORK,model=virtio" +[ ! ${OUTPUT+x} ] || mkdir -p "$OUTPUT" + +if [ "$FORCE" = y ]; then + virsh destroy "$VM_NAME" >/dev/null 2>&1 || true + virsh undefine "$VM_NAME" >/dev/null 2>&1 || true +fi + +virt-install -q \ + --name "$VM_NAME" \ + --os-variant "debianwheezy" \ + --arch "$(dpkg-architecture -A"$ARCH" -qDEB_TARGET_GNU_CPU)" \ + --virt-type "kvm" \ + --cpu "$CPU" ${VCPUS+--vcpus "$VCPUS"} \ + --memory "$MEMORY" --memballoon "virtio" \ + --cdrom "$VMTMPDIR/install.iso" \ + --disk "$DISK,bus=virtio" \ + --channel "unix,target_type=virtio,name=org.qemu.guest_agent.0" \ + --filesystem "source=$VMTMPDIR/virtfs,target=virtfs" \ + --network "$NETWORK" \ + --graphics "$GRAPHICS" \ + --noautoconsole $TRANSIENT + +( + vmdef="$(mktemp --tmpdir)" + trap 'rm -f "$vmdef"' EXIT TERM INT + virsh dumpxml "$VM_NAME" >"$vmdef" + + for xpath in \ + "/domain/devices/filesystem[source/@dir=\"$VMTMPDIR/virtfs\"][target/@dir='virtfs']" \ + "/domain/devices/disk[@type='file'][@device='cdrom']"; do + if [ -z "$TRANSIENT" ]; then + virsh --quiet detach-device --config "$VM_NAME" \ + <(xmlstarlet select --template --copy-of "$xpath" <"$vmdef") + elif [ ${OUTPUT+x} ]; then + xmlstarlet edit --inplace --delete "$xpath" "$vmdef" + fi + done + [ ! ${OUTPUT+x} ] || cp --no-preserve=mode "$vmdef" "$OUTPUT/$VM_NAME.xml" +) +# wait until the VM terminates (there is actually a race condition here, +# but the XML massaging above should be faster than any install) +virsh console "$VM_NAME" --safe >/dev/null + +( + if [ -f "$VMTMPDIR/virtfs/minion.pub" ]; then + echo "Salt minion MD5 fingerprint:" + # salt uses a bizarre scheme... + grep -v '^-----.*-----$' "$VMTMPDIR/virtfs/minion.pub" \ + | openssl dgst -md5 -c | sed '/.*=\s*/ {s//\t/;q}' + fi + echo + + echo "SSH hostkey fingerprints:" + find "$VMTMPDIR/virtfs" -maxdepth 1 -type f -name 'ssh_host_*_key.pub' \ + -execdir ssh-keygen -lf {} \; | sed 's/^/\t/' +) >&2 + +if [ ${OUTPUT+x} ]; then + find "$VMTMPDIR/virtfs" -name '*.pub' -print0 \ + | xargs -r0 cp --no-preserve=mode -t "$OUTPUT" +fi |