about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.xml5
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/rename.nix4
-rw-r--r--nixos/modules/services/databases/mysql.nix12
-rw-r--r--nixos/modules/services/network-filesystems/beegfs.nix357
-rw-r--r--nixos/modules/services/networking/znc/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix4
-rw-r--r--nixos/modules/virtualisation/digital-ocean-config.nix197
-rw-r--r--nixos/modules/virtualisation/digital-ocean-image.nix69
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix95
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix13
-rw-r--r--nixos/tests/all-tests.nix2
-rw-r--r--nixos/tests/beegfs.nix115
-rw-r--r--nixos/tests/bees.nix37
-rw-r--r--nixos/tests/gitolite.nix103
-rw-r--r--nixos/tests/influxdb.nix33
-rw-r--r--nixos/tests/jenkins.nix19
-rw-r--r--nixos/tests/minio.nix32
-rw-r--r--nixos/tests/sudo.nix52
-rw-r--r--nixos/tests/uwsgi.nix10
-rw-r--r--nixos/tests/wordpress.nix42
21 files changed, 562 insertions, 642 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml
index 6916fd225dac..e8e89c5bbc28 100644
--- a/nixos/doc/manual/release-notes/rl-2003.xml
+++ b/nixos/doc/manual/release-notes/rl-2003.xml
@@ -176,6 +176,11 @@
      KDE’s old multimedia framework Phonon no longer supports Qt 4. For that reason, Plasma desktop also does not have <option>enableQt4Support</option> option any more.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     The BeeGFS module has been removed.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5f4a608d74d5..6d1ef0d234ab 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -538,7 +538,6 @@
   ./services/monitoring/zabbix-agent.nix
   ./services/monitoring/zabbix-proxy.nix
   ./services/monitoring/zabbix-server.nix
-  ./services/network-filesystems/beegfs.nix
   ./services/network-filesystems/cachefilesd.nix
   ./services/network-filesystems/davfs2.nix
   ./services/network-filesystems/drbd.nix
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index a109b26a5f3d..c810bcf3bca1 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -280,6 +280,10 @@ with lib;
     # BLCR
     (mkRemovedOptionModule [ "environment.blcr.enable" ] "The BLCR module has been removed")
 
