diff --git a/arion.nix b/arion.nix
index 7a2a04eb4cf7e871c72a9e98ea50108cf6bfb813..7c079a663cc41b010fcfbd569de238ee57ac3d5b 100644
--- a/arion.nix
+++ b/arion.nix
@@ -47,5 +47,11 @@ let
      ln -sr ${docker-yaml} $out/docker-compose.yaml
      '';
    pause = pkgs.callPackage ./pause.nix {};
+   morph = (import "${pkgs.morph.lib}/eval-machines.nix") {
+      networkExpr = morph/grid/production/grid.nix;
+   };
+   morph-output = morph.machines {
+      argsFile = pkgs.writeText "" (builtins.toJSON { Names = lib.attrNames morph.nodes; });
+   };
 in
-  {inherit pkgs arion local-grid bundle arion-eval arion-src docker-yaml package pause systems;}
+  {inherit pkgs arion local-grid bundle arion-eval arion-src docker-yaml package pause systems morph morph-output;}
diff --git a/morph/grid/local/arion-compose.nix b/morph/grid/local/arion-compose.nix
index fec5ecf8558dcb4b437aa29f36286d064655f563..9d56530b440f9628c7802472219518654af3ac65 100644
--- a/morph/grid/local/arion-compose.nix
+++ b/morph/grid/local/arion-compose.nix
@@ -67,20 +67,35 @@ in
   services.storage2 = storage-node { monitor-ip = "172.23.23.13"; public-ip = "10.88.2.31"; };
   services.payment = { pkgs, lib, ...}: {
     imports = [ (node { monitor-ip = "172.23.23.11"; public-ip = "10.88.2.32"; }) ];
-    nixos.configuration = {config, ...}: {
-      imports = [
-        gridlib.issuer
-          (gridlib.customize-issuer (rawConfig // {
-            monitoringvpnIPv4 = "172.23.23.11";
-            }))
-      ];
-      environment.etc = {
-        "secrets/payments-localdev-ssl" = {
-          mode = "direct-symlink";
-          source = "${config.deployment.privateKeyPath}/payments-localdev-ssl";
+    config = {
+      nixos.configuration = {config, pkgs, ...}: {
+        imports = [
+          gridlib.issuer
+            (gridlib.customize-issuer (rawConfig // {
+              monitoringvpnIPv4 = "172.23.23.11";
+              }))
+        ];
+        environment.etc = {
+          "secrets/payments-localdev-ssl" = {
+            mode = "direct-symlink";
+            source = "${config.deployment.privateKeyPath}/payments-localdev-ssl";
+          };
+          "stripe/config.toml" = let
+            toml = pkgs.formats.toml {};
+            api-key = lib.fileContents config.deployment.secrets.stripe-secret-key.source;
+          in {
+            source = toml.generate "stripe.conf" {
+              default = {
+                device_name = "localdev";
+                test_mode_api_key = api-key;
+              };
+            };
+          };
         };
+        services.private-storage-issuer.tls = lib.mkForce false;
+        environment.systemPackages = [ pkgs.stripe-cli ];
       };
-      services.private-storage-issuer.tls = lib.mkForce false;
+      service.ports = ["8080:80"];
     };
   };
   docker-compose.raw = {
diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index 3010cab5d2412e512c641479dd39c66d32b5629d..0bb378d454871f3f5d14fce9abd75852e4716815 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -14,12 +14,15 @@ let
 
   # Configure deployment management authorization for all systems in the grid.
   grid-config = {
+    imports = [
+      gridlib.base
+    ];
     services.private-storage.deployment = {
       authorizedKey = builtins.readFile "${config.publicKeyPath}/deploy_key.pub";
       gridName = "local";
     };
     services.private-storage.sshUsers = config.sshUsers;
-    deployment = {
+    grid = {
       inherit (config) publicKeyPath privateKeyPath; 
     };
   };
@@ -67,7 +70,7 @@ let
       gridlib.monitoring
       gridlib.hardware-virtual
       (gridlib.customize-monitoring {
-        inherit hostsMap vpnClientIPs nodeExporterTargets paymentExporterTargets;
+        inherit paymentExporterTargets;
         inherit (config) domain letsEncryptAdminEmail;
         googleOAuthClientID = config.monitoringGoogleOAuthClientID;
         monitoringvpnIPv4 = "172.23.23.1";
@@ -79,14 +82,6 @@ let
   };
 
   # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [ "payments" "payments.monitoringvpn" ];
-    "172.23.23.12" = [ "storage1" "storage1.monitoringvpn" ];
-    "172.23.23.13" = [ "storage2" "storage2.monitoringvpn" ];
-  };
-  vpnClientIPs = [ "172.23.23.11" "172.23.23.12" "172.23.23.13" ];
-  nodeExporterTargets = [ "monitoring" "payments" "storage1" "storage2" ];
   paymentExporterTargets = [ "payments" ];
 
 in {
diff --git a/morph/grid/local/private-keys/stripe.secret b/morph/grid/local/private-keys/stripe.secret
index ebf3fdabcd222af9ca7ed46bf0b45a2fd18a603e..c94054b98fd652d465a094591d1d4c909642fd6e 100644
--- a/morph/grid/local/private-keys/stripe.secret
+++ b/morph/grid/local/private-keys/stripe.secret
@@ -1 +1 @@
-sk_test_Dr+XLVjkC0oO3Zw8Ws0yWtDLqR1sM+/fmw
+sk_test_Hrs6SAopgFPF0bZXSN3f6ELN
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 5618bcf89128d5cefbb0d084383605b661d4d052..7d43a0ce2c59bbb6ddee005aba9393836d2329c1 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -13,12 +13,16 @@ let
 
   # Configure deployment management authorization for all systems in the grid.
   grid-config = {
+    imports = [
+      gridlib.base
+    ];
+
     services.private-storage.deployment = {
       authorizedKey = builtins.readFile "${config.publicKeyPath}/deploy_key.pub";
       gridName = "production";
     };
     services.private-storage.sshUsers = config.sshUsers;
-    deployment = {
+    grid = {
       inherit (config) publicKeyPath privateKeyPath; 
     };
   };
@@ -32,6 +36,7 @@ let
       }))
       grid-config
     ];
+    monitoringvpnIPv4 = "172.23.23.11";
   };
 
   monitoring = {
@@ -39,13 +44,14 @@ let
       gridlib.monitoring
       gridlib.hardware-aws
       (gridlib.customize-monitoring {
-        inherit hostsMap vpnClientIPs nodeExporterTargets paymentExporterTargets;
+        inherit paymentExporterTargets;
         inherit (config) domain letsEncryptAdminEmail;
         googleOAuthClientID = config.monitoringGoogleOAuthClientID;
         monitoringvpnIPv4 = "172.23.23.1";
       })
       grid-config
     ];
