From 094fced31e934225865dc6f0bab2039cf4a908e4 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 19 Oct 2016 02:26:15 +0200 Subject: Add script to install new virtual machines. --- tdfvm-install | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100755 tdfvm-install (limited to 'tdfvm-install') 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 -- cgit v1.2.3