+    # beegfs
+    (mkRemovedOptionModule [ "services.beegfsEnable" ] "The BeeGFS module has been removed")
+    (mkRemovedOptionModule [ "services.beegfs" ] "The BeeGFS module has been removed")
+
     # Redis
     (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
     (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 39192d059485..5549cfa5cf4d 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -8,15 +8,11 @@ let
 
   mysql = cfg.package;
 
-  isMariaDB =
-    let
-      pName = _p: (builtins.parseDrvName (_p.name)).name;
-    in pName mysql == pName pkgs.mariadb;
+  isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb;
+
   isMysqlAtLeast57 =
-    let
-      pName = _p: (builtins.parseDrvName (_p.name)).name;
-    in (pName mysql == pName pkgs.mysql57)
-       && ((builtins.compareVersions mysql.version "5.7") >= 0);
+    (lib.getName mysql == lib.getName pkgs.mysql57)
+     && (builtins.compareVersions mysql.version "5.7" >= 0);
 
   mysqldOptions =
     "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
diff --git a/nixos/modules/services/network-filesystems/beegfs.nix b/nixos/modules/services/network-filesystems/beegfs.nix
deleted file mode 100644
index 2e03a422665a..000000000000
--- a/nixos/modules/services/network-filesystems/beegfs.nix
+++ /dev/null
@@ -1,357 +0,0 @@
-{ config, lib, pkgs, ...} :
-
-with lib;
-
-let
-  cfg = config.services.beegfs;
-
-  # functions for the generations of config files
-
-  configMgmtd = name: cfg: pkgs.writeText "mgmt-${name}.conf" ''
-    storeMgmtdDirectory = ${cfg.mgmtd.storeDir}
-    storeAllowFirstRunInit = false
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.mgmtd.extraConfig}
-  '';
-
-  configAdmon = name: cfg: pkgs.writeText "admon-${name}.conf" ''
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.admon.extraConfig}
-  '';
-
-  configMeta = name: cfg: pkgs.writeText "meta-${name}.conf" ''
-    storeMetaDirectory = ${cfg.meta.storeDir}
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-    storeAllowFirstRunInit = false
-
-    ${cfg.meta.extraConfig}
-  '';
-
-  configStorage = name: cfg: pkgs.writeText "storage-${name}.conf" ''
-    storeStorageDirectory = ${cfg.storage.storeDir}
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-    storeAllowFirstRunInit = false
-
-    ${cfg.storage.extraConfig}
-  '';
-
-  configHelperd = name: cfg: pkgs.writeText "helperd-${name}.conf" ''
-    connAuthFile = ${cfg.connAuthFile}
-    ${cfg.helperd.extraConfig}
-  '';
-
-  configClientFilename = name : "/etc/beegfs/client-${name}.conf";
-
-  configClient = name: cfg: ''
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.client.extraConfig}
-  '';
-
-  serviceList = [
-    { service = "admon"; cfgFile = configAdmon; }
-    { service = "meta"; cfgFile = configMeta; }
-    { service = "mgmtd"; cfgFile = configMgmtd; }
-    { service = "storage"; cfgFile = configStorage; }
-  ];
-
-  # functions to generate systemd.service entries
-
-  systemdEntry = service: cfgFile: (mapAttrs' ( name: cfg:
-    (nameValuePair "beegfs-${service}-${name}" (mkIf cfg.${service}.enable {
-    wantedBy = [ "multi-user.target" ];
-    requires = [ "network-online.target" ];
-    after = [ "network-online.target" ];
-    serviceConfig = rec {
-      ExecStart = ''
-        ${pkgs.beegfs}/bin/beegfs-${service} \
-          cfgFile=${cfgFile name cfg} \
-          pidFile=${PIDFile}
-      '';
-      PIDFile = "/run/beegfs-${service}-${name}.pid";
-      TimeoutStopSec = "300";
-    };
-  }))) cfg);
-
-  systemdHelperd =  mapAttrs' ( name: cfg:
-    (nameValuePair "beegfs-helperd-${name}" (mkIf cfg.client.enable {
-    wantedBy = [ "multi-user.target" ];
-    requires = [ "network-online.target" ];
-    after = [ "network-online.target" ];
-    serviceConfig = rec {
-      ExecStart = ''
-        ${pkgs.beegfs}/bin/beegfs-helperd \
-          cfgFile=${configHelperd name cfg} \
-          pidFile=${PIDFile}
-      '';
-      PIDFile = "/run/beegfs-helperd-${name}.pid";
-      TimeoutStopSec = "300";
-    };
-   }))) cfg;
-
-  # wrappers to beegfs tools. Avoid typing path of config files
-  utilWrappers = mapAttrsToList ( name: cfg:
-    ( pkgs.runCommand "beegfs-utils-${name}" {
-        nativeBuildInputs = [ pkgs.makeWrapper ];
-        preferLocalBuild = true;
-        } ''
-        mkdir -p $out/bin
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-check-servers \
-                    $out/bin/beegfs-check-servers-${name} \
-                    --add-flags "-c ${configClientFilename name}" \
-                    --prefix PATH : ${lib.makeBinPath [ pkgs.beegfs ]}
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
-                    $out/bin/beegfs-ctl-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}"
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
-                    $out/bin/beegfs-df-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}" \
-                    --add-flags --listtargets  \
-                    --add-flags --hidenodeid \
-                    --add-flags --pools \
-                    --add-flags --spaceinfo
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-fsck \
-                    $out/bin/beegfs-fsck-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}"
-      ''
-     )) cfg;
-in
-{
-  ###### interface
-
-  options = {
-    services.beegfsEnable = mkEnableOption "BeeGFS";
-
-    services.beegfs = mkOption {
-      default = {};
-      description = ''
-        BeeGFS configurations. Every mount point requires a separate configuration.
-      '';
-      type = with types; attrsOf (submodule ({ ... } : {
-        options = {
-          mgmtdHost = mkOption {
-            type = types.str;
-            default = null;
-            example = "master";
-            description = ''Hostname of managament host.'';
-          };
-
-          connAuthFile = mkOption {
-            type = types.str;
-            default = "";
-            example = "/etc/my.key";
-            description = "File containing shared secret authentication.";
-          };
-
-          connPortShift = mkOption {
-            type = types.int;
-            default = 0;
-            example = 5;
-            description = ''
-              For each additional beegfs configuration shift all
-              service TCP/UDP ports by at least 5.
-            '';
-          };
-
-          client = {
-            enable = mkEnableOption "BeeGFS client";
-
-            mount = mkOption {
-              type = types.bool;
-              default = true;
-              description = "Create fstab entry automatically";
-            };
-
-            mountPoint = mkOption {
-              type = types.str;
-              default = "/run/beegfs";
-              description = ''
-                Mount point under which the beegfs filesytem should be mounted.
-                If mounted manually the mount option specifing the config file is needed:
-                cfgFile=/etc/beegfs/beegfs-client-&lt;name&gt;.conf
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-client.conf.
-                See documentation for further details.
-             '';
-            };
-          };
-
-          helperd = {
-            enable = mkOption {
-              type = types.bool;
-              default = true;
-              description = ''
-                Enable the BeeGFS helperd.
-                The helpered is need for logging purposes on the client.
-                Disabling <literal>helperd</literal> allows for runing the client
-                with <literal>allowUnfree = false</literal>.
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-helperd.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          mgmtd = {
-            enable = mkEnableOption "BeeGFS mgmtd daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-mgmtd";
-              description = ''
-                Data directory for mgmtd.
-                Must not be shared with other beegfs daemons.
-                This directory must exist and it must be initialized
-                with beegfs-setup-mgmtd, e.g. "beegfs-setup-mgmtd -C -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-mgmtd.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          admon = {
-            enable = mkEnableOption "BeeGFS admon daemon";
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-admon.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          meta = {
-            enable = mkEnableOption "BeeGFS meta data daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-meta";
-              description = ''
-                Data directory for meta data service.
-                Must not be shared with other beegfs daemons.
-                The underlying filesystem must be mounted with xattr turned on.
-                This directory must exist and it must be initialized
-                with beegfs-setup-meta, e.g.
-                "beegfs-setup-meta -C -s &lt;serviceID&gt; -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.str;
-              default = "";
-              description = ''
-                Additional lines for beegfs-meta.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          storage = {
-            enable = mkEnableOption "BeeGFS storage daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-storage";
-              description = ''
-                Data directories for storage service.
-                Must not be shared with other beegfs daemons.
-                The underlying filesystem must be mounted with xattr turned on.
-                This directory must exist and it must be initialized
-                with beegfs-setup-storage, e.g.
-                "beegfs-setup-storage -C -s &lt;serviceID&gt; -i &lt;storageTargetID&gt; -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.str;
-              default = "";
-              description = ''
-                Addional lines for beegfs-storage.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-        };
-      }));
-    };
-  };
-
-  ###### implementation
-
-  config =
-    mkIf config.services.beegfsEnable {
-
-    environment.systemPackages = utilWrappers;
-
-    # Put the client.conf files in /etc since they are needed
-    # by the commandline tools
-    environment.etc = mapAttrs' ( name: cfg:
-      (nameValuePair "beegfs/client-${name}.conf" (mkIf (cfg.client.enable)
-    {
-      enable = true;
-      text = configClient name cfg;
-    }))) cfg;
-
-    # Kernel module, we need it only once per host.
-    boot = mkIf (
-      foldr (a: b: a || b) false
-        (map (x: x.client.enable) (collect (x: x ? client) cfg)))
-    {
-      kernelModules = [ "beegfs" ];
-      extraModulePackages = [ pkgs.linuxPackages.beegfs-module ];
-    };
-
-    # generate fstab entries
-    fileSystems = mapAttrs' (name: cfg:
-      (nameValuePair cfg.client.mountPoint (optionalAttrs cfg.client.mount (mkIf cfg.client.enable {
-      device = "beegfs_nodev";
-      fsType = "beegfs";
-      mountPoint = cfg.client.mountPoint;
-      options = [ "cfgFile=${configClientFilename name}" "_netdev" ];
-    })))) cfg;
-
-    # generate systemd services
-    systemd.services = systemdHelperd //
-      foldr (a: b: a // b) {}
-        (map (x: systemdEntry x.service x.cfgFile) serviceList);
-  };
-}
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index 05f97bfa539f..0a9848a49349 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -239,7 +239,7 @@ in
     services.znc = {
       configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
       config = {
-        Version = (builtins.parseDrvName pkgs.znc.name).version;
+        Version = lib.getVersion pkgs.znc;
         Listener.l.Port = mkDefault 5000;
         Listener.l.SSL = mkDefault true;
       };
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index e13f0421d38f..9a4db84f7b73 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -47,8 +47,8 @@ let
       grub = f grub;
       grubTarget = f (grub.grubTarget or "");
       shell = "${pkgs.runtimeShell}";
-      fullName = (builtins.parseDrvName realGrub.name).name;
-      fullVersion = (builtins.parseDrvName realGrub.name).version;
+      fullName = lib.getName realGrub;
+      fullVersion = lib.getVersion realGrub;
       grubEfi = f grubEfi;
       grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
       bootPath = args.path;
diff --git a/nixos/modules/virtualisation/digital-ocean-config.nix b/nixos/modules/virtualisation/digital-ocean-config.nix
new file mode 100644
index 000000000000..88cb0cd450e8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -0,0 +1,197 @@
+{ config, pkgs, lib, modulesPath, ... }:
+with lib;
+{
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+    (modulesPath + "/virtualisation/digital-ocean-init.nix")
+  ];
+  options.virtualisation.digitalOcean = with types; {
+    setRootPassword = mkOption {
+      type = bool;
+      default = false;
+      example = true;
+      description = "Whether to set the root password from the Digital Ocean metadata";
+    };
+    setSshKeys = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to fetch ssh keys from Digital Ocean";
+    };
+    seedEntropy = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+    };
+  };
+  config =
+    let
+      cfg = config.virtualisation.digitalOcean;
+      hostName = config.networking.hostName;
+      doMetadataFile = "/run/do-metadata/v1.json";
+    in mkMerge [{
+      fileSystems."/" = {
+        device = "/dev/disk/by-label/nixos";
+        autoResize = true;
+        fsType = "ext4";
+      };
+      boot = {
+        growPartition = true;
+        kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
+        initrd.kernelModules = [ "virtio_scsi" ];
+        kernelModules = [ "virtio_pci" "virtio_net" ];
+        loader = {
+          grub.device = "/dev/vda";
+          timeout = 0;
+          grub.configurationLimit = 0;
+        };
+      };
+      services.openssh = {
+        enable = mkDefault true;
+        passwordAuthentication = mkDefault false;
+      };
+      services.do-agent.enable = mkDefault true;
+      networking = {
+        hostName = mkDefault ""; # use Digital Ocean metadata server
+      };
+
+      /* Check for and wait for the metadata server to become reachable.
+       * This serves as a dependency for all the other metadata services. */
+      systemd.services.digitalocean-metadata = {
+        path = [ pkgs.curl ];
+        description = "Get host metadata provided by Digitalocean";
+        script = ''
+          set -eu
+          DO_DELAY_ATTEMPTS=0
+          while ! curl -fsSL -o $RUNTIME_DIRECTORY/v1.json http://169.254.169.254/metadata/v1.json; do
+            DO_DELAY_ATTEMPTS=$((DO_DELAY_ATTEMPTS + 1))
+            if (( $DO_DELAY_ATTEMPTS >= $DO_DELAY_ATTEMPTS_MAX )); then
+              echo "giving up"
+              exit 1
+            fi
+
+            echo "metadata unavailable, trying again in 1s..."
+            sleep 1
+          done
+          chmod 600 $RUNTIME_DIRECTORY/v1.json
+          '';
+        environment = {
+          DO_DELAY_ATTEMPTS_MAX = "10";
+        };
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          RuntimeDirectory = "do-metadata";
+          RuntimeDirectoryPreserve = "yes";
+        };
+        unitConfig = {
+          ConditionPathExists = "!${doMetadataFile}";
+          After = [ "network-pre.target" ] ++
+            optional config.networking.dhcpcd.enable "dhcpcd.service" ++
+            optional config.systemd.network.enable "systemd-networkd.service";
+        };
+      };
+
+      /* Fetch the root password from the digital ocean metadata.
+       * There is no specific route for this, so we use jq to get
+       * it from the One Big JSON metadata blob */
+      systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword {
+        path = [ pkgs.shadow pkgs.jq ];
+        description = "Set root password provided by Digitalocean";
+        wantedBy = [ "multi-user.target" ];
+        script = ''
+          set -eo pipefail
+          ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile})
+          echo "root:$ROOT_PASSWORD" | chpasswd
+          mkdir -p /etc/do-metadata/set-root-password
+          '';
+        unitConfig = {
+          ConditionPathExists = "!/etc/do-metadata/set-root-password";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Set the hostname from Digital Ocean, unless the user configured it in
+       * the NixOS configuration. The cached metadata file isn't used here
+       * because the hostname is a mutable part of the droplet. */
+      systemd.services.digitalocean-set-hostname = mkIf (hostName == "") {
+        path = [ pkgs.curl pkgs.nettools ];
+        description = "Set hostname provided by Digitalocean";
+        wantedBy = [ "network.target" ];
+        script = ''
+          set -e
+          DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname)
+          hostname "$DIGITALOCEAN_HOSTNAME"
+          if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then
+            printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname
+          fi
+        '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Wants = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Fetch the ssh keys for root from Digital Ocean */
+      systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys {
+        description = "Set root ssh keys provided by Digital Ocean";
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.jq ];
+        script = ''
+          set -e
+          mkdir -m 0700 -p /root/.ssh
+          jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys
+          chmod 600 /root/.ssh/authorized_keys
+        '';
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+        unitConfig = {
+          ConditionPathExists = "!/root/.ssh/authorized_keys";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+      };
+
+      /* Initialize the RNG by running the entropy-seed script from the
+       * Digital Ocean metadata
+       */
+      systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy {
+        description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+        wantedBy = [ "network.target" ];
+        path = [ pkgs.jq pkgs.mpack ];
+        script = ''
+          set -eo pipefail
+          TEMPDIR=$(mktemp -d)
+          jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR
+          ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR)
+          ${pkgs.runtimeShell} $ENTROPY_SEED
+          rm -rf $TEMPDIR
+          '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+    }
+  ];
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+}
+
diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix
new file mode 100644
index 000000000000..b582e235d435
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-image.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.virtualisation.digitalOceanImage;
+in
+{
+
+  imports = [ ./digital-ocean-config.nix ];
+
+  options = {
+    virtualisation.digitalOceanImage.diskSize = mkOption {
+      type = with types; int;
+      default = 4096;
+      description = ''
+        Size of disk image. Unit is MB.
+      '';
+    };
+
+    virtualisation.digitalOceanImage.configFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = ''
+        A path to a configuration file which will be placed at
+        <literal>/etc/nixos/configuration.nix</literal> and be used when switching
+        to a new configuration. If set to <literal>null</literal>, a default
+        configuration is used that imports
+        <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+      '';
+    };
+
+    virtualisation.digitalOceanImage.compressionMethod = mkOption {
+      type = types.enum [ "gzip" "bzip2" ];
+      default = "gzip";
+      example = "bzip2";
+      description = ''
+        Disk image compression method. Choose bzip2 to generate smaller images that
+        take longer to generate but will consume less metered storage space on your
+        Digital Ocean account.
+      '';
+    };
+  };
+
+  #### implementation
+  config = {
+
+    system.build.digitalOceanImage = import ../../lib/make-disk-image.nix {
+      name = "digital-ocean-image";
+      format = "qcow2";
+      postVM = let
+        compress = {
+          "gzip" = "${pkgs.gzip}/bin/gzip";
+          "bzip2" = "${pkgs.bzip2}/bin/bzip2";
+        }.${cfg.compressionMethod};
+      in ''
+        ${compress} $diskImage
+      '';
+      configFile = if cfg.configFile == null
+        then config.virtualisation.digitalOcean.defaultConfigFile
+        else cfg.configFile;
+      inherit (cfg) diskSize;
+      inherit config lib pkgs;
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+
+}
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
new file mode 100644
index 000000000000..02f4de009fa8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -0,0 +1,95 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.virtualisation.digitalOcean;
+  defaultConfigFile = pkgs.writeText "digitalocean-configuration.nix" ''
+    { modulesPath, lib, ... }:
+    {
+      imports = lib.optional (builtins.pathExists ./do-userdata.nix) ./do-userdata.nix ++ [
+        (modulesPath + "/virtualisation/digital-ocean-config.nix")
+      ];
+    }
+  '';
+in {
+  options.virtualisation.digitalOcean.rebuildFromUserData = mkOption {
+    type = types.bool;
+    default = true;
+    example = true;
+    description = "Whether to reconfigure the system from Digital Ocean user data";
+  };
+  options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
+    type = types.path;
+    default = defaultConfigFile;
+    defaultText = ''
+      The default configuration imports user-data if applicable and
+      <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+    '';
+    description = ''
+      A path to a configuration file which will be placed at
+      <literal>/etc/nixos/configuration.nix</literal> and be used when switching to
+      a new configuration.
+    '';
+  };
+
+  config = {
+    systemd.services.digitalocean-init = mkIf cfg.rebuildFromUserData {
+      description = "Reconfigure the system from Digital Ocean userdata on startup";
+      wantedBy = [ "network-online.target" ];
+      unitConfig = {
+        ConditionPathExists = "!/etc/nixos/do-userdata.nix";
+        After = [ "digitalocean-metadata.service" "network-online.target" ];
+        Requires = [ "digitalocean-metadata.service" ];
+        X-StopOnRemoval = false;
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+      restartIfChanged = false;
+      path = [ pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.systemd config.nix.package config.system.build.nixos-rebuild ];
+      environment = {
+        HOME = "/root";
+        NIX_PATH = concatStringsSep ":" [
+          "/nix/var/nix/profiles/per-user/root/channels/nixos"
+          "nixos-config=/etc/nixos/configuration.nix"
+          "/nix/var/nix/profiles/per-user/root/channels"
+        ];
+      };
+      script = ''
+        set -e
+        echo "attempting to fetch configuration from Digital Ocean user data..."
+        userData=$(mktemp)
+        if jq -er '.user_data' /run/do-metadata/v1.json > $userData; then
+          # If the user-data looks like it could be a nix expression,
+          # copy it over. Also, look for a magic three-hash comment and set
+          # that as the channel.
+          if nix-instantiate --parse $userData > /dev/null; then
+            channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
+            printf "%s" "$channels" | while read channel; do
+              echo "writing channel: $channel"
+            done
+
+            if [[ -n "$channels" ]]; then
+              printf "%s" "$channels" > /root/.nix-channels
+              nix-channel --update
+            fi
+
+            echo "setting configuration from Digital Ocean user data"
+            cp "$userData" /etc/nixos/do-userdata.nix
+            if [[ ! -e /etc/nixos/configuration.nix ]]; then
+              install -m0644 ${cfg.defaultConfigFile} /etc/nixos/configuration.nix
+            fi
+          else
+            echo "user data does not appear to be a Nix expression; ignoring"
+            exit
+          fi
+
+          nixos-rebuild switch
+        else
+          echo "no user data is available"
+        fi
+        '';
+    };
+  };
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+}
diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index edec37402308..6b8ad83661fe 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -42,6 +42,9 @@ in {
       default = false;
       description = ''
         Whether to start racoon service for openvswitch.
+        Supported only if openvswitch version is less than 2.6.0.
+        Use <literal>virtualisation.vswitch.package = pkgs.openvswitch-lts</literal>
+        for a version that supports ipsec over GRE.
       '';
     };
   };
@@ -89,6 +92,13 @@ in {
             "${cfg.package}/share/openvswitch/vswitch.ovsschema"
         fi
         chmod -R +w /var/db/openvswitch
+        if ${cfg.package}/bin/ovsdb-tool needs-conversion /var/db/openvswitch/conf.db | grep -q "yes"
+        then
+          echo "Performing database upgrade"
+          ${cfg.package}/bin/ovsdb-tool convert /var/db/openvswitch/conf.db
+        else
+          echo "Database already up to date"
+        fi
         '';
       serviceConfig = {
         ExecStart =
@@ -133,7 +143,7 @@ in {
     };
 
   }
-  (mkIf cfg.ipsec {
+  (mkIf (cfg.ipsec && (versionOlder cfg.package.version "2.6.0")) {
     services.racoon.enable = true;
     services.racoon.configPath = "${runDir}/ipsec/etc/racoon/racoon.conf";
 
@@ -172,5 +182,4 @@ in {
       '';
     };
   })]));
-
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index a2129ef7076e..3d5bc408c445 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -28,7 +28,7 @@ in
   babeld = handleTest ./babeld.nix {};
   bcachefs = handleTestOn ["x86_64-linux"] ./bcachefs.nix {}; # linux-4.18.2018.10.12 is unsupported on aarch64
   beanstalkd = handleTest ./beanstalkd.nix {};
-  beegfs = handleTestOn ["x86_64-linux"] ./beegfs.nix {}; # beegfs is unsupported on aarch64
+  bees = handleTest ./bees.nix {};
   bind = handleTest ./bind.nix {};
   bittorrent = handleTest ./bittorrent.nix {};
   #blivet = handleTest ./blivet.nix {};   # broken since 2017-07024
diff --git a/nixos/tests/beegfs.nix b/nixos/tests/beegfs.nix
deleted file mode 100644
index 3465272f5599..000000000000
--- a/nixos/tests/beegfs.nix
+++ /dev/null
@@ -1,115 +0,0 @@
-import ./make-test.nix ({ ... } :
-
-let
-  connAuthFile="beegfs/auth-def.key";
-
-  client = { pkgs, ... } : {
-    networking.firewall.enable = false;
-    services.beegfsEnable = true;
-    services.beegfs.default = {
-      mgmtdHost = "mgmt";
-      connAuthFile = "/etc/${connAuthFile}";
-      client = {
-        mount = false;
-        enable = true;
-      };
-    };
-
-    fileSystems = pkgs.lib.mkVMOverride # FIXME: this should be creatd by the module
-      [ { mountPoint = "/beegfs";
-          device = "default";
-          fsType = "beegfs";
-          options = [ "cfgFile=/etc/beegfs/client-default.conf" "_netdev" ];
-        }
-      ];
-
-    environment.etc.${connAuthFile} = {
-      enable = true;
-      text = "ThisIsALousySecret";
-      mode = "0600";
-    };
-  };
-
-
-  server = service : { pkgs, ... } : {
-    networking.firewall.enable = false;
-    boot.initrd.postDeviceCommands = ''
-      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
-    '';
-
-    virtualisation.emptyDiskImages = [ 4096 ];
-
-    fileSystems = pkgs.lib.mkVMOverride
-      [ { mountPoint = "/data";
-          device = "/dev/disk/by-label/data";
-          fsType = "ext4";
-        }
-      ];
-
-    environment.systemPackages = with pkgs; [ beegfs ];
-    environment.etc.${connAuthFile} = {
-      enable = true;
-      text = "ThisIsALousySecret";
-      mode = "0600";
-    };
-
-    services.beegfsEnable = true;
-    services.beegfs.default = {
-      mgmtdHost = "mgmt";
-      connAuthFile = "/etc/${connAuthFile}";
-      ${service} = {
-        enable = true;
-        storeDir = "/data";
-      };
-    };
-  };
-
-in
-{
-  name = "beegfs";
-
-  nodes = {
-    meta = server "meta";
-    mgmt = server "mgmtd";
-    storage1 = server "storage";
-    storage2 = server "storage";
-    client1 = client;
-    client2 = client;
-  };
-
-  testScript = ''
-    # Initalize the data directories
-    $mgmt->waitForUnit("default.target");
-    $mgmt->succeed("beegfs-setup-mgmtd -C -f -p /data");
-    $mgmt->succeed("systemctl start beegfs-mgmtd-default");
-
-    $meta->waitForUnit("default.target");
-    $meta->succeed("beegfs-setup-meta -C -f -s 1 -p /data");
-    $meta->succeed("systemctl start beegfs-meta-default");
-
-    $storage1->waitForUnit("default.target");
-    $storage1->succeed("beegfs-setup-storage -C -f -s 1 -i 1 -p /data");
-    $storage1->succeed("systemctl start beegfs-storage-default");
-
-    $storage2->waitForUnit("default.target");
-    $storage2->succeed("beegfs-setup-storage -C -f -s 2 -i 2 -p /data");
-    $storage2->succeed("systemctl start beegfs-storage-default");
-
-    #
-
-    # Basic test
-    $client1->waitForUnit("beegfs.mount");
-    $client1->succeed("beegfs-check-servers-default");
-    $client1->succeed("echo test > /beegfs/test");
-    $client2->waitForUnit("beegfs.mount");
-    $client2->succeed("test -e /beegfs/test");
-    $client2->succeed("cat /beegfs/test | grep test");
-
-    # test raid0/stripping
-    $client1->succeed("dd if=/dev/urandom bs=1M count=10 of=/beegfs/striped");
-    $client2->succeed("cat /beegfs/striped > /dev/null");
-
-    # check if fs is still healthy
-    $client1->succeed("beegfs-fsck-default --checkfs");
-  '';
-})
diff --git a/nixos/tests/bees.nix b/nixos/tests/bees.nix
index 6f68c2f834f1..6e6a9c3446b0 100644
--- a/nixos/tests/bees.nix
+++ b/nixos/tests/bees.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, pkgs, ... }:
 {
   name = "bees";
 
@@ -29,27 +29,34 @@ import ./make-test.nix ({ lib, ... }:
 
   testScript =
   let
-    withRetry = content: maxTests: sleepTime: ''
-      max_tests=${lib.escapeShellArg maxTests}; sleep_time=${lib.escapeShellArg sleepTime}; for ((i=0; i<max_tests; i++)); do ${content} && exit 0; sleep "$sleep_time"; done; exit 1;
+    someContentIsShared = loc: pkgs.writeShellScript "some-content-is-shared" ''
+      [[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]
     '';
-    someContentIsShared = loc: ''[[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]'';
   in ''
     # shut down the instance started by systemd at boot, so we can test our test procedure
-    $machine->succeed("systemctl stop beesd\@aux1.service");
+    machine.succeed("systemctl stop beesd@aux1.service")
 
-    $machine->succeed("dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8");
-    $machine->succeed("cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2");
-    $machine->succeed("cp --reflink=never /aux1/* /aux2/");
-    $machine->succeed("sync");
-    $machine->fail(q(${someContentIsShared "/aux1"}));
-    $machine->fail(q(${someContentIsShared "/aux2"}));
-    $machine->succeed("systemctl start beesd\@aux1.service");
+    machine.succeed(
+        "dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8",
+        "cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2",
+        "cp --reflink=never /aux1/* /aux2/",
+        "sync",
+    )
+    machine.fail(
+        "${someContentIsShared "/aux1"}",
+        "${someContentIsShared "/aux2"}",
+    )
+    machine.succeed("systemctl start beesd@aux1.service")
 
     # assert that "Set Shared" column is nonzero
-    $machine->succeed(q(${withRetry (someContentIsShared "/aux1") 20 2}));
-    $machine->fail(q(${someContentIsShared "/aux2"}));
+    machine.wait_until_succeeds(
+        "${someContentIsShared "/aux1"}",
+    )
+    machine.fail("${someContentIsShared "/aux2"}")
 
     # assert that 16MB hash table size requested was honored
-    $machine->succeed(q([[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]))
+    machine.succeed(
+        "[[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]"
+    )
   '';
 })
diff --git a/nixos/tests/gitolite.nix b/nixos/tests/gitolite.nix
index 690e456ed7c8..a928645bd80f 100644
--- a/nixos/tests/gitolite.nix
+++ b/nixos/tests/gitolite.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...}:
+import ./make-test-python.nix ({ pkgs, ...}:
 
 let
   adminPrivateKey = pkgs.writeText "id_ed25519" ''
@@ -43,7 +43,7 @@ let
     ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZNonUP1ePHLrvn0W9D2hdN6zWWZYFyJc+QR6pOKQEw bob@client
   '';
 
-  gitoliteAdminConfSnippet = ''
+  gitoliteAdminConfSnippet = pkgs.writeText "gitolite-admin-conf-snippet" ''
     repo alice-project
         RW+     =   alice
   '';
@@ -85,55 +85,54 @@ in
   };
 
   testScript = ''
-    startAll;
-
-    subtest "can setup ssh keys on system", sub {
-      $client->mustSucceed("mkdir -p ~root/.ssh");
-      $client->mustSucceed("cp ${adminPrivateKey} ~root/.ssh/id_ed25519");
-      $client->mustSucceed("chmod 600 ~root/.ssh/id_ed25519");
-
-      $client->mustSucceed("sudo -u alice mkdir -p ~alice/.ssh");
-      $client->mustSucceed("sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519");
-      $client->mustSucceed("sudo -u alice chmod 600 ~alice/.ssh/id_ed25519");
-
-      $client->mustSucceed("sudo -u bob mkdir -p ~bob/.ssh");
-      $client->mustSucceed("sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519");
-      $client->mustSucceed("sudo -u bob chmod 600 ~bob/.ssh/id_ed25519");
-    };
-
-    subtest "gitolite server starts", sub {
-      $server->waitForUnit("gitolite-init.service");
-      $server->waitForUnit("sshd.service");
-      $client->mustSucceed('ssh gitolite@server info');
-    };
-
-    subtest "admin can clone and configure gitolite-admin.git", sub {
-      $client->mustSucceed('git clone gitolite@server:gitolite-admin.git');
-      $client->mustSucceed("git config --global user.name 'System Administrator'");
-      $client->mustSucceed("git config --global user.email root\@domain.example");
-      $client->mustSucceed("cp ${alicePublicKey} gitolite-admin/keydir/alice.pub");
-      $client->mustSucceed("cp ${bobPublicKey} gitolite-admin/keydir/bob.pub");
-      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add keys for alice, bob" && git push)');
-      $client->mustSucceed("printf '${gitoliteAdminConfSnippet}' >> gitolite-admin/conf/gitolite.conf");
-      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add repo for alice" && git push)');
-    };
-
-    subtest "non-admins cannot clone gitolite-admin.git", sub {
-      $client->mustFail('sudo -i -u alice git clone gitolite@server:gitolite-admin.git');
-      $client->mustFail('sudo -i -u bob git clone gitolite@server:gitolite-admin.git');
-    };
-
-    subtest "non-admins can clone testing.git", sub {
-      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:testing.git');
-      $client->mustSucceed('sudo -i -u bob git clone gitolite@server:testing.git');
-    };
-
-    subtest "alice can clone alice-project.git", sub {
-      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:alice-project.git');
-    };
-
-    subtest "bob cannot clone alice-project.git", sub {
-      $client->mustFail('sudo -i -u bob git clone gitolite@server:alice-project.git');
-    };
+    start_all()
+
+    with subtest("can setup ssh keys on system"):
+        client.succeed(
+            "mkdir -p ~root/.ssh",
+            "cp ${adminPrivateKey} ~root/.ssh/id_ed25519",
+            "chmod 600 ~root/.ssh/id_ed25519",
+        )
+        client.succeed(
+            "sudo -u alice mkdir -p ~alice/.ssh",
+            "sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519",
+            "sudo -u alice chmod 600 ~alice/.ssh/id_ed25519",
+        )
+        client.succeed(
+            "sudo -u bob mkdir -p ~bob/.ssh",
+            "sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519",
+            "sudo -u bob chmod 600 ~bob/.ssh/id_ed25519",
+        )
+
+    with subtest("gitolite server starts"):
+        server.wait_for_unit("gitolite-init.service")
+        server.wait_for_unit("sshd.service")
+        client.succeed("ssh gitolite@server info")
+
+    with subtest("admin can clone and configure gitolite-admin.git"):
+        client.succeed(
+            "git clone gitolite@server:gitolite-admin.git",
+            "git config --global user.name 'System Administrator'",
+            "git config --global user.email root\@domain.example",
+            "cp ${alicePublicKey} gitolite-admin/keydir/alice.pub",
+            "cp ${bobPublicKey} gitolite-admin/keydir/bob.pub",
+            "(cd gitolite-admin && git add . && git commit -m 'Add keys for alice, bob' && git push)",
+            "cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf",
+            "(cd gitolite-admin && git add . && git commit -m 'Add repo for alice' && git push)",
+        )
+
+    with subtest("non-admins cannot clone gitolite-admin.git"):
+        client.fail("sudo -i -u alice git clone gitolite@server:gitolite-admin.git")
+        client.fail("sudo -i -u bob git clone gitolite@server:gitolite-admin.git")
+
+    with subtest("non-admins can clone testing.git"):
+        client.succeed("sudo -i -u alice git clone gitolite@server:testing.git")
+        client.succeed("sudo -i -u bob git clone gitolite@server:testing.git")
+
+    with subtest("alice can clone alice-project.git"):
+        client.succeed("sudo -i -u alice git clone gitolite@server:alice-project.git")
+
+    with subtest("bob cannot clone alice-project.git"):
+        client.fail("sudo -i -u bob git clone gitolite@server:alice-project.git")
   '';
 })
diff --git a/nixos/tests/influxdb.nix b/nixos/tests/influxdb.nix
index 61201202204b..04ef80461010 100644
--- a/nixos/tests/influxdb.nix
+++ b/nixos/tests/influxdb.nix
@@ -1,6 +1,6 @@
 # This test runs influxdb and checks if influxdb is up and running
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "influxdb";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ offline ];
@@ -9,25 +9,32 @@ import ./make-test.nix ({ pkgs, ...} : {
   nodes = {
     one = { ... }: {
       services.influxdb.enable = true;
+      environment.systemPackages = [ pkgs.httpie ];
     };
   };
 
   testScript = ''
-    startAll;
-  
-    $one->waitForUnit("influxdb.service");
+    import shlex
+
+    start_all()
+
+    one.wait_for_unit("influxdb.service")
 
     # create database
-    $one->succeed(q~
-      curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE test"
-    ~);
+    one.succeed(
+        "curl -XPOST http://localhost:8086/query --data-urlencode 'q=CREATE DATABASE test'"
+    )
 
     # write some points and run simple query
-    $one->succeed(q~
-      curl -XPOST 'http://localhost:8086/write?db=test' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'
-    ~);
-    $one->succeed(q~
-      curl -GET 'http://localhost:8086/query' --data-urlencode "db=test" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"  | grep "0\.64"
-    ~);
+    out = one.succeed(
+        "curl -XPOST 'http://localhost:8086/write?db=test' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'"
+    )
+
+    qv = "SELECT value FROM cpu_load_short WHERE region='us-west'"
+    cmd = f'curl -GET "http://localhost:8086/query?db=test" --data-urlencode {shlex.quote("q="+ qv)}'
+    out = one.succeed(cmd)
+
+    assert "2015-06-11T20:46:02Z" in out
+    assert "0.64" in out
   '';
 })
diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix
index a6eec411ff28..cd64ff512878 100644
--- a/nixos/tests/jenkins.nix
+++ b/nixos/tests/jenkins.nix
@@ -3,7 +3,7 @@
 #   2. jenkins user can be extended on both master and slave
 #   3. jenkins service not started on slave node
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "jenkins";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ bjornfor coconnor domenkozar eelco ];
@@ -33,18 +33,17 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    startAll;
+    start_all()
 
-    $master->waitForUnit("jenkins");
+    master.wait_for_unit("jenkins")
 
-    $master->mustSucceed("curl http://localhost:8080 | grep 'Authentication required'");
+    assert "Authentication required" in master.succeed("curl http://localhost:8080")
 
-    print $master->execute("sudo -u jenkins groups");
-    $master->mustSucceed("sudo -u jenkins groups | grep jenkins | grep users");
+    for host in master, slave:
+        groups = host.succeed("sudo -u jenkins groups")
+        assert "jenkins" in groups
+        assert "users" in groups
 
-    print $slave->execute("sudo -u jenkins groups");
-    $slave->mustSucceed("sudo -u jenkins groups | grep jenkins | grep users");
-
-    $slave->mustFail("systemctl is-enabled jenkins.service");
+    slave.fail("systemctl is-enabled jenkins.service")
   '';
 })
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
index f1218b537711..3b0619742671 100644
--- a/nixos/tests/minio.nix
+++ b/nixos/tests/minio.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, ...} :
 let
     accessKey = "BKIKJAA5BMMU2RHO6IBB";
     secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