+    monitoringvpnIPv4 = "172.23.23.1";
     system.stateVersion = "19.09";
   };
 
@@ -75,6 +81,7 @@ let
       grid-config
     ];
 
+    monitoringvpnIPv4 = vpnIP;
     system.stateVersion = stateVersion;
     # And supply configuration for those hardware / network / bootloader
     # options.  See the 100tb module for handling of this value.  The module
@@ -93,32 +100,6 @@ let
   };
 
   # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [   "payments"   "payments.monitoringvpn" ];
-    "172.23.23.21" = [ "storage001" "storage001.monitoringvpn" ];
-    "172.23.23.22" = [ "storage002" "storage002.monitoringvpn" ];
-    "172.23.23.23" = [ "storage003" "storage003.monitoringvpn" ];
-    "172.23.23.24" = [ "storage004" "storage004.monitoringvpn" ];
-    "172.23.23.25" = [ "storage005" "storage005.monitoringvpn" ];
-  };
-  vpnClientIPs = [
-    "172.23.23.11"
-    "172.23.23.21"
-    "172.23.23.22"
-    "172.23.23.23"
-    "172.23.23.24"
-    "172.23.23.25"
-  ];
-  nodeExporterTargets = [
-    "monitoring"
-    "payments"
-    "storage001"
-    "storage002"
-    "storage003"
-    "storage004"
-    "storage005"
-  ];
   paymentExporterTargets = [ "payments" ];
 
 in {
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index 426ede65048623c027f5bb2b4f5d296364f4598f..5eeb43938c5c2df5a30df3d605fec26090dc4e0c 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -13,12 +13,16 @@ let
 
   # Configure deployment management authorization for all systems in the grid.
   grid-config = {
+    imports = [
+      gridlib.base
+    ];
+
     services.private-storage.deployment = {
       authorizedKey = builtins.readFile "${config.publicKeyPath}/deploy_key.pub";
       gridName = "testing";
     };
     services.private-storage.sshUsers = config.sshUsers;
-    deployment = {
+    grid = {
       inherit (config) publicKeyPath privateKeyPath; 
     };
   };
@@ -63,13 +67,6 @@ let
   };
 
   # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [ "payments"   "payments.monitoringvpn"   ];
-    "172.23.23.12" = [ "storage001" "storage001.monitoringvpn" ];
-  };
-  vpnClientIPs = [ "172.23.23.11" "172.23.23.12" ];
-  nodeExporterTargets = [ "monitoring" "payments" "storage001" ];
   paymentExporterTargets = [ "payments" ];
 
 in {
diff --git a/morph/lib/customize-issuer.nix b/morph/lib/customize-issuer.nix
index e71543f1458216faea87fe6512c037b7173c3930..b3d36c4fec454564a65e75be08070a9107e784c6 100644
--- a/morph/lib/customize-issuer.nix
+++ b/morph/lib/customize-issuer.nix
@@ -29,18 +29,9 @@
 }:
 { config, ... }: 
 let
