[Qa-jenkins-scm] [jenkins.debian.net] 01/01: drop lvc stuff, now that openqa.debian.net is getting into shape

Holger Levsen holger at layer-acht.org
Wed Oct 4 11:17:03 UTC 2017


This is an automated email from the git hooks/post-receive script.

holger pushed a commit to branch master
in repository jenkins.debian.net.

commit 4d8dfa12768c22f137c236b6e47352afb869a4c8
Author: Holger Levsen <holger at layer-acht.org>
Date:   Wed Oct 4 13:16:52 2017 +0200

    drop lvc stuff, now that openqa.debian.net is getting into shape
    
    Signed-off-by: Holger Levsen <holger at layer-acht.org>
---
 README                                             |    3 -
 TODO4lvc-removal.txt                               |    5 +
 bin/jenkins_master_wrapper.sh                      |    9 +-
 bin/jenkins_node_wrapper.sh                        |    7 -
 bin/lvc.sh                                         |  162 ---
 cucumber/README-sikuli-cucumber                    |   42 -
 cucumber/bin/run_test_suite                        |  283 ----
 cucumber/features/config/defaults.yml              |   36 -
 cucumber/features/domains/cdrom.xml                |    5 -
 cucumber/features/domains/default.xml              |   58 -
 cucumber/features/domains/default_net.xml          |   13 -
 cucumber/features/domains/disk.xml                 |    5 -
 cucumber/features/domains/storage_pool.xml         |    6 -
 cucumber/features/domains/volume.xml               |   14 -
 cucumber/features/images/DebianLive7BootSplash.png |  Bin 13399 -> 0 bytes
 .../images/DebianLive7BootSplashTabMsg.png         |  Bin 4260 -> 0 bytes
 cucumber/features/images/DebianLive7Greeter.png    |  Bin 3166 -> 0 bytes
 cucumber/features/images/DebianLiveBootSplash.png  |  Bin 1569 -> 0 bytes
 .../features/images/DebianLiveBootSplashTabMsg.png |  Bin 790 -> 0 bytes
 .../features/images/DebianLoginPromptGnome.png     |  Bin 1409 -> 0 bytes
 cucumber/features/images/DebianLoginPromptKDE.png  |  Bin 165010 -> 0 bytes
 .../features/images/DebianLoginPromptKDE_d9.png    |  Bin 13354 -> 0 bytes
 cucumber/features/images/DebianLoginPromptLXDE.png |  Bin 9159 -> 0 bytes
 .../features/images/DebianLoginPromptLXDE_d9.png   |  Bin 1151 -> 0 bytes
 cucumber/features/images/DebianLoginPromptVT.png   |  Bin 1723 -> 0 bytes
 .../features/images/DebianLoginPromptVT_Edu.png    |  Bin 935 -> 0 bytes
 .../features/images/DebianLoginPromptVT_d9.png     |  Bin 242 -> 0 bytes
 cucumber/features/images/DebianLoginPromptXFCE.png |  Bin 9159 -> 0 bytes
 .../features/images/DebianLoginPromptXFCE_d9.png   |  Bin 1152 -> 0 bytes
 .../features/images/d-i_boot_graphical-default.png |  Bin 15661 -> 0 bytes
 .../images/d-i_boot_graphical-default_9alpha.png   |  Bin 15478 -> 0 bytes
 cucumber/features/images/d-i_boot_miniiso.png      |  Bin 9175 -> 0 bytes
 cucumber/features/images/d-i_boot_text-default.png |  Bin 17082 -> 0 bytes
 .../features/images/d-i_bootmenu_graphical.png     |  Bin 15704 -> 0 bytes
 cucumber/features/images/d-i_gui_ArchiveMirror.png |  Bin 5265 -> 0 bytes
 cucumber/features/images/d-i_gui_BadMirror.png     |  Bin 18795 -> 0 bytes
 .../features/images/d-i_gui_BritishEnglish.png     |  Bin 3841 -> 0 bytes
 .../features/images/d-i_gui_CONTINUEselected.png   |  Bin 3919 -> 0 bytes
 .../features/images/d-i_gui_CONTINUEunselected.png |  Bin 2814 -> 0 bytes
 cucumber/features/images/d-i_gui_CheckSyslog.png   |  Bin 8963 -> 0 bytes
 .../features/images/d-i_gui_ChooseSoftware.png     |  Bin 5924 -> 0 bytes
 cucumber/features/images/d-i_gui_Desktop+Gnome.png |  Bin 15335 -> 0 bytes
 cucumber/features/images/d-i_gui_Desktop+KDE.png   |  Bin 12385 -> 0 bytes
 cucumber/features/images/d-i_gui_Desktop+LXDE.png  |  Bin 15171 -> 0 bytes
 cucumber/features/images/d-i_gui_Desktop+XFCE.png  |  Bin 16919 -> 0 bytes
 .../features/images/d-i_gui_DesktopTask_No.png     |  Bin 1322 -> 0 bytes
 .../features/images/d-i_gui_DesktopTask_Yes.png    |  Bin 8920 -> 0 bytes
 cucumber/features/images/d-i_gui_DomainName.png    |  Bin 2880 -> 0 bytes
 .../features/images/d-i_gui_Edu-AutoPartition.png  |  Bin 8408 -> 0 bytes
 .../features/images/d-i_gui_Edu-LTSPchroot.png     |  Bin 3299 -> 0 bytes
 cucumber/features/images/d-i_gui_Edu-Profile.png   |  Bin 210566 -> 0 bytes
 cucumber/features/images/d-i_gui_English.png       |  Bin 2768 -> 0 bytes
 .../features/images/d-i_gui_EnterTheHostname.png   |  Bin 7878 -> 0 bytes
 .../features/images/d-i_gui_FinishPartitioning.png |  Bin 14728 -> 0 bytes
 cucumber/features/images/d-i_gui_GRUBEnterDev.png  |  Bin 19466 -> 0 bytes
 cucumber/features/images/d-i_gui_GRUBdev.png       |  Bin 12068 -> 0 bytes
 cucumber/features/images/d-i_gui_HttpProxy.png     |  Bin 5087 -> 0 bytes
 .../features/images/d-i_gui_InstallComplete.png    |  Bin 9404 -> 0 bytes
 .../images/d-i_gui_InstallGRUB-heading.png         |  Bin 7340 -> 0 bytes
 cucumber/features/images/d-i_gui_InstallGRUB.png   |  Bin 15554 -> 0 bytes
 .../features/images/d-i_gui_InstallSoftware.png    |  Bin 7357 -> 0 bytes
 .../images/d-i_gui_InstallationStepFailed.png      |  Bin 5283 -> 0 bytes
 .../images/d-i_gui_InstallingBaseSystem.png        |  Bin 5818 -> 0 bytes
 cucumber/features/images/d-i_gui_MirrorCountry.png |  Bin 8224 -> 0 bytes
 cucumber/features/images/d-i_gui_NameOfUser.png    |  Bin 5622 -> 0 bytes
 cucumber/features/images/d-i_gui_No.png            |  Bin 4330 -> 0 bytes
 .../features/images/d-i_gui_NoKernelModules.png    |  Bin 5994 -> 0 bytes
 .../features/images/d-i_gui_PartitionDisks.png     |  Bin 3069 -> 0 bytes
 .../features/images/d-i_gui_PartitioningMethod.png |  Bin 9878 -> 0 bytes
 .../features/images/d-i_gui_PartitioningScheme.png |  Bin 9771 -> 0 bytes
 cucumber/features/images/d-i_gui_RootPassword.png  |  Bin 3650 -> 0 bytes
 cucumber/features/images/d-i_gui_ScanCD.png        |  Bin 12520 -> 0 bytes
 .../images/d-i_gui_SelectDiskToPartition.png       |  Bin 7584 -> 0 bytes
 .../features/images/d-i_gui_SelectYourLocation.png |  Bin 4229 -> 0 bytes
 .../features/images/d-i_gui_ShowRootPassword.png   |  Bin 8967 -> 0 bytes
 .../features/images/d-i_gui_ShowUserPassword.png   |  Bin 12056 -> 0 bytes
 cucumber/features/images/d-i_gui_UnitedKingdom.png |  Bin 4256 -> 0 bytes
 cucumber/features/images/d-i_gui_UseNetMirror.png  |  Bin 1663 -> 0 bytes
 cucumber/features/images/d-i_gui_UserPassword.png  |  Bin 7667 -> 0 bytes
 cucumber/features/images/d-i_gui_Yes.png           |  Bin 4147 -> 0 bytes
 cucumber/features/images/d-i_gui_popcon.png        |  Bin 13176 -> 0 bytes
 .../features/images/d-i_text_ArchiveMirror.png     |  Bin 2908 -> 0 bytes
 cucumber/features/images/d-i_text_BadMirror.png    |  Bin 2316 -> 0 bytes
 .../features/images/d-i_text_BritishEnglish.png    |  Bin 4853 -> 0 bytes
 cucumber/features/images/d-i_text_CheckSyslog.png  |  Bin 5506 -> 0 bytes
 .../features/images/d-i_text_ChooseSoftware.png    |  Bin 4081 -> 0 bytes
 .../features/images/d-i_text_Desktop+Gnome.png     |  Bin 5135 -> 0 bytes
 cucumber/features/images/d-i_text_Desktop+KDE.png  |  Bin 5509 -> 0 bytes
 cucumber/features/images/d-i_text_Desktop+LXDE.png |  Bin 8127 -> 0 bytes
 cucumber/features/images/d-i_text_Desktop+XFCE.png |  Bin 4688 -> 0 bytes
 .../features/images/d-i_text_DesktopTask_No.png    |  Bin 7161 -> 0 bytes
 .../features/images/d-i_text_DesktopTask_Yes.png   |  Bin 2700 -> 0 bytes
 cucumber/features/images/d-i_text_DomainName.png   |  Bin 2225 -> 0 bytes
 .../features/images/d-i_text_Edu-AutoPartition.png |  Bin 60832 -> 0 bytes
 cucumber/features/images/d-i_text_Edu-Profile.png  |  Bin 80161 -> 0 bytes
 cucumber/features/images/d-i_text_English.png      |  Bin 7229 -> 0 bytes
 .../features/images/d-i_text_EnterTheHostname.png  |  Bin 5643 -> 0 bytes
 cucumber/features/images/d-i_text_F12BootMenu.png  |  Bin 482 -> 0 bytes
 .../images/d-i_text_FinishPartitioning.png         |  Bin 8734 -> 0 bytes
 cucumber/features/images/d-i_text_GRUBEnterDev.png |  Bin 2490 -> 0 bytes
 cucumber/features/images/d-i_text_GRUBdev.png      |  Bin 1765 -> 0 bytes
 cucumber/features/images/d-i_text_HttpProxy.png    |  Bin 5346 -> 0 bytes
 .../features/images/d-i_text_InstallComplete.png   |  Bin 2740 -> 0 bytes
 .../images/d-i_text_InstallGRUB-heading.png        |  Bin 13967 -> 0 bytes
 cucumber/features/images/d-i_text_InstallGRUB.png  |  Bin 6684 -> 0 bytes
 .../features/images/d-i_text_InstallSoftware.png   |  Bin 9341 -> 0 bytes
 .../images/d-i_text_InstallationStepFailed.png     |  Bin 3037 -> 0 bytes
 .../images/d-i_text_InstallingBaseSystem.png       |  Bin 3554 -> 0 bytes
 .../features/images/d-i_text_MirrorCountry.png     |  Bin 3769 -> 0 bytes
 cucumber/features/images/d-i_text_NameOfUser.png   |  Bin 3792 -> 0 bytes
 cucumber/features/images/d-i_text_No.png           |  Bin 995 -> 0 bytes
 cucumber/features/images/d-i_text_NoDiskFound.png  |  Bin 4005 -> 0 bytes
 .../images/d-i_text_PartitioningMethod.png         |  Bin 13953 -> 0 bytes
 .../images/d-i_text_PartitioningScheme.png         |  Bin 2879 -> 0 bytes
 cucumber/features/images/d-i_text_RootPassword.png |  Bin 3226 -> 0 bytes
 cucumber/features/images/d-i_text_ScanCD.png       |  Bin 3216 -> 0 bytes
 .../features/images/d-i_text_SelectBootDev.png     |  Bin 559 -> 0 bytes
 .../images/d-i_text_SelectDiskToPartition.png      |  Bin 5403 -> 0 bytes
 .../images/d-i_text_SelectYourLocation.png         |  Bin 5303 -> 0 bytes
 .../features/images/d-i_text_ShowRootPassword.png  |  Bin 3226 -> 0 bytes
 .../features/images/d-i_text_ShowUserPassword.png  |  Bin 4892 -> 0 bytes
 .../features/images/d-i_text_UnitedKingdom.png     |  Bin 4162 -> 0 bytes
 cucumber/features/images/d-i_text_UseNetMirror.png |  Bin 2975 -> 0 bytes
 cucumber/features/images/d-i_text_UserPassword.png |  Bin 4892 -> 0 bytes
 cucumber/features/images/d-i_text_Yes.png          |  Bin 1163 -> 0 bytes
 cucumber/features/images/d-i_text_popcon.png       |  Bin 10013 -> 0 bytes
 cucumber/features/install.feature                  |  145 --
 cucumber/features/misc_files/sample.pdf            |  Bin 22347 -> 0 bytes
 cucumber/features/misc_files/sample.tex            |    8 -
 cucumber/features/scripts/otr-bot.py               |  206 ---
 cucumber/features/scripts/vm-execute               |   55 -
 cucumber/features/step_definitions/apt.rb          |  124 --
 cucumber/features/step_definitions/browser.rb      |  219 ---
 cucumber/features/step_definitions/build.rb        |  153 ---
 cucumber/features/step_definitions/checks.rb       |  230 ----
 cucumber/features/step_definitions/common_steps.rb | 1388 --------------------
 cucumber/features/step_definitions/dhcp.rb         |   23 -
 cucumber/features/step_definitions/electrum.rb     |   54 -
 cucumber/features/step_definitions/encryption.rb   |  133 --
 cucumber/features/step_definitions/evince.rb       |   25 -
 .../features/step_definitions/firewall_leaks.rb    |   33 -
 cucumber/features/step_definitions/git.rb          |   32 -
 cucumber/features/step_definitions/mac_spoofing.rb |  119 --
 cucumber/features/step_definitions/pidgin.rb       |  497 -------
 cucumber/features/step_definitions/po.rb           |    8 -
 .../step_definitions/root_access_control.rb        |   41 -
 cucumber/features/step_definitions/snapshots.rb    |  119 --
 cucumber/features/step_definitions/ssh.rb          |  156 ---
 cucumber/features/step_definitions/time_syncing.rb |   86 --
 cucumber/features/step_definitions/tor.rb          |  406 ------
 .../features/step_definitions/torified_browsing.rb |    5 -
 .../features/step_definitions/torified_gnupg.rb    |  263 ----
 .../features/step_definitions/torified_misc.rb     |   51 -
 cucumber/features/step_definitions/totem.rb        |   44 -
 .../features/step_definitions/unsafe_browser.rb    |  204 ---
 .../step_definitions/untrusted_partitions.rb       |   61 -
 cucumber/features/step_definitions/usb.rb          |  747 -----------
 cucumber/features/support/config.rb                |   85 --
 cucumber/features/support/env.rb                   |  126 --
 cucumber/features/support/extra_hooks.rb           |  187 ---
 .../features/support/helpers/chatbot_helper.rb     |   59 -
 cucumber/features/support/helpers/ctcp_helper.rb   |  126 --
 .../features/support/helpers/display_helper.rb     |   48 -
 cucumber/features/support/helpers/dogtail.rb       |  233 ----
 .../features/support/helpers/firewall_helper.rb    |   94 --
 cucumber/features/support/helpers/misc_helpers.rb  |  344 -----
 cucumber/features/support/helpers/remote_shell.rb  |  171 ---
 cucumber/features/support/helpers/sikuli_helper.rb |  238 ----
 .../features/support/helpers/sniffing_helper.rb    |   53 -
 cucumber/features/support/helpers/sshd_helper.rb   |   67 -
 .../features/support/helpers/storage_helper.rb     |  210 ---
 cucumber/features/support/helpers/vm_helper.rb     |  703 ----------
 cucumber/features/support/hooks.rb                 |  313 -----
 hosts/jenkins-test-vm/job-cfg/lvc.yaml             |    1 -
 job-cfg/lvc.yaml                                   |  101 --
 update_jdn.sh                                      |   42 +-
 176 files changed, 8 insertions(+), 9476 deletions(-)

diff --git a/README b/README
index 6e42137..34e87ea 100644
--- a/README
+++ b/README
@@ -244,9 +244,6 @@ See link:https://jenkins.debian.net/userContent/thanks.html[THANKS].
 
 == License
 
-* everything except features and bin/libvirt_cucumber_tests:
 ** GPLv2, see link:https://anonscm.debian.org/git/qa/jenkins.debian.net.git/LICENSE[LICENSE].
-* features and bin/libvirt_cucumber_tests:
-** GPLv3+
 
 // vim: set filetype=asciidoc:
diff --git a/TODO4lvc-removal.txt b/TODO4lvc-removal.txt
new file mode 100644
index 0000000..9cf3c51
--- /dev/null
+++ b/TODO4lvc-removal.txt
@@ -0,0 +1,5 @@
+cleanup /srv/lvc on pb10 
+cleanup that code in update_jdn.sh
+cleanup LVC in bin/d-i_build.sh
+cleanup THANKS.head for pb10
+cleanup job-cfg/d-i.yaml.py (search for lvc)
diff --git a/bin/jenkins_master_wrapper.sh b/bin/jenkins_master_wrapper.sh
index 11d02dd..6d2f419 100755
--- a/bin/jenkins_master_wrapper.sh
+++ b/bin/jenkins_master_wrapper.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Copyright 2015-2016 Holger Levsen <holger at layer-acht.org>
+# Copyright 2015-2017 Holger Levsen <holger at layer-acht.org>
 #                2016 Phil Hands <phil at hands.com>
 # released under the GPLv=2
 # based on an idea by Peter Palfrader (see bin/jenkins_node_wrapper.sh)
@@ -10,13 +10,6 @@ set -e
 
 # don't try to run on test system
 if [ "$HOSTNAME" = "jenkins-test-vm" ] ; then
-	case $JOB_NAME in
-		lvc_*)
-			exec /srv/jenkins/bin/lvc.sh "$@"
-		    echo "$(date -u) - running on $HOSTNAME, This should not happen."
-		    exit 1
-			;;
-	esac
 	echo "$(date -u) - running on $HOSTNAME, exiting successfully and cleanly immediatly."
 	exit 0
 fi
diff --git a/bin/jenkins_node_wrapper.sh b/bin/jenkins_node_wrapper.sh
index 6167450..6c2e087 100755
--- a/bin/jenkins_node_wrapper.sh
+++ b/bin/jenkins_node_wrapper.sh
@@ -72,13 +72,6 @@ elif [[ "$*" =~ ^rebootstrap_.* ]] ; then
 	REBOOTSTRAPSH="/srv/jenkins/bin/rebootstrap.sh $@"
 	export LC_ALL=C
 	exec $REBOOTSTRAPSH; croak "Exec failed";
-elif [[ "$*" =~ ^lvc_.* ]] ; then
-	export JOB_NAME=$1 ; shift
-	export EXECUTOR_NUMBER=$1 ; shift
-	export TRIGGERING_BRANCH=${1#*=} ; shift
-	export WORKSPACE=/var/lib/jenkins/jobs/$JOB_NAME/workspace
-	COMMAND="/srv/jenkins/bin/lvc.sh $@"
-	exec $COMMAND; croak "Exec failed";
 elif [ "$*" = "reproducible_html_nodes_info" ] ; then
 	exec /srv/jenkins/bin/reproducible_info.sh ; croak "Exec failed";
 elif [ "$1" = "/srv/jenkins/bin/reproducible_build.sh" ] && ( [ "$2" = "1" ] || [ "$2" = "2" ] ) ; then
diff --git a/bin/lvc.sh b/bin/lvc.sh
deleted file mode 100755
index 310ef48..0000000
--- a/bin/lvc.sh
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/bin/bash
-
-# Copyright 2012-2015 Holger Levsen <holger at layer-acht.org>
-# Copyright 2016 Philip Hands <phil at hands.com>
-# released under the GPLv=2
-
-DEBUG=false
-. /srv/jenkins/bin/common-functions.sh
-common_init "$@"
-
-# $1 = wget url/jigdo url
-URL=$1 ; shift
-
-replace_origin_pu() {
-    PREFIX=$1 ; shift
-    BRANCH=$1 ; shift
-    expr "$BRANCH" : 'origin/pu/' >/dev/null || return 1
-    echo "${PREFIX}pu_${BRANCH#origin/pu/}"
-}
-
-# if $URL is set to "use_TRIGGERING_BRANCH" then use the contents of $TRIGGERING_BRANCH to work out the locally built ISO name
-if [ "use_TRIGGERING_BRANCH" = "$URL" ] ; then
-	if PU_ISO="$(replace_origin_pu "/srv/d-i/isos/mini-gtk-" $TRIGGERING_BRANCH).iso" ; then
-		[ -f "$PU_ISO" ] || {
-			echo "looks like we're meant to be testing '$PU_ISO', but it's missing"
-			exit 1
-			}
-		URL=$PU_ISO
-		echo "using locally built ISO image: URL='$URL'"
-	else
-		echo "URL='$URL' but TRIGGERING_BRANCH='$TRIGGERING_BRANCH' -- aborting"
-		exit 1
-	fi
-fi
-
-cleanup_all() {
-        find . -name \*.vlog.png -print0 | xargs -0 -r rm
-	#echo "Trying to preserve last screenshot…"
-	#LAST_SCREENSHOT=$(ls -t1 $RESULTS/*.png | head -1)
-	#if [ -e "$LAST_SCREENSHOT" ] ; then
-	#        cp $LAST_SCREENSHOT $WORKSPACE/screenshot.png
-	#	convert $WORKSPACE/screenshot.png -adaptive-resize 128x96 $WORKSPACE/screenshot-thumb.png
-	#fi
-}
-
-fetch_if_newer() {
-        url="$2"
-        file="$1"
-	if [ -f $url ] ; then
-		echo "the URL turns out to be a local path ($url) -- linking"
-		ln -sf $url $file
-		return
-        fi
-        echo "Downloading $url"
-        curlopts="-L -s -S"
-        if [ -f "$file" ] ; then
-                ls -l $file
-                echo "File exists, will only re-download if a newer one is available..."
-                curlopts="$curlopts -z $file"
-        fi
-        curl $curlopts -o $file.new $url
-        if [ -e  $file.new ] ; then
-          mv -f $file.new $file
-        fi
-}
-
-discard_snapshots() {
-    domain=$1 ; shift
-    # if more parameters are provided, discard any snapshot younger than the files/dirs listed
-    # otherwise, get rid of all of them (hence the [ -z "$1" ] below)
-
-    sudo /usr/bin/virsh -q snapshot-list $domain | \
-        while read snap date time tz state ; do
-            if [ -z "$1" ] || [ "$(find "$@" -newermt "$date $time $tz" -print -quit)" ] ; then
-                sudo /usr/bin/virsh snapshot-delete $domain $snap
-            fi
-        done
-}
-
-#
-# define workspace + results
-#
-if [ -z "$WORKSPACE" ] ; then
-    WORKSPACE=$PWD
-fi
-RESULTS=$WORKSPACE/results
-
-IMAGE=$WORKSPACE/$(basename $URL)
-
-LIBVIRT_DOMAIN_NAME="lvcVM-$JOB_NAME"
-
-rm -rf $RESULTS $WORKSPACE/screenshot{,-thumb}.png
-
-mkdir -p $RESULTS
-
-trap cleanup_all INT TERM EXIT
-
-#
-# install image preparation
-#
-if [ ! -z "$NETBOOT" ] ; then
-        #
-        # if there is a netboot installer tarball...
-        #
-        fetch_if_newer "$NETBOOT" "$URL"
-        sha256sum "$NETBOOT"
-        # try to extract, otherwise clean up and abort
-        if ! tar -zxvf "$NETBOOT" ; then
-                echo "tarball seems corrupt;  deleting it"
-                rm -f "$NETBOOT"
-                exit 1
-        fi
-elif [ ! -z "$IMAGE" ] ; then
-        #
-        # if there is a CD image...
-        #
-        fetch_if_newer "$IMAGE" "$URL"
-        # is this really an .iso?
-        if [ $(file -L "$IMAGE" | grep -cE '(ISO 9660|DOS/MBR boot sector)') -eq 1 ] ; then
-                # yes, so let's md5sum and mount it
-                md5sum $IMAGE
-#                sudo mkdir -p $IMAGE_MNT
-#                grep -q $IMAGE_MNT /proc/mounts && sudo umount -l $IMAGE_MNT
-#                sleep 1
-#                sudo mount -o loop,ro $IMAGE $IMAGE_MNT
-        else
-                # something went wrong
-                figlet "no .iso"
-                echo "ERROR: no valid .iso found"
-                if [ $(file "$IMAGE" | grep -c "HTML document") -eq 1 ] ; then
-                        mv "$IMAGE" "$IMAGE.html"
-                        lynx --dump "$IMAGE.html"
-                        rm "$IMAGE.html"
-                fi
-                exit 1
-        fi
-else
-        #
-        # else netboot gtk
-        #
-        fetch_if_newer "$KERNEL" "$URL/$KERNEL"
-        fetch_if_newer "$INITRD" "$URL/$INITRD"
-fi
-
-# discard any snapshots to ensure a clean run (we used to do this conditionally here, but that proved confusing)
-discard_snapshots $LIBVIRT_DOMAIN_NAME
-
-echo "Debug log available at runtime at https://jenkins.debian.net/view/lvc/job/$JOB_NAME/ws/results/debug.log"
-
-/srv/jenkins/cucumber/bin/run_test_suite --capture-all --keep-snapshots --vnc-server-only --iso $IMAGE --tmpdir $WORKSPACE --old-iso $IMAGE -- --format pretty --format pretty_debug --out $RESULTS/debug.log /srv/jenkins/cucumber/features/step_definitions /srv/jenkins/cucumber/features/support "${@}" || {
-  RETVAL=$?
-  # it may make sense to keep snapshots on failure, so subsequent tests are quicker -- only if we stop discarding them above though
-  discard_snapshots $LIBVIRT_DOMAIN_NAME
-  exit $RETVAL
-}
-
-# FIXME -- decide here if we need to keep any snapshots, and put them somewhere safe for other jobs to find
-discard_snapshots $LIBVIRT_DOMAIN_NAME
-cleanup_all
-
-# don't cleanup twice
-trap - INT TERM EXIT
diff --git a/cucumber/README-sikuli-cucumber b/cucumber/README-sikuli-cucumber
deleted file mode 100644
index 8b6f06d..0000000
--- a/cucumber/README-sikuli-cucumber
+++ /dev/null
@@ -1,42 +0,0 @@
-Key names:
-
-  http://doc.sikuli.org/keys.html
-
-Running the thing by hand:
-
-  ./bin/lvc/run_test_suite --view --iso /var/lib/libvirt/images/debian-8.3.0-amd64-i386-netinst.iso --old-iso /var/lib/libvirt/images/debian-8.3.0-amd64-i386-netinst.iso DebianLive/apt.feature
-
-FIXME - we don't need the OLD_ISO stuff -- make it so that it still works when not specified, so we can push the change upwards
-
-FIXME - we need to be able to run multiple VMs in parallel, and so multiple jobs
-
-FIXME - the VM's network currnetly needs to be started by hand
-
-To do Git push we can use:
-
-    curl 'http://46.16.73.145:8080/git/notifyCommit?url=git://git.debian.org/git/d-i/preseed'
-  or
-    curl 'https://jenkins.debian.net/git/notifyCommit?url=git://git.debian.org/git/d-i/preseed'
-
-Workflow:
-
-  update in local git, then run lvc-kick  (FIXME add lvc-kick to git)
-  use the web interface to build the job (FIXME see if we can nudge the job from lvc-kick)
-
-  ssh -Y -C -L 5900:localhost:5900 jekinstest
-
-  Then we can look at the install (if the --vnc-server-only option has been passed to cucumber ... see bin/lvc.sh) with this:
-
-    xvncviewer -ViewOnly localhost:0
-
-  (you should make sure to use -ViewOnly, since any mouse movement can affect the ability of Sikuli to recognise things on the screen)
-
-  (run from the local system, which gets forwarded via the ssh connection)
-
-  When the install fails, you should get a screenshot of the last moment -- that can be grabbed and cut down for the next match)
-
-  It seems that including a colour transition, such as the edge of a frame, in the thing you're looking for makes life easier for sikuli to find it, but may make it more fragile, we'll see)
-
-  At present, sadly the tesseract OCR stuff spews vast numbers of vlog messages, making it unusable -- this has been fixed upstream (in sikuli IIRC, or maybe cucumber -- something that's now version 1.x but which has not yet hit Debian ... we can use that once it's packaged)
-
-
diff --git a/cucumber/bin/run_test_suite b/cucumber/bin/run_test_suite
deleted file mode 100755
index fe05e69..0000000
--- a/cucumber/bin/run_test_suite
+++ /dev/null
@@ -1,283 +0,0 @@
-#!/bin/bash
-
-set -e
-set -u
-set -o pipefail
-
-NAME=$(basename ${0})
-
-GENERAL_DEPENDENCIES="
-cucumber
-devscripts
-dnsmasq-base
-gawk
-git
-i18nspector
-libav-tools
-libcap2-bin
-libsikuli-script-java
-libvirt-clients
-libvirt-daemon-system
-libvirt-dev
-libvirt0
-openssh-server
-ovmf
-pry
-python-jabberbot
-python-potr
-qemu-kvm
-qemu-system-x86
-redir
-ruby-guestfs
-ruby-json
-ruby-libvirt
-ruby-net-irc
-ruby-packetfu
-ruby-rb-inotify
-ruby-rjb
-ruby-rspec
-ruby-test-unit
-seabios
-tcpdump
-unclutter
-virt-viewer
-xvfb
-"
-
-usage() {
-    echo "Usage: $NAME [OPTION]... [--] [CUCUMBER_ARGS]...
-Sets up an appropriate environment and invokes cucumber. Note that this script
-must be run from the Tails source directory root.
-
-Options for '@product' features:
-  --artifacts-base-uri URI
-                     Pretend that the artifact is located at URI when printing
-                     its location during a scenario failure. This is useful if
-                     you intend to serve the artifacts via the web, for
-                     instance.
-  --capture          Captures failed scenarios into videos stored in the
-                     temporary directory (see --tmpdir below) using x264
-                     encoding. Requires x264.
-  --capture-all      Keep videos for all scenarios, including those that
-                     succeed (implies --capture).
-  --interactive-debugging
-                     On failure, pause test suite until pressing Enter. Also
-                     offer the option to open an interactive Ruby shell (pry)
-                     in the Cucumber world's context.
-  --keep-snapshots   Don't ever delete any snapshots (including ones marked as
-                     temporary). This can be a big time saver when debugging new
-                     features.
-  --retry-find       Print a warning whenever Sikuli fails to find an image
-                     and allow *one* retry after pressing ENTER. This is useful
-                     for updating outdated images.
-  --tmpdir           Directory where various temporary files are written
-                     during a test, e.g. VM snapshots and memory dumps,
-                     failure screenshots, pcap files and disk images
-                     (default is TMPDIR in the environment, and if unset,
-                     /tmp/DebianToaster).
-  --view             Shows the test session in a windows. Requires x11vnc
-                     and tigervnc-viewer.
-  --vnc-server-only  Starts a VNC server for the test session. Requires x11vnc.
-  --iso IMAGE        Test '@product' features using IMAGE.
-  --old-iso IMAGE    For some '@product' features (e.g. usb_install) we need
-                     an older version of Tails, which this options sets to
-                     IMAGE. If none is given, it defaults to the same IMAGE
-                     given by --iso, which will be good enough for most testing
-                     purposes.
-
-Note that '@source' features has no relevant options.
-
-CUCUMBER_ARGS can be used to specify which features to be run, but also any
-cucumber option, although then you must pass \`--\` first to let this wrapper
-script know that we're done with *its* options. For debugging purposes, a
-'debug' formatter has been added so pretty debugging can be enabled with
-\`--format debug\`. You could even combine the default (pretty) formatter with
-pretty debugging printed to a file with \`--format pretty --format debug
---out debug.log\`.
-"
-}
-
-error() {
-    echo "${NAME}: error: ${*}" >&2
-    usage
-    exit 1
-}
-
-package_installed() {
-    local ret
-    set +o pipefail
-    if dpkg -s "${1}" 2>/dev/null | grep -q "^Status:.*installed"; then
-        ret=0
-    else
-        ret=1
-    fi
-    set -o pipefail
-    return ${ret}
-}
-
-check_dependencies() {
-    while [ -n "${1:-}" ]; do
-        if ! which "${1}" >/dev/null && ! package_installed "${1}" ; then
-            error "'${1}' is missing, please install it and run again."
-        fi
-        shift
-    done
-}
-
-display_in_use() {
-    [ -e "/tmp/.X${1#:}-lock" ] || [ -e "/tmp/.X11-unix/X${1#:}" ]
-}
-
-next_free_display() {
-    display_nr=0
-    while display_in_use ":${display_nr}"; do
-	display_nr=$((display_nr+1))
-    done
-    echo ":${display_nr}"
-}
-
-test_suite_cleanup() {
-    (kill -0 ${XVFB_PID} 2>/dev/null && kill ${XVFB_PID}) || /bin/true
-}
-
-start_xvfb() {
-    Xvfb $TARGET_DISPLAY -screen 0 1024x768x24+32 >/dev/null 2>&1 &
-    XVFB_PID=$!
-    # Wait for Xvfb to run on TARGET_DISPLAY
-    until display_in_use $TARGET_DISPLAY; do
-	sleep 1
-    done
-    echo "Virtual X framebuffer started on display ${TARGET_DISPLAY}"
-    # Hide the mouse cursor so it won't mess up Sikuli's screen scanning
-    unclutter -display $TARGET_DISPLAY -root -idle 0 >/dev/null 2>&1 &
-}
-
-start_vnc_server() {
-    check_dependencies x11vnc
-    VNC_SERVER_PORT="$(x11vnc -listen localhost -display ${TARGET_DISPLAY} \
-                              -bg -nopw -forever 2>&1 | \
-                                  grep -m 1 "^PORT=[0-9]\+" | sed 's/^PORT=//')"
-    echo "VNC server running on: localhost:${VNC_SERVER_PORT}"
-}
-
-start_vnc_viewer() {
-    check_dependencies tigervnc-viewer
-    xtigervncviewer -nojpeg -viewonly localhost:${VNC_SERVER_PORT} 1>/dev/null 2>&1 &
-}
-
-capture_session() {
-    check_dependencies libvpx1
-    echo "Capturing guest display into ${CAPTURE_FILE}"
-    avconv -f x11grab -s 1024x768 -r 5 -i ${TARGET_DISPLAY}.0 -an \
-        -vcodec libvpx -y "${CAPTURE_FILE}" >/dev/null 2>&1 &
-}
-
-# main script
-
-# Unset all environment variables used by this script to pass options
-# to cucumber, except TMPDIR since we explicitly want to support
-# setting it that way.
-ARTIFACTS_BASE_URI=
-CAPTURE=
-CAPTURE_ALL=
-LOG_FILE=
-VNC_VIEWER=
-VNC_SERVER=
-INTERACTIVE_DEBUGGING=
-KEEP_SNAPSHOTS=
-SIKULI_RETRY_FINDFAILED=
-ISO=
-OLD_ISO=
-
-LONGOPTS="artifacts-base-uri:,view,vnc-server-only,capture,capture-all,help,tmpdir:,keep-snapshots,retry-find,iso:,old-iso:,interactive-debugging"
-OPTS=$(getopt -o "" --longoptions $LONGOPTS -n "${NAME}" -- "$@")
-eval set -- "$OPTS"
-while [ $# -gt 0 ]; do
-    case $1 in
-        --artifacts-base-uri)
-            shift
-            export ARTIFACTS_BASE_URI="${1}"
-            ;;
-        --view)
-            VNC_VIEWER=yes
-            VNC_SERVER=yes
-            ;;
-        --vnc-server-only)
-            VNC_VIEWER=
-            VNC_SERVER=yes
-            ;;
-        --capture)
-            check_dependencies x264
-            export CAPTURE="yes"
-            ;;
-        --capture-all)
-            check_dependencies x264
-            export CAPTURE="yes"
-            export CAPTURE_ALL="yes"
-            ;;
-        --interactive-debugging)
-            export INTERACTIVE_DEBUGGING="yes"
-            ;;
-        --keep-snapshots)
-            export KEEP_SNAPSHOTS="yes"
-            ;;
-        --retry-find)
-            export SIKULI_RETRY_FINDFAILED="yes"
-            ;;
-        --tmpdir)
-            shift
-            export TMPDIR="$(readlink -f $1)"
-            ;;
-        --iso)
-            shift
-            export ISO="$(readlink -f $1)"
-            ;;
-        --old-iso)
-            shift
-            export OLD_ISO="$(readlink -f $1)"
-            ;;
-        --help)
-	    usage
-            exit 0
-            ;;
-        --)
-            shift
-            break
-            ;;
-    esac
-    shift
-done
-
-trap "test_suite_cleanup" EXIT HUP INT QUIT TERM
-
-check_dependencies ${GENERAL_DEPENDENCIES}
-
-TARGET_DISPLAY=$(next_free_display)
-
-start_xvfb
-
-if [ -n "${VNC_SERVER:-}" ]; then
-    start_vnc_server
-fi
-if [ -n "${VNC_VIEWER:-}" ]; then
-    start_vnc_viewer
-fi
-
-export SIKULI_HOME="/usr/share/java"
-export SIKULI_IMAGE_PATH="/srv/jenkins/cucumber/features/images/"
-export RUBYLIB="/srv/jenkins"
-export VM_XML_PATH="/srv/jenkins/cucumber/features/domains"
-export DISPLAY=${TARGET_DISPLAY}
-check_dependencies cucumber
-
-# cludge ruby to stop buffering output
-RUBY_STDOUT_SYNC=$TMPDIR/.stdout-sync.rb
-echo STDOUT.sync = true > $RUBY_STDOUT_SYNC
-export RUBYOPT="-r $RUBY_STDOUT_SYNC"
-
-unset http_proxy
-
-echo "PWD = $(pwd)"
-[ -e features ] || ln -s /srv/jenkins/cucumber/features
-
-cucumber ${@}
diff --git a/cucumber/features/config/defaults.yml b/cucumber/features/config/defaults.yml
deleted file mode 100644
index bd06307..0000000
--- a/cucumber/features/config/defaults.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-CAPTURE: false
-CAPTURE_ALL: false
-INTERACTIVE_DEBUGGING: false
-MAX_NEW_TOR_CIRCUIT_RETRIES: 10
-SIKULI_RETRY_FINDFAILED: false
-TMPDIR: "/tmp/DebianToaster"
-
-Unsafe_SSH_private_key: |
-  -----BEGIN RSA PRIVATE KEY-----
-  MIIEowIBAAKCAQEAvMUNgUUM/kyuo26m+Xw7igG6zgGFMFbS3u8m5StGsJOn7zLi
-  J8P5Mml/R+4tdOS6owVU4RaZTPsNZZK/ClYmOPhmNvJ04pVChk2DZ8AARg/TANj3
-  qjKs3D+MeKbk1bt6EsA55kgGsTUky5Ti8cc2Wna25jqjagIiyM822PGG9mmI6/zL
-  YR6QLUizNaciXrRM3Q4R4sQkEreVlHeonPEiGUs9zx0swCpLtPM5UIYte1PVHgkw
-  ePsU6vM8UqVTK/VwtLLgLanXnsMFuzq7DTAXPq49+XSFNq4JlxbEF6+PQXZvYZ5N
-  eW00Gq7NSpPP8uoHr6f1J+mMxxnM85jzYtRx+QIDAQABAoIBAA8Bs1MlhCTrP67q
-  awfGYo1UGd+qq0XugREL/hGV4SbEdkNDzkrO/46MaHv1aVOzo0q2b8r9Gu7NvoDm
-  q51Mv/kjdizEFZq1tvYqT1n+H4dyVpnopbe4E5nmy2oECokbQFchRPkTnMSVrvko
-  OupxpdaHPX8MBlW1GcLRBlE00j/gfK1SXX5rcxkF5EHVND1b6iHddTPearDbU8yr
-  wga1XO6WeohAYzqmGtMD0zk6lOk0LmnTNG6WvHiFTAc/0yTiKub6rNOIEMS/82+V
-  l437H0hKcIN/7/mf6FpqRNPJTuhOVFf+L4G/ZQ8zHoMGVIbhuTiIPqZ/KMu3NaUF
-  R634jckCgYEA+jJ31hom/d65LfxWPkmiSkNTEOTfjbfcgpfc7sS3enPsYnfnmn5L
-  O3JJzAKShSVP8NVuPN5Mg5FGp9QLKrN3kV6QWQ3EnqeW748DXMU6zKGJQ5wo7ZVm
-  w2DhJ/3PAuBTL/5X4mjPQL+dr86Aq2JBDC7LHJs40I8O7UbhnsdMxKcCgYEAwSXc
-  3znAkAX8o2g37RiAl36HdONgxr2eaGK7OExp03pbKmoISw6bFbVpicBy6eTytn0A
-  2PuFcBKJRfKrViHyiE8UfAJ31JbUaxpg4bFF6UEszN4CmgKS8fnwEe1aX0qSjvkE
-  NQSuhN5AfykXY/1WVIaWuC500uB7Ow6M16RDyF8CgYEAqFTeNYlg5Hs+Acd9SukF
-  rItBTuN92P5z+NUtyuNFQrjNuK5Nf68q9LL/Hag5ZiVldHZUddVmizpp3C6Y2MDo
-  WEDUQ2Y0/D1rGoAQ1hDIb7bbAEcHblmPSzJaKirkZV4B+g9Yl7bGghypfggkn6o6
-  c3TkKLnybrdhZpjC4a3bY48CgYBnWRYdD27c4Ycz/GDoaZLs/NQIFF5FGVL4cdPR
-  pPl/IdpEEKZNWwxaik5lWedjBZFlWe+pKrRUqmZvWhCZruJyUzYXwM5Tnz0b7epm
-  +Q76Z1hMaoKj27q65UyymvkfQey3ucCpic7D45RJNjiA1R5rbfSZqqnx6BGoIPn1
-  rLxkKwKBgDXiWeUKJCydj0NfHryGBkQvaDahDE3Yigcma63b8vMZPBrJSC4SGAHJ
-  NWema+bArbaF0rKVJpwvpkZWGcr6qRn94Ts0kJAzR+VIVTOjB9sVwdxjadwWHRs5
-  kKnpY0tnSF7hyVRwN7GOsNDJEaFjCW7k4+55D2ZNBy2iN3beW8CZ
-  -----END RSA PRIVATE KEY-----
-Unsafe_SSH_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8xQ2BRQz+TK6jbqb5fDuKAbrOAYUwVtLe7yblK0awk6fvMuInw/kyaX9H7i105LqjBVThFplM+w1lkr8KViY4+GY28nTilUKGTYNnwABGD9MA2PeqMqzcP4x4puTVu3oSwDnmSAaxNSTLlOLxxzZadrbmOqNqAiLIzzbY8Yb2aYjr/MthHpAtSLM1pyJetEzdDhHixCQSt5WUd6ic8SIZSz3PHSzAKku08zlQhi17U9UeCTB4+xTq8zxSpVMr9XC0suAtqdeewwW7OrsNMBc+rj35dIU2rgmXFsQXr49Bdm9hnk15bTQars1Kk8/y6gevp/Un6YzHGczzmPNi1HH5 amnesia at amnesia"
diff --git a/cucumber/features/domains/cdrom.xml b/cucumber/features/domains/cdrom.xml
deleted file mode 100644
index 8bc3be7..0000000
--- a/cucumber/features/domains/cdrom.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<disk type='file' device='cdrom'>
-  <driver name='qemu' type='raw'/>
-  <target dev='hdc' bus='sata'/>
-  <readonly/>
-</disk>
diff --git a/cucumber/features/domains/default.xml b/cucumber/features/domains/default.xml
deleted file mode 100644
index 36e0601..0000000
--- a/cucumber/features/domains/default.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
-  <memory unit='KiB'>1310720</memory>
-  <currentMemory unit='KiB'>1310720</currentMemory>
-  <vcpu>2</vcpu>
-  <os>
-    <type arch='x86_64' machine='pc-q35-2.8'>hvm</type>
-    <boot dev='cdrom'/>
-  </os>
-  <features>
-    <acpi/>
-    <apic/>
-    <pae/>
-  </features>
-  <cpu mode='custom' match='exact'>
-    <model fallback='allow'>kvm64</model>
-  </cpu>
-  <clock offset='utc'/>
-  <on_poweroff>destroy</on_poweroff>
-  <on_reboot>restart</on_reboot>
-  <on_crash>restart</on_crash>
-  <devices>
-    <emulator>/usr/bin/qemu-system-x86_64</emulator>
-    <controller type='usb' index='0' model='ich9-ehci1'/>
-    <controller type='usb' index='0' model='ich9-uhci1'/>
-    <controller type='usb' index='0' model='ich9-uhci2'/>
-    <controller type='usb' index='0' model='ich9-uhci3'/>
-    <controller type='sata' index='0'/>
-    <controller type='virtio-serial' index='0'/>
-    <interface type='network'>
-      <alias name='net0'/>
-      <!-- <mac address='52:54:00:ac:dd:ee'/> -->
-      <source network='lvcNET'/>
-      <model type='virtio'/>
-      <link state='up'/>
-    </interface>
-<!--   FIXME need to uniquify the port, based on EXECUTOR_NUMBER -->
-    <serial type='tcp'>
-      <source mode="bind" host='127.0.0.1' service='1337'/>
-      <target port='0'/>
-    </serial>
-    <input type='tablet' bus='usb'/>
-    <channel type='spicevmc'>
-      <target type='virtio' name='com.redhat.spice.0'/>
-    </channel>
-    <graphics type='spice' port='-1' tlsPort='-1' autoport='yes'>
-      <mouse mode='client'/>
-    </graphics>
-    <sound model='ich6'/>
-    <video>
-      <model type='qxl' ram='65536' vram='131072' vgamem='16384' heads='1'/>
-    </video>
-    <memballoon model='virtio'/>
-    <rng model='virtio'>
-      <backend model='random'>/dev/random</backend>
-    </rng>
-  </devices>
-</domain>
-
diff --git a/cucumber/features/domains/default_net.xml b/cucumber/features/domains/default_net.xml
deleted file mode 100644
index e9a575d..0000000
--- a/cucumber/features/domains/default_net.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<network>
-  <forward mode='nat'/>
-  <bridge name='virbr10' stp='on' delay='0' />
-  <ip address='10.2.1.1' netmask='255.255.255.0'>
-    <dhcp>
-      <range start='10.2.1.2' end='10.2.1.254' />
-      <!-- <host mac="52:54:00:ac:dd:ee" name="amnesia" ip="10.2.1.2" /> -->
-      <!-- <host mac="52:54:00:11:22:33" name="amnesia" ip="10.2.1.3" /> -->
-    </dhcp>
-  </ip>
-  <ip family="ipv6" address="fc00::1" prefix="7" />
-</network>
-
diff --git a/cucumber/features/domains/disk.xml b/cucumber/features/domains/disk.xml
deleted file mode 100644
index 8193fea..0000000
--- a/cucumber/features/domains/disk.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<disk type='file' device='disk'>
-  <driver name='qemu' type=''/>
-  <source file=''/>
-  <target dev='' bus=''/>
-</disk>
diff --git a/cucumber/features/domains/storage_pool.xml b/cucumber/features/domains/storage_pool.xml
deleted file mode 100644
index 1e0d6bb..0000000
--- a/cucumber/features/domains/storage_pool.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<pool type="dir">
-  <name></name>
-  <target>
-    <path></path>
-  </target>
-</pool>
diff --git a/cucumber/features/domains/volume.xml b/cucumber/features/domains/volume.xml
deleted file mode 100644
index 702d5a0..0000000
--- a/cucumber/features/domains/volume.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<volume>
-  <name></name>
-  <allocation>0</allocation>
-  <capacity unit="b"></capacity>
-  <target>
-    <path></path>
-    <format type='qcow2'/>
-    <permissions>
-      <owner></owner>
-      <group></group>
-      <mode>0664</mode>
-    </permissions>
-  </target>
-</volume>
diff --git a/cucumber/features/images/DebianLive7BootSplash.png b/cucumber/features/images/DebianLive7BootSplash.png
deleted file mode 100644
index b64353a..0000000
Binary files a/cucumber/features/images/DebianLive7BootSplash.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLive7BootSplashTabMsg.png b/cucumber/features/images/DebianLive7BootSplashTabMsg.png
deleted file mode 100644
index 150830b..0000000
Binary files a/cucumber/features/images/DebianLive7BootSplashTabMsg.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLive7Greeter.png b/cucumber/features/images/DebianLive7Greeter.png
deleted file mode 100644
index f1afaab..0000000
Binary files a/cucumber/features/images/DebianLive7Greeter.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLiveBootSplash.png b/cucumber/features/images/DebianLiveBootSplash.png
deleted file mode 100644
index 11ee149..0000000
Binary files a/cucumber/features/images/DebianLiveBootSplash.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLiveBootSplashTabMsg.png b/cucumber/features/images/DebianLiveBootSplashTabMsg.png
deleted file mode 100644
index cdddaf1..0000000
Binary files a/cucumber/features/images/DebianLiveBootSplashTabMsg.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptGnome.png b/cucumber/features/images/DebianLoginPromptGnome.png
deleted file mode 100644
index 4344960..0000000
Binary files a/cucumber/features/images/DebianLoginPromptGnome.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptKDE.png b/cucumber/features/images/DebianLoginPromptKDE.png
deleted file mode 100644
index 9b14e55..0000000
Binary files a/cucumber/features/images/DebianLoginPromptKDE.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptKDE_d9.png b/cucumber/features/images/DebianLoginPromptKDE_d9.png
deleted file mode 100644
index f02f25f..0000000
Binary files a/cucumber/features/images/DebianLoginPromptKDE_d9.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptLXDE.png b/cucumber/features/images/DebianLoginPromptLXDE.png
deleted file mode 100644
index f2892b7..0000000
Binary files a/cucumber/features/images/DebianLoginPromptLXDE.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptLXDE_d9.png b/cucumber/features/images/DebianLoginPromptLXDE_d9.png
deleted file mode 100644
index cc768ad..0000000
Binary files a/cucumber/features/images/DebianLoginPromptLXDE_d9.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptVT.png b/cucumber/features/images/DebianLoginPromptVT.png
deleted file mode 100644
index eac19ef..0000000
Binary files a/cucumber/features/images/DebianLoginPromptVT.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptVT_Edu.png b/cucumber/features/images/DebianLoginPromptVT_Edu.png
deleted file mode 100644
index 23a2155..0000000
Binary files a/cucumber/features/images/DebianLoginPromptVT_Edu.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptVT_d9.png b/cucumber/features/images/DebianLoginPromptVT_d9.png
deleted file mode 100644
index 4b21de8..0000000
Binary files a/cucumber/features/images/DebianLoginPromptVT_d9.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptXFCE.png b/cucumber/features/images/DebianLoginPromptXFCE.png
deleted file mode 100644
index f2892b7..0000000
Binary files a/cucumber/features/images/DebianLoginPromptXFCE.png and /dev/null differ
diff --git a/cucumber/features/images/DebianLoginPromptXFCE_d9.png b/cucumber/features/images/DebianLoginPromptXFCE_d9.png
deleted file mode 100644
index 5e23ce4..0000000
Binary files a/cucumber/features/images/DebianLoginPromptXFCE_d9.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_boot_graphical-default.png b/cucumber/features/images/d-i_boot_graphical-default.png
deleted file mode 100644
index 0a2e06f..0000000
Binary files a/cucumber/features/images/d-i_boot_graphical-default.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_boot_graphical-default_9alpha.png b/cucumber/features/images/d-i_boot_graphical-default_9alpha.png
deleted file mode 100644
index ac3063c..0000000
Binary files a/cucumber/features/images/d-i_boot_graphical-default_9alpha.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_boot_miniiso.png b/cucumber/features/images/d-i_boot_miniiso.png
deleted file mode 100644
index 166d01e..0000000
Binary files a/cucumber/features/images/d-i_boot_miniiso.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_boot_text-default.png b/cucumber/features/images/d-i_boot_text-default.png
deleted file mode 100644
index a1138fa..0000000
Binary files a/cucumber/features/images/d-i_boot_text-default.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_bootmenu_graphical.png b/cucumber/features/images/d-i_bootmenu_graphical.png
deleted file mode 100644
index f1c116a..0000000
Binary files a/cucumber/features/images/d-i_bootmenu_graphical.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_ArchiveMirror.png b/cucumber/features/images/d-i_gui_ArchiveMirror.png
deleted file mode 100644
index d965843..0000000
Binary files a/cucumber/features/images/d-i_gui_ArchiveMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_BadMirror.png b/cucumber/features/images/d-i_gui_BadMirror.png
deleted file mode 100644
index d8a09a1..0000000
Binary files a/cucumber/features/images/d-i_gui_BadMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_BritishEnglish.png b/cucumber/features/images/d-i_gui_BritishEnglish.png
deleted file mode 100644
index 466fbec..0000000
Binary files a/cucumber/features/images/d-i_gui_BritishEnglish.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_CONTINUEselected.png b/cucumber/features/images/d-i_gui_CONTINUEselected.png
deleted file mode 100644
index 436eeb6..0000000
Binary files a/cucumber/features/images/d-i_gui_CONTINUEselected.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_CONTINUEunselected.png b/cucumber/features/images/d-i_gui_CONTINUEunselected.png
deleted file mode 100644
index be82588..0000000
Binary files a/cucumber/features/images/d-i_gui_CONTINUEunselected.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_CheckSyslog.png b/cucumber/features/images/d-i_gui_CheckSyslog.png
deleted file mode 100644
index 9c52c29..0000000
Binary files a/cucumber/features/images/d-i_gui_CheckSyslog.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_ChooseSoftware.png b/cucumber/features/images/d-i_gui_ChooseSoftware.png
deleted file mode 100644
index c34906c..0000000
Binary files a/cucumber/features/images/d-i_gui_ChooseSoftware.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Desktop+Gnome.png b/cucumber/features/images/d-i_gui_Desktop+Gnome.png
deleted file mode 100644
index 3d34917..0000000
Binary files a/cucumber/features/images/d-i_gui_Desktop+Gnome.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Desktop+KDE.png b/cucumber/features/images/d-i_gui_Desktop+KDE.png
deleted file mode 100644
index 9a02b7a..0000000
Binary files a/cucumber/features/images/d-i_gui_Desktop+KDE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Desktop+LXDE.png b/cucumber/features/images/d-i_gui_Desktop+LXDE.png
deleted file mode 100644
index 39bbf2b..0000000
Binary files a/cucumber/features/images/d-i_gui_Desktop+LXDE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Desktop+XFCE.png b/cucumber/features/images/d-i_gui_Desktop+XFCE.png
deleted file mode 100644
index 227b881..0000000
Binary files a/cucumber/features/images/d-i_gui_Desktop+XFCE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_DesktopTask_No.png b/cucumber/features/images/d-i_gui_DesktopTask_No.png
deleted file mode 100644
index 7e2cbc2..0000000
Binary files a/cucumber/features/images/d-i_gui_DesktopTask_No.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_DesktopTask_Yes.png b/cucumber/features/images/d-i_gui_DesktopTask_Yes.png
deleted file mode 100644
index c92a8f6..0000000
Binary files a/cucumber/features/images/d-i_gui_DesktopTask_Yes.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_DomainName.png b/cucumber/features/images/d-i_gui_DomainName.png
deleted file mode 100644
index 045ab5a..0000000
Binary files a/cucumber/features/images/d-i_gui_DomainName.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Edu-AutoPartition.png b/cucumber/features/images/d-i_gui_Edu-AutoPartition.png
deleted file mode 100644
index 9bef4d3..0000000
Binary files a/cucumber/features/images/d-i_gui_Edu-AutoPartition.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Edu-LTSPchroot.png b/cucumber/features/images/d-i_gui_Edu-LTSPchroot.png
deleted file mode 100644
index f5a13ab..0000000
Binary files a/cucumber/features/images/d-i_gui_Edu-LTSPchroot.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Edu-Profile.png b/cucumber/features/images/d-i_gui_Edu-Profile.png
deleted file mode 100644
index d9d1df8..0000000
Binary files a/cucumber/features/images/d-i_gui_Edu-Profile.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_English.png b/cucumber/features/images/d-i_gui_English.png
deleted file mode 100644
index 4aadb34..0000000
Binary files a/cucumber/features/images/d-i_gui_English.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_EnterTheHostname.png b/cucumber/features/images/d-i_gui_EnterTheHostname.png
deleted file mode 100644
index c3c9b7d..0000000
Binary files a/cucumber/features/images/d-i_gui_EnterTheHostname.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_FinishPartitioning.png b/cucumber/features/images/d-i_gui_FinishPartitioning.png
deleted file mode 100644
index 2d72176..0000000
Binary files a/cucumber/features/images/d-i_gui_FinishPartitioning.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_GRUBEnterDev.png b/cucumber/features/images/d-i_gui_GRUBEnterDev.png
deleted file mode 100644
index 05ec9e8..0000000
Binary files a/cucumber/features/images/d-i_gui_GRUBEnterDev.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_GRUBdev.png b/cucumber/features/images/d-i_gui_GRUBdev.png
deleted file mode 100644
index 2e96df0..0000000
Binary files a/cucumber/features/images/d-i_gui_GRUBdev.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_HttpProxy.png b/cucumber/features/images/d-i_gui_HttpProxy.png
deleted file mode 100644
index bed3ade..0000000
Binary files a/cucumber/features/images/d-i_gui_HttpProxy.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallComplete.png b/cucumber/features/images/d-i_gui_InstallComplete.png
deleted file mode 100644
index c38b9cb..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallComplete.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallGRUB-heading.png b/cucumber/features/images/d-i_gui_InstallGRUB-heading.png
deleted file mode 100644
index bfd59e6..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallGRUB-heading.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallGRUB.png b/cucumber/features/images/d-i_gui_InstallGRUB.png
deleted file mode 100644
index 63332a5..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallGRUB.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallSoftware.png b/cucumber/features/images/d-i_gui_InstallSoftware.png
deleted file mode 100644
index a2551fc..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallSoftware.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallationStepFailed.png b/cucumber/features/images/d-i_gui_InstallationStepFailed.png
deleted file mode 100644
index 709669c..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallationStepFailed.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_InstallingBaseSystem.png b/cucumber/features/images/d-i_gui_InstallingBaseSystem.png
deleted file mode 100644
index 90147be..0000000
Binary files a/cucumber/features/images/d-i_gui_InstallingBaseSystem.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_MirrorCountry.png b/cucumber/features/images/d-i_gui_MirrorCountry.png
deleted file mode 100644
index ef5fe2a..0000000
Binary files a/cucumber/features/images/d-i_gui_MirrorCountry.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_NameOfUser.png b/cucumber/features/images/d-i_gui_NameOfUser.png
deleted file mode 100644
index bbe538f..0000000
Binary files a/cucumber/features/images/d-i_gui_NameOfUser.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_No.png b/cucumber/features/images/d-i_gui_No.png
deleted file mode 100644
index 7bca8a1..0000000
Binary files a/cucumber/features/images/d-i_gui_No.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_NoKernelModules.png b/cucumber/features/images/d-i_gui_NoKernelModules.png
deleted file mode 100644
index b89b6df..0000000
Binary files a/cucumber/features/images/d-i_gui_NoKernelModules.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_PartitionDisks.png b/cucumber/features/images/d-i_gui_PartitionDisks.png
deleted file mode 100644
index 809c4f4..0000000
Binary files a/cucumber/features/images/d-i_gui_PartitionDisks.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_PartitioningMethod.png b/cucumber/features/images/d-i_gui_PartitioningMethod.png
deleted file mode 100644
index cb668f1..0000000
Binary files a/cucumber/features/images/d-i_gui_PartitioningMethod.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_PartitioningScheme.png b/cucumber/features/images/d-i_gui_PartitioningScheme.png
deleted file mode 100644
index 92688e6..0000000
Binary files a/cucumber/features/images/d-i_gui_PartitioningScheme.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_RootPassword.png b/cucumber/features/images/d-i_gui_RootPassword.png
deleted file mode 100644
index 58a4a66..0000000
Binary files a/cucumber/features/images/d-i_gui_RootPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_ScanCD.png b/cucumber/features/images/d-i_gui_ScanCD.png
deleted file mode 100644
index 93fea2b..0000000
Binary files a/cucumber/features/images/d-i_gui_ScanCD.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_SelectDiskToPartition.png b/cucumber/features/images/d-i_gui_SelectDiskToPartition.png
deleted file mode 100644
index f5c045c..0000000
Binary files a/cucumber/features/images/d-i_gui_SelectDiskToPartition.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_SelectYourLocation.png b/cucumber/features/images/d-i_gui_SelectYourLocation.png
deleted file mode 100644
index b253eb1..0000000
Binary files a/cucumber/features/images/d-i_gui_SelectYourLocation.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_ShowRootPassword.png b/cucumber/features/images/d-i_gui_ShowRootPassword.png
deleted file mode 100644
index 598b019..0000000
Binary files a/cucumber/features/images/d-i_gui_ShowRootPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_ShowUserPassword.png b/cucumber/features/images/d-i_gui_ShowUserPassword.png
deleted file mode 100644
index b70cf92..0000000
Binary files a/cucumber/features/images/d-i_gui_ShowUserPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_UnitedKingdom.png b/cucumber/features/images/d-i_gui_UnitedKingdom.png
deleted file mode 100644
index c3cc54e..0000000
Binary files a/cucumber/features/images/d-i_gui_UnitedKingdom.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_UseNetMirror.png b/cucumber/features/images/d-i_gui_UseNetMirror.png
deleted file mode 100644
index 58dd466..0000000
Binary files a/cucumber/features/images/d-i_gui_UseNetMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_UserPassword.png b/cucumber/features/images/d-i_gui_UserPassword.png
deleted file mode 100644
index 6be3491..0000000
Binary files a/cucumber/features/images/d-i_gui_UserPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_Yes.png b/cucumber/features/images/d-i_gui_Yes.png
deleted file mode 100644
index 2c84ad2..0000000
Binary files a/cucumber/features/images/d-i_gui_Yes.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_gui_popcon.png b/cucumber/features/images/d-i_gui_popcon.png
deleted file mode 100644
index 7eb0fe2..0000000
Binary files a/cucumber/features/images/d-i_gui_popcon.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_ArchiveMirror.png b/cucumber/features/images/d-i_text_ArchiveMirror.png
deleted file mode 100644
index 7e53f18..0000000
Binary files a/cucumber/features/images/d-i_text_ArchiveMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_BadMirror.png b/cucumber/features/images/d-i_text_BadMirror.png
deleted file mode 100644
index 1c34e98..0000000
Binary files a/cucumber/features/images/d-i_text_BadMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_BritishEnglish.png b/cucumber/features/images/d-i_text_BritishEnglish.png
deleted file mode 100644
index c0da761..0000000
Binary files a/cucumber/features/images/d-i_text_BritishEnglish.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_CheckSyslog.png b/cucumber/features/images/d-i_text_CheckSyslog.png
deleted file mode 100644
index f7d18b2..0000000
Binary files a/cucumber/features/images/d-i_text_CheckSyslog.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_ChooseSoftware.png b/cucumber/features/images/d-i_text_ChooseSoftware.png
deleted file mode 100644
index cf239a8..0000000
Binary files a/cucumber/features/images/d-i_text_ChooseSoftware.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Desktop+Gnome.png b/cucumber/features/images/d-i_text_Desktop+Gnome.png
deleted file mode 100644
index 476c8ab..0000000
Binary files a/cucumber/features/images/d-i_text_Desktop+Gnome.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Desktop+KDE.png b/cucumber/features/images/d-i_text_Desktop+KDE.png
deleted file mode 100644
index 3a87b2f..0000000
Binary files a/cucumber/features/images/d-i_text_Desktop+KDE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Desktop+LXDE.png b/cucumber/features/images/d-i_text_Desktop+LXDE.png
deleted file mode 100644
index 1c71ba1..0000000
Binary files a/cucumber/features/images/d-i_text_Desktop+LXDE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Desktop+XFCE.png b/cucumber/features/images/d-i_text_Desktop+XFCE.png
deleted file mode 100644
index bb4a289..0000000
Binary files a/cucumber/features/images/d-i_text_Desktop+XFCE.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_DesktopTask_No.png b/cucumber/features/images/d-i_text_DesktopTask_No.png
deleted file mode 100644
index 6dbf9df..0000000
Binary files a/cucumber/features/images/d-i_text_DesktopTask_No.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_DesktopTask_Yes.png b/cucumber/features/images/d-i_text_DesktopTask_Yes.png
deleted file mode 100644
index 4a00068..0000000
Binary files a/cucumber/features/images/d-i_text_DesktopTask_Yes.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_DomainName.png b/cucumber/features/images/d-i_text_DomainName.png
deleted file mode 100644
index d7fca5f..0000000
Binary files a/cucumber/features/images/d-i_text_DomainName.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Edu-AutoPartition.png b/cucumber/features/images/d-i_text_Edu-AutoPartition.png
deleted file mode 100644
index cfc2f7f..0000000
Binary files a/cucumber/features/images/d-i_text_Edu-AutoPartition.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Edu-Profile.png b/cucumber/features/images/d-i_text_Edu-Profile.png
deleted file mode 100644
index 9c4cdc1..0000000
Binary files a/cucumber/features/images/d-i_text_Edu-Profile.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_English.png b/cucumber/features/images/d-i_text_English.png
deleted file mode 100644
index 85f848d..0000000
Binary files a/cucumber/features/images/d-i_text_English.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_EnterTheHostname.png b/cucumber/features/images/d-i_text_EnterTheHostname.png
deleted file mode 100644
index f1325c8..0000000
Binary files a/cucumber/features/images/d-i_text_EnterTheHostname.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_F12BootMenu.png b/cucumber/features/images/d-i_text_F12BootMenu.png
deleted file mode 100644
index 67a2185..0000000
Binary files a/cucumber/features/images/d-i_text_F12BootMenu.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_FinishPartitioning.png b/cucumber/features/images/d-i_text_FinishPartitioning.png
deleted file mode 100644
index 5039650..0000000
Binary files a/cucumber/features/images/d-i_text_FinishPartitioning.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_GRUBEnterDev.png b/cucumber/features/images/d-i_text_GRUBEnterDev.png
deleted file mode 100644
index 6df484e..0000000
Binary files a/cucumber/features/images/d-i_text_GRUBEnterDev.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_GRUBdev.png b/cucumber/features/images/d-i_text_GRUBdev.png
deleted file mode 100644
index 9d554d7..0000000
Binary files a/cucumber/features/images/d-i_text_GRUBdev.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_HttpProxy.png b/cucumber/features/images/d-i_text_HttpProxy.png
deleted file mode 100644
index 5b1d004..0000000
Binary files a/cucumber/features/images/d-i_text_HttpProxy.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallComplete.png b/cucumber/features/images/d-i_text_InstallComplete.png
deleted file mode 100644
index a856446..0000000
Binary files a/cucumber/features/images/d-i_text_InstallComplete.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallGRUB-heading.png b/cucumber/features/images/d-i_text_InstallGRUB-heading.png
deleted file mode 100644
index b0c2fc8..0000000
Binary files a/cucumber/features/images/d-i_text_InstallGRUB-heading.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallGRUB.png b/cucumber/features/images/d-i_text_InstallGRUB.png
deleted file mode 100644
index 9b82722..0000000
Binary files a/cucumber/features/images/d-i_text_InstallGRUB.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallSoftware.png b/cucumber/features/images/d-i_text_InstallSoftware.png
deleted file mode 100644
index b03e369..0000000
Binary files a/cucumber/features/images/d-i_text_InstallSoftware.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallationStepFailed.png b/cucumber/features/images/d-i_text_InstallationStepFailed.png
deleted file mode 100644
index cfa233a..0000000
Binary files a/cucumber/features/images/d-i_text_InstallationStepFailed.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_InstallingBaseSystem.png b/cucumber/features/images/d-i_text_InstallingBaseSystem.png
deleted file mode 100644
index 0b9e1c7..0000000
Binary files a/cucumber/features/images/d-i_text_InstallingBaseSystem.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_MirrorCountry.png b/cucumber/features/images/d-i_text_MirrorCountry.png
deleted file mode 100644
index 9b4df5e..0000000
Binary files a/cucumber/features/images/d-i_text_MirrorCountry.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_NameOfUser.png b/cucumber/features/images/d-i_text_NameOfUser.png
deleted file mode 100644
index e37c7ec..0000000
Binary files a/cucumber/features/images/d-i_text_NameOfUser.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_No.png b/cucumber/features/images/d-i_text_No.png
deleted file mode 100644
index 1108add..0000000
Binary files a/cucumber/features/images/d-i_text_No.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_NoDiskFound.png b/cucumber/features/images/d-i_text_NoDiskFound.png
deleted file mode 100644
index 671f52d..0000000
Binary files a/cucumber/features/images/d-i_text_NoDiskFound.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_PartitioningMethod.png b/cucumber/features/images/d-i_text_PartitioningMethod.png
deleted file mode 100644
index 9e44360..0000000
Binary files a/cucumber/features/images/d-i_text_PartitioningMethod.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_PartitioningScheme.png b/cucumber/features/images/d-i_text_PartitioningScheme.png
deleted file mode 100644
index 97105b6..0000000
Binary files a/cucumber/features/images/d-i_text_PartitioningScheme.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_RootPassword.png b/cucumber/features/images/d-i_text_RootPassword.png
deleted file mode 100644
index 27368fd..0000000
Binary files a/cucumber/features/images/d-i_text_RootPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_ScanCD.png b/cucumber/features/images/d-i_text_ScanCD.png
deleted file mode 100644
index 5790bcc..0000000
Binary files a/cucumber/features/images/d-i_text_ScanCD.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_SelectBootDev.png b/cucumber/features/images/d-i_text_SelectBootDev.png
deleted file mode 100644
index 7abef3e..0000000
Binary files a/cucumber/features/images/d-i_text_SelectBootDev.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_SelectDiskToPartition.png b/cucumber/features/images/d-i_text_SelectDiskToPartition.png
deleted file mode 100644
index 1f14bb1..0000000
Binary files a/cucumber/features/images/d-i_text_SelectDiskToPartition.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_SelectYourLocation.png b/cucumber/features/images/d-i_text_SelectYourLocation.png
deleted file mode 100644
index fe13099..0000000
Binary files a/cucumber/features/images/d-i_text_SelectYourLocation.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_ShowRootPassword.png b/cucumber/features/images/d-i_text_ShowRootPassword.png
deleted file mode 100644
index 27368fd..0000000
Binary files a/cucumber/features/images/d-i_text_ShowRootPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_ShowUserPassword.png b/cucumber/features/images/d-i_text_ShowUserPassword.png
deleted file mode 100644
index bf9964a..0000000
Binary files a/cucumber/features/images/d-i_text_ShowUserPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_UnitedKingdom.png b/cucumber/features/images/d-i_text_UnitedKingdom.png
deleted file mode 100644
index 279d2cf..0000000
Binary files a/cucumber/features/images/d-i_text_UnitedKingdom.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_UseNetMirror.png b/cucumber/features/images/d-i_text_UseNetMirror.png
deleted file mode 100644
index 2b41228..0000000
Binary files a/cucumber/features/images/d-i_text_UseNetMirror.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_UserPassword.png b/cucumber/features/images/d-i_text_UserPassword.png
deleted file mode 100644
index bf9964a..0000000
Binary files a/cucumber/features/images/d-i_text_UserPassword.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_Yes.png b/cucumber/features/images/d-i_text_Yes.png
deleted file mode 100644
index 17fab5b..0000000
Binary files a/cucumber/features/images/d-i_text_Yes.png and /dev/null differ
diff --git a/cucumber/features/images/d-i_text_popcon.png b/cucumber/features/images/d-i_text_popcon.png
deleted file mode 100644
index 487457c..0000000
Binary files a/cucumber/features/images/d-i_text_popcon.png and /dev/null differ
diff --git a/cucumber/features/install.feature b/cucumber/features/install.feature
deleted file mode 100644
index a90d003..0000000
--- a/cucumber/features/install.feature
+++ /dev/null
@@ -1,145 +0,0 @@
- at product
-Feature: Doing variations on d-i installs
-  As a normal user
-  I should be able to install Debian
-
-  @default @both-ui
-  Scenario Outline: Install Debian, and boot to a login prompt
-    Given I install a <target_ui> Debian system, in <install_ui> mode
-    When I start the computer
-    Then I should see a <login> Login prompt
-
-    Examples:
-      | install_ui | target_ui | login |
-      | gui        | minimal   | VT    |
-      | text       | non-GUI   | VT    |
-      #| gui        | Gnome     | Gnome |
-      #| gui        | LXDE      | LXDE  |
-      #| gui        | XFCE      | XFCE  |
-      #| gui        | KDE       | KDE   |
-
-  @gui
-  Scenario Outline: Install Debian, and boot to a login prompt
-    Given I install a <target_ui> Debian system, in gui mode
-    When I start the computer
-    Then I should see a <login> Login prompt
-
-    Examples:
-      | target_ui | login |
-      | non-GUI   | VT    |
-      | XFCE      | XFCE  |
-      | KDE       | KDE   |
-
-  @text-ui
-  Scenario Outline: Install Debian, and boot to a login prompt
-    Given I install a <target_ui> Debian system, in text mode
-    When I start the computer
-    Then I should see a <login> Login prompt
-
-    Examples:
-      | target_ui | login |
-      | minimal   | VT    |
-      | Gnome     | Gnome |
-      | LXDE      | LXDE  |
-
-  @broken
-  Scenario: Attempt to Install Gnome, expecting it to fail because X doesn't start for some reason
-    Given I have started Debian Installer in text mode and stopped at the Tasksel prompt
-    And I intend to use text mode
-    And I select the Gnome task
-    And I wait while the bulk of the packages are installed
-    And I install GRUB
-    And I allow reboot after the install is complete
-    And I wait for the reboot
-    And I power off the computer
-    And the computer is set to boot from ide drive
-    When I start the computer
-    Then I should see a Gnome Login prompt
-
-#  Scenario: Get a useful error from a bogus HTTP proxy
-#    Given I get d-i to the HTTP proxy prompt
-#    When I set the proxy to "127.23.23.23"
-#    Then I should get an error message that mentions the proxy
-
-  # this is useful for just proving that the d-i image is able to boot
-  @trivial
-  Scenario: Minimal Boot test
-    Given a disk is created for Debian Installer tests
-    And I intend to use gui mode
-    When I start the computer
-    Then I select the install mode
-
-  @preseed
-  Scenario: Preseed using hands.com with checksum
-    Given a disk is created for Debian Installer tests
-    And I intend to use gui mode
-    And I intend to boot with options: auto=true priority=critical wibble.foo=bar url=hands.com classes=jenkins.debian.org/pb10;loc/gb;hands.com/general-tweaks;setup/users;partition/atomic;desktop/lxde hands-off/checksigs=true DEBCONF_DEBUG=5
-    And I start the computer
-    And I execute "grep wibble /proc/cmdline"
-    And I execute "ls /var/lib/register-module"
-    And I select the install mode
-    And I expect package installation to start
-    And I execute "ls /var/lib/register-module"
-    And I execute "grep wibble /target/etc/modprobe.d/local.conf"
-    And I wait while the bulk of the packages are installed
-    And I execute "grep wibble /target/boot/grub/grub.cfg"
-    And the VM shuts down within 20 minutes
-    When the computer is set to boot from ide drive
-    And I start the computer
-    Then I should see a LXDE Login prompt
-
-  @bugtest
-  Scenario: Preseed using hands.com with checksum
-    Given a disk is created for Debian Installer tests
-    And I intend to use gui mode
-    And I intend to boot with options: wibble.foo=bar fsck.bar=baz
-    And I start the computer
-    And I select the install mode
-    And I select British English
-    And running "grep wibble /proc/cmdline" succeeds
-    And running "cat /var/lib/register-module/wibble.params" succeeds
-    And I accept the hostname, using "example.com" as the domain
-    And I set the root password to "rootme"
-    And I set the password for "Philip Hands" to be "verysecret"
-    And I select full-disk, single-filesystem partitioning
-    And I note that the Base system is being installed
-    And I accept the default mirror
-    And I ignore Popcon
-    And we reach the Tasksel prompt
-    And I select the LXDE task
-    And running "grep wibble /target/etc/modprobe.d/local.conf" succeeds
-    And running "grep fsck /target/etc/modprobe.d/local.conf" fails
-    And I wait while the bulk of the packages are installed
-    And I install GRUB
-    And I see the "InstallComplete" screen, after at most 240 seconds
-    And running "grep wibble /target/boot/grub/grub.cfg" succeeds
-    And I allow reboot after the install is complete
-    And I wait for the reboot
-    And I power off the computer
-    And the computer is set to boot from ide drive
-    When I start the computer
-    Then I should see a LXDE Login prompt
-
-  @debedu
-  Scenario: Install default Debian-Edu
-    Given a disk is created for Debian Edu tests
-    And I intend to use gui mode
-    And I intend to boot with options: url=hands.com/d-i/bug/edu-plymouth/preseed.cfg
-    And I start the computer
-    And I select the install mode
-    And I select British English
-    And I select Combi Debian-Edu profile
-    And I use the Debian-Edu Automatic Partitioning
-    And I ignore Popcon
-    And I set the root password to "rootme"
-    And I set the password for "Philip Hands" to be "verysecret"
-    And I wait while the partitions are made
-    And I note that the Base system is being installed
-    And I wait patiently for the package installation to start
-    And I wait while the bulk of the packages are installed
-    And I allow reboot after the install is complete
-    And I wait for the reboot
-    And I power off the computer
-    And the computer is set to boot from ide drive
-    When I start the computer
-    Then I should see a VT_Edu Login prompt
diff --git a/cucumber/features/misc_files/sample.pdf b/cucumber/features/misc_files/sample.pdf
deleted file mode 100644
index d0cc950..0000000
Binary files a/cucumber/features/misc_files/sample.pdf and /dev/null differ
diff --git a/cucumber/features/misc_files/sample.tex b/cucumber/features/misc_files/sample.tex
deleted file mode 100644
index 043faae..0000000
--- a/cucumber/features/misc_files/sample.tex
+++ /dev/null
@@ -1,8 +0,0 @@
-\documentclass[12pt]{article}
-\title{Sample PDF document}
-\author{Tails developers}
-\date{March 12, 2013}
-\begin{document}
-\maketitle
-Does this PDF still have metadata?
-\end{document}
diff --git a/cucumber/features/scripts/otr-bot.py b/cucumber/features/scripts/otr-bot.py
deleted file mode 100755
index 0afd15a..0000000
--- a/cucumber/features/scripts/otr-bot.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/python
-import sys
-import jabberbot
-import xmpp
-import potr
-import logging
-from argparse import ArgumentParser
-
-class OtrContext(potr.context.Context):
-
-    def __init__(self, account, peer):
-        super(OtrContext, self).__init__(account, peer)
-
-    def getPolicy(self, key):
-        return True
-
-    def inject(self, msg, appdata = None):
-        mess = appdata["base_reply"]
-        mess.setBody(msg)
-        appdata["send_raw_message_fn"](mess)
-
-
-class BotAccount(potr.context.Account):
-
-    def __init__(self, jid, keyFilePath):
-        protocol = 'xmpp'
-        max_message_size = 10*1024
-        super(BotAccount, self).__init__(jid, protocol, max_message_size)
-        self.keyFilePath = keyFilePath
-
-    def loadPrivkey(self):
-        with open(self.keyFilePath, 'rb') as keyFile:
-            return potr.crypt.PK.parsePrivateKey(keyFile.read())[0]
-
-
-class OtrContextManager:
-
-    def __init__(self, jid, keyFilePath):
-        self.account = BotAccount(jid, keyFilePath)
-        self.contexts = {}
-
-    def start_context(self, other):
-        if not other in self.contexts:
-            self.contexts[other] = OtrContext(self.account, other)
-        return self.contexts[other]
-
-    def get_context_for_user(self, other):
-        return self.start_context(other)
-
-
-class OtrBot(jabberbot.JabberBot):
-
-    PING_FREQUENCY = 60
-
-    def __init__(self, account, password, otr_key_path,
-                 connect_server = None, log_file = None):
-        self.__connect_server = connect_server
-        self.__password = password
-        self.__log_file = log_file
-        super(OtrBot, self).__init__(account, password)
-        self.__otr_manager = OtrContextManager(account, otr_key_path)
-        self.send_raw_message_fn = super(OtrBot, self).send_message
-        self.__default_otr_appdata = {
-            "send_raw_message_fn": self.send_raw_message_fn
-            }
-
-    def __otr_appdata_for_mess(self, mess):
-        appdata = self.__default_otr_appdata.copy()
-        appdata["base_reply"] = mess
-        return appdata
-
-    # Unfortunately Jabberbot's connect() is not very friendly to
-    # overriding in subclasses so we have to re-implement it
-    # completely (copy-paste mostly) in order to add support for using
-    # an XMPP "Connect Server".
-    def connect(self):
-        logging.basicConfig(filename = self.__log_file,
-                            level = logging.DEBUG)
-        if not self.conn:
-            conn = xmpp.Client(self.jid.getDomain(), debug=[])
-            if self.__connect_server:
-                try:
-                    conn_server, conn_port = self.__connect_server.split(":", 1)
-                except ValueError:
-                    conn_server = self.__connect_server
-                    conn_port = 5222
-                conres = conn.connect((conn_server, int(conn_port)))
-            else:
-                conres = conn.connect()
-            if not conres:
-                return None
-            authres = conn.auth(self.jid.getNode(), self.__password, self.res)
-            if not authres:
-                return None
-            self.conn = conn
-            self.conn.sendInitPresence()
-            self.roster = self.conn.Roster.getRoster()
-            for (handler, callback) in self.handlers:
-                self.conn.RegisterHandler(handler, callback)
-        return self.conn
-
-    # Wrap OTR encryption around Jabberbot's most low-level method for
-    # sending messages.
-    def send_message(self, mess):
-        body = str(mess.getBody())
-        user = str(mess.getTo().getStripped())
-        otrctx = self.__otr_manager.get_context_for_user(user)
-        if otrctx.state == potr.context.STATE_ENCRYPTED:
-            otrctx.sendMessage(potr.context.FRAGMENT_SEND_ALL, body,
-                               appdata = self.__otr_appdata_for_mess(mess))
-        else:
-            self.send_raw_message_fn(mess)
-
-    # Wrap OTR decryption around Jabberbot's callback mechanism.
-    def callback_message(self, conn, mess):
-        body = str(mess.getBody())
-        user = str(mess.getFrom().getStripped())
-        otrctx = self.__otr_manager.get_context_for_user(user)
-        if mess.getType() == "chat":
-            try:
-                appdata = self.__otr_appdata_for_mess(mess.buildReply())
-                decrypted_body, tlvs = otrctx.receiveMessage(body,
-                                                             appdata = appdata)
-                otrctx.processTLVs(tlvs)
-            except potr.context.NotEncryptedError:
-                otrctx.authStartV2(appdata = appdata)
-                return
-            except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage):
-                decrypted_body = body
-        else:
-            decrypted_body = body
-        if decrypted_body == None:
-            return
-        if mess.getType() == "groupchat":
-            bot_prefix = self.jid.getNode() + ": "
-            if decrypted_body.startswith(bot_prefix):
-                decrypted_body = decrypted_body[len(bot_prefix):]
-            else:
-                return
-        mess.setBody(decrypted_body)
-        super(OtrBot, self).callback_message(conn, mess)
-
-    # Override Jabberbot quitting on keep alive failure.
-    def on_ping_timeout(self):
-        self.__lastping = None
-
-    @jabberbot.botcmd
-    def ping(self, mess, args):
-        """Why not just test it?"""
-        return "pong"
-
-    @jabberbot.botcmd
-    def say(self, mess, args):
-        """Unleash my inner parrot"""
-        return args
-
-    @jabberbot.botcmd
-    def clear_say(self, mess, args):
-        """Make me speak in the clear even if we're in an OTR chat"""
-        self.send_raw_message_fn(mess.buildReply(args))
-        return ""
-
-    @jabberbot.botcmd
-    def start_otr(self, mess, args):
-        """Make me *initiate* (but not refresh) an OTR session"""
-        if mess.getType() == "groupchat":
-            return
-        return "?OTRv2?"
-
-    @jabberbot.botcmd
-    def end_otr(self, mess, args):
-        """Make me gracefully end the OTR session if there is one"""
-        if mess.getType() == "groupchat":
-            return
-        user = str(mess.getFrom().getStripped())
-        self.__otr_manager.get_context_for_user(user).disconnect(appdata =
-            self.__otr_appdata_for_mess(mess.buildReply()))
-        return ""
-
-if __name__ == '__main__':
-    parser = ArgumentParser()
-    parser.add_argument("account",
-                        help = "the user account, given as user at domain")
-    parser.add_argument("password",
-                        help = "the user account's password")
-    parser.add_argument("otr_key_path",
-                        help = "the path to the account's OTR key file")
-    parser.add_argument("-c", "--connect-server", metavar = 'ADDRESS',
-                        help = "use a Connect Server, given as host[:port] " +
-                        "(port defaults to 5222)")
-    parser.add_argument("-j", "--auto-join", nargs = '+', metavar = 'ROOMS',
-                        help = "auto-join multi-user chatrooms on start")
-    parser.add_argument("-l", "--log-file", metavar = 'LOGFILE',
-                        help = "Log to file instead of stderr")
-    args = parser.parse_args()
-    otr_bot_opt_args = dict()
-    if args.connect_server:
-        otr_bot_opt_args["connect_server"] = args.connect_server
-    if args.log_file:
-        otr_bot_opt_args["log_file"] = args.log_file
-    otr_bot = OtrBot(args.account, args.password, args.otr_key_path,
-                     **otr_bot_opt_args)
-    if args.auto_join:
-        for room in args.auto_join:
-            otr_bot.join_room(room)
-    otr_bot.serve_forever()
diff --git a/cucumber/features/scripts/vm-execute b/cucumber/features/scripts/vm-execute
deleted file mode 100755
index f3d20f9..0000000
--- a/cucumber/features/scripts/vm-execute
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'optparse'
-begin
-  require "#{`git rev-parse --show-toplevel`.chomp}/cucumber/features/support/helpers/remote_shell.rb"
-  require "#{`git rev-parse --show-toplevel`.chomp}/cucumber/features/support/helpers/misc_helpers.rb"
-
-rescue LoadError => e
-  raise "This script must be run from within Tails' Git directory."
-end
-$config = Hash.new
-
-def debug_log(*args) ; end
-
-class FakeVM
-  def get_remote_shell_port
-    # FIXME -- we really ought to be able to ask the vm to dynamically allocate the port, then tell us what it did, rather than this
-    LIBVIRT_REMOTE_SHELL_PORT
-  end
-end
-
-cmd_opts = {
-  :spawn => false,
-  :user  => "root"
-}
-
-opt_parser = OptionParser.new do |opts|
-  opts.banner = "Usage: cucumber/features/scripts/vm-execute [opts] COMMAND"
-  opts.separator ""
-  opts.separator "Runs commands in the VM guest being tested. This script " \
-                    "must be run from within Tails' Git directory."
-  opts.separator ""
-  opts.separator "Options:"
-
-  opts.on("-h", "--help", "Show this message") do
-    puts opts
-    exit
-  end
-
-  opts.on("-u", "--user USER", "Run command as USER") do |user|
-    cmd_opts[:user] = user
-  end
-
-  opts.on("-s", "--spawn",
-          "Run command in non-blocking mode") do |type|
-    cmd_opts[:spawn] = true
-  end
-end
-opt_parser.parse!(ARGV)
-cmd = ARGV.join(" ")
-c = RemoteShell::ShellCommand.new(FakeVM.new, cmd, cmd_opts)
-puts "Return status: #{c.returncode}"
-puts "STDOUT:\n#{c.stdout}"
-puts "STDERR:\n#{c.stderr}"
-exit c.returncode
diff --git a/cucumber/features/step_definitions/apt.rb b/cucumber/features/step_definitions/apt.rb
deleted file mode 100644
index 52ef9f7..0000000
--- a/cucumber/features/step_definitions/apt.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-require 'uri'
-
-Given /^the only hosts in APT sources are "([^"]*)"$/ do |hosts_str|
-  hosts = hosts_str.split(',')
-  apt_sources = $vm.execute_successfully(
-    "cat /etc/apt/sources.list /etc/apt/sources.list.d/*"
-  ).stdout.chomp
-  apt_sources.each_line do |line|
-    next if ! line.start_with? "deb"
-    source_host = URI(line.split[1]).host
-    if !hosts.include?(source_host)
-      raise "Bad APT source '#{line}'"
-    end
-  end
-end
-
-Given /^no proposed-updates APT suite is enabled$/ do
-  apt_sources = $vm.execute_successfully(
-    'cat /etc/apt/sources.list /etc/apt/sources.list.d/*'
-  ).stdout
-  assert_no_match(/\s\S+-proposed-updates\s/, apt_sources)
-end
-
-When /^I configure APT to use non-onion sources$/ do
-  script = <<-EOF
-  use strict;
-  use warnings FATAL => "all";
-  s{vwakviie2ienjx6t[.]onion}{ftp.us.debian.org};
-  s{sgvtcaew4bxjd7ln[.]onion}{security.debian.org};
-  s{sdscoq7snqtznauu[.]onion}{deb.torproject.org};
-  s{jenw7xbd6tf7vfhp[.]onion}{deb.tails.boum.org};
-EOF
-  # VMCommand:s cannot handle newlines, and they're irrelevant in the
-  # above perl script any way
-  script.delete!("\n")
-  $vm.execute_successfully(
-    "perl -pi -E '#{script}' /etc/apt/sources.list /etc/apt/sources.list.d/*"
-  )
-end
-
-When /^I update APT using apt$/ do
-  recovery_proc = Proc.new do
-    step 'I kill the process "apt"'
-    $vm.execute('rm -rf /var/lib/apt/lists/*')
-  end
-  retry_tor(recovery_proc) do
-    Timeout::timeout(15*60) do
-      $vm.execute_successfully("echo #{@sudo_password} | " +
-                               "sudo -S apt update", :user => LIVE_USER)
-    end
-  end
-end
-
-Then /^I install "(.+)" using apt$/ do |package_name|
-  recovery_proc = Proc.new do
-    step 'I kill the process "apt"'
-    $vm.execute("apt purge #{package_name}")
-  end
-  retry_tor(recovery_proc) do
-    Timeout::timeout(2*60) do
-      $vm.execute_successfully("echo #{@sudo_password} | " +
-                               "sudo -S apt install #{package_name}",
-                               :user => LIVE_USER)
-    end
-  end
-end
-
-When /^I start Synaptic$/ do
-  step 'I start "Synaptic Package Manager" via GNOME Activities Overview'
-  deal_with_polkit_prompt(@sudo_password)
-  @synaptic = Dogtail::Application.new('synaptic')
-  # The seemingly spurious space is needed because that is how this
-  # frame is named...
-  @synaptic.child(
-    'Synaptic Package Manager ', roleName: 'frame', recursive: false
-  )
-end
-
-When /^I update APT using Synaptic$/ do
-  recovery_proc = Proc.new do
-    step 'I kill the process "synaptic"'
-    step "I start Synaptic"
-  end
-  retry_tor(recovery_proc) do
-    @synaptic.button('Reload').click
-    sleep 10 # It might take some time before APT starts downloading
-    try_for(15*60, :msg => "Took too much time to download the APT data") {
-      !$vm.has_process?("/usr/lib/apt/methods/tor+http")
-    }
-    assert_raise(RuntimeError) do
-      @synaptic.child(roleName: 'dialog', recursive: false)
-        .child('Error', roleName: 'icon', retry: false)
-    end
-    if !$vm.has_process?("synaptic")
-      raise "Synaptic process vanished, did it segfault again?"
-    end
-  end
-end
-
-Then /^I install "(.+)" using Synaptic$/ do |package_name|
-  recovery_proc = Proc.new do
-    step 'I kill the process "synaptic"'
-    $vm.execute("apt -y purge #{package_name}")
-    step "I start Synaptic"
-  end
-  retry_tor(recovery_proc) do
-    @synaptic.button('Search').click
-    find_dialog = @synaptic.dialog('Find')
-    find_dialog.child(roleName: 'text').typeText(package_name)
-    find_dialog.button('Search').click
-    package_list = @synaptic.child('Installed Version',
-                                   roleName: 'table column header').parent
-    package_entry = package_list.child(package_name, roleName: 'table cell')
-    package_entry.doubleClick
-    @synaptic.button('Apply').click
-    apply_prompt = nil
-    try_for(60) { apply_prompt = @synaptic.dialog('Summary'); true }
-    apply_prompt.button('Apply').click
-    try_for(4*60) do
-      @synaptic.child('Changes applied', roleName: 'frame', recursive: false)
-      true
-    end
-  end
-end
diff --git a/cucumber/features/step_definitions/browser.rb b/cucumber/features/step_definitions/browser.rb
deleted file mode 100644
index 68d1bca..0000000
--- a/cucumber/features/step_definitions/browser.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-Then /^the Unsafe Browser has started$/ do
-  @screen.wait("UnsafeBrowserHomepage.png", 360)
-end
-
-When /^I start the Unsafe Browser(?: through the GNOME menu)?$/ do
-  step "I start \"Unsafe Browser\" via GNOME Activities Overview"
-end
-
-When /^I successfully start the Unsafe Browser$/ do
-  step "I start the Unsafe Browser"
-  step "I see and accept the Unsafe Browser start verification"
-  step "I see the \"Starting the Unsafe Browser...\" notification after at most 60 seconds"
-  step "the Unsafe Browser has started"
-end
-
-When /^I close the Unsafe Browser$/ do
-  @screen.type("q", Sikuli::KeyModifier.CTRL)
-end
-
-def xul_application_info(application)
-  binary = $vm.execute_successfully(
-    'echo ${TBB_INSTALL}/firefox', :libs => 'tor-browser'
-  ).stdout.chomp
-  address_bar_image = "BrowserAddressBar.png"
-  unused_tbb_libs = ['libnssdbm3.so', "libmozavcodec.so", "libmozavutil.so"]
-  case application
-  when "Tor Browser"
-    user = LIVE_USER
-    cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
-    chroot = ""
-    new_tab_button_image = "TorBrowserNewTabButton.png"
-  when "Unsafe Browser"
-    user = "clearnet"
-    cmd_regex = "#{binary} .* -profile /home/#{user}/\.unsafe-browser/profile\.default"
-    chroot = "/var/lib/unsafe-browser/chroot"
-    new_tab_button_image = "UnsafeBrowserNewTabButton.png"
-  when "Tor Launcher"
-    user = "tor-launcher"
-    # We do not enable AppArmor confinement for the Tor Launcher.
-    binary = "#{binary}-unconfined"
-    tor_launcher_install = $vm.execute_successfully(
-      'echo ${TOR_LAUNCHER_INSTALL}', :libs => 'tor-browser'
-    ).stdout.chomp
-    cmd_regex = "#{binary}\s+-app #{tor_launcher_install}/application\.ini.*"
-    chroot = ""
-    new_tab_button_image = nil
-    address_bar_image = nil
-    # The standalone Tor Launcher uses fewer libs than the full
-    # browser.
-    unused_tbb_libs.concat(["libfreebl3.so", "libnssckbi.so", "libsoftokn3.so"])
-  else
-    raise "Invalid browser or XUL application: #{application}"
-  end
-  return {
-    :user => user,
-    :cmd_regex => cmd_regex,
-    :chroot => chroot,
-    :new_tab_button_image => new_tab_button_image,
-    :address_bar_image => address_bar_image,
-    :unused_tbb_libs => unused_tbb_libs,
-  }
-end
-
-When /^I open a new tab in the (.*)$/ do |browser|
-  info = xul_application_info(browser)
-  @screen.click(info[:new_tab_button_image])
-  @screen.wait(info[:address_bar_image], 10)
-end
-
-When /^I open the address "([^"]*)" in the (.*)$/ do |address, browser|
-  step "I open a new tab in the #{browser}"
-  info = xul_application_info(browser)
-  open_address = Proc.new do
-    @screen.click(info[:address_bar_image])
-    # This static here since we have no reliable visual indicators
-    # that we can watch to know when typing is "safe".
-    sleep 5
-    # The browser sometimes loses keypresses when suggestions are
-    # shown, which we work around by pasting the address from the
-    # clipboard, in one go.
-    $vm.set_clipboard(address)
-    @screen.type('v', Sikuli::KeyModifier.CTRL)
-    @screen.type(Sikuli::Key.ENTER)
-  end
-  recovery_on_failure = Proc.new do
-    @screen.type(Sikuli::Key.ESC)
-    @screen.waitVanish('BrowserReloadButton.png', 3)
-    open_address.call
-  end
-  if browser == "Tor Browser"
-    retry_method = method(:retry_tor)
-  else
-    retry_method = Proc.new { |p, &b| retry_action(10, recovery_proc: p, &b) }
-  end
-  open_address.call
-  retry_method.call(recovery_on_failure) do
-    @screen.wait('BrowserReloadButton.png', 120)
-  end
-end
-
-# This step is limited to the Tor Browser due to #7502 since dogtail
-# uses the same interface.
-Then /^"([^"]+)" has loaded in the Tor Browser$/ do |title|
-  if @language == 'German'
-    browser_name = 'Tor-Browser'
-    reload_action = 'Aktuelle Seite neu laden'
-  else
-    browser_name = 'Tor Browser'
-    reload_action = 'Reload current page'
-  end
-  expected_title = "#{title} - #{browser_name}"
-  try_for(60) { @torbrowser.child(expected_title, roleName: 'frame') }
-  # The 'Reload current page' button (graphically shown as a looping
-  # arrow) is only shown when a page has loaded, so once we see the
-  # expected title *and* this button has appeared, then we can be sure
-  # that the page has fully loaded.
-  try_for(60) { @torbrowser.child(reload_action, roleName: 'push button') }
-end
-
-Then /^the (.*) has no plugins installed$/ do |browser|
-  step "I open the address \"about:plugins\" in the #{browser}"
-  step "I see \"TorBrowserNoPlugins.png\" after at most 30 seconds"
-end
-
-def xul_app_shared_lib_check(pid, chroot, expected_absent_tbb_libs = [])
-  absent_tbb_libs = []
-  unwanted_native_libs = []
-  tbb_libs = $vm.execute_successfully("ls -1 #{chroot}${TBB_INSTALL}/*.so",
-                                      :libs => 'tor-browser').stdout.split
-  firefox_pmap_info = $vm.execute("pmap --show-path #{pid}").stdout
-  for lib in tbb_libs do
-    lib_name = File.basename lib
-    if not /\W#{lib}$/.match firefox_pmap_info
-      absent_tbb_libs << lib_name
-    end
-    native_libs = $vm.execute_successfully(
-                       "find /usr/lib /lib -name \"#{lib_name}\""
-                                           ).stdout.split
-    for native_lib in native_libs do
-      if /\W#{native_lib}$"/.match firefox_pmap_info
-        unwanted_native_libs << lib_name
-      end
-    end
-  end
-  absent_tbb_libs -= expected_absent_tbb_libs
-  assert(absent_tbb_libs.empty? && unwanted_native_libs.empty?,
-         "The loaded shared libraries for the firefox process are not the " +
-         "way we expect them.\n" +
-         "Expected TBB libs that are absent: #{absent_tbb_libs}\n" +
-         "Native libs that we don't want: #{unwanted_native_libs}")
-end
-
-Then /^the (.*) uses all expected TBB shared libraries$/ do |application|
-  info = xul_application_info(application)
-  pid = $vm.execute_successfully("pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'").stdout.chomp
-  assert(/\A\d+\z/.match(pid), "It seems like #{application} is not running")
-  xul_app_shared_lib_check(pid, info[:chroot], info[:unused_tbb_libs])
-end
-
-Then /^the (.*) chroot is torn down$/ do |browser|
-  info = xul_application_info(browser)
-  try_for(30, :msg => "The #{browser} chroot '#{info[:chroot]}' was " \
-                      "not removed") do
-    !$vm.execute("test -d '#{info[:chroot]}'").success?
-  end
-end
-
-Then /^the (.*) runs as the expected user$/ do |browser|
-  info = xul_application_info(browser)
-  assert_vmcommand_success($vm.execute(
-    "pgrep --full --exact '#{info[:cmd_regex]}'"),
-    "The #{browser} is not running")
-  assert_vmcommand_success($vm.execute(
-    "pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'"),
-    "The #{browser} is not running as the #{info[:user]} user")
-end
-
-When /^I download some file in the Tor Browser$/ do
-  @some_file = 'tails-signing.key'
-  some_url = "https://tails.boum.org/#{@some_file}"
-  step "I open the address \"#{some_url}\" in the Tor Browser"
-end
-
-Then /^I get the browser download dialog$/ do
-  @screen.wait('BrowserDownloadDialog.png', 60)
-  @screen.wait('BrowserDownloadDialogSaveAsButton.png', 10)
-end
-
-When /^I save the file to the default Tor Browser download directory$/ do
-  @screen.click('BrowserDownloadDialogSaveAsButton.png')
-  @screen.wait('BrowserDownloadFileToDialog.png', 10)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Then /^the file is saved to the default Tor Browser download directory$/ do
-  assert_not_nil(@some_file)
-  expected_path = "/home/#{LIVE_USER}/Tor Browser/#{@some_file}"
-  try_for(10) { $vm.file_exist?(expected_path) }
-end
-
-When /^I open Tails homepage in the (.+)$/ do |browser|
-  step "I open the address \"https://tails.boum.org\" in the #{browser}"
-end
-
-Then /^Tails homepage loads in the Tor Browser$/ do
-  title = 'Tails - Privacy for anyone anywhere'
-  step "\"#{title}\" has loaded in the Tor Browser"
-end
-
-Then /^Tails homepage loads in the Unsafe Browser$/ do
-  @screen.wait('TailsHomepage.png', 60)
-end
-
-Then /^the Tor Browser shows the "([^"]+)" error$/ do |error|
-  page = @torbrowser.child("Problem loading page", roleName: "document frame")
-  headers = page.children(roleName: "heading")
-  found = headers.any? { |heading| heading.text == error }
-  raise "Could not find the '#{error}' error in the Tor Browser" unless found
-end
diff --git a/cucumber/features/step_definitions/build.rb b/cucumber/features/step_definitions/build.rb
deleted file mode 100644
index e02edc6..0000000
--- a/cucumber/features/step_definitions/build.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-Given /^Tails ([[:alnum:]~.]+) has been released$/ do |version|
-  create_git unless git_exists?
-
-  old_branch = current_branch
-
-  fatal_system "git checkout --quiet stable"
-  old_entries = File.open('debian/changelog') { |f| f.read }
-  File.open('debian/changelog', 'w') do |changelog|
-    changelog.write(<<END_OF_CHANGELOG)
-tails (#{version}) stable; urgency=low
-
-  * New upstream release.
-
- -- Tails developers <tails at boum.org>  Tue, 31 Jan 2012 15:12:57 +0100
-
-#{old_entries}
-END_OF_CHANGELOG
-  end
-  fatal_system "git commit --quiet debian/changelog -m 'Release #{version}'"
-  fatal_system "git tag '#{version.gsub('~', '-')}'"
-
-  if old_branch != 'stable'
-    fatal_system "git checkout --quiet '#{old_branch}'"
-    fatal_system "git merge    --quiet 'stable'"
-  end
-end
-
-Given /^Tails ([[:alnum:].-]+) has been tagged$/ do |version|
-  fatal_system "git tag '#{version}'"
-end
-
-Given /^Tails ([[:alnum:].]+) has not been released yet$/ do |version|
-  !File.exists? ".git/refs/tags/#{version}"
-end
-
-Given /^the last version mentioned in debian\/changelog is ([[:alnum:]~.]+)$/ do |version|
-  last = `dpkg-parsechangelog | awk '/^Version: / { print $2 }'`.strip
-  raise StandardError.new('dpkg-parsechangelog failed.') if $? != 0
-
-  if last != version
-    fatal_system "debchange -v '#{version}' 'New upstream release'"
-  end
-end
-
-Given /^the last versions mentioned in debian\/changelog are ([[:alnum:]~.]+) and ([[:alnum:]~.]+)$/ do |version_a, version_b|
-  step "the last version mentioned in debian/changelog is #{version_a}"
-  step "the last version mentioned in debian/changelog is #{version_b}"
-end
-
-Given(/^no frozen APT snapshot is encoded in config\/APT_snapshots\.d$/) do
-  ['debian', 'debian-security', 'torproject'].map do |origin|
-    File.open("config/APT_snapshots.d/#{origin}/serial", 'w+') do |serial|
-      serial.write("latest\n")
-    end
-  end
-end
-
-Given(/^frozen APT snapshots are encoded in config\/APT_snapshots\.d$/) do
-  ['debian', 'torproject'].map do |origin|
-    File.open("config/APT_snapshots.d/#{origin}/serial", 'w+') do |serial|
-      serial.write("2016060602\n")
-    end
-  end
-  # We never freeze debian-security
-  File.open("config/APT_snapshots.d/debian-security/serial", 'w+') do |serial|
-    serial.write("latest\n")
-  end
-end
-
-Given %r{I am working on the ([[:alnum:]./_-]+) base branch$} do |branch|
-  create_git unless git_exists?
-
-  if current_branch != branch
-    fatal_system "git checkout --quiet '#{branch}'"
-  end
-
-  File.open('config/base_branch', 'w+') do |base_branch_file|
-    base_branch_file.write("#{branch}\n")
-  end
-end
-
-Given %r{^I checkout the ([[:alnum:]~.-]+) tag$} do |tag|
-  create_git unless git_exists?
-  fatal_system "git checkout --quiet #{tag}"
-end
-
-Given %r{I am working on the ([[:alnum:]./_-]+) branch based on ([[:alnum:]./_-]+)$} do |branch, base|
-  create_git unless git_exists?
-
-  if current_branch != branch
-    fatal_system "git checkout --quiet -b '#{branch}' '#{base}'"
-  end
-
-  File.open('config/base_branch', 'w+') do |base_branch_file|
-    base_branch_file.write("#{base}\n")
-  end
-end
-
-When /^I successfully run "?([[:alnum:] -]+)"?$/ do |command|
-  @output = `#{File.expand_path("../../../auto/scripts/#{command}", __FILE__)}`
-  raise StandardError.new("#{command} failed. Exit code: #{$?}") if $? != 0
-end
-
-When /^I run "?([[:alnum:] -]+)"?$/ do |command|
-  @output = `#{File.expand_path("../../../auto/scripts/#{command}", __FILE__)}`
-  @exit_code = $?.exitstatus
-end
-
-Then /^I should see the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite|
-  @output.should have_suite(suite)
-end
-
-Then /^I should see only the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite|
-  assert_equal(1, @output.lines.count)
-  @output.should have_suite(suite)
-end
-
-Then /^I should not see the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite|
-  @output.should_not have_suite(suite)
-end
-
-Given(/^the config\/APT_overlays\.d directory is empty$/) do
-  Dir.glob('config/APT_overlays.d/*').empty? \
-  or raise "config/APT_overlays.d/ is not empty"
-end
-
-Given(/^config\/APT_overlays\.d contains ['"]?([[:alnum:].-]+)['"]?$/) do |suite|
-  FileUtils.touch("config/APT_overlays.d/#{suite}")
-end
-
-Then(/^it should fail$/) do
-  assert_not_equal(0, @exit_code)
-end
-
-Given(/^the (config\/base_branch) file does not exist$/) do |file|
-  File.delete(file)
-end
-
-Given(/^the (config\/APT_overlays\.d) directory does not exist$/) do |dir|
-  Dir.rmdir(dir)
-end
-
-Given(/^the config\/base_branch file is empty$/) do
-  File.truncate('config/base_branch', 0)
-end
-
-Then(/^I should see the ([[:alnum:].-]+) tagged snapshot$/) do |tag|
-  @output.should have_tagged_snapshot(tag)
-end
-
-Then(/^I should see a time\-based snapshot$/) do
-  @output.should have_time_based_snapshot()
-end
diff --git a/cucumber/features/step_definitions/checks.rb b/cucumber/features/step_definitions/checks.rb
deleted file mode 100644
index 142141a..0000000
--- a/cucumber/features/step_definitions/checks.rb
+++ /dev/null
@@ -1,230 +0,0 @@
-def shipped_openpgp_keys
-  shipped_gpg_keys = $vm.execute_successfully('gpg --batch --with-colons --fingerprint --list-key', :user => LIVE_USER).stdout
-  openpgp_fingerprints = shipped_gpg_keys.scan(/^fpr:::::::::([A-Z0-9]+):$/).flatten
-  return openpgp_fingerprints
-end
-
-Then /^the OpenPGP keys shipped with Tails will be valid for the next (\d+) months$/ do |months|
-  invalid = Array.new
-  shipped_openpgp_keys.each do |key|
-    begin
-      step "the shipped OpenPGP key #{key} will be valid for the next #{months} months"
-    rescue Test::Unit::AssertionFailedError
-      invalid << key
-      next
-    end
-  end
-  assert(invalid.empty?, "The following key(s) will not be valid in #{months} months: #{invalid.join(', ')}")
-end
-
-Then /^the shipped (?:Debian repository key|OpenPGP key ([A-Z0-9]+)) will be valid for the next (\d+) months$/ do |fingerprint, max_months|
-  if fingerprint
-    cmd = 'gpg'
-    user = LIVE_USER
-  else
-    fingerprint = TAILS_DEBIAN_REPO_KEY
-    cmd = 'apt-key adv'
-    user = 'root'
-  end
-  shipped_sig_key_info = $vm.execute_successfully("#{cmd} --batch --list-key #{fingerprint}", :user => user).stdout
-  m = /\[expire[ds]: ([0-9-]*)\]/.match(shipped_sig_key_info)
-  if m
-    expiration_date = Date.parse(m[1])
-    assert((expiration_date << max_months.to_i) > DateTime.now,
-           "The shipped key #{fingerprint} will not be valid #{max_months} months from now.")
-  end
-end
-
-Then /^the live user has been setup by live\-boot$/ do
-  assert($vm.execute("test -e /var/lib/live/config/user-setup").success?,
-         "live-boot failed its user-setup")
-  actual_username = $vm.execute(". /etc/live/config/username.conf; " +
-                                "echo $LIVE_USERNAME").stdout.chomp
-  assert_equal(LIVE_USER, actual_username)
-end
-
-Then /^the live user is a member of only its own group and "(.*?)"$/ do |groups|
-  expected_groups = groups.split(" ") << LIVE_USER
-  actual_groups = $vm.execute("groups #{LIVE_USER}").stdout.chomp.sub(/^#{LIVE_USER} : /, "").split(" ")
-  unexpected = actual_groups - expected_groups
-  missing = expected_groups - actual_groups
-  assert_equal(0, unexpected.size,
-         "live user in unexpected groups #{unexpected}")
-  assert_equal(0, missing.size,
-         "live user not in expected groups #{missing}")
-end
-
-Then /^the live user owns its home dir and it has normal permissions$/ do
-  home = "/home/#{LIVE_USER}"
-  assert($vm.execute("test -d #{home}").success?,
-         "The live user's home doesn't exist or is not a directory")
-  owner = $vm.execute("stat -c %U:%G #{home}").stdout.chomp
-  perms = $vm.execute("stat -c %a #{home}").stdout.chomp
-  assert_equal("#{LIVE_USER}:#{LIVE_USER}", owner)
-  assert_equal("700", perms)
-end
-
-Then /^no unexpected services are listening for network connections$/ do
-  for line in $vm.execute_successfully("ss -ltupn").stdout.chomp.split("\n") do
-    splitted = line.split(/[[:blank:]]+/)
-    proto = splitted[0]
-    next unless ['tcp', 'udp'].include?(proto)
-    laddr, lport = splitted[4].split(":")
-    proc = /users:\(\("([^"]+)"/.match(splitted[6])[1]
-    # Services listening on loopback is not a threat
-    if /127(\.[[:digit:]]{1,3}){3}/.match(laddr).nil?
-      if SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, lport] or
-         SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, "*"]
-        puts "Service '#{proc}' is listening on #{laddr}:#{lport} " +
-             "but has an exception"
-      else
-        raise "Unexpected service '#{proc}' listening on #{laddr}:#{lport}"
-      end
-    end
-  end
-end
-
-When /^Tails has booted a 64-bit kernel$/ do
-  assert($vm.execute("uname -r | grep -qs 'amd64$'").success?,
-         "Tails has not booted a 64-bit kernel.")
-end
-
-Then /^the VirtualBox guest modules are available$/ do
-  assert($vm.execute("modinfo vboxguest").success?,
-         "The vboxguest module is not available.")
-end
-
-Then /^the support documentation page opens in Tor Browser$/ do
-  if @language == 'German'
-    expected_title = 'Tails - Hilfe & Support'
-    expected_heading = 'Die Dokumentation durchsuchen'
-  else
-    expected_title = 'Tails - Support'
-    expected_heading = 'Search the documentation'
-  end
-  step "\"#{expected_title}\" has loaded in the Tor Browser"
-  headings = @torbrowser
-             .child(expected_title, roleName: 'document frame')
-             .children(roleName: 'heading')
-  assert(
-    headings.any? { |heading| heading.text == expected_heading }
-  )
-end
-
-Given /^I plug and mount a USB drive containing a sample PNG$/ do
-  @png_dir = share_host_files(Dir.glob("#{MISC_FILES_DIR}/*.png"))
-end
-
-Then /^MAT can clean some sample PNG file$/ do
-  for png_on_host in Dir.glob("#{MISC_FILES_DIR}/*.png") do
-    png_name = File.basename(png_on_host)
-    png_on_guest = "/home/#{LIVE_USER}/#{png_name}"
-    step "I copy \"#{@png_dir}/#{png_name}\" to \"#{png_on_guest}\" as user \"#{LIVE_USER}\""
-    raw_check_cmd = "grep --quiet --fixed-strings --text " +
-                    "'Created with GIMP' '#{png_on_guest}'"
-    assert($vm.execute(raw_check_cmd, user: LIVE_USER).success?,
-           'The comment is not present in the PNG')
-    check_before = $vm.execute_successfully("mat --check '#{png_on_guest}'",
-                                            :user => LIVE_USER).stdout
-    assert(check_before.include?("#{png_on_guest} is not clean"),
-           "MAT failed to see that '#{png_on_host}' is dirty")
-    $vm.execute_successfully("mat '#{png_on_guest}'", :user => LIVE_USER)
-    check_after = $vm.execute_successfully("mat --check '#{png_on_guest}'",
-                                           :user => LIVE_USER).stdout
-    assert(check_after.include?("#{png_on_guest} is clean"),
-           "MAT failed to clean '#{png_on_host}'")
-    assert($vm.execute(raw_check_cmd, user: LIVE_USER).failure?,
-           'The comment is still present in the PNG')
-    $vm.execute_successfully("rm '#{png_on_guest}'")
-  end
-end
-
-
-
-Then /^AppArmor is enabled$/ do
-  assert($vm.execute("aa-status").success?, "AppArmor is not enabled")
-end
-
-Then /^some AppArmor profiles are enforced$/ do
-  assert($vm.execute("aa-status --enforced").stdout.chomp.to_i > 0,
-         "No AppArmor profile is enforced")
-end
-
-def get_seccomp_status(process)
-  assert($vm.has_process?(process), "Process #{process} not running.")
-  pid = $vm.pidof(process)[0]
-  status = $vm.file_content("/proc/#{pid}/status")
-  return status.match(/^Seccomp:\s+([0-9])/)[1].chomp.to_i
-end
-
-def get_apparmor_status(pid)
-  apparmor_status = $vm.file_content("/proc/#{pid}/attr/current").chomp
-  if apparmor_status.include?(')')
-    # matches something like     /usr/sbin/cupsd (enforce)
-    # and only returns what's in the parentheses
-    return apparmor_status.match(/[^\s]+\s+\((.+)\)$/)[1].chomp
-  else
-    return apparmor_status
-  end
-end
-
-Then /^the running process "(.+)" is confined with AppArmor in (complain|enforce) mode$/ do |process, mode|
-  assert($vm.has_process?(process), "Process #{process} not running.")
-  pid = $vm.pidof(process)[0]
-  assert_equal(mode, get_apparmor_status(pid))
-end
-
-Then /^the running process "(.+)" is confined with Seccomp in (filter|strict) mode$/ do |process,mode|
-  status = get_seccomp_status(process)
-  if mode == 'strict'
-    assert_equal(1, status, "#{process} not confined with Seccomp in strict mode")
-  elsif mode == 'filter'
-    assert_equal(2, status, "#{process} not confined with Seccomp in filter mode")
-  else
-    raise "Unsupported mode #{mode} passed"
-  end
-end
-
-Then /^tails-debugging-info is not susceptible to symlink attacks$/ do
-  secret_file = '/secret'
-  secret_contents = 'T0P S3Cr1t -- 3yEs oN1y'
-  $vm.file_append(secret_file, secret_contents)
-  $vm.execute_successfully("chmod u=rw,go= #{secret_file}")
-  $vm.execute_successfully("chown root:root #{secret_file}")
-  script_path = '/usr/local/sbin/tails-debugging-info'
-  script_lines = $vm.file_content(script_path).split("\n")
-  script_lines.grep(/^debug_file\s+/).each do |line|
-    _, user, debug_file = line.split
-    # root can always mount symlink attacks
-    next if user == 'root'
-    # Remove quoting around the file
-    debug_file.gsub!(/["']/, '')
-    # Skip files that do not exist, or cannot be removed (e.g. the
-    # ones in /proc).
-    next if not($vm.execute("rm #{debug_file}").success?)
-    # Check what would happen *if* the amnesia user managed to replace
-    # the debugging file with a symlink to the secret.
-    $vm.execute_successfully("ln -s #{secret_file} #{debug_file}")
-    $vm.execute_successfully("chown --no-dereference #{LIVE_USER}:#{LIVE_USER} #{debug_file}")
-    if $vm.execute("sudo /usr/local/sbin/tails-debugging-info | " +
-                   "grep '#{secret_contents}'",
-                   :user => LIVE_USER).success?
-      raise "The secret was leaked by tails-debugging-info via '#{debug_file}'"
-    end
-    # Remove the secret so it cannot possibly interfere with the
-    # following iterations (even though it should not).
-    $vm.execute_successfully("echo > #{debug_file}")
-  end
-end
-
-When /^I disable all networking in the Tails Greeter$/ do
-  open_greeter_additional_settings()
-  @screen.wait_and_click('TailsGreeterNetworkConnection.png', 30)
-  @screen.wait_and_click('TailsGreeterDisableAllNetworking.png', 10)
-  @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
-end
-
-Then /^the Tor Status icon tells me that Tor is( not)? usable$/ do |not_usable|
-  picture = not_usable ? 'TorStatusNotUsable' : 'TorStatusUsable'
-  @screen.find("#{picture}.png")
-end
diff --git a/cucumber/features/step_definitions/common_steps.rb b/cucumber/features/step_definitions/common_steps.rb
deleted file mode 100644
index e92309e..0000000
--- a/cucumber/features/step_definitions/common_steps.rb
+++ /dev/null
@@ -1,1388 +0,0 @@
-require 'fileutils'
-
-def post_vm_start_hook
-  # Sometimes the first click is lost (presumably it's used to give
-  # focus to virt-viewer or similar) so we do that now rather than
-  # having an important click lost. The point we click should be
-  # somewhere where no clickable elements generally reside.
-  @screen.click_point(@screen.w-1, @screen.h/2)
-end
-
-def context_menu_helper(top, bottom, menu_item)
-  try_for(60) do
-    t = @screen.wait(top, 10)
-    b = @screen.wait(bottom, 10)
-    # In Sikuli, lower x == closer to the left, lower y == closer to the top
-    assert(t.y < b.y)
-    center = Sikuli::Location.new(((t.x + t.w) + b.x)/2,
-                                  ((t.y + t.h) + b.y)/2)
-    @screen.right_click(center)
-    @screen.hide_cursor
-    @screen.wait_and_click(menu_item, 10)
-    return
-  end
-end
-
-def post_snapshot_restore_hook
-  # FIXME -- we've got a brain-damaged version of this, unlike Tails, so it breaks after restores at present
-  # that being the case, let's not worry until we actually miss the feature
-  #$vm.wait_until_remote_shell_is_up
-  post_vm_start_hook
-
-  # debian-TODO: move to tor feature
-  # The guest's Tor's circuits' states are likely to get out of sync
-  # with the other relays, so we ensure that we have fresh circuits.
-  # Time jumps and incorrect clocks also confuses Tor in many ways.
-  #if $vm.has_network?
-  #  if $vm.execute("systemctl --quiet is-active tor at default.service").success?
-  #    $vm.execute("systemctl stop tor at default.service")
-  #    $vm.execute("systemctl --no-block restart tails-tor-has-bootstrapped.target")
-  #    $vm.host_to_guest_time_sync
-  #    $vm.execute("systemctl start tor at default.service")
-  #    wait_until_tor_is_working
-  #  end
-  #else
-  #  $vm.host_to_guest_time_sync
-  #end
-end
-
-Given /^I intend to use ([a-z]*) mode$/ do |ui_mode|
-  @ui_mode = ui_mode
-end
-
-def diui_png(name)
-  return "d-i_#{@ui_mode}_#{name}.png"
-end
-
-Given /^a computer$/ do
-  $vm.destroy_and_undefine if $vm
-  $vm = VM.new($virt, VM_XML_PATH, $vmnet, $vmstorage, DISPLAY)
-end
-
-Then /^the VM shuts down within (\d+) minutes$/ do |mins|
-  timeout = 60*mins.to_i
-  try_for(timeout, :msg => "VM is still running after #{timeout} seconds") do
-    ! $vm.is_running?
-  end
-end
-
-Given /^the computer is set to boot from the Tails DVD$/ do
-  $vm.set_cdrom_boot(TAILS_ISO)
-end
-
-Given /^the computer is set to boot from (.+?) drive$/ do |type|
-  $vm.set_disk_boot(JOB_NAME, type.downcase)
-end
-
-Given /^I (temporarily )?create an? (\d+) ([[:alpha:]]+) disk named "([^"]+)"$/ do |temporary, size, unit, name|
-  $vm.storage.create_new_disk(name, {:size => size, :unit => unit,
-                                     :type => "qcow2"})
-  add_after_scenario_hook { $vm.storage.delete_volume(name) } if temporary
-end
-
-Given /^I plug (.+) drive "([^"]+)"$/ do |bus, name|
-  $vm.plug_drive(name, bus.downcase)
-  if $vm.is_running?
-    step "drive \"#{name}\" is detected by Tails"
-  end
-end
-
-Then /^drive "([^"]+)" is detected by Tails$/ do |name|
-  raise "Tails is not running" unless $vm.is_running?
-  try_for(10, :msg => "Drive '#{name}' is not detected by Tails") do
-    $vm.disk_detected?(name)
-  end
-end
-
-Given /^the network is plugged$/ do
-  $vm.plug_network
-end
-
-Given /^the network is unplugged$/ do
-  $vm.unplug_network
-end
-
-Given /^the network connection is ready(?: within (\d+) seconds)?$/ do |timeout|
-  timeout ||= 30
-  try_for(timeout.to_i) { $vm.has_network? }
-end
-
-Given /^the hardware clock is set to "([^"]*)"$/ do |time|
-  $vm.set_hardware_clock(DateTime.parse(time).to_time)
-end
-
-Given /^I capture all network traffic$/ do
-  @sniffer = Sniffer.new("sniffer", $vmnet)
-  @sniffer.capture
-  add_after_scenario_hook do
-    @sniffer.stop
-    @sniffer.clear
-  end
-end
-
-Given /^I intend to boot with options: (.*)$/ do |options|
-  @boot_options = options
-end
-
-When /^I start the computer$/ do
-  assert(!$vm.is_running?,
-         "Trying to start a VM that is already running")
-  $vm.start
-  post_vm_start_hook
-end
-
-When /^I execute "([^"]*)"$/ do |cmd|
-  info_log($vm.execute(cmd))
-end
-
-When /^running "([^"]*)" (.*)$/ do |cmd, outcome|
-  result = $vm.execute(cmd)
-  assert(result.success? == ('succeeds' == outcome),
-         "Attempting to run '#{cmd}' did not give the expected outcome of: #{outcome}\n" + result.to_s )
-end
-
-Given /^I start Tails( from DVD)?( with network unplugged)?( and I login)?$/ do |dvd_boot, network_unplugged, do_login|
-  step "the computer is set to boot from the Tails DVD" if dvd_boot
-  if network_unplugged
-    step "the network is unplugged"
-  else
-    step "the network is plugged"
-  end
-  step "I start the computer"
-  step "the computer boots Tails"
-  if do_login
-    step "I log in to a new session"
-    if network_unplugged
-      step "all notifications have disappeared"
-    else
-      step "Tor is ready"
-      step "all notifications have disappeared"
-      step "available upgrades have been checked"
-    end
-  end
-end
-
-Given /^I start Tails from (.+?) drive "(.+?)"( with network unplugged)?( and I login( with persistence enabled)?)?$/ do |drive_type, drive_name, network_unplugged, do_login, persistence_on|
-  step "the computer is set to boot from #{drive_type} drive \"#{drive_name}\""
-  if network_unplugged
-    step "the network is unplugged"
-  else
-    step "the network is plugged"
-  end
-  step "I start the computer"
-  step "the computer boots Tails"
-  if do_login
-    step "I enable persistence" if persistence_on
-    step "I log in to a new session"
-    if network_unplugged
-      step "all notifications have disappeared"
-    else
-      step "Tor is ready"
-      step "all notifications have disappeared"
-      step "available upgrades have been checked"
-    end
-  end
-end
-
-When /^I power off the computer$/ do
-  assert($vm.is_running?,
-         "Trying to power off an already powered off VM")
-  $vm.power_off
-end
-
-When /^I cold reboot the computer$/ do
-  step "I power off the computer"
-  step "I start the computer"
-end
-
-When /^I destroy the computer$/ do
-  $vm.destroy_and_undefine
-end
-
-Given /^I select the install mode$/ do
-  boot_timeout = 60
-
-  debug_log("debug: CWD = " + Dir.getwd, :color => :blue)
-
-  on_screen, _ = @screen.waitAny(["d-i_boot_graphical-default.png","d-i_boot_graphical-default_9alpha.png","d-i_boot_text-default.png","d-i_boot_miniiso.png"], boot_timeout * PATIENCE)
-  debug_log("debug: found '#{on_screen}' in the bootspash", :color => :blue)
-  if "d-i_boot_miniiso.png" != on_screen
-    if ("d-i_boot_text-default.png" == on_screen) == ("gui" == @ui_mode)
-      @screen.type(Sikuli::Key.DOWN) 
-    end
-  end
-
-  @screen.type(Sikuli::Key.TAB)
-  @boot_options = "" if @boot_options.nil?
-  initcmd = 'printf \\\\\\\\07; while read x; do printf \\\\\\\\02; eval $x 2>/tmp/.remcmd_stderr; printf \\\\\\\\03%%s\\\\\\\\037 $?; cat /tmp/.remcmd_stderr; printf \\\\\\\\00; done'.gsub(/([$\\;>])/, '\\\\\0')
-  inittab_line = ('ttyS0::respawn:-/bin/sh -c \\\\047' + initcmd + '\\\\047').gsub(/ /, '\\ ')
-  @screen.type( ' preseed/early_command="echo DPMS=-s\\\\ 0 > /lib/debian-installer.d/S61Xnoblank;' +
-		' sed -i /XF86_Switch_VT_/s/F/XF86_Switch_VT_/ /usr/share/X11/xkb/symbols/srvr_ctrl;' +
-		' set -x;' +
-		' { printf ' + inittab_line + ';echo;}>>/etc/inittab;' +
-		' kill -HUP 1" blacklist=psmouse ' +
-		@boot_options +
-               Sikuli::Key.ENTER)
-  debug_log("debug: wait for the remote shell to respond...", :color => :blue)
-  #$vm.wait_until_remote_shell_is_up
-  sleep(60)
-end
-
-Given /^I expect package installation to start$/ do
-  @screen.wait(diui_png("InstallSoftware"), 20 * 60)
-end
-
-Given /^I select British English$/ do
-  @screen.wait(diui_png("English"), 30 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("SelectYourLocation"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.UP)
-  @screen.wait(diui_png("UnitedKingdom"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.click_point(@screen.w-2, @screen.h*2/3)
-  @screen.wait(diui_png("BritishEnglish"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I accept the hostname, using "([^"]*)" as the domain$/ do |domain|
-  @screen.wait(diui_png("EnterTheHostname"), 5*60 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("DomainName"), 10 * PATIENCE)
-  @screen.type(domain + Sikuli::Key.ENTER)
-  @screen.waitVanish(diui_png("DomainName"), 10 * PATIENCE)
-end
-
-Given /^I set the root password to "([^"]*)"$/ do |rootpw|
-# Root Password, twice
-  on_screen, _ = @screen.waitAny([diui_png("ShowRootPassword"),diui_png("RootPassword"),diui_png("MirrorCountry")], 60 * PATIENCE)
-  on_screen, _ = @screen.waitAny([diui_png("ShowRootPassword"),diui_png("RootPassword"),diui_png("MirrorCountry")], 60 * PATIENCE)
-  if diui_png("MirrorCountry") == on_screen
-    step("I accept the default mirror")
-    on_screen, _ = @screen.waitAny([diui_png("ShowRootPassword"),diui_png("RootPassword")], 60 * PATIENCE)
-    on_screen, _ = @screen.waitAny([diui_png("ShowRootPassword"),diui_png("RootPassword")], 60 * PATIENCE)
-  end
-  @screen.type(rootpw)
-  if "gui" == @ui_mode
-    @screen.type(Sikuli::Key.TAB)
-    @screen.type(Sikuli::Key.TAB) if on_screen == diui_png("ShowRootPassword")
-  else
-    @screen.type(Sikuli::Key.ENTER)
-    @screen.waitVanish(diui_png("RootPassword"), 10 * PATIENCE)
-  end
-  @screen.type(rootpw + Sikuli::Key.ENTER)
-end
-
-Given /^I set the password for "([^"]*)" to be "([^"]*)"$/ do |fullname,password|
-# Username, and password twice
-  @screen.wait(diui_png("NameOfUser"), 10 * PATIENCE)
-  @screen.type(fullname + Sikuli::Key.ENTER)
-  @screen.waitVanish(diui_png("NameOfUser"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  on_screen, _ = @screen.waitAny([diui_png("ShowUserPassword"),diui_png("UserPassword")], 10 * PATIENCE)
-  on_screen, _ = @screen.waitAny([diui_png("ShowUserPassword"),diui_png("UserPassword")], 10 * PATIENCE)
-  @screen.type(password)
-  if "gui" == @ui_mode
-    @screen.type(Sikuli::Key.TAB)
-    @screen.type(Sikuli::Key.TAB) if on_screen == diui_png("ShowUserPassword")
-  else
-    @screen.type(Sikuli::Key.ENTER)
-  end
-  @screen.type(password + Sikuli::Key.ENTER)
-end
-
-  #@screen.wait(diui_png("NoDiskFound"), 60)
-
-Given /^I select full-disk, single-filesystem partitioning$/ do
-  @screen.wait(diui_png("PartitioningMethod"), 60 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("SelectDiskToPartition"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("PartitioningScheme"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("FinishPartitioning"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  # prompt about Writing Partitions to disk:
-  @screen.wait(diui_png("No"), 10 * PATIENCE)
-  if "gui" == @ui_mode
-    @screen.type(Sikuli::Key.DOWN)
-  else
-    @screen.type(Sikuli::Key.TAB)
-  end
-  @screen.wait(diui_png("Yes"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I note that the Base system is being installed$/ do
-  on_screen, _ = @screen.waitAny([diui_png("CheckSyslog"),diui_png("InstallingBaseSystem")], 30 * PATIENCE)
-  on_screen, _ = @screen.waitAny([diui_png("CheckSyslog"),diui_png("InstallingBaseSystem")], 30 * PATIENCE)
-  if diui_png("CheckSyslog") == on_screen
-    if "gui" == @ui_mode
-      @screen.type(Sikuli::Key.F4) # for this to work, we need to remap the keyboard -- CtrlAltF4 is apparently untypable :-(
-    else
-      @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-    end
-    sleep(10)
-    raise "Failed to install the Base system"
-  end
-  debug_log("debug: Found InstallingBaseSystem. Wait for it to vanish", :color => :blue)
-  @screen.waitVanish(diui_png("InstallingBaseSystem"), 15 * 60 * PATIENCE)
-  debug_log("debug: InstallingBaseSystem vanished", :color => :blue)
-end
-
-Given /^I accept the default mirror$/ do
-  on_screen, _ = @screen.waitAny([diui_png("popcon"),diui_png("BadMirror"),diui_png("MirrorCountry"),diui_png("ScanCD")], 10 * 60 * PATIENCE)
-  if diui_png("MirrorCountry") == on_screen
-    @screen.wait(diui_png("MirrorCountry"), 10 * 60 * PATIENCE)
-    @screen.type(Sikuli::Key.ENTER)
-    @screen.wait(diui_png("ArchiveMirror"), 5 * PATIENCE)
-    @screen.type(Sikuli::Key.ENTER)
-    @screen.wait(diui_png("HttpProxy"), 5 * PATIENCE)
-    @screen.type("http://local-http-proxy:3128/" + Sikuli::Key.ENTER)
-    #@screen.type(Sikuli::Key.ENTER)
-  elsif diui_png("ScanCD") == on_screen
-    @screen.type(Sikuli::Key.ENTER)
-    step("I accept the default mirror")
-  else
-    step("I ignore Popcon")
-  end
-end
-
-Given /^I neglect to scan more CDs$/ do
-  @screen.wait(diui_png("ScanCD"), 15 * 60 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("UseNetMirror"), 10 * PATIENCE)
-  @screen.wait(diui_png("Yes"), 10 * PATIENCE)
-  if "gui" == @ui_mode
-    @screen.type(Sikuli::Key.DOWN)
-  else
-    @screen.type(Sikuli::Key.TAB)
-  end
-  @screen.wait(diui_png("No"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I ignore Popcon$/ do
-  on_screen, _ = @screen.waitAny([diui_png("popcon"),diui_png("BadMirror"),diui_png("ChooseSoftware")], 10 * 60  * PATIENCE)
-  if diui_png("ChooseSoftware") != on_screen
-    if on_screen == diui_png("BadMirror")
-      if "gui" == @ui_mode
-        @screen.type(Sikuli::Key.F4) # for this to work, we need to remap the keyboard -- CtrlAltF4 is apparently untypable :-(
-      else
-        @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-      end
-      sleep(10)
-      raise "Failed to access the mirror (perhaps a duff proxy?)"
-    end
-    @screen.type(Sikuli::Key.ENTER)
-    @screen.waitVanish(diui_png("popcon"), 10 * PATIENCE)
-  end
-end
-
-Given /^we reach the Tasksel prompt$/ do
-  @screen.wait(diui_png("ChooseSoftware"), 5 * 60 * PATIENCE)
-end
-
-Given /^I select the non-GUI task$/ do
-  @screen.wait(diui_png("DesktopTask_Yes"), 2 * 60 * PATIENCE)
-
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.waitVanish(diui_png("DesktopTask_Yes"), 10 * PATIENCE)
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I select Combi Debian-Edu profile$/ do
-  on_screen, _ = @screen.waitAny([diui_png("Edu-Profile"),diui_png("NoKernelModules")], 2 * 60  * PATIENCE)
-  # just acept the default combination
-  if on_screen == diui_png("NoKernelModules")
-    if "gui" == @ui_mode
-      @screen.type(Sikuli::Key.F4) # for this to work, we need to remap the keyboard -- CtrlAltF4 is apparently untypable :-(
-    else
-      @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-    end
-    sleep(10)
-    raise "No Kernel Modules Found (probably version skew between the .iso and the archive)"
-  end
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I select Main Server Debian-Edu profile$/ do
-  @screen.wait(diui_png("Edu-Profile"), 2 * 60 * PATIENCE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.SPACE)
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I select Standalone Debian-Edu profile$/ do
-  @screen.wait(diui_png("Edu-Profile"), 2 * 60 * PATIENCE)
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.type(Sikuli::Key.SPACE)
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I use the Debian-Edu Automatic Partitioning$/ do
-  @screen.wait(diui_png("Edu-AutoPartition"), 10 * PATIENCE)
-  # prompt about Writing Partitions to disk:
-  @screen.wait(diui_png("No"), 10 * PATIENCE)
-  if "gui" == @ui_mode
-    @screen.type(Sikuli::Key.DOWN)
-  else
-    @screen.type(Sikuli::Key.TAB)
-  end
-  @screen.wait(diui_png("Yes"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I select the minimal task$/ do
-  @screen.wait(diui_png("DesktopTask_Yes"), 2 * 60 * PATIENCE)
-
-  @screen.type(Sikuli::Key.SPACE)
-  8.times do
-    @screen.type(Sikuli::Key.DOWN)
-  end
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.waitVanish(diui_png("DesktopTask_Yes"), 10 * PATIENCE)
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I select the ([A-Z][[:alpha:]]*) task$/ do |desktop|
-  @screen.wait(diui_png("DesktopTask_Yes"), 2 * 60 * PATIENCE)
-  debug_log("debug: Found DesktopTask_Yes", :color => :blue)
-
-  menu = [ '_', 'Gnome', 'XFCE', 'KDE', 'Cinamon', 'MATE', 'LXDE' ]
-
-
-  menu.index(desktop).times do
-    @screen.type(Sikuli::Key.DOWN)
-  end
-  @screen.type(Sikuli::Key.SPACE)
-  expected_result = "Desktop+" + desktop
-  @screen.wait(diui_png(expected_result), 10 * PATIENCE)
-
-  if "gui" == @ui_mode
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-  end
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.waitVanish(diui_png(expected_result), 10 * PATIENCE)
-end
-
-Given /^I wait while the partitions are made$/ do
-  @screen.wait(diui_png("PartitionDisks"), 60)
-  debug_log("debug: we see PartitionDisks", :color => :blue)
-  @screen.waitVanish(diui_png("PartitionDisks"), 2 * 60)
-end
-
-Given /^I wait patiently for the package installation to start$/ do
-  @screen.wait(diui_png("InstallSoftware"), 10 * 60)
-end
-
-Given /^I wait while the bulk of the packages are installed$/ do
-  @screen.wait(diui_png("InstallSoftware"), 10)
-  debug_log("debug: we see InstallSoftware", :color => :blue)
-  failed = false
-  try_for(180*60, :msg => "it seems that the install stalled (timing-out after 3 hours)") do
-    found = false
-    sleep(30)
-    debug_log("debug: check for Install GRUB/Software", :color => :blue)
-    if $vm.is_running?
-      hit, _ = @screen.waitAny([diui_png("InstallGRUB"),diui_png("InstallGRUB-heading"),diui_png("InstallComplete"),diui_png("InstallationStepFailed"),diui_png("InstallSoftware"),diui_png("Edu-LTSPchroot")], 10)
-    else
-      found = true
-      hit = ''
-    end
-    debug_log("debug: found #{hit}", :color => :blue)
-    case hit
-    when diui_png("InstallSoftware"), diui_png("Edu-LTSPchroot"), diui_png("InstallationStepFailed")
-      debug_log("debug: so let's glance at tty4", :color => :blue)
-      if "gui" == @ui_mode
-        @screen.type(Sikuli::Key.F4) # for this to work, we need to remap the keyboard -- CtrlAltF4 is apparently untypable :-(
-      else
-        @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-      end
-      debug_log("debug: typed F4, pausing...", :color => :blue)
-      sleep(5)
-      debug_log("debug: slept 5", :color => :blue)
-      if diui_png("InstallationStepFailed") == hit
-        failed = true
-        sleep(30*60) # pause for 30-mins, for debugging
-      else
-        if "gui" == @ui_mode
-          @screen.type(Sikuli::Key.F5, Sikuli::KeyModifier.ALT)
-        else
-          @screen.type(Sikuli::Key.F1, Sikuli::KeyModifier.ALT)
-        end
-        debug_log("debug: pressed F1", :color => :blue)
-        sleep(20)
-      end
-    when diui_png("InstallGRUB"), diui_png("InstallGRUB-heading"), diui_png("InstallComplete")
-      found = true
-    end
-
-    found || failed
-  end
-  raise "an Instalation Step Failed -- see the screenshot (may need to be in text-mode)" if failed
-end
-
-Given /^I install GRUB$/ do
-  @screen.wait(diui_png("InstallGRUB"), 2 * 60 * PATIENCE)
-  debug_log("debug: Found InstallGRUB", :color => :blue)
-
-  if "gui" == @ui_mode
-    debug_log("debug: We're in GUI mode", :color => :blue)
-    @screen.wait(diui_png("CONTINUEunselected"), 10 * PATIENCE)
-    debug_log("debug: Found CONTINUEunselected", :color => :blue)
-    debug_log("debug: Press TAB", :color => :blue)
-    @screen.type(Sikuli::Key.TAB)
-    @screen.wait(diui_png("CONTINUEselected"), 10 * PATIENCE)
-    debug_log("debug: Found CONTINUEselected", :color => :blue)
-  end
-  debug_log("debug: Press ENTER", :color => :blue)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait(diui_png("GRUBEnterDev"), 10 * 60 * PATIENCE)
-  @screen.type(Sikuli::Key.DOWN)
-  @screen.wait(diui_png("GRUBdev"), 10 * PATIENCE)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I allow reboot after the install is complete$/ do
-  step('I see the "InstallComplete" screen, after at most 240 seconds')
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^I wait for the reboot$/ do
-  @screen.waitAny(["d-i_boot_graphical-default.png","d-i_boot_graphical-default_9alpha.png","d-i_boot_text-default.png","d-i_boot_miniiso.png"], 10 * 60 * PATIENCE)
-end
-
-Given /^I should see a ([a-zA-Z]*) Login prompt$/ do |style|
-  def loginPrompt
-    {
-      'XFCE' => [ 'DebianLoginPromptXFCE.png', 'DebianLoginPromptXFCE_d9.png' ],
-      'KDE'  => [ 'DebianLoginPromptKDE.png',  'DebianLoginPromptKDE_d9.png' ],
-      'LXDE' => [ 'DebianLoginPromptLXDE.png', 'DebianLoginPromptLXDE_d9.png' ],
-      'Gnome' => [ 'DebianLoginPromptGnome.png' ],
-      'VT'   => [ 'DebianLoginPromptVT.png', 'DebianLoginPromptVT_d9.png', 'DebianLoginPromptVT_Edu.png' ],
-    }
-  end
-
-  @screen.waitAny(loginPrompt[style], 20 * 60)
-end
-
-def boot_menu_cmdline_image
-  case @os_loader
-  when "UEFI"
-    'TailsBootMenuKernelCmdlineUEFI.png'
-  else
-    'd-i8_bootsplash.png'
-  end
-end
-
-def boot_menu_tab_msg_image
-  case @os_loader
-  when "UEFI"
-    'TailsBootSplashTabMsgUEFI.png'
-  else
-    #if reboot
-    #  bootsplash = 'TailsBootSplashPostReset.png'
-    #  bootsplash_tab_msg = 'TailsBootSplashTabMsgPostReset.png'
-    #  boot_timeout = 120
-    #else
-      #bootsplash = "DebianLive#{version}BootSplash.png"
-      bootsplash = "DebianLiveBootSplash.png"
-      bootsplash_tab_msg = "DebianLiveBootSplashTabMsg.png"
-      boot_timeout = 30
-    #end
-  end
-end
-
-Given /^the computer (re)?boots Tails$/ do |reboot|
-  step "Tails is at the boot menu's cmdline" + (reboot ? ' after rebooting' : '')
-  @screen.type(" autotest_never_use_this_option blacklist=psmouse #{@boot_options}" +
-               Sikuli::Key.ENTER)
-  @screen.wait('TailsGreeter.png', 5*60)
-  $vm.wait_until_remote_shell_is_up
-  step 'I configure Tails to use a simulated Tor network'
-end
-
-Given /^I log in to a new session(?: in )?(|German)$/ do |lang|
-  case lang
-  when 'German'
-    @language = "German"
-    @screen.wait_and_click('TailsGreeterLanguage.png', 10)
-    @screen.wait('TailsGreeterLanguagePopover.png', 10)
-    @screen.type(@language)
-    sleep(2) # Gtk needs some time to filter the results
-    @screen.type(Sikuli::Key.ENTER)
-    @screen.wait_and_click("TailsGreeterLoginButton#{@language}.png", 10)
-  when ''
-    @screen.wait_and_click('TailsGreeterLoginButton.png', 10)
-  else
-    raise "Unsupported language: #{lang}"
-  end
-  step 'Tails Greeter has applied all settings'
-  step 'the Tails desktop is ready'
-end
-
-def open_greeter_additional_settings
-  @screen.click('TailsGreeterAddMoreOptions.png')
-  @screen.wait('TailsGreeterAdditionalSettingsDialog.png', 10)
-end
-
-Given /^I open Tails Greeter additional settings dialog$/ do
-  open_greeter_additional_settings()
-end
-
-Given /^I enable the specific Tor configuration option$/ do
-  open_greeter_additional_settings()
-  @screen.wait_and_click('TailsGreeterNetworkConnection.png', 30)
-  @screen.wait_and_click("TailsGreeterSpecificTorConfiguration.png", 10)
-  @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
-end
-
-Given /^I set an administration password$/ do
-  open_greeter_additional_settings()
-  @screen.wait_and_click("TailsGreeterAdminPassword.png", 20)
-  @screen.type(@sudo_password)
-  @screen.type(Sikuli::Key.TAB)
-  @screen.type(@sudo_password)
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Given /^Tails Greeter has applied all settings$/ do
-  # I.e. it is done with PostLogin, which is ensured to happen before
-  # a logind session is opened for LIVE_USER.
-  try_for(120) {
-    $vm.execute_successfully("loginctl").stdout
-      .match(/^\s*\S+\s+\d+\s+#{LIVE_USER}\s+seat\d+\s+\S+\s*$/) != nil
-  }
-end
-
-Given /^the Tails desktop is ready$/ do
-  desktop_started_picture = "GnomeApplicationsMenu#{@language}.png"
-  @screen.wait(desktop_started_picture, 180)
-  # We wait for the Florence icon to be displayed to ensure reliable systray icon clicking.
-  @screen.wait("GnomeSystrayFlorence.png", 30)
-  @screen.wait("DesktopTailsDocumentation.png", 30)
-  # Disable screen blanking since we sometimes need to wait long
-  # enough for it to activate, which can mess with Sikuli wait():ing
-  # for some image.
-  $vm.execute_successfully(
-    'gsettings set org.gnome.desktop.session idle-delay 0',
-    :user => LIVE_USER
-  )
-  # We need to enable the accessibility toolkit for dogtail.
-  $vm.execute_successfully(
-    'gsettings set org.gnome.desktop.interface toolkit-accessibility true',
-    :user => LIVE_USER,
-  )
-end
-
-When /^I see the "(.+)" notification(?: after at most (\d+) seconds)?$/ do |title, timeout|
-  timeout = timeout ? timeout.to_i : nil
-  gnome_shell = Dogtail::Application.new('gnome-shell')
-  notification_list = gnome_shell.child(
-    'No Notifications', roleName: 'label', showingOnly: false
-  ).parent.parent
-  try_for(timeout) do
-    notification_list.child?(title, roleName: 'label', showingOnly: false)
-  end
-end
-
-Given /^Tor is ready$/ do
-  step "Tor has built a circuit"
-  step "the time has synced"
-  if $vm.execute('systemctl is-system-running').failure?
-    units_status = $vm.execute('systemctl').stdout
-    raise "At least one system service failed to start:\n#{units_status}"
-  end
-end
-
-Given /^Tor has built a circuit$/ do
-  wait_until_tor_is_working
-end
-
-Given /^the time has synced$/ do
-  ["/run/tordate/done", "/run/htpdate/success"].each do |file|
-    try_for(300) { $vm.execute("test -e #{file}").success? }
-  end
-end
-
-Given /^available upgrades have been checked$/ do
-  try_for(300) {
-    $vm.execute("test -e '/run/tails-upgrader/checked_upgrades'").success?
-  }
-end
-
-When /^I start the Tor Browser( in offline mode)?$/ do |offline|
-  step 'I start "Tor Browser" via GNOME Activities Overview'
-  if offline
-    offline_prompt = Dogtail::Application.new('zenity')
-                     .dialog('Tor is not ready')
-    offline_prompt.button('Start Tor Browser').click
-  end
-  step "the Tor Browser has started#{offline}"
-  if offline
-    step 'the Tor Browser shows the "The proxy server is refusing connections" error'
-  end
-end
-
-Given /^the Tor Browser has started( in offline mode)?$/ do |offline|
-  try_for(60) do
-    @torbrowser = Dogtail::Application.new('Firefox')
-    @torbrowser.child?(roleName: 'frame', recursive: false)
-  end
-end
-
-Given /^the Tor Browser loads the (startup page|Tails roadmap)$/ do |page|
-  case page
-  when "startup page"
-    title = 'Tails - News'
-  when "Tails roadmap"
-    title = 'Roadmap - Tails - RiseupLabs Code Repository'
-  else
-    raise "Unsupported page: #{page}"
-  end
-  step "\"#{title}\" has loaded in the Tor Browser"
-end
-
-When /^I request a new identity using Torbutton$/ do
-  @screen.wait_and_click('TorButtonIcon.png', 30)
-  @screen.wait_and_click('TorButtonNewIdentity.png', 30)
-end
-
-When /^I acknowledge Torbutton's New Identity confirmation prompt$/ do
-  @screen.wait('GnomeQuestionDialogIcon.png', 30)
-  step 'I type "y"'
-end
-
-Given /^I add a bookmark to eff.org in the Tor Browser$/ do
-  url = "https://www.eff.org"
-  step "I open the address \"#{url}\" in the Tor Browser"
-  step 'the Tor Browser shows the "The proxy server is refusing connections" error'
-  @screen.type("d", Sikuli::KeyModifier.CTRL)
-  @screen.wait("TorBrowserBookmarkPrompt.png", 10)
-  @screen.type(url + Sikuli::Key.ENTER)
-end
-
-Given /^the Tor Browser has a bookmark to eff.org$/ do
-  @screen.type("b", Sikuli::KeyModifier.ALT)
-  @screen.wait("TorBrowserEFFBookmark.png", 10)
-end
-
-Given /^all notifications have disappeared$/ do
-  # These magic coordinates always locates GNOME's clock in the top
-  # bar, which when clicked opens the calendar.
-  x, y = 512, 10
-  gnome_shell = Dogtail::Application.new('gnome-shell')
-  retry_action(10, recovery_proc: Proc.new { @screen.type(Sikuli::Key.ESC) }) do
-    @screen.click_point(x, y)
-    unless gnome_shell.child?('No Notifications', roleName: 'label')
-      @screen.click('GnomeCloseAllNotificationsButton.png')
-    end
-    gnome_shell.child?('No Notifications', roleName: 'label')
-  end
-  @screen.type(Sikuli::Key.ESC)
-end
-
-Then /^I (do not )?see "([^"]*)" after at most (\d+) seconds$/ do |negation, image, time|
-  begin
-    @screen.wait(image, time.to_i)
-    raise "found '#{image}' while expecting not to" if negation
-  rescue FindFailed => e
-    raise e if not(negation)
-  end
-end
-
-Then /^I (do not )?see the "([^"]*)" screen, after at most (\d+) seconds$/ do |negation, image, time|
-  begin
-    @screen.wait(diui_png(image), time.to_i)
-    raise "found '" + diui_png(image) + "' while expecting not to" if negation
-  rescue FindFailed => e
-    raise e if not(negation)
-  end
-end
-
-Then /^all Internet traffic has only flowed through Tor$/ do
-  allowed_hosts = allowed_hosts_under_tor_enforcement
-  assert_all_connections(@sniffer.pcap_file) do |c|
-    allowed_hosts.include?({ address: c.daddr, port: c.dport })
-  end
-end
-
-Given /^I enter the sudo password in the pkexec prompt$/ do
-  step "I enter the \"#{@sudo_password}\" password in the pkexec prompt"
-end
-
-def deal_with_polkit_prompt(password, opts = {})
-  opts[:expect_success] ||= true
-  image = 'PolicyKitAuthPrompt.png'
-  @screen.wait(image, 60)
-  @screen.type(password)
-  @screen.type(Sikuli::Key.ENTER)
-  if opts[:expect_success]
-    @screen.waitVanish(image, 20)
-  else
-    @screen.wait('PolicyKitAuthFailure.png', 20)
-  end
-end
-
-Given /^I enter the "([^"]*)" password in the pkexec prompt$/ do |password|
-  deal_with_polkit_prompt(password)
-end
-
-Given /^process "([^"]+)" is (not )?running$/ do |process, not_running|
-  if not_running
-    assert(!$vm.has_process?(process), "Process '#{process}' is running")
-  else
-    assert($vm.has_process?(process), "Process '#{process}' is not running")
-  end
-end
-
-Given /^process "([^"]+)" is running within (\d+) seconds$/ do |process, time|
-  try_for(time.to_i, :msg => "Process '#{process}' is not running after " +
-                             "waiting for #{time} seconds") do
-    $vm.has_process?(process)
-  end
-end
-
-Given /^process "([^"]+)" has stopped running after at most (\d+) seconds$/ do |process, time|
-  try_for(time.to_i, :msg => "Process '#{process}' is still running after " +
-                             "waiting for #{time} seconds") do
-    not $vm.has_process?(process)
-  end
-end
-
-Given /^I kill the process "([^"]+)"$/ do |process|
-  $vm.execute("killall #{process}")
-  try_for(10, :msg => "Process '#{process}' could not be killed") {
-    !$vm.has_process?(process)
-  }
-end
-
-Then /^Tails eventually (shuts down|restarts)$/ do |mode|
-  try_for(3*60) do
-    if mode == 'restarts'
-      @screen.find('TailsGreeter.png')
-      true
-    else
-      ! $vm.is_running?
-    end
-  end
-end
-
-Given /^I shutdown Tails and wait for the computer to power off$/ do
-  $vm.spawn("poweroff")
-  step 'Tails eventually shuts down'
-end
-
-When /^I request a shutdown using the emergency shutdown applet$/ do
-  @screen.hide_cursor
-  @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
-  # Sometimes the next button too fast, before the menu has settled
-  # down to its final size and the icon we want to click is in its
-  # final position. dogtail might allow us to fix that, but given how
-  # rare this problem is, it's not worth the effort.
-  step 'I wait 5 seconds'
-  @screen.wait_and_click('TailsEmergencyShutdownHalt.png', 10)
-end
-
-When /^I warm reboot the computer$/ do
-  $vm.spawn("reboot")
-end
-
-When /^I request a reboot using the emergency shutdown applet$/ do
-  @screen.hide_cursor
-  @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
-  # See comment on /^I request a shutdown using the emergency shutdown applet$/
-  # that explains why we need to wait.
-  step 'I wait 5 seconds'
-  @screen.wait_and_click('TailsEmergencyShutdownReboot.png', 10)
-end
-
-Given /^the package "([^"]+)" is installed$/ do |package|
-  assert($vm.execute("dpkg -s '#{package}' 2>/dev/null | grep -qs '^Status:.*installed$'").success?,
-         "Package '#{package}' is not installed")
-end
-
-Given /^I add a ([a-z0-9.]+ |)wired DHCP NetworkManager connection called "([^"]+)"$/ do |version, con_name|
-  if version and version == '2.x'
-    con_content = <<EOF
-[connection]
-id=#{con_name}
-uuid=b04afa94-c3a1-41bf-aa12-1a743d964162
-interface-name=eth0
-type=ethernet
-EOF
-    con_file = "/etc/NetworkManager/system-connections/#{con_name}"
-    $vm.file_overwrite(con_file, con_content)
-    $vm.execute_successfully("chmod 600 '#{con_file}'")
-    $vm.execute_successfully("nmcli connection load '#{con_file}'")
-  elsif version and version == '3.x'
-    raise "Unsupported version '#{version}'"
-  else
-    $vm.execute_successfully(
-      "nmcli connection add con-name #{con_name} " + \
-      "type ethernet autoconnect yes ifname eth0"
-    )
-  end
-  try_for(10) {
-    nm_con_list = $vm.execute("nmcli --terse --fields NAME connection show").stdout
-    nm_con_list.split("\n").include? "#{con_name}"
-  }
-end
-
-Given /^I switch to the "([^"]+)" NetworkManager connection$/ do |con_name|
-  $vm.execute("nmcli connection up id #{con_name}")
-  try_for(60) do
-    $vm.execute("nmcli --terse --fields NAME,STATE connection show").stdout.chomp.split("\n").include?("#{con_name}:activated")
-  end
-end
-
-When /^I start and focus GNOME Terminal$/ do
-  step 'I start "GNOME Terminal" via GNOME Activities Overview'
-  @screen.wait('GnomeTerminalWindow.png', 40)
-end
-
-When /^I run "([^"]+)" in GNOME Terminal$/ do |command|
-  if !$vm.has_process?("gnome-terminal-server")
-    step "I start and focus GNOME Terminal"
-  else
-    @screen.wait_and_click('GnomeTerminalWindow.png', 20)
-  end
-  @screen.type(command + Sikuli::Key.ENTER)
-end
-
-When /^the file "([^"]+)" exists(?:| after at most (\d+) seconds)$/ do |file, timeout|
-  timeout = 0 if timeout.nil?
-  try_for(
-    timeout.to_i,
-    :msg => "The file #{file} does not exist after #{timeout} seconds"
-  ) {
-    $vm.file_exist?(file)
-  }
-end
-
-When /^the file "([^"]+)" does not exist$/ do |file|
-  assert(! ($vm.file_exist?(file)))
-end
-
-When /^the directory "([^"]+)" exists$/ do |directory|
-  assert($vm.directory_exist?(directory))
-end
-
-When /^the directory "([^"]+)" does not exist$/ do |directory|
-  assert(! ($vm.directory_exist?(directory)))
-end
-
-When /^I copy "([^"]+)" to "([^"]+)" as user "([^"]+)"$/ do |source, destination, user|
-  c = $vm.execute("cp \"#{source}\" \"#{destination}\"", :user => LIVE_USER)
-  assert(c.success?, "Failed to copy file:\n#{c.stdout}\n#{c.stderr}")
-end
-
-def is_persistent?(app)
-  conf = get_persistence_presets(true)["#{app}"]
-  c = $vm.execute("findmnt --noheadings --output SOURCE --target '#{conf}'")
-  # This check assumes that we haven't enabled read-only persistence.
-  c.success? and c.stdout.chomp != "aufs"
-end
-
-Then /^persistence for "([^"]+)" is (|not )enabled$/ do |app, enabled|
-  case enabled
-  when ''
-    assert(is_persistent?(app), "Persistence should be enabled.")
-  when 'not '
-    assert(!is_persistent?(app), "Persistence should not be enabled.")
-  end
-end
-
-Given /^I start "([^"]+)" via GNOME Activities Overview$/ do |app_name|
-  @screen.wait('GnomeApplicationsMenu.png', 10)
-  $vm.execute_successfully('xdotool key Super', user: LIVE_USER)
-  @screen.wait('GnomeActivitiesOverview.png', 10)
-  @screen.type(app_name)
-  @screen.type(Sikuli::Key.ENTER, Sikuli::KeyModifier.CTRL)
-end
-
-When /^I type "([^"]+)"$/ do |string|
-  @screen.type(string)
-end
-
-When /^I press the "([^"]+)" key$/ do |key|
-  begin
-    @screen.type(eval("Sikuli::Key.#{key}"))
-  rescue RuntimeError
-    raise "unsupported key #{key}"
-  end
-end
-
-Then /^the (amnesiac|persistent) Tor Browser directory (exists|does not exist)$/ do |persistent_or_not, mode|
-  case persistent_or_not
-  when "amnesiac"
-    dir = "/home/#{LIVE_USER}/Tor Browser"
-  when "persistent"
-    dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
-  end
-  step "the directory \"#{dir}\" #{mode}"
-end
-
-Then /^there is a GNOME bookmark for the (amnesiac|persistent) Tor Browser directory$/ do |persistent_or_not|
-  case persistent_or_not
-  when "amnesiac"
-    bookmark_image = 'TorBrowserAmnesicFilesBookmark.png'
-  when "persistent"
-    bookmark_image = 'TorBrowserPersistentFilesBookmark.png'
-  end
-  @screen.wait_and_click('GnomePlaces.png', 10)
-  @screen.wait(bookmark_image, 40)
-  @screen.type(Sikuli::Key.ESC)
-end
-
-Then /^there is no GNOME bookmark for the persistent Tor Browser directory$/ do
-  try_for(65) do
-    @screen.wait_and_click('GnomePlaces.png', 10)
-    @screen.wait("GnomePlacesWithoutTorBrowserPersistent.png", 10)
-    @screen.type(Sikuli::Key.ESC)
-  end
-end
-
-def pulseaudio_sink_inputs
-  pa_info = $vm.execute_successfully('pacmd info', :user => LIVE_USER).stdout
-  sink_inputs_line = pa_info.match(/^\d+ sink input\(s\) available\.$/)[0]
-  return sink_inputs_line.match(/^\d+/)[0].to_i
-end
-
-When /^(no|\d+) application(?:s?) (?:is|are) playing audio(?:| after (\d+) seconds)$/ do |nb, wait_time|
-  nb = 0 if nb == "no"
-  sleep wait_time.to_i if ! wait_time.nil?
-  assert_equal(nb.to_i, pulseaudio_sink_inputs)
-end
-
-When /^I double-click on the (Tails documentation|Report an Error) launcher on the desktop$/ do |launcher|
-  image = 'Desktop' + launcher.split.map { |s| s.capitalize } .join + '.png'
-  info = xul_application_info('Tor Browser')
-  # Sometimes the double-click is lost (#12131).
-  retry_action(10) do
-    @screen.wait_and_double_click(image, 10) if $vm.execute("pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'").failure?
-    step 'the Tor Browser has started'
-  end
-end
-
-When /^I click the blocked video icon$/ do
-  @screen.wait_and_click("TorBrowserBlockedVideo.png", 30)
-end
-
-When /^I accept to temporarily allow playing this video$/ do
-  @screen.wait_and_click("TorBrowserOkButton.png", 10)
-end
-
-When /^I click the HTML5 play button$/ do
-  @screen.wait_and_click("TorBrowserHtml5PlayButton.png", 30)
-end
-
-When /^I (can|cannot) save the current page as "([^"]+[.]html)" to the (.*) directory$/ do |should_work, output_file, output_dir|
-  should_work = should_work == 'can' ? true : false
-  @screen.type("s", Sikuli::KeyModifier.CTRL)
-  @screen.wait("TorBrowserSaveDialog.png", 10)
-  if output_dir == "persistent Tor Browser"
-    output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
-    @screen.wait_and_click("GtkTorBrowserPersistentBookmark.png", 10)
-    @screen.wait("GtkTorBrowserPersistentBookmarkSelected.png", 10)
-    # The output filename (without its extension) is already selected,
-    # let's use the keyboard shortcut to focus its field
-    @screen.type("n", Sikuli::KeyModifier.ALT)
-    @screen.wait("TorBrowserSaveOutputFileSelected.png", 10)
-  elsif output_dir == "default downloads"
-    output_dir = "/home/#{LIVE_USER}/Tor Browser"
-  else
-    @screen.type(output_dir + '/')
-  end
-  # Only the part of the filename before the .html extension can be easily replaced
-  # so we have to remove it before typing it into the arget filename entry widget.
-  @screen.type(output_file.sub(/[.]html$/, ''))
-  @screen.type(Sikuli::Key.ENTER)
-  if should_work
-    try_for(10, :msg => "The page was not saved to #{output_dir}/#{output_file}") {
-      $vm.file_exist?("#{output_dir}/#{output_file}")
-    }
-  else
-    @screen.wait("TorBrowserCannotSavePage.png", 10)
-  end
-end
-
-When /^I can print the current page as "([^"]+[.]pdf)" to the (default downloads|persistent Tor Browser) directory$/ do |output_file, output_dir|
-  if output_dir == "persistent Tor Browser"
-    output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
-  else
-    output_dir = "/home/#{LIVE_USER}/Tor Browser"
-  end
-  @screen.type("p", Sikuli::KeyModifier.CTRL)
-  @screen.wait("TorBrowserPrintDialog.png", 20)
-  @screen.wait_and_click("BrowserPrintToFile.png", 10)
-  @screen.wait_and_double_click("TorBrowserPrintOutputFile.png", 10)
-  @screen.hide_cursor
-  @screen.wait("TorBrowserPrintOutputFileSelected.png", 10)
-  # Only the file's basename is selected by double-clicking,
-  # so we type only the desired file's basename to replace it
-  @screen.type(output_dir + '/' + output_file.sub(/[.]pdf$/, '') + Sikuli::Key.ENTER)
-  try_for(30, :msg => "The page was not printed to #{output_dir}/#{output_file}") {
-    $vm.file_exist?("#{output_dir}/#{output_file}")
-  }
-end
-
-Given /^a web server is running on the LAN$/ do
-  @web_server_ip_addr = $vmnet.bridge_ip_addr
-  @web_server_port = 8000
-  @web_server_url = "http://#{@web_server_ip_addr}:#{@web_server_port}"
-  web_server_hello_msg = "Welcome to the LAN web server!"
-
-  # I've tested ruby Thread:s, fork(), etc. but nothing works due to
-  # various strange limitations in the ruby interpreter. For instance,
-  # apparently concurrent IO has serious limits in the thread
-  # scheduler (e.g. sikuli's wait() would block WEBrick from reading
-  # from its socket), and fork():ing results in a lot of complex
-  # cucumber stuff (like our hooks!) ending up in the child process,
-  # breaking stuff in the parent process. After asking some supposed
-  # ruby pros, I've settled on the following.
-  code = <<-EOF
-  require "webrick"
-  STDOUT.reopen("/dev/null", "w")
-  STDERR.reopen("/dev/null", "w")
-  server = WEBrick::HTTPServer.new(:BindAddress => "#{@web_server_ip_addr}",
-                                   :Port => #{@web_server_port},
-                                   :DocumentRoot => "/dev/null")
-  server.mount_proc("/") do |req, res|
-    res.body = "#{web_server_hello_msg}"
-  end
-  server.start
-EOF
-  add_lan_host(@web_server_ip_addr, @web_server_port)
-  proc = IO.popen(['ruby', '-e', code])
-  try_for(10, :msg => "It seems the LAN web server failed to start") do
-    Process.kill(0, proc.pid) == 1
-  end
-
-  add_after_scenario_hook { Process.kill("TERM", proc.pid) }
-
-  # It seems necessary to actually check that the LAN server is
-  # serving, possibly because it isn't doing so reliably when setting
-  # up. If e.g. the Unsafe Browser (which *should* be able to access
-  # the web server) tries to access it too early, Firefox seems to
-  # take some random amount of time to retry fetching. Curl gives a
-  # more consistent result, so let's rely on that instead. Note that
-  # this forces us to capture traffic *after* this step in case
-  # accessing this server matters, like when testing the Tor Browser..
-  try_for(30, :msg => "Something is wrong with the LAN web server") do
-    msg = $vm.execute_successfully("curl #{@web_server_url}",
-                                   :user => LIVE_USER).stdout.chomp
-    web_server_hello_msg == msg
-  end
-end
-
-When /^I open a page on the LAN web server in the (.*)$/ do |browser|
-  step "I open the address \"#{@web_server_url}\" in the #{browser}"
-end
-
-Given /^I wait (?:between (\d+) and )?(\d+) seconds$/ do |min, max|
-  if min
-    time = rand(max.to_i - min.to_i + 1) + min.to_i
-  else
-    time = max.to_i
-  end
-  puts "Slept for #{time} seconds"
-  sleep(time)
-end
-
-Given /^I (?:re)?start monitoring the AppArmor log of "([^"]+)"$/ do |profile|
-  # AppArmor log entries may be dropped if printk rate limiting is
-  # enabled.
-  $vm.execute_successfully('sysctl -w kernel.printk_ratelimit=0')
-  # We will only care about entries for this profile from this time
-  # and on.
-  guest_time = $vm.execute_successfully(
-    'date +"%Y-%m-%d %H:%M:%S"').stdout.chomp
-  @apparmor_profile_monitoring_start ||= Hash.new
-  @apparmor_profile_monitoring_start[profile] = guest_time
-end
-
-When /^AppArmor has (not )?denied "([^"]+)" from opening "([^"]+)"(?: after at most (\d+) seconds)?$/ do |anti_test, profile, file, time|
-  assert(@apparmor_profile_monitoring_start &&
-         @apparmor_profile_monitoring_start[profile],
-         "It seems the profile '#{profile}' isn't being monitored by the " +
-         "'I monitor the AppArmor log of ...' step")
-  audit_line_regex = 'apparmor="DENIED" operation="open" profile="%s" name="%s"' % [profile, file]
-  block = Proc.new do
-    audit_log = $vm.execute(
-      "journalctl --full --no-pager " +
-      "--since='#{@apparmor_profile_monitoring_start[profile]}' " +
-      "SYSLOG_IDENTIFIER=kernel | grep -w '#{audit_line_regex}'"
-    ).stdout.chomp
-    assert(audit_log.empty? == (anti_test ? true : false))
-    true
-  end
-  begin
-    if time
-      try_for(time.to_i) { block.call }
-    else
-      block.call
-    end
-  rescue Timeout::Error, Test::Unit::AssertionFailedError => e
-    raise e, "AppArmor has #{anti_test ? "" : "not "}denied the operation"
-  end
-end
-
-Then /^I force Tor to use a new circuit$/ do
-  force_new_tor_circuit
-end
-
-When /^I eject the boot medium$/ do
-  dev = boot_device
-  dev_type = device_info(dev)['ID_TYPE']
-  case dev_type
-  when 'cd'
-    $vm.eject_cdrom
-  when 'disk'
-    boot_disk_name = $vm.disk_name(dev)
-    $vm.unplug_drive(boot_disk_name)
-  else
-    raise "Unsupported medium type '#{dev_type}' for boot device '#{dev}'"
-  end
-end
-
-Given /^Tails is fooled to think it is running version (.+)$/ do |version|
-  $vm.execute_successfully(
-    "sed -i " +
-    "'s/^TAILS_VERSION_ID=.*$/TAILS_VERSION_ID=\"#{version}\"/' " +
-    "/etc/os-release"
-  )
-end
-
-Then /^Tails is running version (.+)$/ do |version|
-  v1 = $vm.execute_successfully('tails-version').stdout.split.first
-  assert_equal(version, v1, "The version doesn't match tails-version's output")
-  v2 = $vm.file_content('/etc/os-release')
-       .scan(/TAILS_VERSION_ID="(#{version})"/).flatten.first
-  assert_equal(version, v2, "The version doesn't match /etc/os-release")
-end
-
-def share_host_files(files)
-  files = [files] if files.class == String
-  assert_equal(Array, files.class)
-  disk_size = files.map { |f| File.new(f).size } .inject(0, :+)
-  # Let's add some extra space for filesysten overhead etc.
-  disk_size += [convert_to_bytes(1, 'MiB'), (disk_size * 0.10).ceil].max
-  disk = random_alpha_string(10)
-  step "I temporarily create an #{disk_size} bytes disk named \"#{disk}\""
-  step "I create a gpt partition labeled \"#{disk}\" with an ext4 " +
-       "filesystem on disk \"#{disk}\""
-  $vm.storage.guestfs_disk_helper(disk) do |g, _|
-    partition = g.list_partitions().first
-    g.mount(partition, "/")
-    files.each { |f| g.upload(f, "/" + File.basename(f)) }
-  end
-  step "I plug USB drive \"#{disk}\""
-  mount_dir = $vm.execute_successfully('mktemp -d').stdout.chomp
-  dev = $vm.disk_dev(disk)
-  partition = dev + '1'
-  $vm.execute_successfully("mount #{partition} #{mount_dir}")
-  $vm.execute_successfully("chmod -R a+rX '#{mount_dir}'")
-  return mount_dir
-end
-
-def mount_USB_drive(disk, fs_options = {})
-  fs_options[:encrypted] ||= false
-  @tmp_usb_drive_mount_dir = $vm.execute_successfully('mktemp -d').stdout.chomp
-  dev = $vm.disk_dev(disk)
-  partition = dev + '1'
-  if fs_options[:encrypted]
-    password = fs_options[:password]
-    assert_not_nil(password)
-    luks_mapping = "#{disk}_unlocked"
-    $vm.execute_successfully(
-      "echo #{password} | " +
-      "cryptsetup luksOpen #{partition} #{luks_mapping}"
-    )
-    $vm.execute_successfully(
-      "mount /dev/mapper/#{luks_mapping} #{@tmp_usb_drive_mount_dir}"
-    )
-  else
-    $vm.execute_successfully("mount #{partition} #{@tmp_usb_drive_mount_dir}")
-  end
-  @tmp_filesystem_disk = disk
-  @tmp_filesystem_options = fs_options
-  @tmp_filesystem_partition = partition
-  return @tmp_usb_drive_mount_dir
-end
-
-When(/^I plug and mount a (\d+) MiB USB drive with an? (.*)$/) do |size_MiB, fs|
-  disk_size = convert_to_bytes(size_MiB.to_i, 'MiB')
-  disk = random_alpha_string(10)
-  step "I temporarily create an #{disk_size} bytes disk named \"#{disk}\""
-  step "I create a gpt partition labeled \"#{disk}\" with " +
-       "an #{fs} on disk \"#{disk}\""
-  step "I plug USB drive \"#{disk}\""
-  fs_options = {}
-  fs_options[:filesystem] = /(.*) filesystem/.match(fs)[1]
-  if /\bencrypted with password\b/.match(fs)
-    fs_options[:encrypted] = true
-    fs_options[:password] = /encrypted with password "([^"]+)"/.match(fs)[1]
-  end
-  mount_dir = mount_USB_drive(disk, fs_options)
-  @tmp_filesystem_size_b = convert_to_bytes(
-    avail_space_in_mountpoint_kB(mount_dir),
-    'KB'
-  )
-end
-
-When(/^I mount the USB drive again$/) do
-  mount_USB_drive(@tmp_filesystem_disk, @tmp_filesystem_options)
-end
-
-When(/^I umount the USB drive$/) do
-  $vm.execute_successfully("umount #{@tmp_usb_drive_mount_dir}")
-  if @tmp_filesystem_options[:encrypted]
-    $vm.execute_successfully("cryptsetup luksClose #{@tmp_filesystem_disk}_unlocked")
-  end
-end
-
-When /^Tails system time is magically synchronized$/ do
-  $vm.host_to_guest_time_sync
-end
diff --git a/cucumber/features/step_definitions/dhcp.rb b/cucumber/features/step_definitions/dhcp.rb
deleted file mode 100644
index 3c83422..0000000
--- a/cucumber/features/step_definitions/dhcp.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-Then /^the hostname should not have been leaked on the network$/ do
-  begin
-    hostname = $vm.execute("hostname").stdout.chomp
-    packets = PacketFu::PcapFile.new.file_to_array(filename: @sniffer.pcap_file)
-    packets.each do |p|
-      # if PacketFu::TCPPacket.can_parse?(p)
-      #   ipv4_tcp_packets << PacketFu::TCPPacket.parse(p)
-      if PacketFu::IPPacket.can_parse?(p)
-        payload = PacketFu::IPPacket.parse(p).payload
-      elsif PacketFu::IPv6Packet.can_parse?(p)
-        payload = PacketFu::IPv6Packet.parse(p).payload
-      else
-        raise "Found something in the pcap file that either is non-IP, or cannot be parsed"
-      end
-      if payload.match(hostname)
-        raise "Hostname leak detected"
-      end
-    end
-  rescue Exception => e
-    save_failure_artifact("Network capture", @sniffer.pcap_file)
-    raise e
-  end
-end
diff --git a/cucumber/features/step_definitions/electrum.rb b/cucumber/features/step_definitions/electrum.rb
deleted file mode 100644
index eaeb22a..0000000
--- a/cucumber/features/step_definitions/electrum.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-Then /^I start Electrum through the GNOME menu$/ do
-  step "I start \"Electrum Bitcoin Wallet\" via GNOME Activities Overview"
-end
-
-When /^a bitcoin wallet is (|not )present$/ do |existing|
-  wallet = "/home/#{LIVE_USER}/.electrum/wallets/default_wallet"
-  case existing
-  when ""
-    step "the file \"#{wallet}\" exists after at most 30 seconds"
-  when "not "
-    step "the file \"#{wallet}\" does not exist"
-  else
-    raise "Unknown value specified for #{existing}"
-  end
-end
-
-When /^I create a new bitcoin wallet$/ do
-  @screen.wait("ElectrumNoWallet.png", 10)
-  @screen.wait_and_click("ElectrumNextButton.png", 10)
-  @screen.wait("ElectrumCreateNewSeed.png", 10)
-  @screen.wait_and_click("ElectrumNextButton.png", 10)
-  @screen.wait("ElectrumWalletGenerationSeed.png", 15)
-  @screen.wait_and_click("ElectrumWalletSeedTextbox.png", 15)
-  @screen.type('a', Sikuli::KeyModifier.CTRL) # select wallet seed
-  @screen.type('c', Sikuli::KeyModifier.CTRL) # copy seed to clipboard
-  seed = $vm.get_clipboard
-  @screen.wait_and_click("ElectrumNextButton.png", 15)
-  @screen.wait("ElectrumSeedVerificationPrompt.png", 15)
-  @screen.wait_and_click("ElectrumWalletSeedTextbox.png", 15)
-  @screen.type(seed) # Confirm seed
-  @screen.wait_and_click("ElectrumNextButton.png", 10)
-  @screen.wait("ElectrumEncryptWallet.png", 10)
-  @screen.type(Sikuli::Key.TAB)          # focus first password field
-  @screen.type("asdf" + Sikuli::Key.TAB) # set password
-  @screen.type("asdf" + Sikuli::Key.TAB) # confirm password
-  @screen.wait_and_click("ElectrumNextButton.png", 10)
-  @screen.wait("ElectrumPreferencesButton.png", 30)
-end
-
-Then /^I see a warning that Electrum is not persistent$/ do
-  @screen.wait('GnomeQuestionDialogIcon.png', 30)
-end
-
-Then /^I am prompted to configure Electrum$/ do
-  @screen.wait("ElectrumNoWallet.png", 60)
-end
-
-Then /^I see the main Electrum client window$/ do
-  @screen.wait('ElectrumPreferencesButton.png', 20)
-end
-
-Then /^Electrum successfully connects to the network$/ do
-  @screen.wait('ElectrumStatus.png', 180)
-end
diff --git a/cucumber/features/step_definitions/encryption.rb b/cucumber/features/step_definitions/encryption.rb
deleted file mode 100644
index 3b20a5b..0000000
--- a/cucumber/features/step_definitions/encryption.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-def seahorse_menu_click_helper(main, sub, verify = nil)
-  try_for(60) do
-    step "process \"#{verify}\" is running" if verify
-    @screen.hide_cursor
-    @screen.wait_and_click(main, 10)
-    @screen.wait_and_click(sub, 10)
-    return
-  end
-end
-
-Given /^I generate an OpenPGP key named "([^"]+)" with password "([^"]+)"$/ do |name, pwd|
-  @passphrase = pwd
-  @key_name = name
-  gpg_key_recipie = <<EOF
-     Key-Type: RSA
-     Key-Length: 4096
-     Subkey-Type: RSA
-     Subkey-Length: 4096
-     Name-Real: #{@key_name}
-     Name-Comment: Blah
-     Name-Email: #{@key_name}@test.org
-     Expire-Date: 0
-     Passphrase: #{pwd}
-     %commit
-EOF
-  recipe_path = '/tmp/gpg_key_recipe'
-  $vm.file_overwrite(recipe_path, gpg_key_recipie)
-  $vm.execute("chown #{LIVE_USER}:#{LIVE_USER} #{recipe_path}")
-  c = $vm.execute("gpg --batch --gen-key < #{recipe_path}",
-                  :user => LIVE_USER)
-  assert(c.success?, "Failed to generate OpenPGP key:\n#{c.stderr}")
-end
-
-When /^I type a message into gedit$/ do
-  step 'I start "gedit" via GNOME Activities Overview'
-  @screen.wait_and_click("GeditWindow.png", 20)
-  # We don't have a good visual indicator for when we can continue. Without the
-  # sleep we may start typing in the gedit window far too soon, causing
-  # keystrokes to go missing.
-  sleep 5
-  @screen.type("ATTACK AT DAWN")
-end
-
-def maybe_deal_with_pinentry
-  begin
-    @screen.wait_and_click("PinEntryPrompt.png", 10)
-    # Without this sleep here (and reliable visual indicators) we can sometimes
-    # miss keystrokes by typing too soon. This sleep prevents this problem from
-    # coming up.
-    sleep 5
-    @screen.type(@passphrase + Sikuli::Key.ENTER)
-  rescue FindFailed
-    # The passphrase was cached or we wasn't prompted at all (e.g. when
-    # only encrypting to a public key)
-  end
-end
-
-def gedit_copy_all_text
-  context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditSelectAll.png')
-  context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditCopy.png')
-end
-
-def gedit_paste_into_a_new_tab
-  @screen.wait_and_click("GeditNewTab.png", 20)
-  context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditPaste.png')
-end
-
-def encrypt_sign_helper
-  gedit_copy_all_text
-  seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletSignEncrypt.png')
-  @screen.wait_and_click("GpgAppletChooseKeyWindow.png", 30)
-  # We don't have a good visual indicator for when we can continue without
-  # keystrokes being lost.
-  sleep 5
-  yield
-  maybe_deal_with_pinentry
-  gedit_paste_into_a_new_tab
-end
-
-def decrypt_verify_helper(icon)
-  gedit_copy_all_text
-  seahorse_menu_click_helper(icon, 'GpgAppletDecryptVerify.png')
-  maybe_deal_with_pinentry
-  @screen.wait("GpgAppletResults.png", 20)
-  @screen.wait("GpgAppletResultsMsg.png", 20)
-end
-
-When /^I encrypt the message using my OpenPGP key$/ do
-  encrypt_sign_helper do
-    @screen.type(@key_name + Sikuli::Key.ENTER + Sikuli::Key.ENTER)
-  end
-end
-
-Then /^I can decrypt the encrypted message$/ do
-  decrypt_verify_helper("GpgAppletIconEncrypted.png")
-  @screen.wait("GpgAppletResultsEncrypted.png", 20)
-end
-
-When /^I sign the message using my OpenPGP key$/ do
-  encrypt_sign_helper do
-    @screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER)
-  end
-end
-
-Then /^I can verify the message's signature$/ do
-  decrypt_verify_helper("GpgAppletIconSigned.png")
-  @screen.wait("GpgAppletResultsSigned.png", 20)
-end
-
-When /^I both encrypt and sign the message using my OpenPGP key$/ do
-  encrypt_sign_helper do
-    @screen.wait_and_click('GpgAppletEncryptionKey.png', 20)
-    @screen.type(Sikuli::Key.SPACE)
-    @screen.wait('GpgAppletKeySelected.png', 10)
-    @screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER)
-    @screen.type(Sikuli::Key.ENTER)
-  end
-end
-
-Then /^I can decrypt and verify the encrypted message$/ do
-  decrypt_verify_helper("GpgAppletIconEncrypted.png")
-  @screen.wait("GpgAppletResultsEncrypted.png", 20)
-  @screen.wait("GpgAppletResultsSigned.png", 20)
-end
-
-When /^I symmetrically encrypt the message with password "([^"]+)"$/ do |pwd|
-  @passphrase = pwd
-  gedit_copy_all_text
-  seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletEncryptPassphrase.png')
-  maybe_deal_with_pinentry # enter password
-  maybe_deal_with_pinentry # confirm password
-  gedit_paste_into_a_new_tab
-end
diff --git a/cucumber/features/step_definitions/evince.rb b/cucumber/features/step_definitions/evince.rb
deleted file mode 100644
index 9411ac4..0000000
--- a/cucumber/features/step_definitions/evince.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-When /^I(?:| try to) open "([^"]+)" with Evince$/ do |filename|
-  step "I run \"evince #{filename}\" in GNOME Terminal"
-end
-
-Then /^I can print the current document to "([^"]+)"$/ do |output_file|
-  @screen.type("p", Sikuli::KeyModifier.CTRL)
-  @screen.wait("EvincePrintDialog.png", 10)
-  @screen.wait_and_click("EvincePrintToFile.png", 10)
-  @screen.wait_and_click("EvincePrintOutputFileButton.png", 10)
-  @screen.wait("EvincePrintFileDialog.png", 10)
-  # Only the file's basename is selected by double-clicking,
-  # so we type only the desired file's basename to replace it
-  $vm.set_clipboard(output_file.sub(/[.]pdf$/, ''))
-  @screen.type('v', Sikuli::KeyModifier.CTRL)
-  @screen.type(Sikuli::Key.ENTER)
-  @screen.wait_and_click("EvincePrintButton.png", 10)
-  try_for(10, :msg => "The document was not printed to #{output_file}") {
-    $vm.file_exist?(output_file)
-  }
-end
-
-When /^I close Evince$/ do
-  @screen.type("w", Sikuli::KeyModifier.CTRL)
-  step 'process "evince" has stopped running after at most 20 seconds'
-end
diff --git a/cucumber/features/step_definitions/firewall_leaks.rb b/cucumber/features/step_definitions/firewall_leaks.rb
deleted file mode 100644
index 0cd94cc..0000000
--- a/cucumber/features/step_definitions/firewall_leaks.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-Then(/^the firewall leak detector has detected leaks$/) do
-  assert_raise(FirewallAssertionFailedError) do
-    step 'all Internet traffic has only flowed through Tor'
-  end
-end
-
-Given(/^I disable Tails' firewall$/) do
-  $vm.execute("/usr/local/lib/do_not_ever_run_me")
-  iptables = $vm.execute("iptables -L -n -v").stdout.chomp.split("\n")
-  for line in iptables do
-    if !line[/Chain (INPUT|OUTPUT|FORWARD) \(policy ACCEPT/] and
-       !line[/pkts[[:blank:]]+bytes[[:blank:]]+target/] and
-       !line.empty?
-      raise "The Tails firewall was not successfully disabled:\n#{iptables}"
-    end
-  end
-end
-
-When(/^I do a TCP DNS lookup of "(.*?)"$/) do |host|
-  lookup = $vm.execute("host -T -t A #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
-  assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
-end
-
-When(/^I do a UDP DNS lookup of "(.*?)"$/) do |host|
-  lookup = $vm.execute("host -t A #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
-  assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
-end
-
-When(/^I send some ICMP pings$/) do
-  # We ping an IP address to avoid a DNS lookup
-  ping = $vm.execute("ping -c 5 #{SOME_DNS_SERVER}")
-  assert(ping.success?, "Failed to ping #{SOME_DNS_SERVER}:\n#{ping.stderr}")
-end
diff --git a/cucumber/features/step_definitions/git.rb b/cucumber/features/step_definitions/git.rb
deleted file mode 100644
index bd8fcf7..0000000
--- a/cucumber/features/step_definitions/git.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-When /^I clone the Git repository "([\S]+)" in GNOME Terminal$/ do |repo|
-  repo_directory = /[\S]+\/([\S]+)(\.git)?$/.match(repo)[1]
-  assert(!$vm.directory_exist?("/home/#{LIVE_USER}/#{repo_directory}"))
-
-  recovery_proc = Proc.new do
-    $vm.execute("rm -rf /home/#{LIVE_USER}/#{repo_directory}",
-                             :user => LIVE_USER)
-    step 'I kill the process "git"'
-    @screen.type('clear' + Sikuli::Key.ENTER)
-  end
-
-  retry_tor(recovery_proc) do
-    step "I run \"git clone #{repo}\" in GNOME Terminal"
-    m = /^(https?|git):\/\//.match(repo)
-    unless m
-      step 'I verify the SSH fingerprint for the Git repository'
-    end
-    try_for(180, :msg => 'Git process took too long') {
-      !$vm.has_process?('/usr/bin/git')
-    }
-    Dogtail::Application.new('gnome-terminal-server')
-      .child('Terminal', roleName: 'terminal')
-      .text['Unpacking objects: 100%']
-  end
-end
-
-Then /^the Git repository "([\S]+)" has been cloned successfully$/ do |repo|
-  assert($vm.directory_exist?("/home/#{LIVE_USER}/#{repo}/.git"))
-  assert($vm.file_exist?("/home/#{LIVE_USER}/#{repo}/.git/config"))
-  $vm.execute_successfully("cd '/home/#{LIVE_USER}/#{repo}/' && git status",
-                           :user => LIVE_USER)
-end
diff --git a/cucumber/features/step_definitions/mac_spoofing.rb b/cucumber/features/step_definitions/mac_spoofing.rb
deleted file mode 100644
index 260b28f..0000000
--- a/cucumber/features/step_definitions/mac_spoofing.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-def all_ethernet_nics
-  $vm.execute_successfully(
-    "get_all_ethernet_nics", :libs => 'hardware'
-  ).stdout.split
-end
-
-When /^I disable MAC spoofing in Tails Greeter$/ do
-  open_greeter_additional_settings()
-  @screen.wait_and_click("TailsGreeterMACSpoofing.png", 30)
-  @screen.wait_and_click("TailsGreeterDisableMACSpoofing.png", 10)
-  @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
-end
-
-Then /^the (\d+)(?:st|nd|rd|th) network device has (its real|a spoofed) MAC address configured$/ do |dev_nr, mode|
-  is_spoofed = (mode == "a spoofed")
-  alias_name = "net#{dev_nr.to_i - 1}"
-  nic_real_mac = $vm.real_mac(alias_name)
-  nic = "eth#{dev_nr.to_i - 1}"
-  nic_current_mac = $vm.execute_successfully(
-    "get_current_mac_of_nic #{nic}", :libs => 'hardware'
-  ).stdout.chomp
-  begin
-    if is_spoofed
-      if nic_real_mac == nic_current_mac
-        raise "The MAC address was expected to be spoofed but wasn't"
-      end
-    else
-      if nic_real_mac != nic_current_mac
-        raise "The MAC address is spoofed but was expected to not be"
-      end
-    end
-  rescue Exception => e
-    save_failure_artifact("Network capture", @sniffer.pcap_file)
-    raise e
-  end
-end
-
-Then /^no network device leaked the real MAC address$/ do
-  macs = $vm.all_real_macs
-  assert_all_connections(@sniffer.pcap_file) do |c|
-    macs.all? do |mac|
-      not [c.mac_saddr, c.mac_daddr].include?(mac)
-    end
-  end
-end
-
-Then /^some network device leaked the real MAC address$/ do
-  assert_raise(FirewallAssertionFailedError) do
-    step 'no network device leaked the real MAC address'
-  end
-end
-
-Given /^macchanger will fail by not spoofing and always returns ([\S]+)$/ do |mode|
-  $vm.execute_successfully("mv /usr/bin/macchanger /usr/bin/macchanger.orig")
-  $vm.execute_successfully("ln -s /bin/#{mode} /usr/bin/macchanger")
-end
-
-Given /^no network interface modules can be unloaded$/ do
-  # Note that the real /sbin/modprobe is a symlink to /bin/kmod, and
-  # for it to run in modprobe compatibility mode the name must be
-  # exactly "modprobe", so we just move it somewhere our of the path
-  # instead of renaming it ".real" or whatever we usuablly do when
-  # diverting executables for wrappers.
-  modprobe_divert = "/usr/local/lib/modprobe"
-  $vm.execute_successfully(
-    "dpkg-divert --add --rename --divert '#{modprobe_divert}' /sbin/modprobe"
-  )
-  fake_modprobe_wrapper = <<EOF
-#!/bin/sh
-if echo "${@}" | grep -q -- -r; then
-    exit 1
-fi
-exec '#{modprobe_divert}' "${@}"
-EOF
-  $vm.file_append('/sbin/modprobe', fake_modprobe_wrapper)
-  $vm.execute_successfully("chmod a+rx /sbin/modprobe")
-end
-
-Then /^(\d+|no) network interface(?:s)? (?:is|are) enabled$/ do |expected_nr_nics|
-  # note that "no".to_i => 0 in Ruby.
-  expected_nr_nics = expected_nr_nics.to_i
-  nr_nics = all_ethernet_nics.size
-  assert_equal(expected_nr_nics, nr_nics)
-end
-
-Then /^the MAC spoofing panic mode disabled networking$/ do
-  nm_state = $vm.execute_successfully('systemctl show NetworkManager').stdout
-  nm_is_disabled = $vm.pidof('NetworkManager').empty? &&
-                   nm_state[/^LoadState=masked$/] &&
-                   nm_state[/^ActiveState=inactive$/]
-  assert(nm_is_disabled, "NetworkManager was not disabled")
-  all_ethernet_nics.each do |nic|
-    ["nic_ipv4_addr", "nic_ipv6_addr"].each do |function|
-      addr = $vm.execute_successfully(
-        "#{function} #{nic}", :libs => 'hardware'
-      ).stdout.chomp
-      assert_equal("", addr, "NIC #{nic} was assigned address #{addr}")
-    end
-  end
-end
-
-When /^I hotplug a network device( and wait for it to be initialized)?$/ do |wait|
-  initial_nr_nics = wait ? all_ethernet_nics.size : nil
-  xml = <<-EOF
-    <interface type='network'>
-      <alias name='net1'/>
-      <mac address='52:54:00:11:22:33'/>
-      <source network='TailsToasterNet'/>
-      <model type='virtio'/>
-      <link state='up'/>
-    </interface>
-  EOF
-  $vm.plug_device(xml)
-  if wait
-    try_for(20) do
-      all_ethernet_nics.size >= initial_nr_nics + 1
-    end
-  end
-end
diff --git a/cucumber/features/step_definitions/pidgin.rb b/cucumber/features/step_definitions/pidgin.rb
deleted file mode 100644
index 43949b6..0000000
--- a/cucumber/features/step_definitions/pidgin.rb
+++ /dev/null
@@ -1,497 +0,0 @@
-# Extracts the secrets for the XMMP account `account_name`.
-def xmpp_account(account_name, required_options = [])
-  begin
-    account = $config["Pidgin"]["Accounts"]["XMPP"][account_name]
-    check_keys = ["username", "domain", "password"] + required_options
-    for key in check_keys do
-      assert(account.has_key?(key))
-      assert_not_nil(account[key])
-      assert(!account[key].empty?)
-    end
-  rescue NoMethodError, Test::Unit::AssertionFailedError
-    raise(
-<<EOF
-Your Pidgin:Accounts:XMPP:#{account} is incorrect or missing from your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format.
-EOF
-)
-  end
-  return account
-end
-
-def wait_and_focus(img, time = 10, window)
-  begin
-    @screen.wait(img, time)
-  rescue FindFailed
-    $vm.focus_window(window)
-    @screen.wait(img, time)
-  end
-end
-
-def focus_pidgin_irc_conversation_window(account)
-  account = account.sub(/^irc\./, '')
-  try_for(20) do
-    $vm.focus_window(".*#{Regexp.escape(account)}$")
-  end
-end
-
-def pidgin_dbus_call(method, *args)
-  dbus_send(
-    'im.pidgin.purple.PurpleService',
-    '/im/pidgin/purple/PurpleObject',
-    "im.pidgin.purple.PurpleInterface.#{method}",
-    *args, user: LIVE_USER
-  )
-end
-
-def pidgin_account_connected?(account, prpl_protocol)
-  account_id = pidgin_dbus_call('PurpleAccountsFind', account, prpl_protocol)
-  pidgin_dbus_call('PurpleAccountIsConnected', account_id) == 1
-end
-
-When /^I create my XMPP account$/ do
-  account = xmpp_account("Tails_account")
-  @screen.click("PidginAccountManagerAddButton.png")
-  @screen.wait("PidginAddAccountWindow.png", 20)
-  @screen.click_mid_right_edge("PidginAddAccountProtocolLabel.png")
-  @screen.click("PidginAddAccountProtocolXMPP.png")
-  # We first wait for some field that is shown for XMPP but not the
-  # default (IRC) since we otherwise may decide where we click before
-  # the GUI has updated after switching protocol.
-  @screen.wait("PidginAddAccountXMPPDomain.png", 5)
-  @screen.click_mid_right_edge("PidginAddAccountXMPPUsername.png")
-  @screen.type(account["username"])
-  @screen.click_mid_right_edge("PidginAddAccountXMPPDomain.png")
-  @screen.type(account["domain"])
-  @screen.click_mid_right_edge("PidginAddAccountXMPPPassword.png")
-  @screen.type(account["password"])
-  @screen.click("PidginAddAccountXMPPRememberPassword.png")
-  if account["connect_server"]
-    @screen.click("PidginAddAccountXMPPAdvancedTab.png")
-    @screen.click_mid_right_edge("PidginAddAccountXMPPConnectServer.png")
-    @screen.type(account["connect_server"])
-  end
-  @screen.click("PidginAddAccountXMPPAddButton.png")
-end
-
-Then /^Pidgin automatically enables my XMPP account$/ do
-  account = xmpp_account("Tails_account")
-  jid = account["username"] + '@' + account["domain"]
-  try_for(3*60) do
-    pidgin_account_connected?(jid, 'prpl-jabber')
-  end
-  $vm.focus_window('Buddy List')
-  @screen.wait("PidginAvailableStatus.png", 60*3)
-end
-
-Given /^my XMPP friend goes online( and joins the multi-user chat)?$/ do |join_chat|
-  account = xmpp_account("Friend_account", ["otr_key"])
-  bot_opts = account.select { |k, v| ["connect_server"].include?(k) }
-  if join_chat
-    bot_opts["auto_join"] = [@chat_room_jid]
-  end
-  @friend_name = account["username"]
-  @chatbot = ChatBot.new(account["username"] + "@" + account["domain"],
-                         account["password"], account["otr_key"], bot_opts)
-  @chatbot.start
-  add_after_scenario_hook { @chatbot.stop }
-  $vm.focus_window('Buddy List')
-  @screen.wait("PidginFriendOnline.png", 60)
-end
-
-When /^I start a conversation with my friend$/ do
-  $vm.focus_window('Buddy List')
-  # Clicking the middle, bottom of this image should query our
-  # friend, given it's the only subscribed user that's online, which
-  # we assume.
-  r = @screen.find("PidginFriendOnline.png")
-  bottom_left = r.getBottomLeft()
-  x = bottom_left.getX + r.getW/2
-  y = bottom_left.getY
-  @screen.doubleClick_point(x, y)
-  # Since Pidgin sets the window name to the contact, we have no good
-  # way to identify the conversation window. Let's just look for the
-  # expected menu bar.
-  @screen.wait("PidginConversationWindowMenuBar.png", 10)
-end
-
-And /^I say (.*) to my friend( in the multi-user chat)?$/ do |msg, multi_chat|
-  msg = "ping" if msg == "something"
-  msg = msg + Sikuli::Key.ENTER
-  if multi_chat
-    $vm.focus_window(@chat_room_jid.split("@").first)
-    msg = @friend_name + ": " + msg
-  else
-    $vm.focus_window(@friend_name)
-  end
-  @screen.type(msg)
-end
-
-Then /^I receive a response from my friend( in the multi-user chat)?$/ do |multi_chat|
-  if multi_chat
-    $vm.focus_window(@chat_room_jid.split("@").first)
-  else
-    $vm.focus_window(@friend_name)
-  end
-  try_for(60) do
-    if @screen.exists('PidginServerMessage.png')
-      @screen.click('PidginDialogCloseButton.png')
-    end
-    @screen.find('PidginFriendExpectedAnswer.png')
-  end
-end
-
-When /^I start an OTR session with my friend$/ do
-  $vm.focus_window(@friend_name)
-  @screen.click("PidginConversationOTRMenu.png")
-  @screen.hide_cursor
-  @screen.click("PidginOTRMenuStartSession.png")
-end
-
-Then /^Pidgin automatically generates an OTR key$/ do
-  @screen.wait("PidginOTRKeyGenPrompt.png", 30)
-  @screen.wait_and_click("PidginOTRKeyGenPromptDoneButton.png", 30)
-end
-
-Then /^an OTR session was successfully started with my friend$/ do
-  $vm.focus_window(@friend_name)
-  @screen.wait("PidginConversationOTRUnverifiedSessionStarted.png", 10)
-end
-
-# The reason the chat must be empty is to guarantee that we don't mix
-# up messages/events from other users with the ones we expect from the
-# bot.
-When /^I join some empty multi-user chat$/ do
-  $vm.focus_window('Buddy List')
-  @screen.click("PidginBuddiesMenu.png")
-  @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10)
-  @screen.wait_and_click("PidginJoinChatWindow.png", 10)
-  @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png")
-  account = xmpp_account("Tails_account")
-  if account.has_key?("chat_room") && \
-     !account["chat_room"].nil? && \
-     !account["chat_room"].empty?
-    chat_room = account["chat_room"]
-  else
-    chat_room = random_alnum_string(10, 15)
-  end
-  @screen.type(chat_room)
-
-  # We will need the conference server later, when starting the bot.
-  @screen.click_mid_right_edge("PidginJoinChatServerLabel.png")
-  @screen.type("a", Sikuli::KeyModifier.CTRL)
-  @screen.type("c", Sikuli::KeyModifier.CTRL)
-  conference_server =
-    $vm.execute_successfully("xclip -o", :user => LIVE_USER).stdout.chomp
-  @chat_room_jid = chat_room + "@" + conference_server
-
-  @screen.click("PidginJoinChatButton.png")
-  # The following will both make sure that the we joined the chat, and
-  # that it is empty. We'll also deal with the *potential* "Create New
-  # Room" prompt that Pidgin shows for some server configurations.
-  images = ["PidginCreateNewRoomPrompt.png",
-            "PidginChat1UserInRoom.png"]
-  image_found, _ = @screen.waitAny(images, 30)
-  if image_found == "PidginCreateNewRoomPrompt.png"
-    @screen.click("PidginCreateNewRoomAcceptDefaultsButton.png")
-  end
-  $vm.focus_window(@chat_room_jid)
-  @screen.wait("PidginChat1UserInRoom.png", 10)
-end
-
-# Since some servers save the scrollback, and sends it when joining,
-# it's safer to clear it so we do not get false positives from old
-# messages when looking for a particular response, or similar.
-When /^I clear the multi-user chat's scrollback$/ do
-  $vm.focus_window(@chat_room_jid)
-  @screen.click("PidginConversationMenu.png")
-  @screen.wait_and_click("PidginConversationMenuClearScrollback.png", 10)
-end
-
-Then /^I can see that my friend joined the multi-user chat$/ do
-  $vm.focus_window(@chat_room_jid)
-  @screen.wait("PidginChat2UsersInRoom.png", 60)
-end
-
-def configured_pidgin_accounts
-  accounts = Hash.new
-  xml = REXML::Document.new(
-    $vm.file_content("/home/#{LIVE_USER}/.purple/accounts.xml")
-  )
-  xml.elements.each("account/account") do |e|
-    account   = e.elements["name"].text
-    account_name, network = account.split("@")
-    protocol  = e.elements["protocol"].text
-    port      = e.elements["settings/setting[@name='port']"].text
-    username_element  = e.elements["settings/setting[@name='username']"]
-    realname_elemenet = e.elements["settings/setting[@name='realname']"]
-    if username_element
-      nickname  = username_element.text
-    else
-      nickname  = nil
-    end
-    if realname_elemenet
-      real_name = realname_elemenet.text
-    else
-      real_name = nil
-    end
-    accounts[network] = {
-      'name'      => account_name,
-      'network'   => network,
-      'protocol'  => protocol,
-      'port'      => port,
-      'nickname'  => nickname,
-      'real_name' => real_name,
-    }
-  end
-
-  return accounts
-end
-
-def chan_image (account, channel, image)
-  images = {
-    'conference.riseup.net' => {
-      'tails' => {
-        'conversation_tab' => 'PidginTailsConversationTab',
-        'welcome'          => 'PidginTailsChannelWelcome',
-      }
-    },
-  }
-  return images[account][channel][image] + ".png"
-end
-
-def default_chan (account)
-  chans = {
-    'conference.riseup.net' => 'tails',
-  }
-  return chans[account]
-end
-
-def pidgin_otr_keys
-  return $vm.file_content("/home/#{LIVE_USER}/.purple/otr.private_key")
-end
-
-Given /^Pidgin has the expected accounts configured with random nicknames$/ do
-  expected = [
-            ["irc.oftc.net", "prpl-irc", "6697"],
-            ["127.0.0.1",    "prpl-irc", "6668"],
-          ]
-  configured_pidgin_accounts.values.each() do |account|
-    assert(account['nickname'] != "XXX_NICK_XXX", "Nickname was no randomised")
-    assert_equal(account['nickname'], account['real_name'],
-                 "Nickname and real name are not identical: " +
-                 account['nickname'] + " vs. " + account['real_name'])
-    assert_equal(account['name'], account['nickname'],
-                 "Account name and nickname are not identical: " +
-                 account['name'] + " vs. " + account['nickname'])
-    candidate = [account['network'], account['protocol'], account['port']]
-    assert(expected.include?(candidate), "Unexpected account: #{candidate}")
-    expected.delete(candidate)
-  end
-  assert(expected.empty?, "These Pidgin accounts are not configured: " +
-         "#{expected}")
-end
-
-When /^I open Pidgin's account manager window$/ do
-  @screen.wait_and_click('PidginMenuAccounts.png', 20)
-  @screen.wait_and_click('PidginMenuManageAccounts.png', 20)
-  step "I see Pidgin's account manager window"
-end
-
-When /^I see Pidgin's account manager window$/ do
-  @screen.wait("PidginAccountWindow.png", 40)
-end
-
-When /^I close Pidgin's account manager window$/ do
-  @screen.wait_and_click("PidginDialogCloseButton.png", 10)
-end
-
-When /^I close Pidgin$/ do
-  $vm.focus_window('Buddy List')
-  @screen.type("q", Sikuli::KeyModifier.CTRL)
-  @screen.waitVanish('PidginAvailableStatus.png', 10)
-end
-
-When /^I (de)?activate the "([^"]+)" Pidgin account$/ do |deactivate, account|
-  @screen.click("PidginAccount_#{account}.png")
-  @screen.type(Sikuli::Key.LEFT + Sikuli::Key.SPACE)
-  if deactivate
-    @screen.waitVanish('PidginAccountEnabledCheckbox.png', 5)
-  else
-    # wait for the Pidgin to be connecting, otherwise sometimes the step
-    # that closes the account management dialog happens before the account
-    # is actually enabled
-    @screen.waitAny(['PidginConnecting.png', 'PidginAvailableStatus.png'], 5)
-  end
-end
-
-def deactivate_and_activate_pidgin_account(account)
-  debug_log("Deactivating and reactivating Pidgin account #{account}")
-  step "I open Pidgin's account manager window"
-  step "I deactivate the \"#{account}\" Pidgin account"
-  step "I close Pidgin's account manager window"
-  step "I open Pidgin's account manager window"
-  step "I activate the \"#{account}\" Pidgin account"
-  step "I close Pidgin's account manager window"
-end
-
-
-
-Then /^Pidgin successfully connects to the "([^"]+)" account$/ do |account|
-  expected_channel_entry = chan_image(account, default_chan(account), 'roster')
-  reconnect_button = 'PidginReconnect.png'
-  recovery_on_failure = Proc.new do
-    if @screen.exists('PidginReconnect.png')
-      @screen.click('PidginReconnect.png')
-    else
-      deactivate_and_activate_pidgin_account(account)
-    end
-  end
-  retry_tor(recovery_on_failure) do
-    begin
-      $vm.focus_window('Buddy List')
-    rescue ExecutionFailedInVM
-      # Sometimes focusing the window with xdotool will fail with the
-      # conversation window right on top of it. We'll try to close the
-      # conversation window. At worst, the test will still fail...
-      close_pidgin_conversation_window(account)
-    end
-    on_screen, _ = @screen.waitAny([expected_channel_entry, reconnect_button], 60)
-    unless on_screen == expected_channel_entry
-      raise "Connecting to account #{account} failed."
-    end
-  end
-end
-
-Then /^the "([^"]*)" account only responds to PING and VERSION CTCP requests$/ do |irc_server|
-  ctcp_cmds = [
-    "CLIENTINFO", "DATE", "ERRMSG", "FINGER", "PING", "SOURCE", "TIME",
-    "USERINFO", "VERSION"
-  ]
-  expected_ctcp_replies = {
-    "PING" => /^\d+$/,
-    "VERSION" => /^Purple IRC$/
-  }
-  spam_target = configured_pidgin_accounts[irc_server]["nickname"]
-  ctcp_check = CtcpChecker.new(irc_server, 6667, spam_target, ctcp_cmds,
-                               expected_ctcp_replies)
-  ctcp_check.verify_ctcp_responses
-end
-
-Then /^I can join the( pre-configured)? "([^"]+)" channel on "([^"]+)"$/ do |preconfigured, channel, account|
-  if preconfigured
-    @screen.doubleClick(chan_image(account, channel, 'roster'))
-    focus_pidgin_irc_conversation_window(account)
-  else
-    $vm.focus_window('Buddy List')
-    @screen.wait_and_click("PidginBuddiesMenu.png", 20)
-    @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10)
-    @screen.wait_and_click("PidginJoinChatWindow.png", 10)
-    @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png")
-    @screen.type(channel)
-    @screen.click("PidginJoinChatButton.png")
-    @chat_room_jid = channel + "@" + account
-    $vm.focus_window(@chat_room_jid)
-  end
-  @screen.hide_cursor
-  try_for(60) do
-    begin
-      @screen.wait_and_click(chan_image(account, channel, 'conversation_tab'), 5)
-    rescue FindFailed => e
-      # If the channel tab can't be found it could be because there were
-      # multiple connection attempts and the channel tab we want is off the
-      # screen. We'll try closing tabs until the one we want can be found.
-      @screen.type("w", Sikuli::KeyModifier.CTRL)
-      raise e
-    end
-  end
-  @screen.hide_cursor
-  @screen.wait(          chan_image(account, channel, 'welcome'), 10)
-end
-
-Then /^I take note of the configured Pidgin accounts$/ do
-  @persistent_pidgin_accounts = configured_pidgin_accounts
-end
-
-Then /^I take note of the OTR key for Pidgin's "([^"]+)" account$/ do |account_name|
-  @persistent_pidgin_otr_keys = pidgin_otr_keys
-end
-
-Then /^Pidgin has the expected persistent accounts configured$/ do
-  current_accounts = configured_pidgin_accounts
-  assert(current_accounts <=> @persistent_pidgin_accounts,
-         "Currently configured Pidgin accounts do not match the persistent ones:\n" +
-         "Current:\n#{current_accounts}\n" +
-         "Persistent:\n#{@persistent_pidgin_accounts}"
-         )
-end
-
-Then /^Pidgin has the expected persistent OTR keys$/ do
-  assert_equal(pidgin_otr_keys, @persistent_pidgin_otr_keys)
-end
-
-def pidgin_add_certificate_from (cert_file)
-  # Here, we need a certificate that is not already in the NSS database
-  step "I copy \"/usr/share/ca-certificates/mozilla/CNNIC_ROOT.crt\" to \"#{cert_file}\" as user \"amnesia\""
-
-  $vm.focus_window('Buddy List')
-  @screen.wait_and_click('PidginToolsMenu.png', 10)
-  @screen.wait_and_click('PidginCertificatesMenuItem.png', 10)
-  @screen.wait('PidginCertificateManagerDialog.png', 10)
-  @screen.wait_and_click('PidginCertificateAddButton.png', 10)
-  begin
-    @screen.wait_and_click('GtkFileChooserDesktopButton.png', 10)
-  rescue FindFailed
-    # The first time we're run, the file chooser opens in the Recent
-    # view, so we have to browse a directory before we can use the
-    # "Type file name" button. But on subsequent runs, the file
-    # chooser is already in the Desktop directory, so we don't need to
-    # do anything. Hence, this noop exception handler.
-  end
-  @screen.wait_and_click('GtkFileTypeFileNameButton.png', 10)
-  @screen.type("l", Sikuli::KeyModifier.ALT) # "Location" field
-  @screen.type(cert_file + Sikuli::Key.ENTER)
-end
-
-Then /^I can add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir|
-  pidgin_add_certificate_from("#{cert_dir}/test.crt")
-  wait_and_focus('PidginCertificateAddHostnameDialog.png', 10, 'Certificate Import')
-  @screen.type("XXX test XXX" + Sikuli::Key.ENTER)
-  wait_and_focus('PidginCertificateTestItem.png', 10, 'Certificate Manager')
-end
-
-Then /^I cannot add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir|
-  pidgin_add_certificate_from("#{cert_dir}/test.crt")
-  wait_and_focus('PidginCertificateImportFailed.png', 10, 'Import Error')
-end
-
-When /^I close Pidgin's certificate manager$/ do
-  wait_and_focus('PidginCertificateManagerDialog.png', 10, 'Certificate Manager')
-  @screen.type(Sikuli::Key.ESC)
-  # @screen.wait_and_click('PidginCertificateManagerClose.png', 10)
-  @screen.waitVanish('PidginCertificateManagerDialog.png', 10)
-end
-
-When /^I close Pidgin's certificate import failure dialog$/ do
-  @screen.type(Sikuli::Key.ESC)
-  # @screen.wait_and_click('PidginCertificateManagerClose.png', 10)
-  @screen.waitVanish('PidginCertificateImportFailed.png', 10)
-end
-
-When /^I see the Tails roadmap URL$/ do
-  try_for(60) do
-    if @screen.exists('PidginServerMessage.png')
-      @screen.click('PidginDialogCloseButton.png')
-    end
-    begin
-      @screen.find('PidginTailsRoadmapUrl.png')
-    rescue FindFailed => e
-      @screen.type(Sikuli::Key.PAGE_UP)
-      raise e
-    end
-  end
-end
-
-When /^I click on the Tails roadmap URL$/ do
-  @screen.click('PidginTailsRoadmapUrl.png')
-  try_for(60) { @torbrowser = Dogtail::Application.new('Firefox') }
-end
diff --git a/cucumber/features/step_definitions/po.rb b/cucumber/features/step_definitions/po.rb
deleted file mode 100644
index c73bace..0000000
--- a/cucumber/features/step_definitions/po.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-Given /^I am in the Git branch being tested$/ do
-  Dir.chdir(GIT_DIR)
-end
-
-Then /^all the PO files should be correct$/ do
-  File.exists?('./submodules/jenkins-tools/slaves/check_po')
-  cmd_helper(['./submodules/jenkins-tools/slaves/check_po'])
-end
diff --git a/cucumber/features/step_definitions/root_access_control.rb b/cucumber/features/step_definitions/root_access_control.rb
deleted file mode 100644
index 8362342..0000000
--- a/cucumber/features/step_definitions/root_access_control.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-Then /^I should be able to run administration commands as the live user$/ do
-  stdout = $vm.execute("echo #{@sudo_password} | sudo -S whoami",
-                       :user => LIVE_USER).stdout
-  actual_user = stdout.sub(/^\[sudo\] password for #{LIVE_USER}: /, "").chomp
-  assert_equal("root", actual_user, "Could not use sudo")
-end
-
-Then /^I should not be able to run administration commands as the live user with the "([^"]*)" password$/ do |password|
-  stderr = $vm.execute("echo #{password} | sudo -S whoami",
-                       :user => LIVE_USER).stderr
-  sudo_failed = stderr.include?("The administration password is disabled") || stderr.include?("is not allowed to execute")
-  assert(sudo_failed, "The administration password is not disabled:" + stderr)
-end
-
-When /^running a command as root with pkexec requires PolicyKit administrator privileges$/ do
-  action = 'org.freedesktop.policykit.exec'
-  action_details = $vm.execute("pkaction --verbose --action-id #{action}").stdout
-  assert(action_details[/\s+implicit any:\s+auth_admin$/],
-         "Expected 'auth_admin' for 'any':\n#{action_details}")
-  assert(action_details[/\s+implicit inactive:\s+auth_admin$/],
-         "Expected 'auth_admin' for 'inactive':\n#{action_details}")
-  assert(action_details[/\s+implicit active:\s+auth_admin$/],
-         "Expected 'auth_admin' for 'active':\n#{action_details}")
-end
-
-Then /^I should be able to run a command as root with pkexec$/ do
-  step "I run \"pkexec touch /root/pkexec-test\" in GNOME Terminal"
-  step 'I enter the sudo password in the pkexec prompt'
-  try_for(10, :msg => 'The /root/pkexec-test file was not created.') {
-    $vm.execute('ls /root/pkexec-test').success?
-  }
-end
-
-Then /^I should not be able to run a command as root with pkexec and the standard passwords$/ do
-  step "I run \"pkexec touch /root/pkexec-test\" in GNOME Terminal"
-  ['', 'live', 'amnesia'].each do |password|
-    deal_with_polkit_prompt(password, expect_success: false)
-  end
-  @screen.type(Sikuli::Key.ESC)
-  @screen.wait('PolicyKitAuthCompleteFailure.png', 20)
-end
diff --git a/cucumber/features/step_definitions/snapshots.rb b/cucumber/features/step_definitions/snapshots.rb
deleted file mode 100644
index 16e59a4..0000000
--- a/cucumber/features/step_definitions/snapshots.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-def checkpoints
-  cp = Hash.new
-  cp['disk-for-d-i'] = {
-    :temporary => true,
-    :description => "a disk is created for Debian Installer tests",
-    :parent_checkpoint => nil,
-    :steps => [
-      'I create a 8 GiB disk named "'+JOB_NAME+'"',
-      'I plug sata drive "'+JOB_NAME+'"',
-    ]
-  }
-
-  cp['disk-for-deb-edu'] = {
-    :temporary => true,
-    :description => "a disk is created for Debian Edu tests",
-    :parent_checkpoint => nil,
-    :steps => [
-      'I create a 64 GiB disk named "'+JOB_NAME+'"',
-      'I plug sata drive "'+JOB_NAME+'"',
-    ]
-  }
-
-  ['text', 'gui'].each do |m|
-    cp["boot-d-i-#{m}-to-tasksel"] = {
-        :temporary => true,
-        :description => "I have started Debian Installer in #{m} mode and stopped at the Tasksel prompt",
-        :parent_checkpoint => 'disk-for-d-i',
-        :steps => [
-          "I intend to use #{m} mode",
-          'I start the computer',
-          'I select the install mode',
-          'I select British English',
-          'I accept the hostname, using "example.com" as the domain',
-          'I set the root password to "rootme"',
-          'I set the password for "Philip Hands" to be "verysecret"',
-          'I select full-disk, single-filesystem partitioning',
-          'I note that the Base system is being installed',
-          'I accept the default mirror',
-          'I ignore Popcon',
-          'we reach the Tasksel prompt',
-        ]
-    }
-
-    ['minimal', 'non-GUI', 'Gnome', 'XFCE', 'LXDE', 'KDE'].each do |de|
-      cp["debian-#{m}-#{de}-install"] = {
-          :temporary => true,
-          :description => "I install a #{de} Debian system, in #{m} mode",
-          :parent_checkpoint => "boot-d-i-#{m}-to-tasksel",
-          :steps => [
-            "I intend to use #{m} mode",
-            "I select the #{de} task",
-            'I wait while the bulk of the packages are installed',
-            'I install GRUB',
-            'I allow reboot after the install is complete',
-            'I wait for the reboot',
-            'I power off the computer',
-            'the computer is set to boot from sata drive',
-          ]
-        }
-    end
-  end
-
-  return cp
-end
-
-def reach_checkpoint(name)
-  scenario_indent = " "*4
-  step_indent = " "*6
-
-  step "a computer"
-  if VM.snapshot_exists?(name)
-    $vm.restore_snapshot(name)
-    post_snapshot_restore_hook
-  else
-    checkpoint = checkpoints[name]
-    checkpoint_description = checkpoint[:description]
-    parent_checkpoint = checkpoint[:parent_checkpoint]
-    steps = checkpoint[:steps]
-    if parent_checkpoint
-      if VM.snapshot_exists?(parent_checkpoint)
-        $vm.restore_snapshot(parent_checkpoint)
-      else
-        reach_checkpoint(parent_checkpoint)
-      end
-      post_snapshot_restore_hook
-    end
-    debug_log(scenario_indent + "Checkpoint: #{checkpoint_description}",
-              color: :white, timestamp: false)
-    step_action = "Given"
-    if parent_checkpoint
-      parent_description = checkpoints[parent_checkpoint][:description]
-      debug_log(step_indent + "#{step_action} #{parent_description}",
-                color: :green, timestamp: false)
-      step_action = "And"
-    end
-    steps.each do |s|
-      begin
-        step(s)
-      rescue Exception => e
-        debug_log(scenario_indent +
-                  "Step failed while creating checkpoint: #{s}",
-                  color: :red, timestamp: false)
-        raise e
-      end
-      debug_log(step_indent + "#{step_action} #{s}",
-                color: :green, timestamp: false)
-      step_action = "And"
-    end
-    $vm.save_snapshot(name)
-  end
-end
-
-# For each checkpoint we generate a step to reach it.
-checkpoints.each do |name, desc|
-  step_regex = Regexp.new("^#{Regexp.escape(desc[:description])}$")
-  Given step_regex do
-    reach_checkpoint(name)
-  end
-end
diff --git a/cucumber/features/step_definitions/ssh.rb b/cucumber/features/step_definitions/ssh.rb
deleted file mode 100644
index 1fd0efa..0000000
--- a/cucumber/features/step_definitions/ssh.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-require 'socket'
-
-def assert_not_ipaddr(s)
-  err_msg = "'#{s}' looks like a LAN IP address."
-  assert_raise(IPAddr::InvalidAddressError, err_msg) do
-    IPAddr.new(s)
-  end
-end
-
-def read_and_validate_ssh_config srv_type
-  conf  = $config[srv_type]
-  begin
-    required_settings = ["private_key", "public_key", "username", "hostname"]
-    required_settings.each do |key|
-      assert(conf.has_key?(key))
-      assert_not_nil(conf[key])
-      assert(!conf[key].empty?)
-    end
-  rescue NoMethodError
-    raise(
-      <<EOF
-Your #{srv_type} config is incorrect or missing from your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format.
-EOF
-    )
-  end
-
-  case srv_type
-  when 'SSH'
-    @ssh_host        = conf["hostname"]
-    @ssh_port        = conf["port"].to_i if conf["port"]
-    @ssh_username    = conf["username"]
-    assert_not_ipaddr(@ssh_host)
-  when 'SFTP'
-    @sftp_host       = conf["hostname"]
-    @sftp_port       = conf["port"].to_i if conf["port"]
-    @sftp_username   = conf["username"]
-    assert_not_ipaddr(@sftp_host)
-  end
-end
-
-Given /^I have the SSH key pair for an? (Git|SSH|SFTP) (?:repository|server)( on the LAN)?$/ do |server_type, lan|
-  $vm.execute_successfully("install -m 0700 -d '/home/#{LIVE_USER}/.ssh/'",
-                           :user => LIVE_USER)
-  unless server_type == 'Git' || lan
-    read_and_validate_ssh_config server_type
-    secret_key = $config[server_type]["private_key"]
-    public_key = $config[server_type]["public_key"]
-  else
-    secret_key = $config["Unsafe_SSH_private_key"]
-    public_key = $config["Unsafe_SSH_public_key"]
-  end
-
-  $vm.execute_successfully("echo '#{secret_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa'",
-                           :user => LIVE_USER)
-  $vm.execute_successfully("echo '#{public_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa.pub'",
-                           :user => LIVE_USER)
-  $vm.execute_successfully("chmod 0600 '/home/#{LIVE_USER}/.ssh/'id*",
-                           :user => LIVE_USER)
-end
-
-Given /^I (?:am prompted to )?verify the SSH fingerprint for the (?:Git|SSH) (?:repository|server)$/ do
-  @screen.wait("SSHFingerprint.png", 60)
-  sleep 1 # brief pause to ensure that the following keystrokes do not get lost
-  @screen.type('yes' + Sikuli::Key.ENTER)
-end
-
-def get_free_tcp_port
-  server = TCPServer.new('127.0.0.1', 0)
-  return server.addr[1]
-ensure
-  server.close
-end
-
-Given /^an SSH server is running on the LAN$/ do
-  @sshd_server_port = get_free_tcp_port
-  @sshd_server_host = $vmnet.bridge_ip_addr
-  sshd = SSHServer.new(@sshd_server_host, @sshd_server_port)
-  sshd.start
-  add_lan_host(@sshd_server_host, @sshd_server_port)
-  add_after_scenario_hook { sshd.stop }
-end
-
-When /^I connect to an SSH server on the (Internet|LAN)$/ do |location|
-
-  case location
-  when 'Internet'
-    read_and_validate_ssh_config "SSH"
-  when 'LAN'
-    @ssh_port = @sshd_server_port
-    @ssh_username = 'user'
-    @ssh_host = @sshd_server_host
-  end
-
-  ssh_port_suffix = "-p #{@ssh_port}" if @ssh_port
-
-  cmd = "ssh #{@ssh_username}@#{@ssh_host} #{ssh_port_suffix}"
-
-  step 'process "ssh" is not running'
-
-  recovery_proc = Proc.new do
-    step 'I kill the process "ssh"' if $vm.has_process?("ssh")
-    step 'I run "clear" in GNOME Terminal'
-  end
-
-  retry_tor(recovery_proc) do
-    step "I run \"#{cmd}\" in GNOME Terminal"
-    step 'process "ssh" is running within 10 seconds'
-    step 'I verify the SSH fingerprint for the SSH server'
-  end
-end
-
-Then /^I have sucessfully logged into the SSH server$/ do
-  @screen.wait('SSHLoggedInPrompt.png', 60)
-end
-
-Then /^I connect to an SFTP server on the Internet$/ do
-  read_and_validate_ssh_config "SFTP"
-
-  @sftp_port ||= 22
-  @sftp_port = @sftp_port.to_s
-
-  recovery_proc = Proc.new do
-    step 'I kill the process "ssh"'
-    step 'I kill the process "nautilus"'
-  end
-
-  retry_tor(recovery_proc) do
-    step 'I start "Nautilus" via GNOME Activities Overview'
-    nautilus = Dogtail::Application.new('nautilus')
-    nautilus.child(roleName: 'frame')
-    nautilus.child('Other Locations', roleName: 'label').click
-    connect_bar = nautilus.child('Connect to Server', roleName: 'label').parent
-    connect_bar
-      .child(roleName: 'filler', recursive: false)
-      .child(roleName: 'text', recursive: false)
-      .text = "sftp://" + @sftp_username + "@" + @sftp_host + ":" + @sftp_port
-    connect_bar.button('Connect', recursive: false).click
-    step "I verify the SSH fingerprint for the SFTP server"
-  end
-end
-
-Then /^I verify the SSH fingerprint for the SFTP server$/ do
-  try_for(30) do
-    Dogtail::Application.new('gnome-shell').child?('Log In Anyway')
-  end
-  # Here we'd like to click on the button using Dogtail, but something
-  # is buggy so let's just use the keyboard.
-  @screen.type(Sikuli::Key.ENTER)
-end
-
-Then /^I successfully connect to the SFTP server$/ do
-  try_for(60) do
-    Dogtail::Application.new('nautilus')
-      .child?("#{@sftp_username} on #{@sftp_host}")
-  end
-end
diff --git a/cucumber/features/step_definitions/time_syncing.rb b/cucumber/features/step_definitions/time_syncing.rb
deleted file mode 100644
index d1b8107..0000000
--- a/cucumber/features/step_definitions/time_syncing.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# In some steps below we allow some slack when verifying that the date
-# was set appropriately because it may take time to send the `date`
-# command over the remote shell and get the answer back, parsing and
-# post-processing of the result, etc.
-def max_time_drift
-  10
-end
-
-When /^I set the system time to "([^"]+)"$/ do |time|
-  $vm.execute_successfully("date -s '#{time}'")
-  new_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time
-  expected_time_lower_bound = DateTime.parse(time).to_time
-  expected_time_upper_bound = expected_time_lower_bound + max_time_drift
-  assert(expected_time_lower_bound <= new_time &&
-         new_time <= expected_time_upper_bound,
-         "The guest's time was supposed to be set to " \
-         "'#{expected_time_lower_bound}' but is '#{new_time}'")
-end
-
-When /^I bump the (hardware clock's|system) time with "([^"]+)"$/ do |clock_type, timediff|
-  case clock_type
-  when "hardware clock's"
-    old_time = DateTime.parse($vm.execute_successfully("hwclock -r").stdout).to_time
-    $vm.execute_successfully("hwclock --set --date 'now #{timediff}'")
-    new_time = DateTime.parse($vm.execute_successfully("hwclock -r").stdout).to_time
-  when 'system'
-    old_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time
-    $vm.execute_successfully("date -s 'now #{timediff}'")
-    new_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time
-  end
-  expected_time_lower_bound = DateTime.parse(
-      cmd_helper(["date", "-d", "#{old_time} #{timediff}"])).to_time
-  expected_time_upper_bound = expected_time_lower_bound + max_time_drift
-  assert(expected_time_lower_bound <= new_time &&
-         new_time <= expected_time_upper_bound,
-         "The #{clock_type} time was supposed to be bumped to " \
-         "'#{expected_time_lower_bound}' but is '#{new_time}'")
-end
-
-Then /^Tails clock is less than (\d+) minutes incorrect$/ do |max_diff_mins|
-  guest_time_str = $vm.execute("date --rfc-2822").stdout.chomp
-  guest_time = Time.rfc2822(guest_time_str)
-  host_time = Time.now
-  diff = (host_time - guest_time).abs
-  assert(diff < max_diff_mins.to_i*60,
-         "The guest's clock is off by #{diff} seconds (#{guest_time})")
-  puts "Time was #{diff} seconds off"
-end
-
-Then /^the system clock is just past Tails' source date$/ do
-  system_time_str = $vm.execute_successfully('date').to_s
-  system_time = DateTime.parse(system_time_str).to_time
-  source_time_cmd = 'sed -n -e "1s/^.* - \([0-9]\+\)$/\1/p;q" ' +
-                    '/etc/amnesia/version'
-  source_time_str = $vm.execute_successfully(source_time_cmd).to_s
-  source_time = DateTime.parse(source_time_str).to_time
-  diff = system_time - source_time  # => in seconds
-  # Half an hour should be enough to boot Tails on any reasonable
-  # hardware and VM setup.
-  max_diff = 30*60
-  assert(diff > 0,
-         "The system time (#{system_time}) is before the Tails " +
-         "source date (#{source_time})")
-  assert(diff <= max_diff,
-         "The system time (#{system_time}) is more than #{max_diff} seconds " +
-         "past the source date (#{source_time})")
-end
-
-Then /^Tails' hardware clock is close to the host system's time$/ do
-  host_time = Time.now
-  hwclock_time_str = $vm.execute('hwclock -r').stdout.chomp
-  hwclock_time = DateTime.parse(hwclock_time_str).to_time
-  diff = (hwclock_time - host_time).abs
-  assert(diff <= max_time_drift)
-end
-
-Then /^the hardware clock is still off by "([^"]+)"$/ do |timediff|
-  hwclock = DateTime.parse($vm.execute_successfully("hwclock -r").stdout.chomp).to_time
-  expected_time_lower_bound = DateTime.parse(
-      cmd_helper(["date", "-d", "now #{timediff}"])).to_time - max_time_drift
-  expected_time_upper_bound = expected_time_lower_bound + max_time_drift
-  assert(expected_time_lower_bound <= hwclock &&
-         hwclock <= expected_time_upper_bound,
-         "The host's hwclock should be approximately " \
-         "'#{expected_time_lower_bound}' but is actually '#{hwclock}'")
-end
diff --git a/cucumber/features/step_definitions/tor.rb b/cucumber/features/step_definitions/tor.rb
deleted file mode 100644
index 04852f7..0000000
--- a/cucumber/features/step_definitions/tor.rb
+++ /dev/null
@@ -1,406 +0,0 @@
-def iptables_chains_parse(iptables, table = "filter", &block)
-  assert(block_given?)
-  cmd = "#{iptables}-save -c -t #{table} | iptables-xml"
-  xml_str = $vm.execute_successfully(cmd).stdout
-  rexml = REXML::Document.new(xml_str)
-  rexml.get_elements('iptables-rules/table/chain').each do |element|
-    yield(
-      element.attribute('name').to_s,
-      element.attribute('policy').to_s,
-      element.get_elements('rule')
-    )
-  end
-end
-
-def ip4tables_chains(table = "filter", &block)
-  iptables_chains_parse('iptables', table, &block)
-end
-
-def ip6tables_chains(table = "filter", &block)
-  iptables_chains_parse('ip6tables', table, &block)
-end
-
-def iptables_rules_parse(iptables, chain, table)
-  iptables_chains_parse(iptables, table) do |name, _, rules|
-    return rules if name == chain
-  end
-  return nil
-end
-
-def iptables_rules(chain, table = "filter")
-  iptables_rules_parse("iptables", chain, table)
-end
-
-def ip6tables_rules(chain, table = "filter")
-  iptables_rules_parse("ip6tables", chain, table)
-end
-
-def ip4tables_packet_counter_sum(filters = {})
-  pkts = 0
-  ip4tables_chains do |name, _, rules|
-    next if filters[:tables] && not(filters[:tables].include?(name))
-    rules.each do |rule|
-      next if filters[:uid] && not(rule.elements["conditions/owner/uid-owner[text()=#{filters[:uid]}]"])
-      pkts += rule.attribute('packet-count').to_s.to_i
-    end
-  end
-  return pkts
-end
-
-def try_xml_element_text(element, xpath, default = nil)
-  node = element.elements[xpath]
-  (node.nil? or not(node.has_text?)) ? default : node.text
-end
-
-Then /^the firewall's policy is to (.+) all IPv4 traffic$/ do |expected_policy|
-  expected_policy.upcase!
-  ip4tables_chains do |name, policy, _|
-    if ["INPUT", "FORWARD", "OUTPUT"].include?(name)
-      assert_equal(expected_policy, policy,
-                   "Chain #{name} has unexpected policy #{policy}")
-    end
-  end
-end
-
-Then /^the firewall is configured to only allow the (.+) users? to connect directly to the Internet over IPv4$/ do |users_str|
-  users = users_str.split(/, | and /)
-  expected_uids = Set.new
-  users.each do |user|
-    expected_uids << $vm.execute_successfully("id -u #{user}").stdout.to_i
-  end
-  allowed_output = iptables_rules("OUTPUT").find_all do |rule|
-    out_iface = rule.elements['conditions/match/o']
-    is_maybe_accepted = rule.get_elements('actions/*').find do |action|
-      not(["DROP", "REJECT", "LOG"].include?(action.name))
-    end
-    is_maybe_accepted &&
-    (
-      # nil => match all interfaces according to iptables-xml
-      out_iface.nil? ||
-      ((out_iface.text == 'lo') == (out_iface.attribute('invert').to_s == '1'))
-    )
-  end
-  uids = Set.new
-  allowed_output.each do |rule|
-    rule.elements.each('actions/*') do |action|
-      destination = try_xml_element_text(rule, "conditions/match/d")
-      if action.name == "ACCEPT"
-        # nil == 0.0.0.0/0 according to iptables-xml
-        assert(destination == '0.0.0.0/0' || destination.nil?,
-               "The following rule has an unexpected destination:\n" +
-               rule.to_s)
-        state_cond = try_xml_element_text(rule, "conditions/state/state")
-        next if state_cond == "ESTABLISHED"
-        assert_not_nil(rule.elements['conditions/owner/uid-owner'])
-        rule.elements.each('conditions/owner/uid-owner') do |owner|
-          uid = owner.text.to_i
-          uids << uid
-          assert(expected_uids.include?(uid),
-                 "The following rule allows uid #{uid} to access the " +
-                 "network, but we only expect uids #{expected_uids.to_a} " +
-                 "(#{users_str}) to have such access:\n#{rule.to_s}")
-        end
-      elsif action.name == "call" && action.elements[1].name == "lan"
-        lan_subnets = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
-        assert(lan_subnets.include?(destination),
-               "The following lan-targeted rule's destination is " +
-               "#{destination} which may not be a private subnet:\n" +
-               rule.to_s)
-      else
-        raise "Unexpected iptables OUTPUT chain rule:\n#{rule.to_s}"
-      end
-    end
-  end
-  uids_not_found = expected_uids - uids
-  assert(uids_not_found.empty?,
-         "Couldn't find rules allowing uids #{uids_not_found.to_a.to_s} " \
-         "access to the network")
-end
-
-Then /^the firewall's NAT rules only redirect traffic for Tor's TransPort and DNSPort$/ do
-  loopback_address = "127.0.0.1/32"
-  tor_onion_addr_space = "127.192.0.0/10"
-  tor_trans_port = "9040"
-  dns_port = "53"
-  tor_dns_port = "5353"
-  ip4tables_chains('nat') do |name, _, rules|
-    if name == "OUTPUT"
-      good_rules = rules.find_all do |rule|
-        redirect = rule.get_elements('actions/*').all? do |action|
-          action.name == "REDIRECT"
-        end
-        destination = try_xml_element_text(rule, "conditions/match/d")
-        redir_port = try_xml_element_text(rule, "actions/REDIRECT/to-ports")
-        redirected_to_trans_port = redir_port == tor_trans_port
-        udp_destination_port = try_xml_element_text(rule, "conditions/udp/dport")
-        dns_redirected_to_tor_dns_port = (udp_destination_port == dns_port) &&
-                                         (redir_port == tor_dns_port)
-        redirect &&
-        (
-         (destination == tor_onion_addr_space && redirected_to_trans_port) ||
-         (destination == loopback_address && dns_redirected_to_tor_dns_port)
-        )
-      end
-      bad_rules = rules - good_rules
-      assert(bad_rules.empty?,
-             "The NAT table's OUTPUT chain contains some unexpected " +
-             "rules:\n#{bad_rules}")
-    else
-      assert(rules.empty?,
-             "The NAT table contains unexpected rules for the #{name} " +
-             "chain:\n#{rules}")
-    end
-  end
-end
-
-Then /^the firewall is configured to block all external IPv6 traffic$/ do
-  ip6_loopback = '::1/128'
-  expected_policy = "DROP"
-  ip6tables_chains do |name, policy, rules|
-    assert_equal(expected_policy, policy,
-                 "The IPv6 #{name} chain has policy #{policy} but we " \
-                 "expected #{expected_policy}")
-    good_rules = rules.find_all do |rule|
-      ["DROP", "REJECT", "LOG"].any? do |target|
-        rule.elements["actions/#{target}"]
-      end \
-      ||
-      ["s", "d"].all? do |x|
-        try_xml_element_text(rule, "conditions/match/#{x}") == ip6_loopback
-      end
-    end
-    bad_rules = rules - good_rules
-    assert(bad_rules.empty?,
-           "The IPv6 table's #{name} chain contains some unexpected rules:\n" +
-           (bad_rules.map { |r| r.to_s }).join("\n"))
-  end
-end
-
-def firewall_has_dropped_packet_to?(proto, host, port)
-  regex = "^Dropped outbound packet: .* "
-  regex += "DST=#{Regexp.escape(host)} .* "
-  regex += "PROTO=#{Regexp.escape(proto)} "
-  regex += ".* DPT=#{port} " if port
-  $vm.execute("journalctl --dmesg --output=cat | grep -qP '#{regex}'").success?
-end
-
-When /^I open an untorified (TCP|UDP|ICMP) connection to (\S*)(?: on port (\d+))?$/ do |proto, host, port|
-  assert(!firewall_has_dropped_packet_to?(proto, host, port),
-         "A #{proto} packet to #{host}" +
-         (port.nil? ? "" : ":#{port}") +
-         " has already been dropped by the firewall")
-  @conn_proto = proto
-  @conn_host = host
-  @conn_port = port
-  case proto
-  when "TCP"
-    assert_not_nil(port)
-    cmd = "echo | nc.traditional #{host} #{port}"
-    user = LIVE_USER
-  when "UDP"
-    assert_not_nil(port)
-    cmd = "echo | nc.traditional -u #{host} #{port}"
-    user = LIVE_USER
-  when "ICMP"
-    cmd = "ping -c 5 #{host}"
-    user = 'root'
-  end
-  @conn_res = $vm.execute(cmd, :user => user)
-end
-
-Then /^the untorified connection fails$/ do
-  case @conn_proto
-  when "TCP"
-    expected_in_stderr = "Connection refused"
-    conn_failed = !@conn_res.success? &&
-      @conn_res.stderr.chomp.end_with?(expected_in_stderr)
-  when "UDP", "ICMP"
-    conn_failed = !@conn_res.success?
-  end
-  assert(conn_failed,
-         "The untorified #{@conn_proto} connection didn't fail as expected:\n" +
-         @conn_res.to_s)
-end
-
-Then /^the untorified connection is logged as dropped by the firewall$/ do
-  assert(firewall_has_dropped_packet_to?(@conn_proto, @conn_host, @conn_port),
-         "No #{@conn_proto} packet to #{@conn_host}" +
-         (@conn_port.nil? ? "" : ":#{@conn_port}") +
-         " was dropped by the firewall")
-end
-
-When /^the system DNS is(?: still)? using the local DNS resolver$/ do
-  resolvconf = $vm.file_content("/etc/resolv.conf")
-  bad_lines = resolvconf.split("\n").find_all do |line|
-    !line.start_with?("#") && !/^nameserver\s+127\.0\.0\.1$/.match(line)
-  end
-  assert_empty(bad_lines,
-               "The following bad lines were found in /etc/resolv.conf:\n" +
-               bad_lines.join("\n"))
-end
-
-def stream_isolation_info(application)
-  case application
-  when "htpdate"
-    {
-      :grep_monitor_expr => 'users:(("curl"',
-      :socksport => 9062
-    }
-  when "tails-security-check"
-    {
-      :grep_monitor_expr => 'users:(("tails-security-"',
-      :socksport => 9062
-    }
-  when "tails-upgrade-frontend-wrapper"
-    {
-      :grep_monitor_expr => 'users:(("tails-iuk-get-u"',
-      :socksport => 9062
-    }
-  when "Tor Browser"
-    {
-      :grep_monitor_expr => 'users:(("firefox"',
-      :socksport => 9150,
-      :controller => true,
-    }
-  when "Gobby"
-    {
-      :grep_monitor_expr => 'users:(("gobby-0.5"',
-      :socksport => 9050
-    }
-  when "SSH"
-    {
-      :grep_monitor_expr => 'users:(("\(nc\|ssh\)"',
-      :socksport => 9050
-    }
-  when "whois"
-    {
-      :grep_monitor_expr => 'users:(("whois"',
-      :socksport => 9050
-    }
-  else
-    raise "Unknown application '#{application}' for the stream isolation tests"
-  end
-end
-
-When /^I monitor the network connections of (.*)$/ do |application|
-  @process_monitor_log = "/tmp/ss.log"
-  info = stream_isolation_info(application)
-  $vm.spawn("while true; do " +
-            "  ss -taupen | grep '#{info[:grep_monitor_expr]}'; " +
-            "  sleep 0.1; " +
-            "done > #{@process_monitor_log}")
-end
-
-Then /^I see that (.+) is properly stream isolated$/ do |application|
-  info = stream_isolation_info(application)
-  expected_ports = [info[:socksport]]
-  expected_ports << 9051 if info[:controller]
-  assert_not_nil(@process_monitor_log)
-  log_lines = $vm.file_content(@process_monitor_log).split("\n")
-  assert(log_lines.size > 0,
-         "Couldn't see any connection made by #{application} so " \
-         "something is wrong")
-  log_lines.each do |line|
-    ip_port = line.split(/\s+/)[5]
-    assert(expected_ports.map { |port| "127.0.0.1:#{port}" }.include?(ip_port),
-           "#{application} should only connect to #{expected_ports} but " \
-           "was seen connecting to #{ip_port}")
-  end
-end
-
-And /^I re-run tails-security-check$/ do
-  $vm.execute_successfully("tails-security-check", :user => LIVE_USER)
-end
-
-And /^I re-run htpdate$/ do
-  $vm.execute_successfully("service htpdate stop && " \
-                           "rm -f /run/htpdate/* && " \
-                           "systemctl --no-block start htpdate.service")
-  step "the time has synced"
-end
-
-And /^I re-run tails-upgrade-frontend-wrapper$/ do
-  $vm.execute_successfully("tails-upgrade-frontend-wrapper", :user => LIVE_USER)
-end
-
-When /^I connect Gobby to "([^"]+)"$/ do |host|
-  gobby = Dogtail::Application.new('gobby-0.5')
-  gobby.child('Welcome to Gobby', roleName: 'label')
-  gobby.button('Close').click
-  # This indicates that Gobby has finished initializing itself
-  # (generating DH parameters, etc.) -- before, the UI is not responsive
-  # and our CTRL-t is lost.
-  gobby.child('Failed to share documents', roleName: 'label')
-  gobby.menu('File').click
-  gobby.menuItem('Connect to Server...').click
-  @screen.type("t", Sikuli::KeyModifier.CTRL)
-  connect_dialog = gobby.dialog('Connect to Server')
-  connect_dialog.child('', roleName: 'text').typeText(host)
-  connect_dialog.button('Connect').click
-  # This looks for the live user's presence entry in the chat, which
-  # will only be shown if the connection succeeded.
-  try_for(60) { gobby.child(LIVE_USER, roleName: 'table cell'); true }
-end
-
-When /^the Tor Launcher autostarts$/ do
-  @screen.wait('TorLauncherWindow.png', 60)
-end
-
-When /^I configure some (\w+) pluggable transports in Tor Launcher$/ do |bridge_type|
-  @screen.wait_and_click('TorLauncherConfigureButton.png', 10)
-  @screen.wait('TorLauncherBridgePrompt.png', 10)
-  @screen.wait_and_click('TorLauncherYesRadioOption.png', 10)
-  @screen.wait_and_click('TorLauncherNextButton.png', 10)
-  @screen.wait_and_click('TorLauncherBridgeList.png', 10)
-  @bridge_hosts = []
-  chutney_src_dir = "#{GIT_DIR}/submodules/chutney"
-  bridge_dirs = Dir.glob(
-    "#{$config['TMPDIR']}/chutney-data/nodes/*#{bridge_type}/"
-  )
-  bridge_dirs.each do |bridge_dir|
-    address = $vmnet.bridge_ip_addr
-    port = nil
-    fingerprint = nil
-    extra = nil
-    if bridge_type == 'bridge'
-      open(bridge_dir + "/torrc") do |f|
-        port = f.grep(/^OrPort\b/).first.split.last
-      end
-    else
-      # This is the pluggable transport case. While we could set a
-      # static port via ServerTransportListenAddr we instead let it be
-      # picked randomly so an already used port is not picked --
-      # Chutney already has issues with that for OrPort selection.
-      pt_re = /Registered server transport '#{bridge_type}' at '[^']*:(\d+)'/
-      open(bridge_dir + "/notice.log") do |f|
-        pt_lines = f.grep(pt_re)
-        port = pt_lines.last.match(pt_re)[1]
-      end
-      if bridge_type == 'obfs4'
-        open(bridge_dir + "/pt_state/obfs4_bridgeline.txt") do |f|
-          extra = f.readlines.last.chomp.sub(/^.* cert=/, 'cert=')
-        end
-      end
-    end
-    open(bridge_dir + "/fingerprint") do |f|
-      fingerprint = f.read.chomp.split.last
-    end
-    @bridge_hosts << { address: address, port: port.to_i }
-    bridge_line = bridge_type + " " + address + ":" + port
-    [fingerprint, extra].each { |e| bridge_line += " " + e.to_s if e }
-    @screen.type(bridge_line + Sikuli::Key.ENTER)
-  end
-  @screen.wait_and_click('TorLauncherNextButton.png', 10)
-  @screen.hide_cursor
-  @screen.wait_and_click('TorLauncherFinishButton.png', 10)
-  @screen.wait('TorLauncherConnectingWindow.png', 10)
-  @screen.waitVanish('TorLauncherConnectingWindow.png', 120)
-end
-
-When /^all Internet traffic has only flowed through the configured pluggable transports$/ do
-  assert_not_nil(@bridge_hosts, "No bridges has been configured via the " +
-                 "'I configure some ... bridges in Tor Launcher' step")
-  assert_all_connections(@sniffer.pcap_file) do |c|
-    @bridge_hosts.include?({ address: c.daddr, port: c.dport })
-  end
-end
diff --git a/cucumber/features/step_definitions/torified_browsing.rb b/cucumber/features/step_definitions/torified_browsing.rb
deleted file mode 100644
index 7676078..0000000
--- a/cucumber/features/step_definitions/torified_browsing.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-Then /^no traffic was sent to the web server on the LAN$/ do
-  assert_no_connections(@sniffer.pcap_file) do |c|
-    c.daddr == @web_server_ip_addr and c.dport == @web_server_port
-  end
-end
diff --git a/cucumber/features/step_definitions/torified_gnupg.rb b/cucumber/features/step_definitions/torified_gnupg.rb
deleted file mode 100644
index f5f61ce..0000000
--- a/cucumber/features/step_definitions/torified_gnupg.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-require 'resolv'
-
-class OpenPGPKeyserverCommunicationError < StandardError
-end
-
-def count_gpg_signatures(key)
-  output = $vm.execute_successfully("gpg --batch --list-sigs #{key}",
-                                    :user => LIVE_USER).stdout
-  output.scan(/^sig/).count
-end
-
-def check_for_seahorse_error
-  if @screen.exists('GnomeCloseButton.png')
-    raise OpenPGPKeyserverCommunicationError.new(
-      "Found GnomeCloseButton.png' on the screen"
-    )
-  end
-end
-
-def start_or_restart_seahorse
-  assert_not_nil(@withgpgapplet)
-  if @withgpgapplet
-    seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletManageKeys.png')
-  else
-    step 'I start "Passwords and Keys" via GNOME Activities Overview'
-  end
-  step 'Seahorse has opened'
-end
-
-Then /^the key "([^"]+)" has (only|more than) (\d+) signatures$/ do |key, qualifier, num|
-  count = count_gpg_signatures(key)
-  case qualifier
-  when 'only'
-    assert_equal(count, num.to_i, "Expected #{num} signatures but instead found #{count}")
-  when 'more than'
-    assert(count > num.to_i, "Expected more than #{num} signatures but found #{count}")
-  else
-    raise "Unknown operator #{qualifier} passed"
-  end
-end
-
-When /^the "([^"]+)" OpenPGP key is not in the live user's public keyring$/ do |keyid|
-  assert(!$vm.execute("gpg --batch --list-keys '#{keyid}'",
-                      :user => LIVE_USER).success?,
-         "The '#{keyid}' key is in the live user's public keyring.")
-end
-
-def setup_onion_keyserver
-  resolver = Resolv::DNS.new
-  keyservers = resolver.getaddresses('pool.sks-keyservers.net').select do |addr|
-    addr.class == Resolv::IPv4
-  end
-  onion_keyserver_address = keyservers.sample
-  hkp_port = 11371
-  @onion_keyserver_job = chutney_onionservice_redir(
-    onion_keyserver_address, hkp_port
-  )
-end
-
-When /^I fetch the "([^"]+)" OpenPGP key using the GnuPG CLI( without any signatures)?$/ do |keyid, without|
-  # Make keyid an instance variable so we can reference it in the Seahorse
-  # keysyncing step.
-  @fetched_openpgp_keyid = keyid
-  if without
-    importopts = '--keyserver-options import-clean'
-  else
-    importopts = ''
-  end
-  retry_tor(Proc.new { setup_onion_keyserver }) do
-    @gnupg_recv_key_res = $vm.execute_successfully(
-      "timeout 120 gpg --batch #{importopts} --recv-key '#{@fetched_openpgp_keyid}'",
-      :user => LIVE_USER)
-    if @gnupg_recv_key_res.failure?
-      raise "Fetching keys with the GnuPG CLI failed with:\n" +
-            "#{@gnupg_recv_key_res.stdout}\n" +
-            "#{@gnupg_recv_key_res.stderr}"
-    end
-  end
-end
-
-When /^the GnuPG fetch is successful$/ do
-  assert(@gnupg_recv_key_res.success?,
-         "gpg keyserver fetch failed:\n#{@gnupg_recv_key_res.stderr}")
-end
-
-When /^the Seahorse operation is successful$/ do
-  !@screen.exists('GnomeCloseButton.png')
-  $vm.has_process?('seahorse')
-end
-
-When /^the "([^"]+)" key is in the live user's public keyring(?: after at most (\d) seconds)?$/ do |keyid, delay|
-  delay = 10 unless delay
-  try_for(delay.to_i, :msg => "The '#{keyid}' key is not in the live user's public keyring") {
-    $vm.execute("gpg --batch --list-keys '#{keyid}'",
-                :user => LIVE_USER).success?
-  }
-end
-
-When /^I start Seahorse( via the OpenPGP Applet)?$/ do |withgpgapplet|
-  @withgpgapplet = !!withgpgapplet
-  start_or_restart_seahorse
-end
-
-Then /^Seahorse has opened$/ do
-  @screen.wait('SeahorseWindow.png', 20)
-end
-
-Then /^I enable key synchronization in Seahorse$/ do
-  step 'process "seahorse" is running'
-  @screen.wait_and_click("SeahorseWindow.png", 10)
-  seahorse_menu_click_helper('GnomeEditMenu.png', 'SeahorseEditPreferences.png', 'seahorse')
-  @screen.wait('SeahorsePreferences.png', 20)
-  @screen.type("p", Sikuli::KeyModifier.ALT) # Option: "Publish keys to...".
-  @screen.type(Sikuli::Key.DOWN) # select HKP server
-  @screen.type("c", Sikuli::KeyModifier.ALT) # Button: "Close"
-end
-
-Then /^I synchronize keys in Seahorse$/ do
-  recovery_proc = Proc.new do
-    setup_onion_keyserver
-    # The version of Seahorse in Jessie will abort with a
-    # segmentation fault whenever there's any sort of network error while
-    # syncing keys. This will usually happens after clicking away the error
-    # message. This does not appear to be a problem in Stretch.
-    #
-    # We'll kill the Seahorse process to avoid waiting for the inevitable
-    # segfault. We'll also make sure the process is still running (=  hasn't
-    # yet segfaulted) before terminating it.
-    if @screen.exists('GnomeCloseButton.png') || !$vm.has_process?('seahorse')
-      step 'I kill the process "seahorse"' if $vm.has_process?('seahorse')
-      debug_log('Restarting Seahorse.')
-      start_or_restart_seahorse
-    end
-  end
-
-  def change_of_status?
-    # Due to a lack of visual feedback in Seahorse we'll break out of the
-    # try_for loop below by returning "true" when there's something we can act
-    # upon.
-    if count_gpg_signatures(@fetched_openpgp_keyid) > 2 || \
-      @screen.exists('GnomeCloseButton.png')  || \
-      !$vm.has_process?('seahorse')
-        true
-    end
-  end
-
-  retry_tor(recovery_proc) do
-    @screen.wait_and_click("SeahorseWindow.png", 10)
-    seahorse_menu_click_helper('SeahorseRemoteMenu.png',
-                               'SeahorseRemoteMenuSync.png',
-                               'seahorse')
-    @screen.wait('SeahorseSyncKeys.png', 20)
-    @screen.type("s", Sikuli::KeyModifier.ALT) # Button: Sync
-    # There's no visual feedback of Seahorse in Tails/Jessie, except on error.
-    try_for(120) {
-      change_of_status?
-    }
-    check_for_seahorse_error
-    raise OpenPGPKeyserverCommunicationError.new(
-      'Seahorse crashed with a segfault.') unless $vm.has_process?('seahorse')
-   end
-end
-
-When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the OpenPGP Applet)?$/ do |keyid, withgpgapplet|
-  step "I start Seahorse#{withgpgapplet}"
-
-  def change_of_status?(keyid)
-    # Due to a lack of visual feedback in Seahorse we'll break out of the
-    # try_for loop below by returning "true" when there's something we can act
-    # upon.
-    if $vm.execute_successfully(
-      "gpg --batch --list-keys '#{keyid}'", :user => LIVE_USER) ||
-      @screen.exists('GnomeCloseButton.png')
-      true
-    end
-  end
-
-  recovery_proc = Proc.new do
-    setup_onion_keyserver
-    @screen.click('GnomeCloseButton.png') if @screen.exists('GnomeCloseButton.png')
-    @screen.type("w", Sikuli::KeyModifier.CTRL)
-  end
-  retry_tor(recovery_proc) do
-    @screen.wait_and_click("SeahorseWindow.png", 10)
-    seahorse_menu_click_helper('SeahorseRemoteMenu.png',
-                               'SeahorseRemoteMenuFind.png',
-                               'seahorse')
-    @screen.wait('SeahorseFindKeysWindow.png', 10)
-    # Seahorse doesn't seem to support searching for fingerprints
-    @screen.type(keyid + Sikuli::Key.ENTER)
-    begin
-      @screen.waitAny(['SeahorseFoundKeyResult.png',
-                       'GnomeCloseButton.png'], 120)
-    rescue FindAnyFailed
-      # We may end up here if Seahorse appears to be "frozen".
-      # Sometimes--but not always--if we click another window
-      # the main Seahorse window will unfreeze, allowing us
-      # to continue normally.
-      @screen.click("SeahorseSearch.png")
-    end
-    check_for_seahorse_error
-    @screen.click("SeahorseKeyResultWindow.png")
-    @screen.click("SeahorseFoundKeyResult.png")
-    @screen.click("SeahorseImport.png")
-    try_for(120) do
-      change_of_status?(keyid)
-    end
-    check_for_seahorse_error
-  end
-end
-
-Given /^(GnuPG|Seahorse) is configured to use Chutney's onion keyserver$/ do |app|
-  setup_onion_keyserver unless @onion_keyserver_job
-  _, _, onion_address, onion_port = chutney_onionservice_info
-  case app
-  when 'GnuPG'
-    # Validate the shipped configuration ...
-    server = /keyserver\s+(\S+)$/.match($vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf"))[1]
-    assert_equal(
-      "hkp://#{CONFIGURED_KEYSERVER_HOSTNAME}", server,
-      "GnuPG's dirmngr does not use the correct keyserver"
-    )
-    # ... before replacing it
-    $vm.execute_successfully(
-      "sed -i 's/#{CONFIGURED_KEYSERVER_HOSTNAME}/#{onion_address}:#{onion_port}/' " +
-      "'/home/#{LIVE_USER}/.gnupg/dirmngr.conf'"
-    )
-  when 'Seahorse'
-    # Validate the shipped configuration ...
-    @gnome_keyservers = YAML.load(
-      $vm.execute_successfully(
-        'gsettings get org.gnome.crypto.pgp keyservers',
-        user: LIVE_USER
-      ).stdout
-    )
-    assert_equal(1, @gnome_keyservers.count,
-                 'Seahorse should only have one keyserver configured.')
-    assert_equal(
-      'hkp://' + CONFIGURED_KEYSERVER_HOSTNAME, @gnome_keyservers[0],
-      "GnuPG's dirmngr does not use the correct keyserver"
-    )
-    # ... before replacing it
-    $vm.execute_successfully(
-      "gsettings set org.gnome.crypto.pgp keyservers \"['hkp://#{onion_address}:#{onion_port}']\"",
-      user: LIVE_USER
-    )
-  end
-end
-
-Then /^GnuPG's dirmngr uses the configured keyserver$/ do
-  _, _, onion_keyserver_address, _ = chutney_onionservice_info
-  dirmngr_request = $vm.execute_successfully(
-    'gpg-connect-agent --dirmngr "keyserver --hosttable" /bye', user: LIVE_USER
-  )
-  server = dirmngr_request.stdout.chomp.lines[1].split[4]
-  server = /keyserver\s+(\S+)$/.match(
-    $vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf")
-  )[1]
-  assert_equal(
-    "hkp://#{onion_keyserver_address}:5858", server,
-    "GnuPG's dirmngr does not use the correct keyserver"
-  )
-end
diff --git a/cucumber/features/step_definitions/torified_misc.rb b/cucumber/features/step_definitions/torified_misc.rb
deleted file mode 100644
index 7ccdb22..0000000
--- a/cucumber/features/step_definitions/torified_misc.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'resolv'
-
-When /^I query the whois directory service for "([^"]+)"$/ do |domain|
-  retry_tor do
-    @vm_execute_res = $vm.execute("whois '#{domain}'", :user => LIVE_USER)
-    if @vm_execute_res.failure? || @vm_execute_res.stdout['LIMIT EXCEEDED']
-      raise "Looking up whois info for #{domain} failed with:\n" +
-            "#{@vm_execute_res.stdout}\n" +
-            "#{@vm_execute_res.stderr}"
-    end
-  end
-end
-
-When /^I wget "([^"]+)" to stdout(?:| with the '([^']+)' options)$/ do |target, options|
-  retry_tor do
-    if target == "some Tails mirror"
-      host = 'dl.amnesia.boum.org'
-      address = Resolv.new.getaddresses(host).sample
-      puts "Resolved #{host} to #{address}"
-      url = "http://#{address}/tails/stable/"
-    else
-      url = target
-    end
-    arguments = "-O - '#{url}'"
-    arguments = "#{options} #{arguments}" if options
-    @vm_execute_res = $vm.execute("wget #{arguments}", :user => LIVE_USER)
-    if @vm_execute_res.failure?
-      raise "wget:ing #{url} with options #{options} failed with:\n" +
-            "#{@vm_execute_res.stdout}\n" +
-            "#{@vm_execute_res.stderr}"
-    end
-  end
-end
-
-Then /^the (wget|whois) command is successful$/ do |command|
-  assert(
-    @vm_execute_res.success?,
-    "#{command} failed:\n" +
-    "#{@vm_execute_res.stdout}\n" +
-    "#{@vm_execute_res.stderr}"
-  )
-end
-
-Then /^the (wget|whois) standard output contains "([^"]+)"$/ do |command, text|
-  assert(
-    @vm_execute_res.stdout[text],
-    "The #{command} standard output does not contain #{text}:\n" +
-    "#{@vm_execute_res.stdout}\n" +
-    "#{@vm_execute_res.stderr}"
-  )
-end
diff --git a/cucumber/features/step_definitions/totem.rb b/cucumber/features/step_definitions/totem.rb
deleted file mode 100644
index a5b88d1..0000000
--- a/cucumber/features/step_definitions/totem.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-Given /^I create sample videos$/ do
-  @video_dir_on_host = "#{$config["TMPDIR"]}/video_dir"
-  FileUtils.mkdir_p(@video_dir_on_host)
-  add_after_scenario_hook { FileUtils.rm_r(@video_dir_on_host) }
-  fatal_system("avconv -loop 1 -t 30 -f image2 " +
-               "-i 'features/images/USBTailsLogo.png' " +
-               "-an -vcodec libx264 -y " +
-               '-filter:v "crop=in_w-mod(in_w\,2):in_h-mod(in_h\,2)" ' +
-               "'#{@video_dir_on_host}/video.mp4' >/dev/null 2>&1")
-end
-
-Given /^I plug and mount a USB drive containing sample videos$/ do
-  @video_dir_on_guest = share_host_files(
-    Dir.glob("#{@video_dir_on_host}/*")
-  )
-end
-
-Given /^I copy the sample videos to "([^"]+)" as user "([^"]+)"$/ do |destination, user|
-  for video_on_host in Dir.glob("#{@video_dir_on_host}/*.mp4") do
-    video_name = File.basename(video_on_host)
-    src_on_guest = "#{@video_dir_on_guest}/#{video_name}"
-    dst_on_guest = "#{destination}/#{video_name}"
-    step "I copy \"#{src_on_guest}\" to \"#{dst_on_guest}\" as user \"amnesia\""
-  end
-end
-
-When /^I(?:| try to) open "([^"]+)" with Totem$/ do |filename|
-  step "I run \"totem #{filename}\" in GNOME Terminal"
-end
-
-When /^I close Totem$/ do
-  step 'I kill the process "totem"'
-end
-
-Then /^I can watch a WebM video over HTTPs$/ do
-  test_url = 'https://tails.boum.org/lib/test_suite/test.webm'
-  recovery_on_failure = Proc.new do
-    step 'I close Totem'
-  end
-  retry_tor(recovery_on_failure) do
-    step "I open \"#{test_url}\" with Totem"
-    @screen.wait("SampleRemoteWebMVideoFrame.png", 120)
-  end
-end
diff --git a/cucumber/features/step_definitions/unsafe_browser.rb b/cucumber/features/step_definitions/unsafe_browser.rb
deleted file mode 100644
index 160279c..0000000
--- a/cucumber/features/step_definitions/unsafe_browser.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-When /^I see and accept the Unsafe Browser start verification(?:| in the "([^"]+)" locale)$/ do |locale|
-  @screen.wait('GnomeQuestionDialogIcon.png', 30)
-  if ['ar_EG.utf8', 'fa_IR'].include?(locale)
-    # Take into account button ordering in RTL languages
-    @screen.type(Sikuli::Key.LEFT + Sikuli::Key.ENTER)
-  else
-    @screen.type(Sikuli::Key.RIGHT + Sikuli::Key.ENTER)
-  end
-end
-
-def supported_torbrowser_languages
-  localization_descriptions = "#{Dir.pwd}/config/chroot_local-includes/usr/share/tails/browser-localization/descriptions"
-  File.read(localization_descriptions).split("\n").map do |line|
-    # The line will be of the form "xx:YY:..." or "xx-YY:YY:..."
-    first, second = line.sub('-', '_').split(':')
-    candidates = ["#{first}_#{second}.UTF-8", "#{first}_#{second}.utf8",
-                  "#{first}.UTF-8", "#{first}.utf8",
-                  "#{first}_#{second}", first]
-    when_not_found = Proc.new { raise "Could not find a locale for '#{line}'" }
-    candidates.find(when_not_found) do |candidate|
-      $vm.directory_exist?("/usr/lib/locale/#{candidate}")
-    end
-  end
-end
-
-Then /^I start the Unsafe Browser in the "([^"]+)" locale$/ do |loc|
-  step "I run \"LANG=#{loc} LC_ALL=#{loc} sudo unsafe-browser\" in GNOME Terminal"
-  step "I see and accept the Unsafe Browser start verification in the \"#{loc}\" locale"
-end
-
-Then /^the Unsafe Browser works in all supported languages$/ do
-  failed = Array.new
-  supported_torbrowser_languages.sample(3).each do |lang|
-    step "I start the Unsafe Browser in the \"#{lang}\" locale"
-    begin
-      step "the Unsafe Browser has started"
-    rescue RuntimeError
-      failed << lang
-      next
-    end
-    step "I close the Unsafe Browser"
-    step "the Unsafe Browser chroot is torn down"
-  end
-  assert(failed.empty?, "Unsafe Browser failed to launch in the following locale(s): #{failed.join(', ')}")
-end
-
-Then /^the Unsafe Browser has no add-ons installed$/ do
-  step "I open the address \"about:addons\" in the Unsafe Browser"
-  step "I see \"UnsafeBrowserNoAddons.png\" after at most 30 seconds"
-end
-
-Then /^the Unsafe Browser has only Firefox's default bookmarks configured$/ do
-  info = xul_application_info("Unsafe Browser")
-  # "Show all bookmarks"
-  @screen.type("o", Sikuli::KeyModifier.SHIFT + Sikuli::KeyModifier.CTRL)
-  @screen.wait_and_click("UnsafeBrowserExportBookmarksButton.png", 20)
-  @screen.wait_and_click("UnsafeBrowserExportBookmarksMenuEntry.png", 20)
-  @screen.wait("UnsafeBrowserExportBookmarksSavePrompt.png", 20)
-  path = "/home/#{info[:user]}/bookmarks"
-  @screen.type(path + Sikuli::Key.ENTER)
-  chroot_path = "#{info[:chroot]}/#{path}.json"
-  try_for(10) { $vm.file_exist?(chroot_path) }
-  dump = JSON.load($vm.file_content(chroot_path))
-
-  def check_bookmarks_helper(a)
-    mozilla_uris_counter = 0
-    places_uris_counter = 0
-    a.each do |h|
-      h.each_pair do |k, v|
-        if k == "children"
-          m, p = check_bookmarks_helper(v)
-          mozilla_uris_counter += m
-          places_uris_counter += p
-        elsif k == "uri"
-          uri = v
-          if uri.match("^https://www\.mozilla\.org/")
-            mozilla_uris_counter += 1
-          elsif uri.match("^place:(sort|folder|type)=")
-            places_uris_counter += 1
-          else
-            raise "Unexpected Unsafe Browser bookmark for '#{uri}'"
-          end
-        end
-      end
-    end
-    return [mozilla_uris_counter, places_uris_counter]
-  end
-
-  mozilla_uris_counter, places_uris_counter =
-    check_bookmarks_helper(dump["children"])
-  assert_equal(5, mozilla_uris_counter,
-               "Unexpected number (#{mozilla_uris_counter}) of mozilla " \
-               "bookmarks")
-  assert_equal(2, places_uris_counter,
-               "Unexpected number (#{places_uris_counter}) of places " \
-               "bookmarks")
-  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-end
-
-Then /^the Unsafe Browser has a red theme$/ do
-  @screen.wait("UnsafeBrowserRedTheme.png", 10)
-end
-
-Then /^the Unsafe Browser shows a warning as its start page$/ do
-  @screen.wait("UnsafeBrowserStartPage.png", 10)
-end
-
-Then /^I see a warning about another instance already running$/ do
-  @screen.wait('UnsafeBrowserWarnAlreadyRunning.png', 10)
-end
-
-Then /^I can start the Unsafe Browser again$/ do
-  step "I start the Unsafe Browser"
-end
-
-Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
-  socks_proxy = 'C' # Alt+Shift+c for socks proxy
-  no_proxy    = 'y' # Alt+y for no proxy
-  proxies = [[no_proxy, nil, nil]]
-  socksport_lines =
-    $vm.execute_successfully('grep -w "^SocksPort" /etc/tor/torrc').stdout
-  assert(socksport_lines.size >= 4, "We got fewer than four Tor SocksPorts")
-  socksports = socksport_lines.scan(/^SocksPort\s([^:]+):(\d+)/)
-  proxies += socksports.map { |host, port| [socks_proxy, host, port] }
-
-  proxies.each do |proxy_type, proxy_host, proxy_port|
-    @screen.hide_cursor
-
-    # Open proxy settings
-    @screen.click('UnsafeBrowserMenuButton.png')
-    @screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10)
-    @screen.wait_and_click('UnsafeBrowserAdvancedSettingsButton.png', 10)
-    hit, _ = @screen.waitAny(['UnsafeBrowserNetworkTabAlreadySelected.png',
-                              'UnsafeBrowserNetworkTab.png'], 10)
-    @screen.click(hit) if hit == 'UnsafeBrowserNetworkTab.png'
-    @screen.wait_and_click('UnsafeBrowserNetworkTabSettingsButton.png', 10)
-    @screen.wait_and_click('UnsafeBrowserProxySettingsWindow.png', 10)
-
-    # Ensure the desired proxy configuration
-    if proxy_type == no_proxy
-      @screen.type(proxy_type, Sikuli::KeyModifier.ALT)
-      @screen.wait('UnsafeBrowserNoProxySelected.png', 10)
-    else
-      @screen.type("M", Sikuli::KeyModifier.ALT)
-      @screen.type(proxy_type, Sikuli::KeyModifier.ALT)
-      @screen.type(proxy_host + Sikuli::Key.TAB + proxy_port)
-    end
-
-    # Close settings
-    @screen.click('UnsafeBrowserProxySettingsOkButton.png')
-    @screen.waitVanish('UnsafeBrowserProxySettingsWindow.png', 10)
-
-    # Test that the proxy settings work as they should
-    step 'I open Tails homepage in the Unsafe Browser'
-    if proxy_type == no_proxy
-      step 'Tails homepage loads in the Unsafe Browser'
-    else
-      @screen.wait('UnsafeBrowserProxyRefused.png', 60)
-    end
-  end
-end
-
-Then /^the Unsafe Browser has no proxy configured$/ do
-  @screen.click('UnsafeBrowserMenuButton.png')
-  @screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10)
-  @screen.wait_and_click('UnsafeBrowserAdvancedSettingsButton.png', 10)
-  @screen.wait_and_click('UnsafeBrowserNetworkTab.png', 10)
-  @screen.wait_and_click('UnsafeBrowserNetworkTabSettingsButton.png', 10)
-  @screen.wait('UnsafeBrowserProxySettingsWindow.png', 10)
-  @screen.wait('UnsafeBrowserNoProxySelected.png', 10)
-  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-  @screen.type("w", Sikuli::KeyModifier.CTRL)
-end
-
-Then /^the Unsafe Browser complains that no DNS server is configured$/ do
-  assert_not_nil(
-    Dogtail::Application.new('zenity')
-    .child(roleName: 'label')
-    .text['No DNS server was obtained']
-  )
-end
-
-Then /^I configure the Unsafe Browser to check for updates more frequently$/ do
-  prefs = '/usr/share/tails/chroot-browsers/unsafe-browser/prefs.js'
-  $vm.file_append(prefs, 'pref("app.update.idletime", 1);')
-  $vm.file_append(prefs, 'pref("app.update.promptWaitTime", 1);')
-  $vm.file_append(prefs, 'pref("app.update.interval", 5);')
-end
-
-But /^checking for updates is disabled in the Unsafe Browser's configuration$/ do
-  prefs = '/usr/share/tails/chroot-browsers/common/prefs.js'
-  assert($vm.file_content(prefs).include?('pref("app.update.enabled", false)'))
-end
-
-Then /^the clearnet user has (|not )sent packets out to the Internet$/ do |sent|
-  uid = $vm.execute_successfully("id -u clearnet").stdout.chomp.to_i
-  pkts = ip4tables_packet_counter_sum(:tables => ['OUTPUT'], :uid => uid)
-  case sent
-  when ''
-    assert(pkts > 0, "Packets have not gone out to the internet.")
-  when 'not'
-    assert_equal(pkts, 0, "Packets have gone out to the internet.")
-  end
-end
diff --git a/cucumber/features/step_definitions/untrusted_partitions.rb b/cucumber/features/step_definitions/untrusted_partitions.rb
deleted file mode 100644
index 603c8b4..0000000
--- a/cucumber/features/step_definitions/untrusted_partitions.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-Given /^I create an? ([[:alnum:]]+) swap partition on disk "([^"]+)"$/ do |parttype, name|
-  $vm.storage.disk_mkswap(name, parttype)
-end
-
-Then /^an? "([^"]+)" partition was detected by Tails on drive "([^"]+)"$/ do |type, name|
-  part_info = $vm.execute_successfully(
-      "blkid '#{$vm.disk_dev(name)}'").stdout.strip
-  assert(part_info.split.grep(/^TYPE=\"#{Regexp.escape(type)}\"$/),
-         "No #{type} partition was detected by Tails on disk '#{name}'")
-end
-
-Then /^Tails has no disk swap enabled$/ do
-  # Skip first line which contain column headers
-  swap_info = $vm.execute_successfully("tail -n+2 /proc/swaps").stdout
-  assert(swap_info.empty?,
-         "Disk swapping is enabled according to /proc/swaps:\n" + swap_info)
-  mem_info = $vm.execute_successfully("grep '^Swap' /proc/meminfo").stdout
-  assert(mem_info.match(/^SwapTotal:\s+0 kB$/),
-             "Disk swapping is enabled according to /proc/meminfo:\n" +
-             mem_info)
-end
-
-Given /^I create an? ([[:alnum:]]+) partition( labeled "([^"]+)")? with an? ([[:alnum:]]+) filesystem( encrypted with password "([^"]+)")? on disk "([^"]+)"$/ do |parttype, has_label, label, fstype, is_encrypted, luks_password, name|
-  opts = {}
-  opts.merge!(:label => label) if has_label
-  opts.merge!(:luks_password => luks_password) if is_encrypted
-  $vm.storage.disk_mkpartfs(name, parttype, fstype, opts)
-end
-
-Given /^I write the Tails ISO image to disk "([^"]+)"$/ do |name|
-  src_disk = {
-    :path => TAILS_ISO,
-    :opts => {
-      :format => "raw",
-      :readonly => true
-    }
-  }
-  dest_disk = {
-    :path => $vm.storage.disk_path(name),
-    :opts => {
-      :format => $vm.storage.disk_format(name)
-    }
-  }
-  $vm.storage.guestfs_disk_helper(src_disk, dest_disk) do |g, src_disk_handle, dest_disk_handle|
-    g.copy_device_to_device(src_disk_handle, dest_disk_handle, {})
-  end
-end
-
-Then /^drive "([^"]+)" is not mounted$/ do |name|
-  dev = $vm.disk_dev(name)
-  assert(!$vm.execute("grep -qs '^#{dev}' /proc/mounts").success?,
-         "an untrusted partition from drive '#{name}' was automounted")
-end
-
-Then /^Tails Greeter has( not)? detected a persistence partition$/ do |no_persistence|
-  expecting_persistence = no_persistence.nil?
-  @screen.find('TailsGreeter.png')
-  found_persistence = ! @screen.exists('TailsGreeterPersistencePassphrase.png').nil?
-  assert_equal(expecting_persistence, found_persistence,
-               "Persistence is unexpectedly#{no_persistence} enabled")
-end
diff --git a/cucumber/features/step_definitions/usb.rb b/cucumber/features/step_definitions/usb.rb
deleted file mode 100644
index e030f68..0000000
--- a/cucumber/features/step_definitions/usb.rb
+++ /dev/null
@@ -1,747 +0,0 @@
-# Returns a hash that for each preset the running Tails is aware of
-# maps the source to the destination.
-def get_persistence_presets(skip_links = false)
-  # Perl script that prints all persistence presets (one per line) on
-  # the form: <mount_point>:<comma-separated-list-of-options>
-  script = <<-EOF
-  use strict;
-  use warnings FATAL => "all";
-  use Tails::Persistence::Configuration::Presets;
-  foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
-    say $preset->destination, ":", join(",", @{$preset->options});
-  }
-EOF
-  # VMCommand:s cannot handle newlines, and they're irrelevant in the
-  # above perl script any way
-  script.delete!("\n")
-  presets = $vm.execute_successfully("perl -E '#{script}'").stdout.chomp.split("\n")
-  assert presets.size >= 10, "Got #{presets.size} persistence presets, " +
-                             "which is too few"
-  persistence_mapping = Hash.new
-  for line in presets
-    destination, options_str = line.split(":")
-    options = options_str.split(",")
-    is_link = options.include? "link"
-    next if is_link and skip_links
-    source_str = options.find { |option| /^source=/.match option }
-    # If no source is given as an option, live-boot's persistence
-    # feature defaults to the destination minus the initial "/".
-    if source_str.nil?
-      source = destination.partition("/").last
-    else
-      source = source_str.split("=")[1]
-    end
-    persistence_mapping[source] = destination
-  end
-  return persistence_mapping
-end
-
-def persistent_dirs
-  get_persistence_presets
-end
-
-def persistent_mounts
-  get_persistence_presets(true)
-end
-
-def persistent_volumes_mountpoints
-  $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
-end
-
-def recover_from_upgrader_failure
-    $vm.execute('killall tails-upgrade-frontend tails-upgrade-frontend-wrapper zenity')
-    # Remove unnecessary sleep for retry
-    $vm.execute_successfully('sed -i "/^sleep 30$/d" ' +
-                             '/usr/local/bin/tails-upgrade-frontend-wrapper')
-    $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
-end
-
-Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
-  $vm.storage.clone_to_new_disk(from, to)
-end
-
-Given /^I unplug USB drive "([^"]+)"$/ do |name|
-  $vm.unplug_drive(name)
-end
-
-Given /^the computer is set to boot from the old Tails DVD$/ do
-  $vm.set_cdrom_boot(OLD_TAILS_ISO)
-end
-
-Given /^the computer is set to boot in UEFI mode$/ do
-  $vm.set_os_loader('UEFI')
-  @os_loader = 'UEFI'
-end
-
-def tails_installer_selected_device
-  @installer.child('Target Device:', roleName: 'label').parent
-    .child('', roleName: 'combo box', recursive: false).name
-end
-
-def tails_installer_is_device_selected?(name)
-  device = $vm.disk_dev(name)
-  tails_installer_selected_device[/#{device}\d*$/]
-end
-
-def tails_installer_match_status(pattern)
-  @installer.child('', roleName: 'text').text[pattern]
-end
-
-class UpgradeNotSupported < StandardError
-end
-
-def usb_install_helper(name)
-  if tails_installer_match_status(/It is impossible to upgrade the device .+ #{$vm.disk_dev(name)}\d* /)
-    raise UpgradeNotSupported
-  end
-  assert(tails_installer_is_device_selected?(name))
-  begin
-    @installer.button('Install Tails').click
-    @installer.child('Question', roleName: 'alert').button('Yes').click
-    try_for(30*60) do
-      @installer
-        .child('Information', roleName: 'alert')
-        .child('Installation complete!', roleName: 'label')
-      true
-    end
-  rescue FindFailed => e
-    path = $vm.execute_successfully('ls -1 /tmp/tails-installer-*').stdout.chomp
-    debug_log("Tails Installer debug log:\n" + $vm.file_content(path))
-    raise e
-  end
-end
-
-When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
-  step 'I run "export DEBUG=1 ; tails-installer-launcher" in GNOME Terminal'
-  installer_launcher = Dogtail::Application.new('tails-installer-launcher')
-                         .child('Tails Installer', roleName: 'frame')
-  # Sometimes Dogtail will find the button and click it before it is
-  # shown (searchShowingOnly is not perfect) which generally means
-  # clicking somewhere on the Terminal => the click is lost *and* the
-  # installer does no go to the foreground. So let's wait a bit extra.
-  sleep 3
-  installer_launcher.button(mode).click
-  @installer = Dogtail::Application.new('tails-installer')
-  @installer.child('Tails Installer', roleName: 'frame')
-  # ... and something similar (for consecutive steps) again.
-  sleep 3
-  $vm.focus_window('Tails Installer')
-end
-
-Then /^Tails Installer detects that a device is too small$/ do
-  try_for(10) do
-    tails_installer_match_status(/^The device .* is too small to install Tails/)
-  end
-end
-
-When /^I am told that the destination device cannot be upgraded$/ do
-  try_for(10) do
-    tails_installer_match_status(/^It is impossible to upgrade the device/)
-  end
-end
-
-When /^I am suggested to do a "Install by cloning"$/ do
-  try_for(10) do
-    tails_installer_match_status(
-      /You should instead use "Install by cloning" to upgrade Tails/
-    )
-  end
-end
-
-Then /^a suitable USB device is (?:still )?not found$/ do
-  @installer.child(
-    'No device suitable to install Tails could be found', roleName: 'label'
-  )
-end
-
-Then /^(no|the "([^"]+)") USB drive is selected$/ do |mode, name|
-  try_for(30) do
-    if mode == 'no'
-      tails_installer_selected_device == ''
-    else
-      tails_installer_is_device_selected?(name)
-    end
-  end
-end
-
-When /^I "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
-  step "I start Tails Installer in \"#{mode}\" mode"
-  usb_install_helper(name)
-end
-
-When /^I fail to "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
-  begin
-    step "I \"#{mode}\" Tails to USB drive \"#{name}\""
-  rescue UpgradeNotSupported
-    # this is what we expect
-  else
-    raise "The USB installer should not succeed"
-  end
-end
-
-Given /^I plug and mount a USB drive containing the Tails ISO$/ do
-  iso_dir = share_host_files(TAILS_ISO)
-  @iso_path = "#{iso_dir}/#{File.basename(TAILS_ISO)}"
-end
-
-When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
-  step 'I start Tails Installer in "Upgrade from ISO" mode'
-  @installer.child('Use existing Live system ISO:', roleName: 'label')
-    .parent.button('(None)').click
-  file_chooser = @installer.child('Select a File', roleName: 'file chooser')
-  @screen.type("l", Sikuli::KeyModifier.CTRL)
-  # The only visible text element will be the path entry
-  file_chooser.child(roleName: 'text').typeText(@iso_path + '\n')
-  file_chooser.button('Open').click
-  usb_install_helper(name)
-end
-
-Given /^I enable all persistence presets$/ do
-  @screen.wait('PersistenceWizardPresets.png', 20)
-  # Select the "Persistent" folder preset, which is checked by default.
-  @screen.type(Sikuli::Key.TAB)
-  # Check all non-default persistence presets, i.e. all *after* the
-  # "Persistent" folder, which are unchecked by default.
-  (persistent_dirs.size - 1).times do
-    @screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE)
-  end
-  @screen.wait_and_click('PersistenceWizardSave.png', 10)
-  @screen.wait('PersistenceWizardDone.png', 60)
-  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-end
-
-When /^I disable the first persistence preset$/ do
-  step 'I start "Configure persistent volume" via GNOME Activities Overview'
-  @screen.wait('PersistenceWizardPresets.png', 300)
-  @screen.type(Sikuli::Key.SPACE)
-  @screen.wait_and_click('PersistenceWizardSave.png', 10)
-  @screen.wait('PersistenceWizardDone.png', 30)
-  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
-end
-
-Given /^I create a persistent partition$/ do
-  step 'I start "Configure persistent volume" via GNOME Activities Overview'
-  @screen.wait('PersistenceWizardStart.png', 60)
-  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
-  @screen.wait('PersistenceWizardPresets.png', 300)
-  step "I enable all persistence presets"
-end
-
-def check_disk_integrity(name, dev, scheme)
-  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
-  info_split = info.split("\n  org\.freedesktop\.UDisks2\.PartitionTable:\n")
-  dev_info = info_split[0]
-  part_table_info = info_split[1]
-  assert(part_table_info.match("^    Type: +#{scheme}$"),
-         "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
-end
-
-def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
-  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
-  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
-  dev_info = info_split[0]
-  part_info = info_split[1]
-  assert(dev_info.match("^    IdUsage: +#{usage}$"),
-         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
-  assert(dev_info.match("^    IdType: +#{fs_type}$"),
-         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
-  assert(part_info.match("^    Name: +#{part_label}$"),
-         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
-  if part_type
-    assert(part_info.match("^    Type: +#{part_type}$"),
-           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
-  end
-end
-
-def tails_is_installed_helper(name, tails_root, loader)
-  disk_dev = $vm.disk_dev(name)
-  part_dev = disk_dev + "1"
-  check_disk_integrity(name, disk_dev, "gpt")
-  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
-                       # EFI System Partition
-                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
-
-  target_root = "/mnt/new"
-  $vm.execute("mkdir -p #{target_root}")
-  $vm.execute("mount #{part_dev} #{target_root}")
-
-  c = $vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'")
-  assert(c.success?,
-         "USB drive '#{name}' has differences in /live:\n#{c.stdout}\n#{c.stderr}")
-
-  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
-  # We deal with these files separately
-  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
-  for f in syslinux_files - ignores do
-    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
-                    "'#{target_root}/syslinux/#{f}'")
-    assert(c.success?, "USB drive '#{name}' has differences in " +
-           "'/syslinux/#{f}'")
-  end
-
-  # The main .cfg is named differently vs isolinux
-  c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{loader}.cfg' " +
-                  "'#{target_root}/syslinux/syslinux.cfg'")
-  assert(c.success?, "USB drive '#{name}' has differences in " +
-         "'/syslinux/syslinux.cfg'")
-
-  $vm.execute("umount #{target_root}")
-  $vm.execute("sync")
-end
-
-Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
-  loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
-  tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader)
-end
-
-Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
-  iso_root = "/mnt/iso"
-  $vm.execute("mkdir -p #{iso_root}")
-  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
-  tails_is_installed_helper(target_name, iso_root, "isolinux")
-  $vm.execute("umount #{iso_root}")
-end
-
-Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
-  data_part_dev = $vm.disk_dev(name) + "2"
-  assert(!$vm.execute("test -b #{data_part_dev}").success?,
-         "USB drive #{name} has a partition '#{data_part_dev}'")
-end
-
-Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
-  dev = $vm.disk_dev(name) + "2"
-  check_part_integrity(name, dev, "crypto", "crypto_LUKS", "TailsData")
-
-  # The LUKS container may already be opened, e.g. by udisks after
-  # we've run tails-persistence-setup.
-  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
-  if c.success?
-    for candidate in c.stdout.split("\n")
-      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
-      if luks_info.success? and luks_info.stdout.match("^\s+device:\s+#{dev}$")
-        luks_dev = "/dev/mapper/#{candidate}"
-        break
-      end
-    end
-  end
-  if luks_dev.nil?
-    c = $vm.execute("echo #{@persistence_password} | " +
-                    "cryptsetup luksOpen #{dev} #{name}")
-    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
-    luks_dev = "/dev/mapper/#{name}"
-  end
-
-  # Adapting check_part_integrity() seems like a bad idea so here goes
-  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
-  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
-  assert info.match("^    IdUsage: +filesystem$")
-  assert info.match("^    IdType: +ext[34]$")
-  assert info.match("^    IdLabel: +TailsData$")
-
-  mount_dir = "/mnt/#{name}"
-  $vm.execute("mkdir -p #{mount_dir}")
-  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
-  assert(c.success?,
-         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
-
-  $vm.execute("umount #{mount_dir}")
-  $vm.execute("sync")
-  $vm.execute("cryptsetup luksClose #{name}")
-end
-
-Given /^I enable persistence$/ do
-  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 10)
-  @screen.type(@persistence_password + Sikuli::Key.ENTER)
-  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
-end
-
-def tails_persistence_enabled?
-  persistence_state_file = "/var/lib/live/config/tails.persistence"
-  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
-         $vm.execute(". '#{persistence_state_file}' && " +
-                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
-end
-
-Given /^all persistence presets(| from the old Tails version)(| but the first one) are enabled$/ do |old_tails, except_first|
-  assert(old_tails.empty? || except_first.empty?, "Unsupported case.")
-  try_for(120, :msg => "Persistence is disabled") do
-    tails_persistence_enabled?
-  end
-  unexpected_mounts = Array.new
-  # Check that all persistent directories are mounted
-  if old_tails.empty?
-    expected_mounts = persistent_mounts
-    if ! except_first.empty?
-      first_expected_mount_source      = expected_mounts.keys[0]
-      first_expected_mount_destination = expected_mounts[first_expected_mount_source]
-      expected_mounts.delete(first_expected_mount_source)
-      unexpected_mounts = [first_expected_mount_destination]
-    end
-  else
-    assert_not_nil($remembered_persistence_mounts)
-    expected_mounts = $remembered_persistence_mounts
-  end
-  mount = $vm.execute("mount").stdout.chomp
-  for _, dir in expected_mounts do
-    assert(mount.include?("on #{dir} "),
-           "Persistent directory '#{dir}' is not mounted")
-  end
-  for dir in unexpected_mounts do
-    assert(! mount.include?("on #{dir} "),
-           "Persistent directory '#{dir}' is mounted")
-  end
-end
-
-Given /^persistence is disabled$/ do
-  assert(!tails_persistence_enabled?, "Persistence is enabled")
-end
-
-def boot_device
-  # Approach borrowed from
-  # config/chroot_local_includes/lib/live/config/998-permissions
-  boot_dev_id = $vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
-  boot_dev = $vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
-  return boot_dev
-end
-
-def device_info(dev)
-  # Approach borrowed from
-  # config/chroot_local_includes/lib/live/config/998-permissions
-  info = $vm.execute("udevadm info --query=property --name='#{dev}'").stdout.chomp
-  info.split("\n").map { |e| e.split('=') } .to_h
-end
-
-def boot_device_type
-  device_info(boot_device)['ID_BUS']
-end
-
-Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
-  bus = bus.downcase
-  case bus
-  when "sata"
-    expected_bus = "ata"
-  else
-    expected_bus = bus
-  end
-  assert_equal(expected_bus, boot_device_type)
-  actual_dev = boot_device
-  # The boot partition differs between an using Tails installer and
-  # isohybrids. There's also a strange case isohybrids are thought to
-  # be booting from the "raw" device, and not a partition of it
-  # (#10504).
-  expected_devs = ['', '1', '4'].map { |e| $vm.disk_dev(name) + e }
-  assert(expected_devs.include?(actual_dev),
-         "We are running from device #{actual_dev}, but for #{bus} drive " +
-         "'#{name}' we expected to run from one of #{expected_devs}")
-end
-
-Then /^the boot device has safe access rights$/ do
-
-  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
-  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
-  assert(devs.size > 0, "Could not determine boot device")
-  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
-  all_users_with_groups = all_users.collect do |user|
-    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
-    [user, groups]
-  end
-  for dev in devs do
-    dev_owner = $vm.execute("stat -c %U #{dev}").stdout.chomp
-    dev_group = $vm.execute("stat -c %G #{dev}").stdout.chomp
-    dev_perms = $vm.execute("stat -c %a #{dev}").stdout.chomp
-    assert_equal("root", dev_owner)
-    assert(dev_group == "disk" || dev_group == "root",
-           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
-           "'disk' or 'root'.")
-    assert_equal("660", dev_perms)
-    for user, groups in all_users_with_groups do
-      next if user == "root"
-      assert(!(groups.include?(dev_group)),
-             "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
-             "owns boot device '#{dev}'")
-    end
-  end
-
-  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
-  assert(info.match("^    HintSystem: +true$"),
-         "Boot device '#{super_boot_dev}' is not system internal for udisks")
-end
-
-Then /^all persistent filesystems have safe access rights$/ do
-  persistent_volumes_mountpoints.each do |mountpoint|
-    fs_owner = $vm.execute("stat -c %U #{mountpoint}").stdout.chomp
-    fs_group = $vm.execute("stat -c %G #{mountpoint}").stdout.chomp
-    fs_perms = $vm.execute("stat -c %a #{mountpoint}").stdout.chomp
-    assert_equal("root", fs_owner)
-    assert_equal("root", fs_group)
-    assert_equal('775', fs_perms)
-  end
-end
-
-Then /^all persistence configuration files have safe access rights$/ do
-  persistent_volumes_mountpoints.each do |mountpoint|
-    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
-           "#{mountpoint}/persistence.conf does not exist, while it should")
-    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
-           "#{mountpoint}/live-persistence.conf does exist, while it should not")
-    $vm.execute(
-      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
-    ).stdout.chomp.split.each do |f|
-      file_owner = $vm.execute("stat -c %U '#{f}'").stdout.chomp
-      file_group = $vm.execute("stat -c %G '#{f}'").stdout.chomp
-      file_perms = $vm.execute("stat -c %a '#{f}'").stdout.chomp
-      assert_equal("tails-persistence-setup", file_owner)
-      assert_equal("tails-persistence-setup", file_group)
-      assert_equal("600", file_perms)
-    end
-  end
-end
-
-Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
-  if old_tails.empty?
-    expected_dirs = persistent_dirs
-  else
-    assert_not_nil($remembered_persistence_dirs)
-    expected_dirs = $remembered_persistence_dirs
-  end
-  persistent_volumes_mountpoints.each do |mountpoint|
-    expected_dirs.each do |src, dest|
-      full_src = "#{mountpoint}/#{src}"
-      assert_vmcommand_success $vm.execute("test -d #{full_src}")
-      dir_perms = $vm.execute_successfully("stat -c %a '#{full_src}'").stdout.chomp
-      dir_owner = $vm.execute_successfully("stat -c %U '#{full_src}'").stdout.chomp
-      if dest.start_with?("/home/#{LIVE_USER}")
-        expected_perms = "700"
-        expected_owner = LIVE_USER
-      else
-        expected_perms = "755"
-        expected_owner = "root"
-      end
-      assert_equal(expected_perms, dir_perms,
-                   "Persistent source #{full_src} has permission " \
-                   "#{dir_perms}, expected #{expected_perms}")
-      assert_equal(expected_owner, dir_owner,
-                   "Persistent source #{full_src} has owner " \
-                   "#{dir_owner}, expected #{expected_owner}")
-    end
-  end
-end
-
-When /^I write some files expected to persist$/ do
-  persistent_mounts.each do |_, dir|
-    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
-    assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?,
-           "Could not create file in persistent directory #{dir}")
-  end
-end
-
-When /^I write some dotfile expected to persist$/ do
-  assert($vm.execute("touch /live/persistence/TailsData_unlocked/dotfiles/.XXX_persist",
-                     :user => LIVE_USER).success?,
-         "Could not create a file in the dotfiles persistence.")
-end
-
-When /^I remove some files expected to persist$/ do
-  persistent_mounts.each do |_, dir|
-    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
-    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
-           "Could not remove file in persistent directory #{dir}")
-  end
-end
-
-When /^I write some files not expected to persist$/ do
-  persistent_mounts.each do |_, dir|
-    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
-    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
-           "Could not create file in persistent directory #{dir}")
-  end
-end
-
-When /^I take note of which persistence presets are available$/ do
-  $remembered_persistence_mounts = persistent_mounts
-  $remembered_persistence_dirs = persistent_dirs
-end
-
-Then /^the expected persistent files(| created with the old Tails version) are present in the filesystem$/ do |old_tails|
-  if old_tails.empty?
-    expected_mounts = persistent_mounts
-  else
-    assert_not_nil($remembered_persistence_mounts)
-    expected_mounts = $remembered_persistence_mounts
-  end
-  expected_mounts.each do |_, dir|
-    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
-           "Could not find expected file in persistent directory #{dir}")
-    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
-           "Found file that should not have persisted in persistent directory #{dir}")
-  end
-end
-
-Then /^the expected persistent dotfile is present in the filesystem$/ do
-  expected_dirs = persistent_dirs
-  assert($vm.execute("test -L #{expected_dirs['dotfiles']}/.XXX_persist").success?,
-         "Could not find expected persistent dotfile link.")
-  assert($vm.execute("test -e $(readlink -f #{expected_dirs['dotfiles']}/.XXX_persist)").success?,
-           "Could not find expected persistent dotfile link target.")
-end
-
-Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
-  assert(!$vm.is_running?)
-  disk = {
-    :path => $vm.storage.disk_path(name),
-    :opts => {
-      :format => $vm.storage.disk_format(name),
-      :readonly => true
-    }
-  }
-  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
-    partitions = g.part_list(disk_handle).map do |part_desc|
-      disk_handle + part_desc["part_num"].to_s
-    end
-    partition = partitions.find do |part|
-      g.blkid(part)["PART_ENTRY_NAME"] == "TailsData"
-    end
-    assert_not_nil(partition, "Could not find the 'TailsData' partition " \
-                              "on disk '#{disk_handle}'")
-    luks_mapping = File.basename(partition) + "_unlocked"
-    g.luks_open(partition, @persistence_password, luks_mapping)
-    luks_dev = "/dev/mapper/#{luks_mapping}"
-    mount_point = "/"
-    g.mount(luks_dev, mount_point)
-    assert_not_nil($remembered_persistence_mounts)
-    $remembered_persistence_mounts.each do |dir, _|
-      # Guestfs::exists may have a bug; if the file exists, 1 is
-      # returned, but if it doesn't exist false is returned. It seems
-      # the translation of C types into Ruby types is glitchy.
-      assert(g.exists("/#{dir}/XXX_persist") == 1,
-             "Could not find expected file in persistent directory #{dir}")
-      assert(g.exists("/#{dir}/XXX_gone") != 1,
-             "Found file that should not have persisted in persistent directory #{dir}")
-    end
-    g.umount(mount_point)
-    g.luks_close(luks_dev)
-  end
-end
-
-When /^I delete the persistent partition$/ do
-  step 'I start "Delete persistent volume" via GNOME Activities Overview'
-  @screen.wait("PersistenceWizardDeletionStart.png", 120)
-  @screen.type(" ")
-  @screen.wait("PersistenceWizardDone.png", 120)
-end
-
-Then /^Tails has started in UEFI mode$/ do
-  assert($vm.execute("test -d /sys/firmware/efi").success?,
-         "/sys/firmware/efi does not exist")
- end
-
-Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
-  $vm.storage.disk_mklabel(name, type)
-end
-
-Given /^the file system changes introduced in version (.+) are (not )?present(?: in the (\S+) Browser's chroot)?$/ do |version, not_present, chroot_browser|
-  assert_equal('1.1~test', version)
-  upgrade_applied = not_present.nil?
-  chroot_browser = "#{chroot_browser.downcase}-browser" if chroot_browser
-  changes = [
-    {
-      filesystem: :rootfs,
-      path: 'some_new_file',
-      status: :added,
-      new_content: <<-EOF
-Some content
-      EOF
-    },
-    {
-      filesystem: :rootfs,
-      path: 'etc/amnesia/version',
-      status: :modified,
-      new_content: <<-EOF
-#{version} - 20380119
-ffffffffffffffffffffffffffffffffffffffff
-live-build: 3.0.5+really+is+2.0.12-0.tails2
-live-boot: 4.0.2-1
-live-config: 4.0.4-1
-      EOF
-    },
-    {
-      filesystem: :rootfs,
-      path: 'etc/os-release',
-      status: :modified,
-      new_content: <<-EOF
-TAILS_PRODUCT_NAME="Tails"
-TAILS_VERSION_ID="#{version}"
-      EOF
-    },
-    {
-      filesystem: :rootfs,
-      path: 'usr/share/common-licenses/BSD',
-      status: :removed
-    },
-    {
-      filesystem: :medium,
-      path: 'utils/linux/syslinux',
-      status: :removed
-    },
-  ]
-  changes.each do |change|
-    case change[:filesystem]
-    when :rootfs
-      path = '/'
-      path += "var/lib/#{chroot_browser}/chroot/" if chroot_browser
-      path += change[:path]
-    when :medium
-      path = '/lib/live/mount/medium/' + change[:path]
-    else
-      raise "Unknown filesysten '#{change[:filesystem]}'"
-    end
-    case change[:status]
-    when :removed
-      assert_equal(!upgrade_applied, $vm.file_exist?(path))
-    when :added
-      assert_equal(upgrade_applied, $vm.file_exist?(path))
-      if upgrade_applied && change[:new_content]
-        assert_equal(change[:new_content], $vm.file_content(path))
-      end
-    when :modified
-      assert($vm.file_exist?(path))
-      if upgrade_applied
-        assert_not_nil(change[:new_content])
-        assert_equal(change[:new_content], $vm.file_content(path))
-      end
-    else
-      raise "Unknown status '#{change[:status]}'"
-    end
-  end
-end
-
-Then /^I am proposed to install an incremental upgrade to version (.+)$/ do |version|
-  recovery_proc = Proc.new do
-    recover_from_upgrader_failure
-  end
-  failure_pic = 'TailsUpgraderFailure.png'
-  success_pic = "TailsUpgraderUpgradeTo#{version}.png"
-  retry_tor(recovery_proc) do
-    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
-    assert_equal(success_pic, match)
-  end
-end
-
-When /^I agree to install the incremental upgrade$/ do
-  @screen.click('TailsUpgraderUpgradeNowButton.png')
-end
-
-Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
-  step 'I agree to install the incremental upgrade'
-  recovery_proc = Proc.new do
-    recover_from_upgrader_failure
-    step "I am proposed to install an incremental upgrade to version #{version}"
-    step 'I agree to install the incremental upgrade'
-  end
-  failure_pic = 'TailsUpgraderFailure.png'
-  success_pic = "TailsUpgraderDone.png"
-  retry_tor(recovery_proc) do
-    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
-    assert_equal(success_pic, match)
-  end
-end
diff --git a/cucumber/features/support/config.rb b/cucumber/features/support/config.rb
deleted file mode 100644
index 54a0f1c..0000000
--- a/cucumber/features/support/config.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'fileutils'
-require 'yaml'
-require "cucumber/features/support/helpers/misc_helpers.rb"
-
-# These files deal with options like some of the settings passed
-# to the `run_test_suite` script, and "secrets" like credentials
-# (passwords, SSH keys) to be used in tests.
-CONFIG_DIR = "/srv/jenkins/cucumber/features/config"
-DEFAULTS_CONFIG_FILE = "#{CONFIG_DIR}/defaults.yml"
-LOCAL_CONFIG_FILE = "#{CONFIG_DIR}/local.yml"
-LOCAL_CONFIG_DIRS_FILES_GLOB = "#{CONFIG_DIR}/*.d/*.yml"
-
-# Dynamic
-$tails_iso = ENV['ISO'] || get_newest_iso
-$old_tails_iso = ENV['OLD_ISO'] || get_oldest_iso
-$tmp_dir = ENV['PWD']
-$vm_xml_path = ENV['VM_XML_PATH']
-$misc_files_dir = "cucumber/features/misc_files"
-$keep_snapshots = !ENV['KEEP_SNAPSHOTS'].nil?
-$x_display = ENV['DISPLAY']
-$debug = !ENV['DEBUG'].nil?
-$pause_on_fail = !ENV['PAUSE_ON_FAIL'].nil?
-$time_at_start = Time.now
-$live_user = "user"
-$sikuli_retry_findfailed = !ENV['SIKULI_RETRY_FINDFAILED'].nil?
-$executor_number = ENV['EXECUTOR_NUMBER']
-
-assert File.exists?(DEFAULTS_CONFIG_FILE)
-$config = YAML.load(File.read(DEFAULTS_CONFIG_FILE))
-config_files = Dir.glob(LOCAL_CONFIG_DIRS_FILES_GLOB).sort
-config_files.insert(0, LOCAL_CONFIG_FILE) if File.exists?(LOCAL_CONFIG_FILE)
-config_files.each do |config_file|
-  yaml_struct = YAML.load(File.read(config_file)) || Hash.new
-  if not(yaml_struct.instance_of?(Hash))
-    raise "Local configuration file '#{config_file}' is malformed"
-  end
-  $config.merge!(yaml_struct)
-end
-# Options passed to the `run_test_suite` script will always take
-# precedence. The way we import these keys is only safe for values
-# with types boolean or string. If we need more, we'll have to invoke
-# YAML's type autodetection on ENV some how.
-$config.merge!(ENV)
-
-# Export TMPDIR back to the environment for subprocesses that we start
-# (e.g. guestfs). Note that this export will only make a difference if
-# TMPDIR wasn't already set and --tmpdir wasn't passed, i.e. only when
-# we use the default.
-ENV['TMPDIR'] = $config['TMPDIR']
-
-# Dynamic constants initialized through the environment or similar,
-# e.g. options we do not want to be configurable through the YAML
-# configuration files.
-DEBUG_LOG_PSEUDO_FIFO = "#{$config["TMPDIR"]}/debug_log_pseudo_fifo"
-DISPLAY = ENV['DISPLAY']
-GIT_DIR = ENV['PWD']
-KEEP_SNAPSHOTS = !ENV['KEEP_SNAPSHOTS'].nil?
-LIVE_USER = "live_user"
-TAILS_ISO = ENV['ISO']
-OLD_TAILS_ISO = ENV['OLD_ISO'] || TAILS_ISO
-TIME_AT_START = Time.now
-ARTIFACTS_DIR = $config['TMPDIR'] + "/results"
-JOB_NAME = ENV['JOB_NAME']
-BUILD_ID = ENV['BUILD_ID']
-PATIENCE = ENV['PATIENCE'] || 1
-
-# Constants that are statically initialized.
-CONFIGURED_KEYSERVER_HOSTNAME = 'hkps.pool.sks-keyservers.net'
-LIBVIRT_DOMAIN_NAME = "lvcVM-" + JOB_NAME
-#LIBVIRT_DOMAIN_UUID = "203552d5-819c-41f3-800e-2c8ef2546%03d" % $executor_number
-LIBVIRT_NETWORK_NAME = "lvcNET"
-#LIBVIRT_NETWORK_UUID = "f2305af3-2a64-4f16-afe6-b9dbf02a597e"
-LIBVIRT_REMOTE_SHELL_PORT = 13370 + Integer($executor_number)
-MISC_FILES_DIR = "/srv/jenkins/cucumber/features/misc_files"
-SERVICES_EXPECTED_ON_ALL_IFACES =
-  [
-   ["cupsd",    "*", "631"],
-   ["dhclient", "*", "*"]
-  ]
-# OpenDNS
-SOME_DNS_SERVER = "208.67.222.222"
-VM_XML_PATH = "/srv/jenkins/cucumber/features/domains"
-
-#TAILS_SIGNING_KEY = cmd_helper(". #{Dir.pwd}/config/amnesia; echo ${AMNESIA_DEV_KEYID}").tr(' ', '').chomp
-TAILS_DEBIAN_REPO_KEY = "221F9A3C6FA3E09E182E060BC7988EA7A358D82E"
diff --git a/cucumber/features/support/env.rb b/cucumber/features/support/env.rb
deleted file mode 100644
index c52afff..0000000
--- a/cucumber/features/support/env.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-require 'rubygems'
-require "cucumber/features/support/extra_hooks.rb"
-require 'time'
-require 'rspec'
-
-# Force UTF-8. Ruby will default to the system locale, and if it is
-# non-UTF-8, String-methods will fail when operating on non-ASCII
-# strings.
-Encoding.default_external = Encoding::UTF_8
-Encoding.default_internal = Encoding::UTF_8
-
-def fatal_system(str)
-  unless system(str)
-    raise StandardError.new("Command exited with #{$?}")
-  end
-end
-
-def git_exists?
-  File.exists? '.git'
-end
-
-def create_git
-  Dir.mkdir 'config'
-  FileUtils.touch('config/base_branch')
-  Dir.mkdir('config/APT_overlays.d')
-  Dir.mkdir('config/APT_snapshots.d')
-  ['debian', 'debian-security', 'torproject'].map do |origin|
-    Dir.mkdir("config/APT_snapshots.d/#{origin}")
-  end
-  Dir.mkdir 'debian'
-  File.open('debian/changelog', 'w') do |changelog|
-    changelog.write(<<END_OF_CHANGELOG)
-tails (0) stable; urgency=low
-
-  * First release.
-
- -- Tails developers <tails at boum.org>  Mon, 30 Jan 2012 01:00:00 +0000
-END_OF_CHANGELOG
-  end
-
-  fatal_system "git init --quiet"
-  fatal_system "git config user.email 'tails at boum.org'"
-  fatal_system "git config user.name 'Tails developers'"
-  fatal_system "git add debian/changelog"
-  fatal_system "git commit --quiet debian/changelog -m 'First release'"
-  fatal_system "git branch -M stable"
-  fatal_system "git branch testing stable"
-  fatal_system "git branch devel stable"
-  fatal_system "git branch feature/jessie devel"
-end
-
-def current_branch
-  cmd = 'git rev-parse --symbolic-full-name --abbrev-ref HEAD'.split
-  branch = cmd_helper(cmd).strip
-  assert_not_equal("HEAD", branch, "We are in 'detached HEAD' state")
-  return branch
-end
-
-# In order: if git HEAD is tagged, return its name; if a branch is
-# checked out, return its name; otherwise we are in 'detached HEAD'
-# state, and we return the empty string.
-def describe_git_head
-  cmd_helper("git describe --tags --exact-match #{current_commit}".split).strip
-rescue Test::Unit::AssertionFailedError
-  begin
-    current_branch
-  rescue Test::Unit::AssertionFailedError
-    ""
-  end
-end
-
-def current_commit
-  cmd_helper('git rev-parse HEAD'.split).strip
-end
-
-def current_short_commit
-  current_commit[0, 7]
-end
-
-RSpec::Matchers.define :have_suite do |suite|
-  match do |string|
-    # e.g.: `deb http://deb.tails.boum.org/ 0.10 main contrib non-free`
-    %r{^deb +http://deb\.tails\.boum\.org/ +#{Regexp.escape(suite)} main}.match(string)
-  end
-  failure_message_for_should do |string|
-    "expected the sources to include #{suite}\nCurrent sources : #{string}"
-  end
-  failure_message_for_should_not do |string|
-    "expected the sources to exclude #{suite}\nCurrent sources : #{string}"
-  end
-  description do
-    "expected an output with #{suite}"
-  end
-end
-
-RSpec::Matchers.define :have_tagged_snapshot do |tag|
-  match do |string|
-    # e.g.: `http://tagged.snapshots.deb.tails.boum.org/0.10`
-    %r{^http://tagged\.snapshots\.deb\.tails\.boum\.org/#{Regexp.escape(tag)}/[a-z-]+$}.match(string)
-  end
-  failure_message_for_should do |string|
-    "expected the mirror to be #{tag}\nCurrent mirror: #{string}"
-  end
-  failure_message_for_should_not do |string|
-    "expected the mirror not to be #{tag}\nCurrent mirror: #{string}"
-  end
-  description do
-    "expected an output with #{tag}"
-  end
-end
-
-RSpec::Matchers.define :have_time_based_snapshot do |tag|
-  match do |string|
-    # e.g.: `http://time-based.snapshots.deb.tails.boum.org/debian/2016060602`
-    %r{^http://time\-based\.snapshots\.deb\.tails\.boum\.org/[^/]+/\d+}.match(string)
-  end
-  failure_message_for_should do |string|
-    "expected the mirror to be a time-based snapshot\nCurrent mirror: #{string}"
-  end
-  failure_message_for_should_not do |string|
-    "expected the mirror not to be a time-based snapshot\nCurrent mirror: #{string}"
-  end
-  description do
-    "expected a time-based snapshot"
-  end
-end
diff --git a/cucumber/features/support/extra_hooks.rb b/cucumber/features/support/extra_hooks.rb
deleted file mode 100644
index c2c5749..0000000
--- a/cucumber/features/support/extra_hooks.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-# Make the code below work with cucumber >= 2.0. Once we stop
-# supporting <2.0 we should probably do this differently, but this way
-# we can easily support both at the same time.
-
-begin
-  if not(Cucumber::Core::Ast::Feature.instance_methods.include?(:accept_hook?))
-    if Gem::Version.new(Cucumber::VERSION) >= Gem::Version.new('2.4.0')
-      require 'cucumber/core/gherkin/tag_expression'
-    else
-      require 'gherkin/tag_expression'
-      Cucumber::Core::Gherkin = Gherkin
-    end
-    class Cucumber::Core::Ast::Feature
-      # Code inspired by Cucumber::Core::Test::Case.match_tags?() in
-      # cucumber-ruby-core 1.1.3, lib/cucumber/core/test/case.rb:~59.
-      def accept_hook?(hook)
-        tag_expr = Cucumber::Core::Gherkin::TagExpression.new(hook.tag_expressions.flatten)
-        tag_expr.evaluate(@tags)
-      end
-    end
-  end
-rescue NameError => e
-  raise e if e.to_s != "uninitialized constant Cucumber::Core"
-end
-
-# Sort of inspired by Cucumber::RbSupport::RbHook (from cucumber
-# < 2.0) but really we just want an object with a 'tag_expressions'
-# attribute to make accept_hook?() (used below) happy.
-class SimpleHook
-  attr_reader :tag_expressions
-
-  def initialize(tag_expressions, proc)
-    @tag_expressions = tag_expressions
-    @proc = proc
-  end
-
-  def invoke(arg)
-    @proc.call(arg)
-  end
-end
-
-def BeforeFeature(*tag_expressions, &block)
-  $before_feature_hooks ||= []
-  $before_feature_hooks << SimpleHook.new(tag_expressions, block)
-end
-
-def AfterFeature(*tag_expressions, &block)
-  $after_feature_hooks ||= []
-  $after_feature_hooks << SimpleHook.new(tag_expressions, block)
-end
-
-require 'cucumber/formatter/console'
-if not($at_exit_print_artifacts_dir_patching_done)
-  module Cucumber::Formatter::Console
-    if method_defined?(:print_stats)
-      alias old_print_stats print_stats
-    end
-    def print_stats(*args)
-      @io.puts "Artifacts directory: #{ARTIFACTS_DIR}"
-      @io.puts
-      @io.puts "Debug log:           #{ARTIFACTS_DIR}/debug.log"
-      @io.puts
-      if self.class.method_defined?(:old_print_stats)
-        old_print_stats(*args)
-      end
-    end
-  end
-  $at_exit_print_artifacts_dir_patching_done = true
-end
-
-def info_log(message = "", options = {})
-  options[:color] = :clear
-  # This trick allows us to use a module's (~private) method on a
-  # one-off basis.
-  cucumber_console = Class.new.extend(Cucumber::Formatter::Console)
-  puts cucumber_console.format_string(message, options[:color])
-end
-
-def debug_log(message, options = {})
-  options[:timestamp] = true unless options.has_key?(:timestamp)
-  if $debug_log_fns
-    if options[:timestamp]
-      # Force UTC so the local timezone difference vs UTC won't be
-      # added to the result.
-      elapsed = (Time.now - TIME_AT_START.to_f).utc.strftime("%H:%M:%S.%9N")
-      message = "#{elapsed}: #{message}"
-    end
-    $debug_log_fns.each { |fn| fn.call(message, options) }
-  end
-end
-
-require 'cucumber/formatter/pretty'
-# Backport part of commit af940a8 from the cucumber-ruby repo. This
-# fixes the "out hook output" for the Pretty formatter so stuff
-# written via `puts` after a Scenario has run its last step will be
-# written, instead of delayed to the next Feature/Scenario (if any) or
-# dropped completely (if not).
-# XXX: This can be removed once we stop supporting Debian Jessie
-# around when Debian Stretch is released.
-if Gem::Version.new(Cucumber::VERSION) < Gem::Version.new('2.0.0.beta.4')
-  module Cucumber
-    module Formatter
-      class Pretty
-        def after_feature_element(feature_element)
-          print_messages
-          @io.puts
-          @io.flush
-        end
-      end
-    end
-  end
-end
-
-module ExtraFormatters
-  # This is a null formatter in the sense that it doesn't ever output
-  # anything. We only use it do hook into the correct events so we can
-  # add our extra hooks.
-  class ExtraHooks
-    def initialize(runtime, io, options)
-      # We do not care about any of the arguments.
-      # XXX: We should be able to just have `*args` for the arguments
-      # in the prototype, but since moving to cucumber 2.4 that breaks
-      # this formatter for some unknown reason.
-    end
-
-    def before_feature(feature)
-      if $before_feature_hooks
-        $before_feature_hooks.each do |hook|
-          hook.invoke(feature) if feature.accept_hook?(hook)
-        end
-      end
-    end
-
-    def after_feature(feature)
-      if $after_feature_hooks
-        $after_feature_hooks.reverse.each do |hook|
-          hook.invoke(feature) if feature.accept_hook?(hook)
-        end
-      end
-    end
-  end
-
-  # The pretty formatter with debug logging mixed into its output.
-  class PrettyDebug < Cucumber::Formatter::Pretty
-    def initialize(runtime, io, options)
-      super(runtime, io, options)
-      $debug_log_fns ||= []
-      $debug_log_fns << self.method(:debug_log)
-    end
-
-    def debug_log(message, options)
-      options[:color] ||= :blue
-      @io.puts(format_string(message, options[:color]))
-      @io.flush
-    end
-  end
-
-end
-
-module Cucumber
-  module Cli
-    class Options
-      BUILTIN_FORMATS['pretty_debug'] =
-        [
-          'ExtraFormatters::PrettyDebug',
-          'Prints the feature with debugging information - in colours.'
-        ]
-      BUILTIN_FORMATS['debug'] = BUILTIN_FORMATS['pretty_debug']
-    end
-  end
-end
-
-AfterConfiguration do |config|
-  # Cucumber may read this file multiple times, and hence run this
-  # AfterConfiguration hook multiple times. We only want our
-  # ExtraHooks formatter to be loaded once, otherwise the hooks would
-  # be run miltiple times.
-  extra_hooks = [
-    ['ExtraFormatters::ExtraHooks', '/dev/null'],
-    ['Cucumber::Formatter::Pretty', "#{ARTIFACTS_DIR}/pretty.log"],
-    ['Cucumber::Formatter::Json', "#{ARTIFACTS_DIR}/cucumber.json"],
-    ['ExtraFormatters::PrettyDebug', "#{ARTIFACTS_DIR}/debug.log"],
-  ]
-  extra_hooks.each do |hook|
-    config.formats << hook if not(config.formats.include?(hook))
-  end
-end
diff --git a/cucumber/features/support/helpers/chatbot_helper.rb b/cucumber/features/support/helpers/chatbot_helper.rb
deleted file mode 100644
index 23ce3e1..0000000
--- a/cucumber/features/support/helpers/chatbot_helper.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'tempfile'
-
-class ChatBot
-
-  def initialize(account, password, otr_key, opts = Hash.new)
-    @account = account
-    @password = password
-    @otr_key = otr_key
-    @opts = opts
-    @pid = nil
-    @otr_key_file = nil
-  end
-
-  def start
-    @otr_key_file = Tempfile.new("otr_key.", $config["TMPDIR"])
-    @otr_key_file << @otr_key
-    @otr_key_file.close
-
-    cmd_helper(['/usr/bin/convertkey', @otr_key_file.path])
-    cmd_helper(["mv", "#{@otr_key_file.path}3", @otr_key_file.path])
-
-    cmd = [
-           "#{GIT_DIR}/features/scripts/otr-bot.py",
-           @account,
-           @password,
-           @otr_key_file.path
-          ]
-    cmd += ["--connect-server", @opts["connect_server"]] if @opts["connect_server"]
-    cmd += ["--auto-join"] + @opts["auto_join"] if @opts["auto_join"]
-    cmd += ["--log-file", DEBUG_LOG_PSEUDO_FIFO]
-
-    job = IO.popen(cmd)
-    @pid = job.pid
-  end
-
-  def stop
-    @otr_key_file.delete
-    begin
-      Process.kill("TERM", @pid)
-    rescue
-      # noop
-    end
-  end
-
-  def active?
-    begin
-      ret = Process.kill(0, @pid)
-    rescue Errno::ESRCH => e
-      if e.message == "No such process"
-        return false
-      else
-        raise e
-      end
-    end
-    assert_equal(1, ret, "This shouldn't happen")
-    return true
-  end
-
-end
diff --git a/cucumber/features/support/helpers/ctcp_helper.rb b/cucumber/features/support/helpers/ctcp_helper.rb
deleted file mode 100644
index ee5180a..0000000
--- a/cucumber/features/support/helpers/ctcp_helper.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-require 'net/irc'
-require 'timeout'
-
-class CtcpChecker < Net::IRC::Client
-
-  CTCP_SPAM_DELAY = 5
-
-  # `spam_target`: the nickname of the IRC user to CTCP spam.
-  # `ctcp_cmds`: the Array of CTCP commands to send.
-  # `expected_ctcp_replies`: Hash where the keys are the exact set of replies
-  # we expect, and their values a regex the reply data must match.
-  def initialize(host, port, spam_target, ctcp_cmds, expected_ctcp_replies)
-    @spam_target = spam_target
-    @ctcp_cmds =  ctcp_cmds
-    @expected_ctcp_replies = expected_ctcp_replies
-    nickname = self.class.random_irc_nickname
-    opts = {
-      :nick => nickname,
-      :user => nickname,
-      :real => nickname,
-    }
-    opts[:logger] = Logger.new(DEBUG_LOG_PSEUDO_FIFO)
-    super(host, port, opts)
-  end
-
-  # Makes sure that only the expected CTCP replies are received.
-  def verify_ctcp_responses
-    @sent_ctcp_cmds = Set.new
-    @received_ctcp_replies = Set.new
-
-    # Give 60 seconds for connecting to the server and other overhead
-    # beyond the expected time to spam all CTCP commands.
-    expected_ctcp_spam_time = @ctcp_cmds.length * CTCP_SPAM_DELAY
-    timeout = expected_ctcp_spam_time + 60
-
-    begin
-      Timeout::timeout(timeout) do
-        start
-      end
-    rescue Timeout::Error
-      # Do nothing as we'll check for errors below.
-    ensure
-      finish
-    end
-
-    ctcp_cmds_not_sent = @ctcp_cmds - @sent_ctcp_cmds.to_a
-    expected_ctcp_replies_not_received =
-      @expected_ctcp_replies.keys - @received_ctcp_replies.to_a
-
-    if !ctcp_cmds_not_sent.empty? || !expected_ctcp_replies_not_received.empty?
-      raise "Failed to spam all CTCP commands and receive the expected " +
-            "replies within #{timeout} seconds.\n" +
-            (ctcp_cmds_not_sent.empty? ? "" :
-            "CTCP commands not sent: #{ctcp_cmds_not_sent}\n") +
-            (expected_ctcp_replies_not_received.empty? ? "" :
-            "Expected CTCP replies not received: " +
-            expected_ctcp_replies_not_received.to_s)
-    end
-
-  end
-
-  # Generate a random IRC nickname, in this case an alpha-numeric
-  # string with length 10 to 15. To make it legal, the first character
-  # is forced to be alpha.
-  def self.random_irc_nickname
-    random_alpha_string(1) + random_alnum_string(9, 14)
-  end
-
-  def spam(spam_target)
-    post(NOTICE, spam_target, "Hi! I'm gonna test your CTCP capabilities now.")
-    @ctcp_cmds.each do |cmd|
-      sleep CTCP_SPAM_DELAY
-      full_cmd = cmd
-      case cmd
-      when "PING"
-        full_cmd += " #{Time.now.to_i}"
-      when "ACTION"
-        full_cmd += " barfs on the floor."
-      when "ERRMSG"
-        full_cmd += " Pidgin should not respond to this."
-      end
-      post(PRIVMSG, spam_target, ctcp_encode(full_cmd))
-      @sent_ctcp_cmds << cmd
-    end
-  end
-
-  def on_rpl_welcome(m)
-    super
-    Thread.new { spam(@spam_target) }
-  end
-
-  def on_message(m)
-    if m.command == ERR_NICKNAMEINUSE
-      finish
-      new_nick = self.class.random_irc_nickname
-      @opts.marshal_load({
-                           :nick => new_nick,
-                           :user => new_nick,
-                           :real => new_nick,
-                         })
-      start
-      return
-    end
-
-    if m.ctcp? and /^:#{Regexp.escape(@spam_target)}!/.match(m)
-      m.ctcps.each do |ctcp_reply|
-        reply_type, _, reply_data = ctcp_reply.partition(" ")
-        if @expected_ctcp_replies.has_key?(reply_type)
-          if @expected_ctcp_replies[reply_type].match(reply_data)
-            @received_ctcp_replies << reply_type
-          else
-            raise "Received expected CTCP reply '#{reply_type}' but with " +
-                  "unexpected data '#{reply_data}' "
-          end
-        else
-          raise "Received unexpected CTCP reply '#{reply_type}' with " +
-                "data '#{reply_data}'"
-        end
-      end
-    end
-    if Set.new(@ctcp_cmds) == @sent_ctcp_cmds && \
-       Set.new(@expected_ctcp_replies.keys) == @received_ctcp_replies
-      finish
-    end
-  end
-end
diff --git a/cucumber/features/support/helpers/display_helper.rb b/cucumber/features/support/helpers/display_helper.rb
deleted file mode 100644
index b4dce73..0000000
--- a/cucumber/features/support/helpers/display_helper.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-
-class Display
-
-  def initialize(domain, x_display)
-    @domain = domain
-    @x_display = x_display
-  end
-
-  def active?
-    p = IO.popen(["xprop", "-display", @x_display,
-                  "-name", "#{@domain} (1) - Virt Viewer",
-                  :err => ["/dev/null", "w"]])
-    Process.wait(p.pid)
-    $?.success?
-  end
-
-  def start
-    @virtviewer = IO.popen(["virt-viewer", "--direct",
-                                           "--kiosk",
-                                           "--reconnect",
-                                           "--connect", "qemu:///system",
-                                           "--display", @x_display,
-                                           @domain,
-                                           :err => ["/dev/null", "w"]])
-    # We wait for the display to be active to not lose actions
-    # (e.g. key presses via sikuli) that come immediately after
-    # starting (or restoring) a vm
-    try_for(20, { :delay => 0.1, :msg => "virt-viewer failed to start"}) {
-      active?
-    }
-  end
-
-  def stop
-    return if @virtviewer.nil?
-    Process.kill("TERM", @virtviewer.pid)
-    @virtviewer.close
-  rescue IOError
-    # IO.pid throws this if the process wasn't started yet. Possibly
-    # there's a race when doing a start() and then quickly running
-    # stop().
-  end
-
-  def restart
-    stop
-    start
-  end
-
-end
diff --git a/cucumber/features/support/helpers/dogtail.rb b/cucumber/features/support/helpers/dogtail.rb
deleted file mode 100644
index 2a92649..0000000
--- a/cucumber/features/support/helpers/dogtail.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-module Dogtail
-  module Mouse
-    LEFT_CLICK = 1
-    MIDDLE_CLICK = 2
-    RIGHT_CLICK = 3
-  end
-
-  TREE_API_NODE_SEARCHES = [
-    :button,
-    :child,
-    :childLabelled,
-    :childNamed,
-    :dialog,
-    :menu,
-    :menuItem,
-    :tab,
-    :textentry,
-  ]
-
-  TREE_API_NODE_SEARCH_FIELDS = [
-    :parent,
-  ]
-
-  TREE_API_NODE_ACTIONS = [
-    :click,
-    :doubleClick,
-    :grabFocus,
-    :keyCombo,
-    :point,
-    :typeText,
-  ]
-
-  TREE_API_APP_SEARCHES = TREE_API_NODE_SEARCHES + [
-    :dialog,
-    :window,
-  ]
-
-  # We want to keep this class immutable so that handles always are
-  # left intact when doing new (proxied) method calls.  This way we
-  # can support stuff like:
-  #
-  #     app = Dogtail::Application.new('gedit')
-  #     menu = app.menu('Menu')
-  #     menu.click()
-  #     menu.something_else()
-  #     menu.click()
-  #
-  # i.e. the object referenced by `menu` is never modified by method
-  # calls and can be used as expected.
-
-  class Application
-    @@node_counter ||= 0
-
-    def initialize(app_name, opts = {})
-      @var = "node#{@@node_counter += 1}"
-      @app_name = app_name
-      @opts = opts
-      @opts[:user] ||= LIVE_USER
-      @find_code = "dogtail.tree.root.application('#{@app_name}')"
-      script_lines = [
-        "import dogtail.config",
-        "import dogtail.tree",
-        "import dogtail.predicate",
-        "dogtail.config.logDebugToFile = False",
-        "dogtail.config.logDebugToStdOut = False",
-        "dogtail.config.blinkOnActions = True",
-        "dogtail.config.searchShowingOnly = True",
-        "#{@var} = #{@find_code}",
-      ]
-      run(script_lines)
-    end
-
-    def to_s
-      @var
-    end
-
-    def run(code)
-      code = code.join("\n") if code.class == Array
-      c = RemoteShell::PythonCommand.new($vm, code, user: @opts[:user])
-      if c.failure?
-        raise RuntimeError.new("The Dogtail script raised: #{c.exception}")
-      end
-      return c
-    end
-
-    def child?(*args)
-      !!child(*args)
-    rescue
-      false
-    end
-
-    def exist?
-      run("dogtail.config.searchCutoffCount = 0")
-      run(@find_code)
-      return true
-    rescue
-      return false
-    ensure
-      run("dogtail.config.searchCutoffCount = 20")
-    end
-
-    def self.value_to_s(v)
-      if v == true
-        'True'
-      elsif v == false
-        'False'
-      elsif v.class == String
-        "'#{v}'"
-      elsif [Fixnum, Float].include?(v.class)
-        v.to_s
-      else
-        raise "#{self.class.name} does not know how to handle argument type '#{v.class}'"
-      end
-    end
-
-    # Generates a Python-style parameter list from `args`. If the last
-    # element of `args` is a Hash, it's used as Python's kwargs dict.
-    # In the end, the resulting string should be possible to copy-paste
-    # into the parentheses of a Python function call.
-    # Example: [42, {:foo => 'bar'}] => "42, foo = 'bar'"
-    def self.args_to_s(args)
-      return "" if args.size == 0
-      args_list = args
-      args_hash = nil
-      if args_list.class == Array && args_list.last.class == Hash
-        *args_list, args_hash = args_list
-      end
-      (
-        (args_list.nil? ? [] : args_list.map { |e| self.value_to_s(e) }) +
-        (args_hash.nil? ? [] : args_hash.map { |k, v| "#{k}=#{self.value_to_s(v)}" })
-      ).join(', ')
-    end
-
-    # Equivalent to the Tree API's Node.findChildren(), with the
-    # arguments constructing a GenericPredicate to use as parameter.
-    def children(*args)
-      non_predicates = [:recursive, :showingOnly]
-      findChildren_opts = []
-      findChildren_opts_hash = Hash.new
-      if args.last.class == Hash
-        args_hash = args.last
-        non_predicates.each do |opt|
-          if args_hash.has_key?(opt)
-            findChildren_opts_hash[opt] = args_hash[opt]
-            args_hash.delete(opt)
-          end
-        end
-      end
-      findChildren_opts = ""
-      if findChildren_opts_hash.size > 0
-        findChildren_opts = ", " + self.class.args_to_s([findChildren_opts_hash])
-      end
-      predicate_opts = self.class.args_to_s(args)
-      nodes_var = "nodes#{@@node_counter += 1}"
-      find_script_lines = [
-        "#{nodes_var} = #{@var}.findChildren(dogtail.predicate.GenericPredicate(#{predicate_opts})#{findChildren_opts})",
-        "print(len(#{nodes_var}))",
-      ]
-      size = run(find_script_lines).stdout.chomp.to_i
-      return size.times.map do |i|
-        Node.new("#{nodes_var}[#{i}]", @opts)
-      end
-    end
-
-    def get_field(key)
-      run("print(#{@var}.#{key})").stdout.chomp
-    end
-
-    def set_field(key, value)
-      run("#{@var}.#{key} = #{self.class.value_to_s(value)}")
-    end
-
-    def text
-      get_field('text')
-    end
-
-    def text=(value)
-      set_field('text', value)
-    end
-
-    def name
-      get_field('name')
-    end
-
-    def roleName
-      get_field('roleName')
-    end
-
-    TREE_API_APP_SEARCHES.each do |method|
-      define_method(method) do |*args|
-        args_str = self.class.args_to_s(args)
-        method_call = "#{method.to_s}(#{args_str})"
-        Node.new("#{@var}.#{method_call}", @opts)
-      end
-    end
-
-    TREE_API_NODE_SEARCH_FIELDS.each do |field|
-      define_method(field) do
-        Node.new("#{@var}.#{field}", @opts)
-      end
-    end
-
-  end
-
-  class Node < Application
-
-    def initialize(expr, opts = {})
-      @expr = expr
-      @opts = opts
-      @opts[:user] ||= LIVE_USER
-      @find_code = expr
-      @var = "node#{@@node_counter += 1}"
-      run("#{@var} = #{@find_code}")
-    end
-
-    TREE_API_NODE_SEARCHES.each do |method|
-      define_method(method) do |*args|
-        args_str = self.class.args_to_s(args)
-        method_call = "#{method.to_s}(#{args_str})"
-        Node.new("#{@var}.#{method_call}", @opts)
-      end
-    end
-
-    TREE_API_NODE_ACTIONS.each do |method|
-      define_method(method) do |*args|
-        args_str = self.class.args_to_s(args)
-        method_call = "#{method.to_s}(#{args_str})"
-        run("#{@var}.#{method_call}")
-      end
-    end
-
-  end
-end
diff --git a/cucumber/features/support/helpers/firewall_helper.rb b/cucumber/features/support/helpers/firewall_helper.rb
deleted file mode 100644
index f88091d..0000000
--- a/cucumber/features/support/helpers/firewall_helper.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'packetfu'
-
-def looks_like_dhcp_packet?(eth_packet, protocol, sport, dport, ip_packet)
-  protocol == "udp" && sport == 68 && dport == 67 && 
-    eth_packet.eth_daddr == "ff:ff:ff:ff:ff:ff" &&
-    ip_packet && ip_packet.ip_daddr == "255.255.255.255"
-end
-
-# Returns the unique edges (based on protocol, source/destination
-# address/port) in the graph of all network flows.
-def pcap_connections_helper(pcap_file, opts = {})
-  opts[:ignore_dhcp] = true unless opts.has_key?(:ignore_dhcp)
-  connections = Array.new
-  packets = PacketFu::PcapFile.new.file_to_array(:filename => pcap_file)
-  packets.each do |p|
-    if PacketFu::EthPacket.can_parse?(p)
-      eth_packet = PacketFu::EthPacket.parse(p)
-    else
-      raise 'Found something that is not an ethernet packet'
-    end
-    sport = nil
-    dport = nil
-    if PacketFu::IPv6Packet.can_parse?(p)
-      ip_packet = PacketFu::IPv6Packet.parse(p)
-      protocol = 'ipv6'
-    elsif PacketFu::TCPPacket.can_parse?(p)
-      ip_packet = PacketFu::TCPPacket.parse(p)
-      protocol = 'tcp'
-      sport = ip_packet.tcp_sport
-      dport = ip_packet.tcp_dport
-    elsif PacketFu::UDPPacket.can_parse?(p)
-      ip_packet = PacketFu::UDPPacket.parse(p)
-      protocol = 'udp'
-      sport = ip_packet.udp_sport
-      dport = ip_packet.udp_dport
-    elsif PacketFu::ICMPPacket.can_parse?(p)
-      ip_packet = PacketFu::ICMPPacket.parse(p)
-      protocol = 'icmp'
-    elsif PacketFu::IPPacket.can_parse?(p)
-      ip_packet = PacketFu::IPPacket.parse(p)
-      protocol = 'ip'
-    else
-      raise "Found something that cannot be parsed"
-    end
-
-    next if opts[:ignore_dhcp] &&
-            looks_like_dhcp_packet?(eth_packet, protocol,
-                                    sport, dport, ip_packet)
-
-    packet_info = {
-      mac_saddr: eth_packet.eth_saddr,
-      mac_daddr: eth_packet.eth_daddr,
-      protocol: protocol,
-      sport: sport,
-      dport: dport,
-    }
-
-    begin
-      packet_info[:saddr] = ip_packet.ip_saddr
-      packet_info[:daddr] = ip_packet.ip_daddr
-    rescue NoMethodError, NameError
-      begin
-        packet_info[:saddr] = ip_packet.ipv6_saddr
-        packet_info[:daddr] = ip_packet.ipv6_daddr
-      rescue NoMethodError, NameError
-        puts "We were hit by #11508. PacketFu bug? Packet info: #{ip_packet}"
-        packet_info[:saddr] = nil
-        packet_info[:daddr] = nil
-      end
-    end
-    connections << packet_info
-  end
-  connections.uniq.map { |p| OpenStruct.new(p) }
-end
-
-class FirewallAssertionFailedError < Test::Unit::AssertionFailedError
-end
-
-# These assertions are made from the perspective of the system under
-# testing when it comes to the concepts of "source" and "destination".
-def assert_all_connections(pcap_file, opts = {}, &block)
-  all = pcap_connections_helper(pcap_file, opts)
-  good = all.find_all(&block)
-  bad = all - good
-  unless bad.empty?
-    raise FirewallAssertionFailedError.new(
-            "Unexpected connections were made:\n" +
-            bad.map { |e| "  #{e}" } .join("\n"))
-  end
-end
-
-def assert_no_connections(pcap_file, opts = {}, &block)
-  assert_all_connections(pcap_file, opts) { |*args| not(block.call(*args)) }
-end
diff --git a/cucumber/features/support/helpers/misc_helpers.rb b/cucumber/features/support/helpers/misc_helpers.rb
deleted file mode 100644
index 865d297..0000000
--- a/cucumber/features/support/helpers/misc_helpers.rb
+++ /dev/null
@@ -1,344 +0,0 @@
-require 'date'
-require 'io/console'
-require 'pry'
-require 'timeout'
-require 'test/unit'
-
-# Test::Unit adds an at_exit hook which, among other things, consumes
-# the command-line arguments that were intended for cucumber. If
-# e.g. `--format` was passed it will throw an error since it's not a
-# valid option for Test::Unit, and it throwing an error at this time
-# (at_exit) will make Cucumber think it failed and consequently exit
-# with an error. Fooling Test::Unit that this hook has already run
-# works around this craziness.
-Test::Unit.run = true
-
-# Make all the assert_* methods easily accessible in any context.
-include Test::Unit::Assertions
-
-def assert_vmcommand_success(p, msg = nil)
-  assert(p.success?, msg.nil? ? "Command failed: #{p.cmd}\n" + \
-                                "error code: #{p.returncode}\n" \
-                                "stderr: #{p.stderr}" : \
-                                msg)
-end
-
-# It's forbidden to throw this exception (or subclasses) in anything
-# but try_for() below. Just don't use it anywhere else!
-class UniqueTryForTimeoutError < Exception
-end
-
-# Call block (ignoring any exceptions it may throw) repeatedly with
-# one second breaks until it returns true, or until `timeout` seconds have
-# passed when we throw a Timeout::Error exception. If `timeout` is `nil`,
-# then we just run the code block with no timeout.
-def try_for(timeout, options = {})
-  if block_given? && timeout.nil?
-    return yield
-  end
-  options[:delay] ||= 1
-  last_exception = nil
-  # Create a unique exception used only for this particular try_for
-  # call's Timeout to allow nested try_for:s. If we used the same one,
-  # the innermost try_for would catch all outer ones', creating a
-  # really strange situation.
-  unique_timeout_exception = Class.new(UniqueTryForTimeoutError)
-  Timeout::timeout(timeout, unique_timeout_exception) do
-    loop do
-      begin
-        return if yield
-      rescue NameError, UniqueTryForTimeoutError => e
-        # NameError most likely means typos, and hiding that is rarely
-        # (never?) a good idea, so we rethrow them. See below why we
-        # also rethrow *all* the unique exceptions.
-        raise e
-      rescue Exception => e
-        # All other exceptions are ignored while trying the
-        # block. Well we save the last exception so we can print it in
-        # case of a timeout.
-        last_exception = e
-      end
-      sleep options[:delay]
-    end
-  end
-  # At this point the block above either succeeded and we'll return,
-  # or we are throwing an exception. If the latter, we either have a
-  # NameError that we'll not catch (and will any try_for below us in
-  # the stack), or we have a unique exception. That can mean one of
-  # two things:
-  # 1. it's the one unique to this try_for, and in that case we'll
-  #    catch it, rethrowing it as something that will be ignored by
-  #    inside the blocks of all try_for:s below us in the stack.
-  # 2. it's an exception unique to another try_for. Assuming that we
-  #    do not throw the unique exceptions in any other place or way
-  #    than we do it in this function, this means that there is a
-  #    try_for below us in the stack to which this exception must be
-  #    unique to.
-  # Let 1 be the base step, and 2 the inductive step, and we sort of
-  # an inductive proof for the correctness of try_for when it's
-  # nested. It shows that for an infinite stack of try_for:s, any of
-  # the unique exceptions will be caught only by the try_for instance
-  # it is unique to, and all try_for:s in between will ignore it so it
-  # ends up there immediately.
-rescue unique_timeout_exception => e
-  msg = options[:msg] || 'try_for() timeout expired'
-  exc_class = options[:exception] || Timeout::Error
-  if last_exception
-    msg += "\nLast ignored exception was: " +
-           "#{last_exception.class}: #{last_exception}"
-  end
-  raise exc_class.new(msg)
-end
-
-class TorFailure < StandardError
-end
-
-class MaxRetriesFailure < StandardError
-end
-
-def force_new_tor_circuit()
-  debug_log("Forcing new Tor circuit...")
-  # Tor rate limits NEWNYM to at most one per 10 second period.
-  interval = 10
-  if $__last_newnym
-    elapsed = Time.now - $__last_newnym
-    # We sleep an extra second to avoid tight timings.
-    sleep interval - elapsed + 1 if 0 < elapsed && elapsed < interval
-  end
-  $vm.execute_successfully('tor_control_send "signal NEWNYM"', :libs => 'tor')
-  $__last_newnym = Time.now
-end
-
-# This will retry the block up to MAX_NEW_TOR_CIRCUIT_RETRIES
-# times. The block must raise an exception for a run to be considered
-# as a failure. After a failure recovery_proc will be called (if
-# given) and the intention with it is to bring us back to the state
-# expected by the block, so it can be retried.
-def retry_tor(recovery_proc = nil, &block)
-  tor_recovery_proc = Proc.new do
-    force_new_tor_circuit
-    recovery_proc.call if recovery_proc
-  end
-
-  retry_action($config['MAX_NEW_TOR_CIRCUIT_RETRIES'],
-               :recovery_proc => tor_recovery_proc,
-               :operation_name => 'Tor operation', &block)
-end
-
-def retry_action(max_retries, options = {}, &block)
-  assert(max_retries.is_a?(Integer), "max_retries must be an integer")
-  options[:recovery_proc] ||= nil
-  options[:operation_name] ||= 'Operation'
-
-  retries = 1
-  loop do
-    begin
-      block.call
-      return
-    rescue NameError => e
-      # NameError most likely means typos, and hiding that is rarely
-      # (never?) a good idea, so we rethrow them.
-      raise e
-    rescue Exception => e
-      if retries <= max_retries
-        debug_log("#{options[:operation_name]} failed (Try #{retries} of " +
-                  "#{max_retries}) with:\n" +
-                  "#{e.class}: #{e.message}")
-        options[:recovery_proc].call if options[:recovery_proc]
-        retries += 1
-      else
-        raise MaxRetriesFailure.new("#{options[:operation_name]} failed (despite retrying " +
-                                    "#{max_retries} times) with\n" +
-                                    "#{e.class}: #{e.message}")
-      end
-    end
-  end
-end
-
-alias :retry_times :retry_action
-
-class TorBootstrapFailure < StandardError
-end
-
-def wait_until_tor_is_working
-  try_for(270) { $vm.execute('/usr/local/sbin/tor-has-bootstrapped').success? }
-rescue Timeout::Error
-  raise TorBootstrapFailure.new('Tor failed to bootstrap')
-end
-
-def convert_bytes_mod(unit)
-  case unit
-  when "bytes", "b" then mod = 1
-  when "KB"         then mod = 10**3
-  when "k", "KiB"   then mod = 2**10
-  when "MB"         then mod = 10**6
-  when "M", "MiB"   then mod = 2**20
-  when "GB"         then mod = 10**9
-  when "G", "GiB"   then mod = 2**30
-  when "TB"         then mod = 10**12
-  when "T", "TiB"   then mod = 2**40
-  else
-    raise "invalid memory unit '#{unit}'"
-  end
-  return mod
-end
-
-def convert_to_bytes(size, unit)
-  return (size*convert_bytes_mod(unit)).to_i
-end
-
-def convert_to_MiB(size, unit)
-  return (size*convert_bytes_mod(unit) / (2**20)).to_i
-end
-
-def convert_from_bytes(size, unit)
-  return size.to_f/convert_bytes_mod(unit).to_f
-end
-
-def cmd_helper(cmd, env = {})
-  if cmd.instance_of?(Array)
-    cmd << {:err => [:child, :out]}
-  elsif cmd.instance_of?(String)
-    cmd += " 2>&1"
-  end
-  env = ENV.to_h.merge(env)
-  IO.popen(env, cmd) do |p|
-    out = p.readlines.join("\n")
-    p.close
-    ret = $?
-    assert_equal(0, ret, "Command failed (returned #{ret}): #{cmd}:\n#{out}")
-    return out
-  end
-end
-
-def all_tor_hosts
-  nodes = Array.new
-  chutney_torrcs = Dir.glob(
-    "#{$config['TMPDIR']}/chutney-data/nodes/*/torrc"
-  )
-  chutney_torrcs.each do |torrc|
-    open(torrc) do |f|
-      nodes += f.grep(/^(Or|Dir)Port\b/).map do |line|
-        { address: $vmnet.bridge_ip_addr, port: line.split.last.to_i }
-      end
-    end
-  end
-  return nodes
-end
-
-def allowed_hosts_under_tor_enforcement
-  all_tor_hosts + @lan_hosts
-end
-
-def get_free_space(machine, path)
-  case machine
-  when 'host'
-    assert(File.exists?(path), "Path '#{path}' not found on #{machine}.")
-    free = cmd_helper(["df", path])
-  when 'guest'
-    assert($vm.file_exist?(path), "Path '#{path}' not found on #{machine}.")
-    free = $vm.execute_successfully("df '#{path}'")
-  else
-    raise 'Unsupported machine type #{machine} passed.'
-  end
-  output = free.split("\n").last
-  return output.match(/[^\s]\s+[0-9]+\s+[0-9]+\s+([0-9]+)\s+.*/)[1].chomp.to_i
-end
-
-def random_string_from_set(set, min_len, max_len)
-  len = (min_len..max_len).to_a.sample
-  len ||= min_len
-  (0..len-1).map { |n| set.sample }.join
-end
-
-def random_alpha_string(min_len, max_len = 0)
-  alpha_set = ('A'..'Z').to_a + ('a'..'z').to_a
-  random_string_from_set(alpha_set, min_len, max_len)
-end
-
-def random_alnum_string(min_len, max_len = 0)
-  alnum_set = ('A'..'Z').to_a + ('a'..'z').to_a + (0..9).to_a.map { |n| n.to_s }
-  random_string_from_set(alnum_set, min_len, max_len)
-end
-
-# Sanitize the filename from unix-hostile filename characters
-def sanitize_filename(filename, options = {})
-  options[:replacement] ||= '_'
-  bad_unix_filename_chars = Regexp.new("[^A-Za-z0-9_\\-.,+:]")
-  filename.gsub(bad_unix_filename_chars, options[:replacement])
-end
-
-def info_log_artifact_location(type, path)
-  if $config['ARTIFACTS_BASE_URI']
-    # Remove any trailing slashes, we'll add one ourselves
-    base_url = $config['ARTIFACTS_BASE_URI'].gsub(/\/*$/, "")
-    path = "#{base_url}/#{File.basename(path)}"
-  end
-  info_log("#{type.capitalize}: #{path}")
-end
-
-def notify_user(message)
-  alarm_script = $config['NOTIFY_USER_COMMAND']
-  return if alarm_script.nil? || alarm_script.empty?
-  cmd_helper(alarm_script.gsub('%m', message))
-end
-
-def pause(message = "Paused")
-  notify_user(message)
-  STDERR.puts
-  STDERR.puts message
-  # Ring the ASCII bell for a helpful notification in most terminal
-  # emulators.
-  STDOUT.write "\a"
-  STDERR.puts
-  loop do
-    STDERR.puts "Return: Continue; d: Debugging REPL"
-    c = STDIN.getch
-    case c
-    when "\r"
-      return
-    when "d"
-      binding.pry(quiet: true)
-    end
-  end
-end
-
-def dbus_send(service, object_path, method, *args, **opts)
-  opts ||= {}
-  ruby_type_to_dbus_type = {
-    String => 'string',
-    Fixnum => 'int32',
-  }
-  typed_args = args.map do |arg|
-    type = ruby_type_to_dbus_type[arg.class]
-    assert_not_nil(type, "No DBus type conversion for Ruby type '#{arg.class}'")
-    "#{type}:#{arg}"
-  end
-  ret = $vm.execute_successfully(
-    "dbus-send --print-reply --dest=#{service} #{object_path} " +
-    "    #{method} #{typed_args.join(' ')}",
-    **opts
-  ).stdout.lines
-  # The first line written is about timings and other stuff we don't
-  # care about; we only care about the return values.
-  ret.shift
-  ret.map! do |s|
-    type, val = /^\s*(\S+)\s+(\S+)$/.match(s)[1,2]
-    case type
-    when 'string'
-      # Unquote
-      val[1, val.length - 2]
-    when 'int32'
-      val.to_i
-    else
-      raise "No Ruby type conversion for DBus type '#{type}'"
-    end
-  end
-  if ret.size == 0
-    return nil
-  elsif ret.size == 1
-    return ret.first
-  else
-    return ret
-  end
-end
diff --git a/cucumber/features/support/helpers/remote_shell.rb b/cucumber/features/support/helpers/remote_shell.rb
deleted file mode 100644
index b890578..0000000
--- a/cucumber/features/support/helpers/remote_shell.rb
+++ /dev/null
@@ -1,171 +0,0 @@
-require 'base64'
-require 'json'
-require 'socket'
-require 'timeout'
-
-module RemoteShell
-  class ServerFailure < StandardError
-  end
-
-  # Used to differentiate vs Timeout::Error, which is thrown by
-  # try_for() (by default) and often wraps around remote shell usage
-  # -- in that case we don't want to catch that "outer" exception in
-  # our handling of remote shell timeouts below.
-  class Timeout < ServerFailure
-  end
-
-  DEFAULT_TIMEOUT = 20*60
-
-  # Counter providing unique id:s for each communicate() call.
-  @@request_id ||= 0
-
-  def communicate(vm, *args, **opts)
-    opts[:timeout] ||= DEFAULT_TIMEOUT
-    socket = TCPSocket.new("127.0.0.1", vm.get_remote_shell_port)
-    id = (@@request_id += 1)
-    # Since we already have defined our own Timeout in the current
-    # scope, we have to be more careful when referring to the Timeout
-    # class from the 'timeout' module. However, note that we want it
-    # to throw our own Timeout exception.
-    Object::Timeout.timeout(opts[:timeout], Timeout) do
-      socket.puts(JSON.dump([id] + args))
-      socket.flush
-      loop do
-        line = socket.readline("\n").chomp("\n")
-        response_id, status, *rest = JSON.load(line)
-        if response_id == id
-          if status != "success"
-            if status == "error" and rest.class == Array and rest.size == 1
-              msg = rest.first
-              raise ServerFailure.new("#{msg}")
-            else
-              raise ServerFailure.new("Uncaught exception: #{status}: #{rest}")
-            end
-          end
-          return rest
-        else
-          debug_log("Dropped out-of-order remote shell response: " +
-                    "got id #{response_id} but expected id #{id}")
-        end
-      end
-    end
-  ensure
-    socket.close if defined?(socket) && socket
-  end
-
-  module_function :communicate
-  private :communicate
-
-  class ShellCommand
-    # If `:spawn` is false the server will block until it has finished
-    # executing `cmd`. If it's true the server won't block, and the
-    # response will always be [0, "", ""] (only used as an
-    # ACK). execute() will always block until a response is received,
-    # though. Spawning is useful when starting processes in the
-    # background (or running scripts that does the same) or any
-    # application we want to interact with.
-    def self.execute(vm, cmd, **opts)
-      opts[:user] ||= "root"
-      opts[:spawn] = false unless opts.has_key?(:spawn)
-      type = opts[:spawn] ? "spawn" : "call"
-      debug_log("#{type}ing as #{opts[:user]}: #{cmd}")
-      ret = RemoteShell.communicate(vm, 'sh_' + type, opts[:user], cmd, **opts)
-      debug_log("#{type} returned: #{ret}") if not(opts[:spawn])
-      return ret
-    end
-
-    attr_reader :cmd, :returncode, :stdout, :stderr
-
-    def initialize(vm, cmd, **opts)
-      @cmd = cmd
-      @returncode, @stdout, @stderr = self.class.execute(vm, cmd, **opts)
-    end
-
-    def success?
-      return @returncode == 0
-    end
-
-    def failure?
-      return not(success?)
-    end
-
-    def to_s
-      "Return status: #{@returncode}\n" +
-        "STDOUT:\n" +
-        @stdout +
-        "STDERR:\n" +
-        @stderr
-    end
-  end
-
-  class PythonCommand
-    def self.execute(vm, code, **opts)
-      opts[:user] ||= "root"
-      show_code = code.chomp
-      if show_code["\n"]
-        show_code = "\n" + show_code.lines.map { |l| " "*4 + l.chomp } .join("\n")
-      end
-      debug_log("executing Python as #{opts[:user]}: #{show_code}")
-      ret = RemoteShell.communicate(
-        vm, 'python_execute', opts[:user], code, **opts
-      )
-      debug_log("execution complete")
-      return ret
-    end
-
-    attr_reader :code, :exception, :stdout, :stderr
-
-    def initialize(vm, code, **opts)
-      @code = code
-      @exception, @stdout, @stderr = self.class.execute(vm, code, **opts)
-    end
-
-    def success?
-      return @exception == nil
-    end
-
-    def failure?
-      return not(success?)
-    end
-
-    def to_s
-      "Exception: #{@exception}\n" +
-        "STDOUT:\n" +
-        @stdout +
-        "STDERR:\n" +
-        @stderr
-    end
-  end
-
-  # An IO-like object that is more or less equivalent to a File object
-  # opened in rw mode.
-  class File
-    def self.open(vm, mode, path, *args, **opts)
-      debug_log("opening file #{path} in '#{mode}' mode")
-      ret = RemoteShell.communicate(vm, 'file_' + mode, path, *args, **opts)
-      if ret.size != 1
-        raise ServerFailure.new("expected 1 value but got #{ret.size}")
-      end
-      debug_log("#{mode} complete")
-      return ret.first
-    end
-
-    attr_reader :vm, :path
-
-    def initialize(vm, path)
-      @vm, @path = vm, path
-    end
-
-    def read()
-      Base64.decode64(self.class.open(@vm, 'read', @path))
-    end
-
-    def write(data)
-      self.class.open(@vm, 'write', @path, Base64.encode64(data))
-    end
-
-    def append(data)
-      self.class.open(@vm, 'append', @path, Base64.encode64(data))
-    end
-  end
-end
diff --git a/cucumber/features/support/helpers/sikuli_helper.rb b/cucumber/features/support/helpers/sikuli_helper.rb
deleted file mode 100644
index 264a3ec..0000000
--- a/cucumber/features/support/helpers/sikuli_helper.rb
+++ /dev/null
@@ -1,238 +0,0 @@
-require 'rjb'
-require 'rjbextension'
-$LOAD_PATH << ENV['SIKULI_HOME']
-begin
-  require 'sikulixapi.jar'
-  USING_SIKULIX = true
-rescue LoadError
-  require 'sikuli-script.jar'
-  USING_SIKULIX = false
-end
-Rjb::load
-
-def using_sikulix?
-  USING_SIKULIX
-end
-
-package_members = [
-                   "java.io.FileOutputStream",
-                   "java.io.PrintStream",
-                   "java.lang.System",
-                   "org.sikuli.script.Finder",
-                   "org.sikuli.script.Key",
-                   "org.sikuli.script.KeyModifier",
-                   "org.sikuli.script.Location",
-                   "org.sikuli.script.Match",
-                   "org.sikuli.script.Pattern",
-                   "org.sikuli.script.Region",
-                   "org.sikuli.script.Screen",
-                  ]
-
-if using_sikulix?
-  package_members << "org.sikuli.basics.Settings"
-  package_members << "org.sikuli.script.ImagePath"
-else
-  package_members << "org.sikuli.script.Settings"
-end
-
-translations = Hash[
-                    "org.sikuli.script", "Sikuli",
-                    "org.sikuli.basics", "Sikuli",
-                    "java.lang", "Java::Lang",
-                    "java.io", "Java::Io",
-                   ]
-
-for p in package_members
-  imported_class = Rjb::import(p)
-  package, ignore, class_name = p.rpartition(".")
-  next if ! translations.include? package
-  mod_name = translations[package]
-  mod = mod_name.split("::").inject(Object) do |parent_obj, child_name|
-    if parent_obj.const_defined?(child_name, false)
-      parent_obj.const_get(child_name, false)
-    else
-      child_obj = Module.new
-      parent_obj.const_set(child_name, child_obj)
-    end
-  end
-  mod.const_set(class_name, imported_class)
-end
-
-# Bind Java's stdout to debug_log() via our magical pseudo fifo
-# logger.
-def bind_java_to_pseudo_fifo_logger
-  file_output_stream = Java::Io::FileOutputStream.new(DEBUG_LOG_PSEUDO_FIFO)
-  print_stream = Java::Io::PrintStream.new(file_output_stream)
-  Java::Lang::System.setOut(print_stream)
-end
-
-def findfailed_hook(pic)
-  pause("FindFailed for: '#{pic}'")
-end
-
-# Since rjb imports Java classes without creating a corresponding
-# Ruby class (it's just an instance of Rjb_JavaProxy) we can't
-# monkey patch any class, so additional methods must be added
-# to each Screen object.
-#
-# All Java classes' methods are immediately available in the proxied
-# Ruby classes, but care has to be given to match their type. For a
-# list of methods, see: <http://doc.sikuli.org/javadoc/index.html>.
-# The type "PRSML" is a union of Pattern, Region, Screen, Match and
-# Location.
-#
-# Also, due to limitations in Ruby's syntax we can't do:
-#     def Sikuli::Screen.new
-# so we work around it with the following vairable.
-sikuli_script_proxy = Sikuli::Screen
-$_original_sikuli_screen_new ||= Sikuli::Screen.method :new
-
-# For waitAny()/findAny() we are forced to throw this exception since
-# Rjb::throw doesn't block until the Java exception has been received
-# by Ruby, so strange things can happen.
-class FindAnyFailed < StandardError
-end
-
-def sikuli_script_proxy.new(*args)
-  s = $_original_sikuli_screen_new.call(*args)
-
-  if $config["SIKULI_RETRY_FINDFAILED"]
-    # The usage of `_invoke()` below exemplifies how one can wrap
-    # around Java objects' methods when they're imported using RJB. It
-    # isn't pretty. The seconds argument is the parameter signature,
-    # which can be obtained by creating the intended Java object using
-    # RJB, and then calling its `java_methods` method.
-
-    def s.wait(pic, time)
-      self._invoke('wait', 'Ljava.lang.Object;D', pic, time)
-    rescue FindFailed => e
-      findfailed_hook(pic)
-      self._invoke('wait', 'Ljava.lang.Object;D', pic, time)
-    end
-
-    def s.find(pic)
-      self._invoke('find', 'Ljava.lang.Object;', pic)
-    rescue FindFailed => e
-      findfailed_hook(pic)
-      self._invoke('find', 'Ljava.lang.Object;', pic)
-    end
-
-    def s.waitVanish(pic, time)
-      self._invoke('waitVanish', 'Ljava.lang.Object;D', pic, time)
-    rescue FindFailed => e
-      findfailed_hook(pic)
-      self._invoke('waitVanish', 'Ljava.lang.Object;D', pic, time)
-    end
-
-    def s.click(pic)
-      self._invoke('click', 'Ljava.lang.Object;', pic)
-    rescue FindFailed => e
-      findfailed_hook(pic)
-      self._invoke('click', 'Ljava.lang.Object;', pic)
-    end
-  end
-
-  def s.click_point(x, y)
-    self.click(Sikuli::Location.new(x, y))
-  end
-
-  def s.doubleClick_point(x, y)
-    self.doubleClick(Sikuli::Location.new(x, y))
-  end
-
-  def s.click_mid_right_edge(pic)
-    r = self.find(pic)
-    top_right = r.getTopRight()
-    x = top_right.getX
-    y = top_right.getY + r.getH/2
-    self.click_point(x, y)
-  end
-
-  def s.wait_and_click(pic, time)
-    self.click(self.wait(pic, time))
-  end
-
-  def s.wait_and_double_click(pic, time)
-    self.doubleClick(self.wait(pic, time))
-  end
-
-  def s.wait_and_right_click(pic, time)
-    self.rightClick(self.wait(pic, time))
-  end
-
-  def s.wait_and_hover(pic, time)
-    self.hover(self.wait(pic, time))
-  end
-
-  def s.existsAny(images)
-    images.each do |image|
-      region = self.exists(image)
-      return [image, region] if region
-    end
-    return nil
-  end
-
-  def s.findAny(images)
-    images.each do |image|
-      begin
-        return [image, self.find(image)]
-      rescue FindFailed
-        # Ignore. We deal we'll throw an appropriate exception after
-        # having looped through all images and found none of them.
-      end
-    end
-    # If we've reached this point, none of the images could be found.
-    raise FindAnyFailed.new("can not find any of the images #{images} on the " +
-                            "screen")
-  end
-
-  def s.waitAny(images, time)
-    Timeout::timeout(time) do
-      loop do
-        result = self.existsAny(images)
-        return result if result
-      end
-    end
-  rescue Timeout::Error
-    raise FindAnyFailed.new("can not find any of the images #{images} on the " +
-                            "screen")
-  end
-
-  def s.hover_point(x, y)
-    self.hover(Sikuli::Location.new(x, y))
-  end
-
-  def s.hide_cursor
-    self.hover_point(self.w - 1, self.h/2)
-  end
-
-  s
-end
-
-# Configure sikuli
-if using_sikulix?
-  Sikuli::ImagePath.add("#{Dir.pwd}/features/images/")
-else
-  java.lang.System.setProperty("SIKULI_IMAGE_PATH",
-                               "#{Dir.pwd}/features/images/")
-  ENV["SIKULI_IMAGE_PATH"] = "#{Dir.pwd}/features/images/"
-end
-
-# ruby and rjb doesn't play well together when it comes to static
-# fields (and possibly methods) so we instantiate and access the field
-# via objects instead. It actually works inside this file, but when
-# it's required from "outside", and the file has been completely
-# required, ruby's require method complains that the method for the
-# field accessor is missing.
-sikuli_settings = Sikuli::Settings.new
-sikuli_settings.OcrDataPath = $config["TMPDIR"]
-# sikuli_ruby, which we used before, defaulted to 0.9 minimum
-# similarity, so all our current images are adapted to that value.
-# Also, Sikuli's default of 0.7 is simply too low (many false
-# positives).
-sikuli_settings.MinSimilarity = 0.9
-sikuli_settings.ActionLogs = true
-sikuli_settings.DebugLogs = true
-sikuli_settings.InfoLogs = true
-sikuli_settings.ProfileLogs = false
-sikuli_settings.WaitScanRate = 0.25
diff --git a/cucumber/features/support/helpers/sniffing_helper.rb b/cucumber/features/support/helpers/sniffing_helper.rb
deleted file mode 100644
index 38b1382..0000000
--- a/cucumber/features/support/helpers/sniffing_helper.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Sniffer is a very dumb wrapper to start and stop tcpdumps instances, possibly
-# with customized filters. Captured traffic is stored in files whose name
-# depends on the sniffer name. The resulting captured packets for each sniffers
-# can be accessed as an array through its `packets` method.
-#
-# Use of more rubyish internal ways to sniff a network like with pcap-able gems
-# is waaay to much resource consumming, notmuch reliable and soooo slow. Let's
-# not bother too much with that. :)
-#
-# Should put all that in a Module.
-
-class Sniffer
-
-  attr_reader :name, :pcap_file, :pid
-
-  def initialize(name, vmnet)
-    @name = name
-    @vmnet = vmnet
-    pcap_name = sanitize_filename("#{name}.pcap")
-    @pcap_file = "#{$config["TMPDIR"]}/#{pcap_name}"
-  end
-
-  def capture(filter="not ether src host #{@vmnet.bridge_mac} and not ether proto \\arp and not ether proto \\rarp")
-    job = IO.popen(
-      [
-        "/usr/sbin/tcpdump",
-        "-n",
-        "-U",
-        "--immediate-mode",
-        "-i", @vmnet.bridge_name,
-        "-w", @pcap_file,
-        filter,
-        :err => ["/dev/null", "w"]
-      ]
-    )
-    @pid = job.pid
-  end
-
-  def stop
-    begin
-      Process.kill("TERM", @pid)
-    rescue
-      # noop
-    end
-  end
-
-  def clear
-    if File.exist?(@pcap_file)
-      File.delete(@pcap_file)
-    end
-  end
-end
diff --git a/cucumber/features/support/helpers/sshd_helper.rb b/cucumber/features/support/helpers/sshd_helper.rb
deleted file mode 100644
index 2e0069c..0000000
--- a/cucumber/features/support/helpers/sshd_helper.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'tempfile'
-
-class SSHServer
-  def initialize(sshd_host, sshd_port, authorized_keys = nil)
-    @sshd_host = sshd_host
-    @sshd_port = sshd_port
-    @authorized_keys = authorized_keys
-    @pid = nil
-  end
-
-  def start
-    @sshd_key_file = Tempfile.new("ssh_host_rsa_key", $config["TMPDIR"])
-    # 'hack' to prevent ssh-keygen from prompting to overwrite the file
-    File.delete(@sshd_key_file.path)
-    cmd_helper(['ssh-keygen', '-t', 'rsa', '-N', "", '-f', "#{@sshd_key_file.path}"])
-    @sshd_key_file.close
-
-    sshd_config =<<EOF
-Port #{@sshd_port}
-ListenAddress #{@sshd_host}
-UsePrivilegeSeparation no
-HostKey #{@sshd_key_file.path}
-Pidfile #{$config['TMPDIR']}/ssh.pid
-EOF
-
-    @sshd_config_file = Tempfile.new("sshd_config", $config["TMPDIR"])
-    @sshd_config_file.write(sshd_config)
-
-    if @authorized_keys
-      @authorized_keys_file = Tempfile.new("authorized_keys", $config['TMPDIR'])
-      @authorized_keys_file.write(@authorized_keys)
-      @authorized_keys_file.close
-      @sshd_config_file.write("AuthorizedKeysFile #{@authorized_keys_file.path}")
-    end
-
-    @sshd_config_file.close
-
-    cmd = ["/usr/sbin/sshd", "-4", "-f", @sshd_config_file.path, "-D"]
-
-    job = IO.popen(cmd)
-    @pid = job.pid
-  end
-
-  def stop
-    File.delete("#{@sshd_key_file.path}.pub")
-    File.delete("#{$config['TMPDIR']}/ssh.pid")
-    begin
-      Process.kill("TERM", @pid)
-    rescue
-      # noop
-    end
-  end
-
-  def active?
-    begin
-      ret = Process.kill(0, @pid)
-    rescue Errno::ESRCH => e
-      if e.message == "No such process"
-        return false
-      else
-        raise e
-      end
-    end
-    assert_equal(1, ret, "This shouldn't happen")
-    return true
-  end
-end
diff --git a/cucumber/features/support/helpers/storage_helper.rb b/cucumber/features/support/helpers/storage_helper.rb
deleted file mode 100644
index 3bbdb69..0000000
--- a/cucumber/features/support/helpers/storage_helper.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-# Helper class for manipulating VM storage *volumes*, i.e. it deals
-# only with creation of images and keeps a name => volume path lookup
-# table (plugging drives or getting info of plugged devices is done in
-# the VM class). We'd like better coupling, but given the ridiculous
-# disconnect between Libvirt::StoragePool and Libvirt::Domain (hint:
-# they have nothing with each other to do whatsoever) it's what makes
-# sense.
-
-require 'libvirt'
-require 'guestfs'
-require 'rexml/document'
-require 'etc'
-
-class VMStorage
-
-  def initialize(virt, xml_path)
-    @virt = virt
-    @xml_path = xml_path
-    pool_xml = REXML::Document.new(File.read("#{@xml_path}/storage_pool.xml"))
-    pool_name = LIBVIRT_DOMAIN_NAME
-    pool_xml.elements['pool/name'].text = pool_name
-    @pool_path = "/srv/lvc/vm-pools/#{pool_name}"  # FIXME -- hardwiring the .../vm-pools path semms like a poor effort
-    begin
-      @pool = @virt.lookup_storage_pool_by_name(pool_name)
-    rescue Libvirt::RetrieveError
-      @pool = nil
-    end
-    if @pool and (not(KEEP_SNAPSHOTS) or
-                  (KEEP_SNAPSHOTS and not(Dir.exists?(@pool_path))))
-      VMStorage.clear_storage_pool(@pool)
-      @pool = nil
-    end
-    if not(Dir.exists?(@pool_path))
-      # We'd like to use @pool.build, which will just create the
-      # @pool_path directory, but it does so with root:root as owner
-      # (at least with libvirt 1.2.21-2). libvirt itself can handle
-      # that situation, but guestfs (at least with <=
-      # 1:1.28.12-1+b3) cannot when invoked by a non-root user,
-      # which we want to support.
-      FileUtils.mkdir(@pool_path)
-      FileUtils.chown(nil, 'libvirt-qemu', @pool_path)
-      FileUtils.chmod("ug+wrx", @pool_path)
-    end
-    unless @pool
-      pool_xml.elements['pool/target/path'].text = @pool_path
-      @pool = @virt.define_storage_pool_xml(pool_xml.to_s)
-    end
-    @pool.create unless @pool.active?
-    @pool.refresh
-  end
-
-  def VMStorage.clear_storage_pool_volumes(pool)
-    was_not_active = !pool.active?
-    if was_not_active
-      pool.create
-    end
-    pool.list_volumes.each do |vol_name|
-      vol = pool.lookup_volume_by_name(vol_name)
-      vol.delete
-    end
-    if was_not_active
-      pool.destroy
-    end
-  rescue
-    # Some of the above operations can fail if the pool's path was
-    # deleted by external means; let's ignore that.
-  end
-
-  def VMStorage.clear_storage_pool(pool)
-    VMStorage.clear_storage_pool_volumes(pool)
-    pool.destroy if pool.active?
-    pool.undefine
-  end
-
-  def clear_pool
-    VMStorage.clear_storage_pool(@pool)
-  end
-
-  def clear_volumes
-    VMStorage.clear_storage_pool_volumes(@pool)
-  end
-
-  def list_volumes
-    @pool.list_volumes
-  end
-
-  def delete_volume(name)
-    @pool.lookup_volume_by_name(name).delete
-  end
-
-  def create_new_disk(name, options = {})
-    options[:size] ||= 2
-    options[:unit] ||= "GiB"
-    options[:type] ||= "qcow2"
-    # Require 'slightly' more space to be available to give a bit more leeway
-    # with rounding, temp file creation, etc.
-    reserved = 500
-    needed = convert_to_MiB(options[:size].to_i, options[:unit])
-    avail = convert_to_MiB(get_free_space('host', @pool_path), "KiB")
-    assert(avail - reserved >= needed,
-           "Error creating disk \"#{name}\" in \"#{@pool_path}\". " \
-           "Need #{needed} MiB but only #{avail} MiB is available of " \
-           "which #{reserved} MiB is reserved for other temporary files.")
-    begin
-      old_vol = @pool.lookup_volume_by_name(name)
-    rescue Libvirt::RetrieveError
-      # noop
-    else
-      old_vol.delete
-    end
-    uid = Etc::getpwnam("libvirt-qemu").uid
-    gid = Etc::getgrnam("libvirt-qemu").gid
-    vol_xml = REXML::Document.new(File.read("#{@xml_path}/volume.xml"))
-    vol_xml.elements['volume/name'].text = name
-    size_b = convert_to_bytes(options[:size].to_f, options[:unit])
-    vol_xml.elements['volume/capacity'].text = size_b.to_s
-    vol_xml.elements['volume/target/format'].attributes["type"] = options[:type]
-    vol_xml.elements['volume/target/path'].text = "#{@pool_path}/#{name}"
-    vol_xml.elements['volume/target/permissions/owner'].text = uid.to_s
-    vol_xml.elements['volume/target/permissions/group'].text = gid.to_s
-    vol = @pool.create_volume_xml(vol_xml.to_s)
-    @pool.refresh
-  end
-
-  def clone_to_new_disk(from, to)
-    begin
-      old_to_vol = @pool.lookup_volume_by_name(to)
-    rescue Libvirt::RetrieveError
-      # noop
-    else
-      old_to_vol.delete
-    end
-    from_vol = @pool.lookup_volume_by_name(from)
-    xml = REXML::Document.new(from_vol.xml_desc)
-    pool_path = REXML::Document.new(@pool.xml_desc).elements['pool/target/path'].text
-    xml.elements['volume/name'].text = to
-    xml.elements['volume/target/path'].text = "#{pool_path}/#{to}"
-    @pool.create_volume_xml_from(xml.to_s, from_vol)
-  end
-
-  def disk_format(name)
-    vol = @pool.lookup_volume_by_name(name)
-    vol_xml = REXML::Document.new(vol.xml_desc)
-    return vol_xml.elements['volume/target/format'].attributes["type"]
-  end
-
-  def disk_path(name)
-    @pool.lookup_volume_by_name(name).path
-  end
-
-  def disk_mklabel(name, parttype)
-    guestfs_disk_helper(name) do |g, disk_handle|
-      g.part_init(disk_handle, parttype)
-    end
-  end
-
-  def disk_mkpartfs(name, parttype, fstype, opts = {})
-    opts[:label] ||= nil
-    opts[:luks_password] ||= nil
-    guestfs_disk_helper(name) do |g, disk_handle|
-      g.part_disk(disk_handle, parttype)
-      g.part_set_name(disk_handle, 1, opts[:label]) if opts[:label]
-      primary_partition = g.list_partitions()[0]
-      if opts[:luks_password]
-        g.luks_format(primary_partition, opts[:luks_password], 0)
-        luks_mapping = File.basename(primary_partition) + "_unlocked"
-        g.luks_open(primary_partition, opts[:luks_password], luks_mapping)
-        luks_dev = "/dev/mapper/#{luks_mapping}"
-        g.mkfs(fstype, luks_dev)
-        g.luks_close(luks_dev)
-      else
-        g.mkfs(fstype, primary_partition)
-      end
-    end
-  end
-
-  def disk_mkswap(name, parttype)
-    guestfs_disk_helper(name) do |g, disk_handle|
-      g.part_disk(disk_handle, parttype)
-      primary_partition = g.list_partitions()[0]
-      g.mkswap(primary_partition)
-    end
-  end
-
-  def guestfs_disk_helper(*disks)
-    assert(block_given?)
-    g = Guestfs::Guestfs.new()
-    g.set_trace(1)
-    message_callback = Proc.new do |event, _, message, _|
-      debug_log("libguestfs: #{Guestfs.event_to_string(event)}: #{message}")
-    end
-    g.set_event_callback(message_callback,
-                         Guestfs::EVENT_TRACE)
-    g.set_autosync(1)
-    disks.each do |disk|
-      if disk.class == String
-        g.add_drive_opts(disk_path(disk), format: disk_format(disk))
-      elsif disk.class == Hash
-        g.add_drive_opts(disk[:path], disk[:opts])
-      else
-        raise "cannot handle type '#{disk.class}'"
-      end
-    end
-    g.launch()
-    yield(g, *g.list_devices())
-  ensure
-    g.close
-  end
-
-end
diff --git a/cucumber/features/support/helpers/vm_helper.rb b/cucumber/features/support/helpers/vm_helper.rb
deleted file mode 100644
index 5722772..0000000
--- a/cucumber/features/support/helpers/vm_helper.rb
+++ /dev/null
@@ -1,703 +0,0 @@
-require 'ipaddr'
-require 'libvirt'
-require 'rexml/document'
-
-class ExecutionFailedInVM < StandardError
-end
-
-class VMNet
-
-  attr_reader :net_name, :net
-
-  def initialize(virt, xml_path)
-    @virt = virt
-    @net_name = LIBVIRT_NETWORK_NAME
-    net_xml = File.read("#{xml_path}/default_net.xml")
-    rexml = REXML::Document.new(net_xml)
-    rexml.elements['network'].add_element('name')
-    rexml.elements['network/name'].text = @net_name
-    begin
-      old_net = @virt.lookup_network_by_name(@net_name)
-      rexml.elements['network'].add_element('uuid')
-      rexml.elements['network/uuid'].text = old_net.uuid
-      old_net.undefine
-    rescue
-    end
-    update(rexml.to_s)
-    update(rexml.to_s)
-  rescue Exception => e
-    destroy_and_undefine
-    raise e
-  end
-
-  # We lookup by name so we also catch networks from previous test
-  # suite runs that weren't properly cleaned up (e.g. aborted).
-  def destroy_and_undefine
-    begin
-      old_net = @virt.lookup_network_by_name(@net_name)
-      old_net.destroy if old_net.active?
-      old_net.undefine
-    rescue
-    end
-  end
-
-  def update(xml)
-    destroy_and_undefine
-    @net = @virt.define_network_xml(xml)
-    @net.create
-  end
-
-  def bridge_name
-    @net.bridge_name
-  end
-
-  def bridge_ip_addr
-    net_xml = REXML::Document.new(@net.xml_desc)
-    IPAddr.new(net_xml.elements['network/ip'].attributes['address']).to_s
-  end
-
-  def bridge_mac
-    File.open("/sys/class/net/#{bridge_name}/address", "rb").read.chomp
-  end
-end
-
-
-class VM
-
-  attr_reader :domain, :domain_name, :display, :vmnet, :storage
-
-  def initialize(virt, xml_path, vmnet, storage, x_display)
-    @virt = virt
-    @xml_path = xml_path
-    @vmnet = vmnet
-    @storage = storage
-    @domain_name = LIBVIRT_DOMAIN_NAME
-    default_domain_xml = File.read("#{@xml_path}/default.xml")
-    rexml = REXML::Document.new(default_domain_xml)
-    rexml.elements['domain'].add_element('name')
-    rexml.elements['domain/name'].text = @domain_name
-    begin
-      old_domain = @virt.lookup_domain_by_name(@domain_name)
-      rexml.elements['domain'].add_element('uuid')
-      rexml.elements['domain/uuid'].text = old_domain.uuid
-      old_domain.undefine
-    rescue
-    end
-    rexml.elements['domain/devices/serial/source'].attributes['service'] = LIBVIRT_REMOTE_SHELL_PORT
-    update(rexml.to_s)
-    @display = Display.new(@domain_name, x_display)
-    set_cdrom_boot(TAILS_ISO)
-    plug_network
-  rescue Exception => e
-    destroy_and_undefine
-    raise e
-  end
-
-  def update(xml)
-    destroy_and_undefine
-    @domain = @virt.define_domain_xml(xml)
-  end
-
-  # We lookup by name so we also catch domains from previous test
-  # suite runs that weren't properly cleaned up (e.g. aborted).
-  def destroy_and_undefine
-    @display.stop if @display && @display.active?
-    begin
-      old_domain = @virt.lookup_domain_by_name(@domain_name)
-      old_domain.destroy if old_domain.active?
-      old_domain.undefine
-    rescue
-    end
-  end
-
-  def real_mac(alias_name)
-    REXML::Document.new(@domain.xml_desc)
-      .elements["domain/devices/interface[@type='network']/" +
-                "alias[@name='#{alias_name}']"]
-      .parent.elements['mac'].attributes['address'].to_s
-  end
-
-  def all_real_macs
-    macs = []
-    REXML::Document.new(@domain.xml_desc)
-      .elements.each("domain/devices/interface[@type='network']") do |nic|
-      macs << nic.elements['mac'].attributes['address'].to_s
-    end
-    macs
-  end
-
-  def set_hardware_clock(time)
-    assert(not(is_running?), 'The hardware clock cannot be set when the ' +
-                             'VM is running')
-    assert(time.instance_of?(Time), "Argument must be of type 'Time'")
-    adjustment = (time - Time.now).to_i
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    clock_rexml_element = domain_rexml.elements['domain'].add_element('clock')
-    clock_rexml_element.add_attributes('offset' => 'variable',
-                                       'basis' => 'utc',
-                                       'adjustment' => adjustment.to_s)
-    update(domain_rexml.to_s)
-  end
-
-  def network_link_state
-    REXML::Document.new(@domain.xml_desc)
-      .elements['domain/devices/interface/link'].attributes['state']
-  end
-
-  def set_network_link_state(state)
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements['domain/devices/interface/link'].attributes['state'] = state
-    if is_running?
-      @domain.update_device(domain_xml.elements['domain/devices/interface'].to_s)
-    else
-      update(domain_xml.to_s)
-    end
-  end
-
-  def plug_network
-    set_network_link_state('up')
-  end
-
-  def unplug_network
-    set_network_link_state('down')
-  end
-
-  def set_boot_device(dev)
-    if is_running?
-      raise "boot settings can only be set for inactive vms"
-    end
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements['domain/os/boot'].attributes['dev'] = dev
-    update(domain_xml.to_s)
-  end
-
-  def add_cdrom_device
-    if is_running?
-      raise "Can't attach a CDROM device to a running domain"
-    end
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    if domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-      raise "A CDROM device already exists"
-    end
-    cdrom_rexml = REXML::Document.new(File.read("#{@xml_path}/cdrom.xml")).root
-    domain_rexml.elements['domain/devices'].add_element(cdrom_rexml)
-    update(domain_rexml.to_s)
-  end
-
-  def remove_cdrom_device
-    if is_running?
-      raise "Can't detach a CDROM device to a running domain"
-    end
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-    if cdrom_el.nil?
-      raise "No CDROM device is present"
-    end
-    domain_rexml.elements["domain/devices"].delete_element(cdrom_el)
-    update(domain_rexml.to_s)
-  end
-
-  def eject_cdrom
-    execute_successfully('/usr/bin/eject -m')
-  end
-
-  def remove_cdrom_image
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-    if cdrom_el.nil?
-      raise "No CDROM device is present"
-    end
-    cdrom_el.delete_element('source')
-    update(domain_rexml.to_s)
-  rescue Libvirt::Error => e
-    # While the CD-ROM is removed successfully we still get this
-    # error, so let's ignore it.
-    acceptable_error =
-      "Call to virDomainUpdateDeviceFlags failed: internal error: unable to " +
-      "execute QEMU command 'eject': (Tray of device '.*' is not open|" +
-      "Device '.*' is locked)"
-    raise e if not(Regexp.new(acceptable_error).match(e.to_s))
-  end
-
-  def set_cdrom_image(image)
-    if image.nil? or image == ''
-      raise "Can't set cdrom image to an empty string"
-    end
-    remove_cdrom_image
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-    cdrom_el.add_element('source', { 'file' => image })
-    update(domain_rexml.to_s)
-  end
-
-  def set_cdrom_boot(image)
-    if is_running?
-      raise "boot settings can only be set for inactive vms"
-    end
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    if not domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-      add_cdrom_device
-    end
-    set_cdrom_image(image)
-    set_boot_device('cdrom')
-  end
-
-  def list_disk_devs
-    ret = []
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/disk') do |e|
-      ret << e.elements['target'].attribute('dev').to_s
-    end
-    return ret
-  end
-
-  def plug_device(xml)
-    if is_running?
-      @domain.attach_device(xml.to_s)
-    else
-      domain_xml = REXML::Document.new(@domain.xml_desc)
-      domain_xml.elements['domain/devices'].add_element(xml)
-      update(domain_xml.to_s)
-    end
-  end
-
-  def plug_drive(name, type)
-    if disk_plugged?(name)
-      raise "disk '#{name}' already plugged"
-    end
-    removable_usb = nil
-    case type
-    when "removable usb", "usb"
-      type = "usb"
-      removable_usb = "on"
-    when "non-removable usb"
-      type = "usb"
-      removable_usb = "off"
-    end
-    # Get the next free /dev/sdX on guest
-    letter = 'a'
-    dev = "sd" + letter
-    while list_disk_devs.include?(dev)
-      letter = (letter[0].ord + 1).chr
-      dev = "sd" + letter
-    end
-    assert letter <= 'z'
-
-    xml = REXML::Document.new(File.read("#{@xml_path}/disk.xml"))
-    xml.elements['disk/source'].attributes['file'] = @storage.disk_path(name)
-    xml.elements['disk/driver'].attributes['type'] = @storage.disk_format(name)
-    xml.elements['disk/target'].attributes['dev'] = dev
-    xml.elements['disk/target'].attributes['bus'] = type
-    xml.elements['disk/target'].attributes['removable'] = removable_usb if removable_usb
-
-    plug_device(xml)
-  end
-
-  def disk_xml_desc(name)
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/disk') do |e|
-      begin
-        if e.elements['source'].attribute('file').to_s == @storage.disk_path(name)
-          return e.to_s
-        end
-      rescue
-        next
-      end
-    end
-    return nil
-  end
-
-  def disk_rexml_desc(name)
-    xml = disk_xml_desc(name)
-    if xml
-      return REXML::Document.new(xml)
-    else
-      return nil
-    end
-  end
-
-  def unplug_drive(name)
-    xml = disk_xml_desc(name)
-    @domain.detach_device(xml)
-  end
-
-  def disk_type(dev)
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/disk') do |e|
-      if e.elements['target'].attribute('dev').to_s == dev
-        return e.elements['driver'].attribute('type').to_s
-      end
-    end
-    raise "No such disk device '#{dev}'"
-  end
-
-  def disk_dev(name)
-    rexml = disk_rexml_desc(name) or return nil
-    return "/dev/" + rexml.elements['disk/target'].attribute('dev').to_s
-  end
-
-  def disk_name(dev)
-    dev = File.basename(dev)
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/disk') do |e|
-      if /^#{e.elements['target'].attribute('dev').to_s}/.match(dev)
-        return File.basename(e.elements['source'].attribute('file').to_s)
-      end
-    end
-    raise "No such disk device '#{dev}'"
-  end
-
-  def udisks_disk_dev(name)
-    return disk_dev(name).gsub('/dev/', '/org/freedesktop/UDisks/devices/')
-  end
-
-  def disk_detected?(name)
-    dev = disk_dev(name) or return false
-    return execute("test -b #{dev}").success?
-  end
-
-  def disk_plugged?(name)
-    return not(disk_xml_desc(name).nil?)
-  end
-
-  def set_disk_boot(name, type)
-    if is_running?
-      raise "boot settings can only be set for inactive vms"
-    end
-    plug_drive(name, type) if not(disk_plugged?(name))
-    set_boot_device('hd')
-    # XXX:Stretch: since our isotesters upgraded QEMU from
-    # 2.5+dfsg-4~bpo8+1 to 2.6+dfsg-3.1~bpo8+1 it seems we must remove
-    # the CDROM device to allow disk boot. This is not the case with the same
-    # version on Debian Sid. Let's hope we can remove this ugly
-    # workaround when we only support running the automated test suite
-    # on Stretch.
-    domain_rexml = REXML::Document.new(@domain.xml_desc)
-    if domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
-      remove_cdrom_device
-    end
-  end
-
-  # XXX-9p: Shares don't work together with snapshot save+restore. See
-  # XXX-9p in common_steps.rb for more information.
-  def add_share(source, tag)
-    if is_running?
-      raise "shares can only be added to inactive vms"
-    end
-    # The complete source directory must be group readable by the user
-    # running the virtual machine, and world readable so the user inside
-    # the VM can access it (since we use the passthrough security model).
-    FileUtils.chown_R(nil, "libvirt-qemu", source)
-    FileUtils.chmod_R("go+rX", source)
-    xml = REXML::Document.new(File.read("#{@xml_path}/fs_share.xml"))
-    xml.elements['filesystem/source'].attributes['dir'] = source
-    xml.elements['filesystem/target'].attributes['dir'] = tag
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements['domain/devices'].add_element(xml)
-    update(domain_xml.to_s)
-  end
-
-  def list_shares
-    list = []
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/filesystem') do |e|
-      list << e.elements['target'].attribute('dir').to_s
-    end
-    return list
-  end
-
-  def set_os_loader(type)
-    if is_running?
-      raise "boot settings can only be set for inactive vms"
-    end
-    if type == 'UEFI'
-      domain_xml = REXML::Document.new(@domain.xml_desc)
-      domain_xml.elements['domain/os'].add_element(REXML::Document.new(
-        '<loader>/usr/share/ovmf/OVMF.fd</loader>'
-      ))
-      update(domain_xml.to_s)
-    else
-      raise "unsupported OS loader type"
-    end
-  end
-
-  def is_running?
-    begin
-      return @domain.active?
-    rescue
-      return false
-    end
-  end
-
-  def execute(cmd, options = {})
-    options[:user] ||= "root"
-    options[:spawn] = false unless options.has_key?(:spawn)
-    if options[:libs]
-      libs = options[:libs]
-      options.delete(:libs)
-      libs = [libs] if not(libs.methods.include? :map)
-      cmds = libs.map do |lib_name|
-        ". /usr/local/lib/tails-shell-library/#{lib_name}.sh"
-      end
-      cmds << cmd
-      cmd = cmds.join(" && ")
-    end
-    return RemoteShell::ShellCommand.new(self, cmd, options)
-  end
-
-  def execute_successfully(*args)
-    p = execute(*args)
-    begin
-      assert_vmcommand_success(p)
-    rescue Test::Unit::AssertionFailedError => e
-      raise ExecutionFailedInVM.new(e)
-    end
-    return p
-  end
-
-  def spawn(cmd, options = {})
-    options[:spawn] = true
-    return execute(cmd, options)
-  end
-
-  def wait_until_remote_shell_is_up(timeout = 90)
-    msg = 'hello?'
-    try_for(timeout, :msg => "Remote shell seems to be down") do
-      Timeout::timeout(3) do
-        execute_successfully("echo '#{msg}'").stdout.chomp == msg
-      end
-    end
-  end
-
-  def host_to_guest_time_sync
-    host_time= DateTime.now.strftime("%s").to_s
-    execute("date -s '@#{host_time}'").success?
-  end
-
-  def has_network?
-    nmcli_info = execute('nmcli device show eth0').stdout
-    has_ipv4_addr = /^IP4.ADDRESS(\[\d+\])?:\s*([0-9.\/]+)$/.match(nmcli_info)
-    network_link_state == 'up' && has_ipv4_addr
-  end
-
-  def has_process?(process)
-    return execute("pidof -x -o '%PPID' " + process).success?
-  end
-
-  def pidof(process)
-    return execute("pidof -x -o '%PPID' " + process).stdout.chomp.split
-  end
-
-  def select_virtual_desktop(desktop_number, user = LIVE_USER)
-    assert(desktop_number >= 0 && desktop_number <=3,
-           "Only values between 0 and 1 are valid virtual desktop numbers")
-    execute_successfully(
-      "xdotool set_desktop '#{desktop_number}'",
-      :user => user
-    )
-  end
-
-  def focus_window(window_title, user = LIVE_USER)
-    def do_focus(window_title, user)
-      execute_successfully(
-        "xdotool search --name '#{window_title}' windowactivate --sync",
-        :user => user
-      )
-    end
-
-    begin
-      do_focus(window_title, user)
-    rescue ExecutionFailedInVM
-      # Often when xdotool fails to focus a window it'll work when retried
-      # after redrawing the screen.  Switching to a new virtual desktop then
-      # back seems to be a reliable way to handle this.
-      # Sadly we have to rely on a lot of sleep() here since there's
-      # little on the screen etc that we truly can rely on.
-      sleep 5
-      select_virtual_desktop(1)
-      sleep 5
-      select_virtual_desktop(0)
-      sleep 5
-      do_focus(window_title, user)
-    end
-  rescue
-    # noop
-  end
-
-  def file_exist?(file)
-    execute("test -e '#{file}'").success?
-  end
-
-  def directory_exist?(directory)
-    execute("test -d '#{directory}'").success?
-  end
-
-  def file_open(path)
-    f = RemoteShell::File.new(self, path)
-    yield f if block_given?
-    return f
-  end
-
-  def file_content(path)
-    file_open(path) { |f| return f.read() }
-  end
-
-  def file_overwrite(path, lines)
-    lines = lines.join("\n") if lines.class == Array
-    file_open(path) { |f| return f.write(lines) }
-  end
-
-  def file_append(path, lines)
-    lines = lines.join("\n") if lines.class == Array
-    file_open(path) { |f| return f.append(lines) }
-  end
-
-  def set_clipboard(text)
-    execute_successfully("echo -n '#{text}' | xsel --input --clipboard",
-                         :user => LIVE_USER)
-  end
-
-  def get_clipboard
-    execute_successfully("xsel --output --clipboard", :user => LIVE_USER).stdout
-  end
-
-  def internal_snapshot_xml(name)
-    disk_devs = list_disk_devs
-    disks_xml = "    <disks>\n"
-    for dev in disk_devs
-      snapshot_type = disk_type(dev) == "qcow2" ? 'internal' : 'no'
-      disks_xml +=
-        "      <disk name='#{dev}' snapshot='#{snapshot_type}'></disk>\n"
-    end
-    disks_xml += "    </disks>"
-    return <<-EOF
-<domainsnapshot>
-  <name>#{name}</name>
-  <description>Snapshot for #{name}</description>
-#{disks_xml}
-  </domainsnapshot>
-EOF
-  end
-
-  def VM.ram_only_snapshot_path(name)
-    return "#{$config["TMPDIR"]}/#{name}-snapshot.memstate"
-  end
-
-  def save_snapshot(name)
-    # If we have no qcow2 disk device, we'll use "memory state"
-    # snapshots, and if we have at least one qcow2 disk device, we'll
-    # use internal "system checkpoint" (memory + disks) snapshots. We
-    # have to do this since internal snapshots don't work when no
-    # such disk is available. We can do this with external snapshots,
-    # which are better in many ways, but libvirt doesn't know how to
-    # restore (revert back to) them yet.
-    # WARNING: If only transient disks, i.e. disks that were plugged
-    # after starting the domain, are used then the memory state will
-    # be dropped. External snapshots would also fix this.
-    internal_snapshot = false
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/disk') do |e|
-      if e.elements['driver'].attribute('type').to_s == "qcow2"
-        internal_snapshot = true
-        break
-      end
-    end
-
-    # Note: In this case the "opposite" of `internal_snapshot` is not
-    # anything relating to external snapshots, but actually "memory
-    # state"(-only) snapshots.
-    if internal_snapshot
-      xml = internal_snapshot_xml(name)
-      @domain.snapshot_create_xml(xml)
-    else
-      snapshot_path = VM.ram_only_snapshot_path(name)
-      @domain.save(snapshot_path)
-      # For consistency with the internal snapshot case (which is
-      # "live", so the domain doesn't go down) we immediately restore
-      # the snapshot.
-      # Assumption: that *immediate* save + restore doesn't mess up
-      # with network state and similar, and is fast enough to not make
-      # the clock drift too much.
-      restore_snapshot(name)
-    end
-  end
-
-  def restore_snapshot(name)
-    @domain.destroy if is_running?
-    @display.stop if @display and @display.active?
-    # See comment in save_snapshot() for details on why we use two
-    # different type of snapshots.
-    potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name)
-    if File.exist?(potential_ram_only_snapshot_path)
-      Libvirt::Domain::restore(@virt, potential_ram_only_snapshot_path)
-      @domain = @virt.lookup_domain_by_name(@domain_name)
-    else
-      begin
-        potential_internal_snapshot = @domain.lookup_snapshot_by_name(name)
-        @domain.revert_to_snapshot(potential_internal_snapshot)
-      rescue Libvirt::RetrieveError
-        raise "No such (internal nor external) snapshot #{name}"
-      end
-    end
-    @display.start
-  end
-
-  def VM.remove_snapshot(name)
-    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
-    potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name)
-    if File.exist?(potential_ram_only_snapshot_path)
-      File.delete(potential_ram_only_snapshot_path)
-    else
-      snapshot = old_domain.lookup_snapshot_by_name(name)
-      snapshot.delete
-    end
-  end
-
-  def VM.snapshot_exists?(name)
-    return true if File.exist?(VM.ram_only_snapshot_path(name))
-    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
-    snapshot = old_domain.lookup_snapshot_by_name(name)
-    return snapshot != nil
-  rescue Libvirt::RetrieveError
-    return false
-  end
-
-  def VM.remove_all_snapshots
-    Dir.glob("#{$config["TMPDIR"]}/*-snapshot.memstate").each do |file|
-      File.delete(file)
-    end
-    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
-    old_domain.list_all_snapshots.each { |snapshot| snapshot.delete }
-  rescue Libvirt::RetrieveError
-    # No such domain, so no snapshots either.
-  end
-
-  def start
-    return if is_running?
-    @domain.create
-    @display.start
-  end
-
-  def reset
-    @domain.reset if is_running?
-  end
-
-  def power_off
-    @domain.destroy if is_running?
-    @display.stop
-  end
-
-  def take_screenshot(description)
-    @display.take_screenshot(description)
-  end
-
-  def get_remote_shell_port
-    domain_xml = REXML::Document.new(@domain.xml_desc)
-    domain_xml.elements.each('domain/devices/serial') do |e|
-      if e.attribute('type').to_s == "tcp"
-        return e.elements['source'].attribute('service').to_s.to_i
-      end
-    end
-  end
-
-end
diff --git a/cucumber/features/support/hooks.rb b/cucumber/features/support/hooks.rb
deleted file mode 100644
index 9c3abb5..0000000
--- a/cucumber/features/support/hooks.rb
+++ /dev/null
@@ -1,313 +0,0 @@
-require 'fileutils'
-require 'rb-inotify'
-require 'time'
-require 'tmpdir'
-
-# Run once, before any feature
-AfterConfiguration do |config|
-  # Reorder the execution of some features. As we progress through a
-  # run we accumulate more and more snapshots and hence use more and
-  # more disk space, but some features will leave nothing behind
-  # and/or possibly use large amounts of disk space temporarily for
-  # various reasons. By running these first we minimize the amount of
-  # disk space needed.
-  prioritized_features = [
-    # Features not using snapshots but using large amounts of scratch
-    # space for other reasons:
-    'features/untrusted_partitions.feature',
-    # Features using temporary snapshots:
-    'features/apt.feature',
-    'features/root_access_control.feature',
-    'features/time_syncing.feature',
-    'features/tor_bridges.feature',
-    # Features using large amounts of scratch space for other reasons:
-    'features/erase_memory.feature',
-    # This feature needs the almost biggest snapshot (USB install,
-    # excluding persistence) and will create yet another disk and
-    # install Tails on it. This should be the peak of disk usage.
-    'features/usb_install.feature',
-    # This feature needs a copy of the ISO and creates a new disk.
-    'features/usb_upgrade.feature',
-    # This feature needs a very big snapshot (USB install with persistence)
-    # and another, network-enabled snapshot.
-    'features/emergency_shutdown.feature',
-  ]
-  feature_files = config.feature_files
-  # The &-intersection is specified to keep the element ordering of
-  # the *left* operand.
-  intersection = prioritized_features & feature_files
-  if not intersection.empty?
-    feature_files -= intersection
-    feature_files = intersection + feature_files
-    config.define_singleton_method(:feature_files) { feature_files }
-  end
-
-  # Used to keep track of when we start our first @product feature, when
-  # we'll do some special things.
-  $started_first_product_feature = false
-
-  if File.exist?($config["TMPDIR"])
-    if !File.directory?($config["TMPDIR"])
-      raise "Temporary directory '#{$config["TMPDIR"]}' exists but is not a " +
-            "directory"
-    end
-    if !File.owned?($config["TMPDIR"])
-      raise "Temporary directory '#{$config["TMPDIR"]}' must be owned by the " +
-            "current user"
-    end
-    FileUtils.chmod(0755, $config["TMPDIR"])
-  else
-    begin
-      FileUtils.mkdir_p($config["TMPDIR"])
-    rescue Errno::EACCES => e
-      raise "Cannot create temporary directory: #{e.to_s}"
-    end
-  end
-
-  # Start a thread that monitors a pseudo fifo file and debug_log():s
-  # anything written to it "immediately" (well, as fast as inotify
-  # detects it). We're forced to a convoluted solution like this
-  # because CRuby's thread support is horribly as soon as IO is mixed
-  # in (other threads get blocked).
-  FileUtils.rm(DEBUG_LOG_PSEUDO_FIFO) if File.exist?(DEBUG_LOG_PSEUDO_FIFO)
-  FileUtils.touch(DEBUG_LOG_PSEUDO_FIFO)
-  at_exit do
-    FileUtils.rm(DEBUG_LOG_PSEUDO_FIFO) if File.exist?(DEBUG_LOG_PSEUDO_FIFO)
-  end
-  Thread.new do
-    File.open(DEBUG_LOG_PSEUDO_FIFO) do |fd|
-      watcher = INotify::Notifier.new
-      watcher.watch(DEBUG_LOG_PSEUDO_FIFO, :modify) do
-        line = fd.read.chomp
-        debug_log(line) if line and line.length > 0
-      end
-      watcher.run
-    end
-  end
-  # Fix Sikuli's debug_log():ing.
-  bind_java_to_pseudo_fifo_logger
-end
-
-# Common
-########
-
-After do
-  if @after_scenario_hooks
-    @after_scenario_hooks.each { |block| block.call }
-  end
-  @after_scenario_hooks = Array.new
-end
-
-BeforeFeature('@product', '@source') do |feature|
-  raise "Feature #{feature.file} is tagged both @product and @source, " +
-        "which is an impossible combination"
-end
-
-at_exit do
-  $vm.destroy_and_undefine if $vm
-  if $virt
-    unless KEEP_SNAPSHOTS
-      VM.remove_all_snapshots
-      $vmstorage.clear_pool
-    end
-    #$vmnet.destroy_and_undefine
-    $virt.close
-  end
-  # The artifacts directory is empty (and useless) if it contains
-  # nothing but the mandatory . and ..
-  if Dir.entries(ARTIFACTS_DIR).size <= 2
-    FileUtils.rmdir(ARTIFACTS_DIR)
-  end
-end
-
-# For @product tests
-####################
-
-def add_after_scenario_hook(&block)
-  @after_scenario_hooks ||= Array.new
-  @after_scenario_hooks << block
-end
-
-def save_failure_artifact(type, path)
-  $failure_artifacts << [type, path]
-end
-
-# Due to Tails' Tor enforcement, we only allow contacting hosts that
-# are Tor nodes or located on the LAN. However, when we try
-# to verify that only such hosts are contacted we have a problem --
-# we run all Tor nodes (via Chutney) *and* LAN hosts (used on some
-# tests) on the same host, the one running the test suite. Hence we
-# need to always explicitly track which nodes are LAN or not.
-#
-# Warning: when a host is added via this function, it is only added
-# for the current scenario. As such, if this is done before saving a
-# snapshot, it will not remain after the snapshot is loaded.
-def add_lan_host(ipaddr, port)
-  @lan_hosts ||= []
-  @lan_hosts << { address: ipaddr, port: port }
-end
-
-BeforeFeature('@product') do |feature|
-  if TAILS_ISO.nil?
-    raise "No ISO image specified, and none could be found in the " +
-          "current directory"
-  end
-  if File.exist?(TAILS_ISO)
-    # Workaround: when libvirt takes ownership of the ISO image it may
-    # become unreadable for the live user inside the guest in the
-    # host-to-guest share used for some tests.
-
-    if !File.world_readable?(TAILS_ISO)
-      if File.owned?(TAILS_ISO)
-        File.chmod(0644, TAILS_ISO)
-      else
-        raise "warning: the Tails ISO image must be world readable or be " +
-              "owned by the current user to be available inside the guest " +
-              "VM via host-to-guest shares, which is required by some tests"
-      end
-    end
-  else
-    raise "The specified Tails ISO image '#{TAILS_ISO}' does not exist"
-  end
-  if !File.exist?(OLD_TAILS_ISO)
-    raise "The specified old Tails ISO image '#{OLD_TAILS_ISO}' does not exist"
-  end
-  if not($started_first_product_feature)
-    $virt = Libvirt::open("qemu:///system")
-    VM.remove_all_snapshots if !KEEP_SNAPSHOTS
-    $vmnet = VMNet.new($virt, VM_XML_PATH)
-    $vmstorage = VMStorage.new($virt, VM_XML_PATH)
-    $started_first_product_feature = true
-  end
-  #ensure_chutney_is_running
-end
-
-AfterFeature('@product') do
-  unless KEEP_SNAPSHOTS
-    checkpoints.each do |name, vals|
-      if vals[:temporary] and VM.snapshot_exists?(name)
-        VM.remove_snapshot(name)
-      end
-    end
-  end
-  $vmstorage.list_volumes.each do |vol_name|
-    next if vol_name == '__internal'
-    $vmstorage.delete_volume(vol_name)
-  end
-end
-
-# Cucumber Before hooks are executed in the order they are listed, and
-# we want this hook to always run first, so it must always be the
-# *first* Before hook matching @product listed in this file.
-Before('@product') do |scenario|
-  $failure_artifacts = Array.new
-  if $config["CAPTURE"]
-    video_name = sanitize_filename("#{scenario.name}.mpg")
-    @video_path = "#{ARTIFACTS_DIR}/#{video_name}"
-    capture = IO.popen(['avconv',
-                        '-f', 'x11grab',
-                        '-s', '1024x768',
-                        '-r', '5',
-                        '-i', "#{$config['DISPLAY']}.0",
-                        '-an',
-                        '-c:v', 'libx264',
-                        '-y',
-                        @video_path,
-                        :err => ['/dev/null', 'w'],
-                       ])
-    @video_capture_pid = capture.pid
-  end
-  @screen = Sikuli::Screen.new
-  # English will be assumed if this is not overridden
-  @language = ""
-  @os_loader = "MBR"
-  @sudo_password = "asdf"
-  @persistence_password = "asdf"
-  # See comment for add_lan_host() above.
-  @lan_hosts ||= []
-end
-
-# Cucumber After hooks are executed in the *reverse* order they are
-# listed, and we want this hook to always run second last, so it must always
-# be the *second* After hook matching @product listed in this file --
-# hooks added dynamically via add_after_scenario_hook() are supposed to
-# truly be last.
-After('@product') do |scenario|
-  if @video_capture_pid
-    # We can be incredibly fast at detecting errors sometimes, so the
-    # screen barely "settles" when we end up here and kill the video
-    # capture. Let's wait a few seconds more to make it easier to see
-    # what the error was.
-    sleep 3 if scenario.failed?
-    Process.kill("INT", @video_capture_pid)
-    save_failure_artifact("Video", @video_path)
-  end
-  if scenario.failed?
-    time_of_fail = Time.now - TIME_AT_START
-    secs = "%02d" % (time_of_fail % 60)
-    mins = "%02d" % ((time_of_fail / 60) % 60)
-    hrs  = "%02d" % (time_of_fail / (60*60))
-    elapsed = "#{hrs}:#{mins}:#{secs}"
-    info_log("Scenario failed at time #{elapsed}")
-    screen_capture = @screen.capture
-    save_failure_artifact("Screenshot", screen_capture.getFilename)
-    if scenario.exception.kind_of?(FirewallAssertionFailedError)
-      Dir.glob("#{$config["TMPDIR"]}/*.pcap").each do |pcap_file|
-        save_failure_artifact("Network capture", pcap_file)
-      end
-    end
-    $failure_artifacts.sort!
-    $failure_artifacts.each do |type, file|
-      artifact_name = sanitize_filename("#{elapsed}_#{scenario.name}#{File.extname(file)}")
-      artifact_path = "#{ARTIFACTS_DIR}/#{artifact_name}"
-      assert(File.exist?(file))
-      FileUtils.mv(file, artifact_path)
-      info_log
-      info_log_artifact_location(type, artifact_path)
-    end
-    if $config["INTERACTIVE_DEBUGGING"]
-      pause(
-        "Scenario failed: #{scenario.name}. " +
-        "The error was: #{scenario.exception.class.name}: #{scenario.exception}"
-      )
-    end
-  else
-    if @video_path && File.exist?(@video_path) && not($config['CAPTURE_ALL'])
-      FileUtils.rm(@video_path)
-    end
-  end
-end
-
-Before('@product', '@check_tor_leaks') do |scenario|
-  @tor_leaks_sniffer = Sniffer.new(sanitize_filename(scenario.name), $vmnet)
-  @tor_leaks_sniffer.capture
-  add_after_scenario_hook do
-    @tor_leaks_sniffer.clear
-  end
-end
-
-After('@product', '@check_tor_leaks') do |scenario|
-  @tor_leaks_sniffer.stop
-  if scenario.passed?
-    allowed_nodes = @bridge_hosts ? @bridge_hosts : allowed_hosts_under_tor_enforcement
-    assert_all_connections(@tor_leaks_sniffer.pcap_file) do |c|
-      allowed_nodes.include?({ address: c.daddr, port: c.dport })
-    end
-  end
-end
-
-# For @source tests
-###################
-
-# BeforeScenario
-Before('@source') do
-  @orig_pwd = Dir.pwd
-  @git_clone = Dir.mktmpdir 'tails-apt-tests'
-  Dir.chdir @git_clone
-end
-
-# AfterScenario
-After('@source') do
-  Dir.chdir @orig_pwd
-  FileUtils.remove_entry_secure @git_clone
-end
diff --git a/hosts/jenkins-test-vm/job-cfg/lvc.yaml b/hosts/jenkins-test-vm/job-cfg/lvc.yaml
deleted file mode 120000
index e274ac6..0000000
--- a/hosts/jenkins-test-vm/job-cfg/lvc.yaml
+++ /dev/null
@@ -1 +0,0 @@
-../../../job-cfg/lvc.yaml
\ No newline at end of file
diff --git a/job-cfg/lvc.yaml b/job-cfg/lvc.yaml
deleted file mode 100644
index d0bdfbd..0000000
--- a/job-cfg/lvc.yaml
+++ /dev/null
@@ -1,101 +0,0 @@
-- defaults:
-    name: lvc
-    project-type: freestyle
-    disabled: true
-    description: &desc '{my_description}<br><br>Job  configuration source is <a href="https://anonscm.debian.org/git/qa/jenkins.debian.net.git/tree/job-cfg/lvc.yaml">lvc.yaml</a>.'
-    properties:
-      - sidebar: &sb1
-          url: https://jenkins.debian.net/userContent/about.html
-          text: About jenkins.debian.net
-          icon: /userContent/images/debian-swirl-24x24.png
-      - sidebar: &sb2
-          url: https://jenkins.debian.net/view/lvc
-          text: Jobs for libvirt and cucumber based tests
-          icon: /userContent/images/debian-jenkins-24x24.png
-      - sidebar: &sb3
-          url: http://www.profitbricks.co.uk
-          text: Sponsored by Profitbricks
-          icon: /userContent/images/profitbricks-24x24.png
-      - throttle:
-          max-total: 1
-          max-per-node: 1
-          enabled: true
-          option: category
-          categories:
-            - lvc
-    parameters:
-      - string:
-          name: TRIGGERING_BRANCH
-          description: The git branch that triggered the build that reulted in this subsequent build
-    logrotate:
-      daysToKeep: 90
-      numToKeep: 60
-      artifactDaysToKeep: -1
-      artifactNumToKeep: -1
-    publishers:
-      - email:
-          recipients: 'qa-jenkins-scm at lists.alioth.debian.org, phil at hands.com'
-      - archive:
-          artifacts: '{my_pngs}, results/*.mpg, results/*.log'
-          latest-only: false
-      - image-gallery:
-          - title: '{my_title}'
-            includes: '{my_pngs}'
-            image-width: 300
-    builders:
-      - shell: '/srv/jenkins/bin/jenkins_master_wrapper.sh {my_iso} /srv/jenkins/cucumber/features {my_opts}'
-    triggers:
-      - timed: '{my_time}'
-    node: 'lvc'
-
-- job-template:
-    defaults: lvc
-    name: '{name}_{distro}'
-
-- project:
-    name: lvc
-    my_title: 'Cucumber: {dist_name}'
-    my_time: '45 23 01 11 *'
-    my_pngs: 'results/*.png'
-    my_description: 'Work in progress...'
-    my_opts: '--tags @default'
-    jobs:
-        - '{name}_{distro}':
-            distro:
-                - 'debian-stable':
-                    dist_name: 'Debian-Installer Stable'
-                    my_iso: 'http://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-9.1.0-amd64-netinst.iso'
-                    my_time: 'H H * * H'
-                - 'debian-testing-daily':
-                    dist_name: 'Debian-Installer Testing (Daily)'
-                    my_iso: 'http://cdimage.debian.org/cdimage/daily-builds/daily/arch-latest/amd64/iso-cd/debian-testing-amd64-netinst.iso'
-                    my_time: 'H H/8 * * *'
-                - 'debian-DI-miniiso-gui-daily':
-                    dist_name: 'Debian-Installer Testing GTK-miniiso (Daily)'
-                    my_iso: 'https://d-i.debian.org/daily-images/amd64/daily/netboot/gtk/mini.iso'
-                    my_time: 'H H/8 * * *'
-                    my_opts: '--tags @gui'
-                - 'debian-DI-miniiso-text-daily':
-                    dist_name: 'Debian-Installer Testing Text-miniiso (Daily)'
-                    my_iso: 'https://d-i.debian.org/daily-images/amd64/daily/netboot/mini.iso'
-                    my_time: 'H H/8 * * *'
-                    my_opts: '--tags @text-ui'
-                - 'debian-miniiso':
-                    dist_name: 'Debian-Installer local mini.iso'
-                    my_iso: 'use_TRIGGERING_BRANCH'
-                    my_opts: '--tags @bugtest'
-                - 'debian-DI-miniiso-gui-daily-bugtest':
-                    dist_name: 'Debian-Installer Testing GTK-miniiso (Daily) bugtest'
-                    my_iso: 'https://d-i.debian.org/daily-images/amd64/daily/netboot/gtk/mini.iso'
-                    my_time: 'H H/8 * * *'
-                    my_opts: '--tags @bugtest'
-                - 'debianedu-testing-netinst':
-                    dist_name: 'Debian-Edu Testing Netinst'
-                    my_iso: 'http://ftp.skolelinux.org/cd-stretch-amd64-i386-netinst/debian-edu-amd64-i386-NETINST-1.iso'
-                    my_time: 'H H * * H'
-                    my_opts: '--tags @debedu'
-                #- 'debian-testing-daily-broken':
-                #    dist_name: 'Debian-Installer Stretch alpha8 -- Broken Scenarios'
-                #    my_iso: 'http://cdimage.debian.org/cdimage/stretch_di_alpha8/amd64/iso-cd/debian-stretch-DI-alpha8-amd64-netinst.iso'
-                #    my_time: 'H H * * H'
-                #    my_opts: '--tags @broken'
diff --git a/update_jdn.sh b/update_jdn.sh
index b73f0bc..de1f765 100755
--- a/update_jdn.sh
+++ b/update_jdn.sh
@@ -388,39 +388,6 @@ if [ -f /etc/debian_version ] ; then
 			;;
 			*) ;;
 		esac
-		# cucumber dependencies (for lvc jobs)
-		case $HOSTNAME in
-			profitbricks-build10-amd64|jenkins-test-vm) DEBS="$DEBS
-				cucumber
-				tesseract-ocr
-				i18nspector
-				imagemagick
-				libav-tools
-				libsikuli-script-java
-				libvirt-clients
-				libvirt-daemon
-				libvirt-daemon-system
-				ovmf
-				pry
-				python-jabberbot
-				python-potr
-				python3-yaml
-				redir
-				ruby-guestfs
-				ruby-json
-				ruby-libvirt
-				ruby-net-irc
-				ruby-packetfu
-				ruby-rb-inotify
-				ruby-rjb
-				ruby-test-unit
-				tcpdump
-				unclutter
-				virt-viewer
-				x264
-				xvfb
-				x11vnc"
-			   ;;
 			*) ;;
 		esac
 		if [ "$HOSTNAME" = "jenkins-test-vm" ] ; then
@@ -622,7 +589,7 @@ explain "packages configured."
 #
 cd $BASEDIR
 [ -d /srv/jenkins/features ] && sudo rm -rf /srv/jenkins/features
-for dir in bin logparse cucumber live mustache-templates ; do
+for dir in bin logparse live mustache-templates ; do
 	sudo mkdir -p /srv/jenkins/$dir
 	sudo rsync -rpt --delete $dir/ /srv/jenkins/$dir/
 	sudo chown -R jenkins-adm.jenkins-adm /srv/jenkins/$dir
@@ -793,12 +760,7 @@ if [ "$HOSTNAME" = "jenkins" ] || [ "$HOSTNAME" = "jenkins-test-vm" ] ; then
 	rgrep FI[X]ME $BASEDIR/* | grep -v echo > $TMPFILE || true
 	if [ -s $TMPFILE ] ; then
 		echo
-		# only show cucumber FIXMEs when deploying on jenkins-test-vm
-		if [ "$HOSTNAME" = "jenkins-test-vm" ] ; then
-			cat $TMPFILE
-		else
-			cat $TMPFILE | grep -v cucumber
-		fi
+		cat $TMPFILE
 		echo
 	fi
 fi

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/qa/jenkins.debian.net.git



More information about the Qa-jenkins-scm mailing list