@@ -18,7 +18,7 @@ let
       sio.seek(0)
       minioClient.put_object('test-bucket', 'test.txt', sio, sio_len, content_type='text/plain')
     '';
-  in {
+in {
   name = "minio";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ bachp ];
@@ -37,19 +37,19 @@ let
     };
   };
 
-  testScript =
-    ''
-      startAll;
-      $machine->waitForUnit("minio.service");
-      $machine->waitForOpenPort(9000);
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("minio.service")
+    machine.wait_for_open_port(9000)
 
-      # Create a test bucket on the server
-      $machine->succeed("mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} S3v4");
-      $machine->succeed("mc mb minio/test-bucket");
-      $machine->succeed("${minioPythonScript}");
-      $machine->succeed("mc ls minio") =~ /test-bucket/ or die;
-      $machine->succeed("mc cat minio/test-bucket/test.txt") =~ /Test from Python/ or die;
-      $machine->shutdown;
-
-    '';
+    # Create a test bucket on the server
+    machine.succeed(
+        "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} S3v4"
+    )
+    machine.succeed("mc mb minio/test-bucket")
+    machine.succeed("${minioPythonScript}")
+    assert "test-bucket" in machine.succeed("mc ls minio")
+    assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
+    machine.shutdown()
+  '';
 })
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
index fc16b99cc19c..5bbec3d57269 100644
--- a/nixos/tests/sudo.nix
+++ b/nixos/tests/sudo.nix
@@ -4,7 +4,7 @@ let
   password = "helloworld";
 
 in