-  inherit (config.deployment) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath;
   inherit (config) monitoringvpnIPv4;
 in {
-  imports = [ ./deployment.nix ];
-  # The morph default deployment target the name of the node in the network
-  # attrset.  We don't always want to give the node its proper public address
-  # there (because it depends on which domain is associated with the grid
-  # being configured and using variable names complicates a lot of things).
-  # Instead, just tell morph how to reach the node here - by using its fully
-  # qualified domain name.
-  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
-
   networking.domain = domain;
 
   services.private-storage.monitoring.vpn.client = {
diff --git a/morph/lib/customize-monitoring.nix b/morph/lib/customize-monitoring.nix
index 6a81186574c6311b56a9120483bb6ce94bc43692..fedb51ea10ba9e9c36ddfe67e816699b5bcaccfd 100644
--- a/morph/lib/customize-monitoring.nix
+++ b/morph/lib/customize-monitoring.nix
@@ -1,26 +1,11 @@
 # Define a function which returns a value which fills in all the holes left by
 # ``monitoring.nix``.
 {
-  # A set mapping VPN IP addresses as strings to lists of hostnames as
-  # strings.  The system's ``/etc/hosts`` will be populated with this
-  # information.  Apart from helping with normal forward resolution, this
-  # *also* gives us reverse resolution from the VPN IPs to hostnames which
-  # allows Grafana to show us hostnames instead of VPN IP addresses.
-  hostsMap
-
   # See ``customize-issuer.nix``.
-, monitoringvpnIPv4
+  monitoringvpnIPv4
 , domain
 , letsEncryptAdminEmail
 
-  # A list of VPN IP addresses as strings indicating which clients will be
-  # allowed onto the VPN.
-, vpnClientIPs
-
-  # A list of VPN clients (IP addresses or hostnames) as strings indicating
-  # which nodes to scrape "nodeExporter" metrics from.
-, nodeExporterTargets
-
   # A list of VPN clients (IP addresses or hostnames) as strings indicating
   # which nodes to scrape "nginxExporter" metrics from.
 , nginxExporterTargets ? []
@@ -35,14 +20,26 @@
 
 , ...
 }:
-{ config, ... }:
+{ lib, nodes, config, ... }:
 let
-  inherit (config.deployment) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath;
+  clients = lib.mapAttrs (n: v: let
+      vpnConfig = v.config.services.private-storage.monitoring.vpn;
+    in if lib.attrByPath ["client" "enable"] false vpnConfig then {
+      vpn = true; ip = vpnConfig.client.ip;
+    } else {
+      vpn = false; ip = vpnConfig.server.ip;
+    }) nodes;
+    
+  vpnClientIPs = lib.mapAttrsToList (name: node: node.ip) (lib.filterAttrs (n: v: v.vpn) clients);
+  nodeExporterTargets = lib.attrNames clients;
+  # A set mapping VPN IP addresses as strings to lists of hostnames as
+  # strings.  The system's ``/etc/hosts`` will be populated with this
+  # information.  Apart from helping with normal forward resolution, this
+  # *also* gives us reverse resolution from the VPN IPs to hostnames which
+  # allows Grafana to show us hostnames instead of VPN IP addresses.
+  hostsMap = lib.mapAttrs' (n: v: { name = v.ip; value = [n "${n}.monitoringvpn" ]; }) clients; 
 in {
-  imports = [ ./deployment.nix ];
-  # See customize-issuer.nix for an explanatoin of targetHost value.
-  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
-
   deployment.secrets = let
     # When Grafana SSO is disabled there is not necessarily any client secret
     # available.  Avoid telling morph that there is one in this case (so it
diff --git a/morph/lib/customize-storage.nix b/morph/lib/customize-storage.nix
index 805101257e9bc823de5efb0b6b6603f9830bdc9c..7ccdad3ea7b62e2a41454688fc088b15d716baa8 100644
--- a/morph/lib/customize-storage.nix
+++ b/morph/lib/customize-storage.nix
@@ -16,13 +16,9 @@
 }:
 { config, ... }: 
 let
-  inherit (config.deployment) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath;
   inherit (config) monitoringvpnIPv4;
 in {
-  imports = [ ./deployment.nix ];
-  # See customize-issuer.nix for an explanatoin of targetHost value.
-  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
-
   networking.domain = domain;
 
   services.private-storage = {
diff --git a/morph/lib/default.nix b/morph/lib/default.nix
index 3c620a0479f0f0fc4811607fdbf7b8426e2b4a4f..6d06aa29e785b4251031a01c1df171fac308ef6b 100644
--- a/morph/lib/default.nix
+++ b/morph/lib/default.nix
@@ -2,6 +2,8 @@
 # coherent public interface.  Application code should prefer these names over
 # directly importing the source files in this directory.
 {
+  base = import ./base.nix;
+
   hardware-aws = import ./issuer-aws.nix;
   hardware-virtual = import ./hardware-virtual.nix;
   hardware-docker = import ./hardware-docker.nix;
@@ -14,4 +16,6 @@
 
   monitoring = import ./monitoring.nix;
   customize-monitoring = import ./customize-monitoring.nix;
+
+  modules = ../nixos/modules;
 }
diff --git a/morph/lib/deployment.nix b/morph/lib/deployment.nix
index 0e4fc8b088d5dfa68dd03bf63a172b5c7ee7f65e..66d94ecc6852e3f0b3bba5ba8f630a3d9dbefebe 100644
--- a/morph/lib/deployment.nix
+++ b/morph/lib/deployment.nix
@@ -3,22 +3,4 @@
 let
   cfg = config.hardware-virtual;
 in {
-  options.deployment = {
-    publicKeyPath = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-      A path on the deployment system of a directory containing all of the
-      public keys for the system.  For example, this holds Wireguard public keys
-      for the VPN configuration and SSH public keys to configure SSH
-      authentication.
-      '';
-    };
-    privateKeyPath = lib.mkOption {
-      type = lib.types.path;
-      description = ''
-      A path on the deployment system of a directory containing all of the
-      corresponding private keys for the system.
-      '';
-    };
-  };
 }
diff --git a/morph/lib/issuer.nix b/morph/lib/issuer.nix
index 9b62a808afb55816a7596dcc908abb585984de02..0c81f32e5567f7720e91401f72ac56f13d509ac0 100644
--- a/morph/lib/issuer.nix
+++ b/morph/lib/issuer.nix
@@ -6,15 +6,12 @@
 # will be filled by a sibling module created by ``customize-issuer.nix``.
 { lib, config, ...}:
 let
-  inherit (config.deployment) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath;
   inherit (config) monitoringvpnIPv4;
 in {
   options.monitoringvpnIPv4 = lib.mkOption {};
 
   imports = [
-    # Allow us to remotely trigger updates to this system.
-    ../../nixos/modules/deployment.nix
-
     ../../nixos/modules/issuer.nix
     ../../nixos/modules/monitoring/vpn/client.nix
     ../../nixos/modules/monitoring/exporters/node.nix
diff --git a/morph/lib/monitoring.nix b/morph/lib/monitoring.nix
index 8dedab10ecd41201948b298d82b6deff4da9f7bc..8e6a135ab8dc84e4766bed786a5b469ae9092f33 100644
--- a/morph/lib/monitoring.nix
+++ b/morph/lib/monitoring.nix
@@ -1,33 +1,36 @@
 # Similar to ``issuer.nix`` but for a "monitoring"-type system.  Holes are
 # filled by ``customize-monitoring.nix``.
-{
-  deployment = {
-    secrets = {
-      "monitoringvpn-private-key" = {
-        destination = "/run/keys/monitoringvpn/server.key";
-	source = "${privateKeyPath}/monitoringvpn/server.key";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
-      };
-      "monitoringvpn-preshared-key" = {
-        destination = "/run/keys/monitoringvpn/preshared.key";
-	source = "${privateKeyPath}/monitoringvpn/preshared.key";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+{ lib, config, ...}:
+let
+  inherit (config.grid) publicKeyPath privateKeyPath;
+  inherit (config) monitoringvpnIPv4;
+in {
+  options.monitoringvpnIPv4 = lib.mkOption {};
+
+  config = {
+    deployment = {
+      secrets = {
+        "monitoringvpn-private-key" = {
+          destination = "/run/keys/monitoringvpn/server.key";
+          source = "${privateKeyPath}/monitoringvpn/server.key";
+          owner.user = "root";
+          owner.group = "root";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+        };
+        "monitoringvpn-preshared-key" = {
+          destination = "/run/keys/monitoringvpn/preshared.key";
+          source = "${privateKeyPath}/monitoringvpn/preshared.key";
+          owner.user = "root";
+          owner.group = "root";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+        };
       };
     };
   };
 
   imports = [
-    # Give it a good SSH configuration.
-    ../../nixos/modules/ssh.nix
-    # Allow us to remotely trigger updates to this system.
-    ../../nixos/modules/deployment.nix
-
     ../../nixos/modules/monitoring/vpn/server.nix
     ../../nixos/modules/monitoring/server/grafana.nix
     ../../nixos/modules/monitoring/server/prometheus.nix
diff --git a/morph/lib/storage.nix b/morph/lib/storage.nix
index a127e08968618771d8660569af275de5ebc869fb..27d8c9dd7e019c53b42ea6150d8153c97ad284ad 100644
--- a/morph/lib/storage.nix
+++ b/morph/lib/storage.nix
@@ -2,15 +2,13 @@
 # by ``customize-storage.nix``.
 { lib, config, ...} :
 let
-  inherit (config.deployment) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath;
   inherit (config) monitoringvpnIPv4;
 in {
   options.monitoringvpnIPv4 = lib.mkOption {};
 
   # Any extra NixOS modules to load on this server.
   imports = [
-    # Allow us to remotely trigger updates to this system.
-    ../../nixos/modules/deployment.nix
     # Bring in our module for configuring the Tahoe-LAFS service and other
     # Private Storage-specific things.
     ../../nixos/modules/private-storage.nix
diff --git a/nixos/modules/issuer.nix b/nixos/modules/issuer.nix
index ce1f928b2738066811425a3c7e3e3c85c03ac272..3a8d05bfb8afbaad63fac0ee52f0bdf3d3cbb956 100644
--- a/nixos/modules/issuer.nix
+++ b/nixos/modules/issuer.nix
@@ -5,11 +5,6 @@
   # Our own nixpkgs fork:
   ourpkgs = import ../../nixpkgs-ps.nix {};
 in {
-  imports = [
-    # Give it a good SSH configuration.
-    ../../nixos/modules/ssh.nix
-  ];
-
   options = {
     services.private-storage-issuer.enable = lib.mkEnableOption "PrivateStorage ZKAP Issuer Service";
     services.private-storage-issuer.package = lib.mkOption {
diff --git a/nixos/modules/private-storage.nix b/nixos/modules/private-storage.nix
index fa5fea837c544e66ae8811a2e3c468a67a18759e..d3bc9e61bb8a805d4432edf7d37d51a9501ecc1e 100644
--- a/nixos/modules/private-storage.nix
+++ b/nixos/modules/private-storage.nix
@@ -30,8 +30,6 @@ let
 in
 {
   imports = [
-    # Give it a good SSH configuration.
-    ./ssh.nix
     # Load our tahoe-lafs module.  It is configurable in the way I want it to
     # be configurable.
     ./tahoe.nix
diff --git a/shell.nix b/shell.nix
index 1cdd35c2de63c75b163227e1f962680a360a9cc9..5254462f9e2b3bda675dbb57f4481868238cce73 100644
--- a/shell.nix
+++ b/shell.nix
@@ -11,5 +11,6 @@ pkgs.mkShell {
     pkgs.jp
     pkgs.just
     pkgs.morph
+    pkgs.stripe-cli
   ] ++ pkgs.lib.optional vagrant pkgs.vagrant;
 }