aboutsummaryrefslogtreecommitdiffstats
path: root/tdfvm-install
diff options
context:
space:
mode:
Diffstat (limited to 'tdfvm-install')
-rwxr-xr-xtdfvm-install279
1 files changed, 279 insertions, 0 deletions
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