-  import ./make-test.nix ({ pkgs, ...} : {
+  import ./make-test-python.nix ({ pkgs, ...} : {
     name = "sudo";
     meta = with pkgs.stdenv.lib.maintainers; {
       maintainers = [ lschuermann ];
@@ -50,44 +50,34 @@ in
 
     testScript =
       ''
-        subtest "users in wheel group should have passwordless sudo", sub {
-            $machine->succeed("su - test0 -c \"sudo -u root true\"");
-        };
+        with subtest("users in wheel group should have passwordless sudo"):
+            machine.succeed('su - test0 -c "sudo -u root true"')
 
-        subtest "test1 user should have sudo with password", sub {
-            $machine->succeed("su - test1 -c \"echo ${password} | sudo -S -u root true\"");
-        };
+        with subtest("test1 user should have sudo with password"):
+            machine.succeed('su - test1 -c "echo ${password} | sudo -S -u root true"')
 
-        subtest "test1 user should not be able to use sudo without password", sub {
-            $machine->fail("su - test1 -c \"sudo -n -u root true\"");
-        };
+        with subtest("test1 user should not be able to use sudo without password"):
+            machine.fail('su - test1 -c "sudo -n -u root true"')
 
-        subtest "users in group 'foobar' should be able to use sudo with password", sub {
-            $machine->succeed("sudo -u test2 echo ${password} | sudo -S -u root true");
-        };
+        with subtest("users in group 'foobar' should be able to use sudo with password"):
+            machine.succeed("sudo -u test2 echo ${password} | sudo -S -u root true")
 
-        subtest "users in group 'barfoo' should be able to use sudo without password", sub {
-            $machine->succeed("sudo -u test3 sudo -n -u root true");
-        };
+        with subtest("users in group 'barfoo' should be able to use sudo without password"):
+            machine.succeed("sudo -u test3 sudo -n -u root true")
 
-        subtest "users in group 'baz' (GID 1337) should be able to use sudo without password", sub {
-            $machine->succeed("sudo -u test4 sudo -n -u root echo true");
-        };
+        with subtest("users in group 'baz' (GID 1337)"):
+            machine.succeed("sudo -u test4 sudo -n -u root echo true")
 
-        subtest "test5 user should be able to run commands under test1", sub {
-            $machine->succeed("sudo -u test5 sudo -n -u test1 true");
-        };
+        with subtest("test5 user should be able to run commands under test1"):
+            machine.succeed("sudo -u test5 sudo -n -u test1 true")
 
-        subtest "test5 user should not be able to run commands under root", sub {
-            $machine->fail("sudo -u test5 sudo -n -u root true");
-        };
+        with subtest("test5 user should not be able to run commands under root"):
+            machine.fail("sudo -u test5 sudo -n -u root true")
 
-        subtest "test5 user should be able to keep his environment", sub {
-            $machine->succeed("sudo -u test5 sudo -n -E -u test1 true");
-        };
+        with subtest("test5 user should be able to keep his environment"):
+            machine.succeed("sudo -u test5 sudo -n -E -u test1 true")
 
-        subtest "users in group 'barfoo' should not be able to keep their environment", sub {
-            $machine->fail("sudo -u test3 sudo -n -E -u root true");
-        };
+        with subtest("users in group 'barfoo' should not be able to keep their environment"):
+            machine.fail("sudo -u test3 sudo -n -E -u root true")
       '';
   })
diff --git a/nixos/tests/uwsgi.nix b/nixos/tests/uwsgi.nix
index afc03e74ed7e..78a87147f55c 100644
--- a/nixos/tests/uwsgi.nix
+++ b/nixos/tests/uwsgi.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, ... }:
 {
   name = "uwsgi";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -30,9 +30,9 @@ import ./make-test.nix ({ pkgs, ... }:
 
   testScript =
     ''
-      $machine->waitForUnit('multi-user.target');
-      $machine->waitForUnit('uwsgi.service');
-      $machine->waitForOpenPort(8000);
-      $machine->succeed('curl -v 127.0.0.1:8000 | grep "Hello World!"');
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("uwsgi.service")
+      machine.wait_for_open_port(8000)
+      assert "Hello World" in machine.succeed("curl -v 127.0.0.1:8000")
     '';
 })
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index c6acfa6c1f3d..64c533d70f42 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -1,9 +1,13 @@
-import ./make-test.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, ... }:
 
 {
   name = "wordpress";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ grahamc ]; # under duress!
+    maintainers = [
+      flokli
+      grahamc # under duress!
+      mmilata
+    ];
   };
 
   machine =
