diff options
Diffstat (limited to 'tdfvm-install')
-rwxr-xr-x | tdfvm-install | 136 |
1 files changed, 91 insertions, 45 deletions
diff --git a/tdfvm-install b/tdfvm-install index 052e173..d0138c6 100755 --- a/tdfvm-install +++ b/tdfvm-install @@ -13,15 +13,60 @@ usage() { exit 1 } +# Run a command locally or remotely +unset SSH_ARGS +run() { + if [ ${SSH_ARGS+x} ]; then ssh $SSH_ARGS -- "$@"; else "$@"; fi +} + +unset CDROM +unset SSH_COMMAND +unset OUTPUT +unset VMDIR +SEED=$(head -c24 /dev/urandom | base64 -w0 | tr '+/' '-_') +SSH_CONTROLPATH="${XDG_RUNTIME_DIR:-$HOME/.ssh}/S.$SEED" # unique, unpredicable socket path +cleanup() { + set +e + [ ! ${CDROM+x} ] || run rm -f "$CDROM" + [ ! ${SSH_COMMAND+x} ] || rm -f "$SSH_COMMAND" + [ "${OUTPUT+x}" -o ! "${VMDIR+x}" ] || rm -rf "$VMDIR" + [ ! -S "$SSH_CONTROLPATH" ] || ssh -Oexit -S"$SSH_CONTROLPATH" x 2>/dev/null +} +trap cleanup EXIT TERM INT LIBVIRT_URI=qemu:///system +VIRT_USER="$USER" +prepare_remote_uri() { + local dest="$1" user port + [ ! ${SSH_COMMAND+x} ] || return # already defined + + if [ "${dest%:*}" != "$dest" ]; then + port="${dest##*:}" + dest="${dest%:*}" + fi + if [ "${dest%@*}" != "$dest" ]; then + VIRT_USER="${dest%%@*}" + dest="${dest#*@}" + fi + + SSH_COMMAND="$(mktemp --tmpdir)" + SSH_ARGS="-oControlMaster=auto -oControlPath=$SSH_CONTROLPATH ${VIRT_USER+-l $VIRT_USER} ${port+-p $port} $dest" + cat >"$SSH_COMMAND" <<-EOF + #!/bin/sh + export SSH_AUTH_SOCK="${SSH_AUTH_SOCK-}" + exec ssh -oControlPersist=yes $SSH_ARGS -- nc -q0 -U /run/libvirt/libvirt-sock + EOF + chmod u+x "$SSH_COMMAND" + LIBVIRT_URI="qemu+ext:///system?command=${SSH_COMMAND//\//%2F}" +} + + 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 @@ -34,13 +79,14 @@ TRANSIENT=n 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: "$USER_NAME") - -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 + --ssh=[USERNAME@]HOSTNAME[:PORT] connect over SSH to a remote libvirtd + --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: "$USER_NAME") + -p,--password prompt for USERNAME's password (password login are disabled by default) + --authorized-keys=FILENAME path 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") @@ -69,6 +115,7 @@ while [ $# -gt 0 ]; do -o|--output) OUTPUT="$2"; shift;; -o*) OUTPUT="${1#-o}";; --output=*) OUTPUT="${1#--output=}";; + --ssh=*) prepare_remote_uri "${1#--ssh=}";; --vcpus) VCPUS="$2"; shift;; --vcpus=*) VCPUS="${1#--vcpus=}";; @@ -108,7 +155,6 @@ 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='*' @@ -116,7 +162,7 @@ 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 @@ -138,19 +184,14 @@ 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" - +CDROM="$(run mktemp --tmpdir "$VM_NAME-XXXXXX.iso")" ( 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" @@ -211,7 +252,9 @@ install -o libvirt-qemu -m 0700 --directory "$VMTMPDIR/virtfs" -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" ./ + ./ \ + | if [ ${SSH_ARGS+x} ]; then run "cat >$CDROM"; else cat >"$CDROM"; fi + run chmod g+r "$CDROM" ) @@ -226,6 +269,7 @@ if [ "$FORCE" = y ]; then virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" >/dev/null 2>&1 || true fi +VMDIR="${OUTPUT-$(mktemp --tmpdir --directory)}" virt-install -q --connect "$LIBVIRT_URI" \ --name "$VM_NAME" \ --os-variant "debianwheezy" \ @@ -233,60 +277,62 @@ virt-install -q --connect "$LIBVIRT_URI" \ --virt-type "kvm" \ --cpu "$CPU" ${VCPUS+--vcpus "$VCPUS"} \ --memory "$MEMORY" --memballoon "virtio" \ - --disk "path=$VMTMPDIR/install.iso,device=cdrom,bus=sata,readonly=on" \ + --disk "path=$CDROM,device=cdrom,bus=sata,readonly=on,seclabel.model=dac,seclabel.label=$VIRT_USER:libvirt-qemu" \ --disk "$DISK,bus=virtio" \ --channel "unix,target_type=virtio,name=org.qemu.guest_agent.0" \ --rng "random,device=/dev/random,model=virtio,rate_bytes=512" \ - --security "type=static,model=dac,label=libvirt-qemu:libvirt-qemu,relabel=no" \ - --filesystem "source=$VMTMPDIR/virtfs,target=virtfs" \ + --security "type=static,model=dac,label=libvirt-qemu:libvirt-qemu,relabel=yes" \ --network "$NETWORK" \ --graphics "$GRAPHICS" \ --controller "virtio-serial" \ --noautoconsole \ - --print-xml 1 >"$VMTMPDIR/domain.xml" + --print-xml 1 >"$VMDIR/$VM_NAME.xml" -virsh -c "$LIBVIRT_URI" create "$VMTMPDIR/domain.xml" +virsh -c "$LIBVIRT_URI" create "$VMDIR/$VM_NAME.xml" # never boot again on CDROM, detach unnecessary devices and remove # unnecessary controllers xmlstarlet edit --inplace \ --delete "/domain/os/boot[@dev='cdrom']" \ - --delete "/domain/devices/filesystem[source/@dir='$VMTMPDIR/virtfs'][target/@dir='virtfs']" \ --delete "/domain/devices/disk[@type='file'][@device='cdrom']" \ --delete "/domain/devices/sound" \ --delete "/domain/devices/controller[@type='usb']" \ --delete "/domain/devices/input[@type='tablet'][@bus='usb']" \ --delete "/domain/devices/redirdev[@type='spicevmc'][@bus='usb']" \ - "$VMTMPDIR/domain.xml" + "$VMDIR/$VM_NAME.xml" + +[ "$TRANSIENT" = y ] || virsh -c "$LIBVIRT_URI" define "$VMDIR/$VM_NAME.xml" -[ "$TRANSIENT" = y ] || virsh -c "$LIBVIRT_URI" define "$VMTMPDIR/domain.xml" +# wait until the guest starts its QEMU Agent at the end of the installation +while :; do + ./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" ping && break || rv=$? + [ $rv -eq 128 ] || exit $rv + sleep 1 +done + +# then copy the public key material +for path in /etc/ssh/ssh_host_rsa_key.pub \ + /etc/ssh/ssh_host_ed25519_key.pub \ + /etc/salt/pki/minion/minion.pub; do + ./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" cat "$path" >"$VMDIR/${path##*/}" +done -# wait until the VM terminates (there is actually a race condition here, -# but the XML massaging above should be faster than any install) -virsh -c "$LIBVIRT_URI" console "$VM_NAME" --safe >/dev/null +./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" touch /etc/tdf-install-continue +virsh -c "$LIBVIRT_URI" console "$VM_NAME" --safe >/dev/null # wait until shutdown ( - 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 "Salt minion MD5 fingerprint:" + # salt uses a bizarre scheme... + grep -v '^-----.*-----$' "$VMDIR/minion.pub" \ + | openssl dgst -md5 -c | sed '/.*=\s*/ {s//\t/;q}' echo - sshkeys=$(find "$VMTMPDIR/virtfs" -maxdepth 1 -type f -name 'ssh_host_*_key.pub') - if [ "$sshkeys" ]; then - echo "SSH hostkey fingerprints:" - for pk in $sshkeys; do - ssh-keygen -lf "$pk" - done | sed 's/^/\t/' - fi + echo "SSH hostkey fingerprints:" + find "$VMDIR" -maxdepth 1 -type f -name 'ssh_host_*_key.pub' \ + -execdir ssh-keygen -lf {} \; | sed 's/^/\t/' ) >&2 if [ ${OUTPUT+x} ]; then - cp --no-preserve=mode "$VMTMPDIR/domain.xml" "$OUTPUT/$VM_NAME.xml" - find "$VMTMPDIR/virtfs" -name '*.pub' -print0 \ - | xargs -r0 cp --no-preserve=mode -t "$OUTPUT" printf "\nExported files:\n" >&2 find "$OUTPUT" -type f -printf '\t%p\n' >&2 fi |