@@ -23,19 +27,31 @@ import ./make-test.nix ({ pkgs, ... }:
     };
 
   testScript = ''
-    startAll;
+    import re
 
-    $machine->waitForUnit("httpd");
-    $machine->waitForUnit("phpfpm-wordpress-site1.local");
-    $machine->waitForUnit("phpfpm-wordpress-site2.local");
+    start_all()
 
-    $machine->succeed("curl -L site1.local | grep 'Welcome to the famous'");
-    $machine->succeed("curl -L site2.local | grep 'Welcome to the famous'");
+    machine.wait_for_unit("httpd")
 
-    $machine->succeed("systemctl --no-pager show wordpress-init-site1.local.service | grep 'ExecStart=.*status=0'");
-    $machine->succeed("systemctl --no-pager show wordpress-init-site2.local.service | grep 'ExecStart=.*status=0'");
-    $machine->succeed("grep -E '^define.*NONCE_SALT.{64,};\$' /var/lib/wordpress/site1.local/secret-keys.php");
-    $machine->succeed("grep -E '^define.*NONCE_SALT.{64,};\$' /var/lib/wordpress/site2.local/secret-keys.php");
-  '';
+    machine.wait_for_unit("phpfpm-wordpress-site1.local")
+    machine.wait_for_unit("phpfpm-wordpress-site2.local")
+
+    site_names = ["site1.local", "site2.local"]
+
+    with subtest("website returns welcome screen"):
+        for site_name in site_names:
+            assert "Welcome to the famous" in machine.succeed(f"curl -L {site_name}")
 
+    with subtest("wordpress-init went through"):
+        for site_name in site_names:
+            info = machine.get_unit_info(f"wordpress-init-{site_name}")
+            assert info.Result == "success"
+
+    with subtest("secret keys are set"):
+        re.compile(r"^define.*NONCE_SALT.{64,};$")
+        for site_name in site_names:
+            assert r.match(
+                machine.succeed(f"cat /var/lib/wordpress/{site_name}/secret-keys.php")
+            )
+  '';
 })