diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index 4a1524c6b6b7f5e085766aec6a79af5b569e72ba..ed3cda9e787ba567c5c5eed19557fc281a80ceb6 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -59,6 +59,7 @@ let
     grid = {
       publicKeyPath = toString ./. + "/${grid-config.publicKeyPath}";
       privateKeyPath = toString ./. + "/${grid-config.privateKeyPath}";
+      inherit (grid-config) monitoringvpnEndpoint;
     };
     # Configure deployment management authorization for all systems in the grid.
     services.private-storage.deployment = {
@@ -70,41 +71,44 @@ let
   payments = {
     imports = [
       gridlib.issuer
-      (gridlib.customize-issuer (grid-config // {
-          monitoringvpnIPv4 = "172.23.23.11";
-      }))
       grid-module
     ];
     config = {
+      grid.monitoringvpnIPv4 = "172.23.23.11";
       grid.publicIPv4 = "192.168.67.21";
+      grid.issuer = {
+        inherit (grid-config) letsEncryptAdminEmail issuerDomains allowedChargeOrigins;
+      };
     };
   };
 
   storage1 = {
     imports = [
       gridlib.storage
-      (gridlib.customize-storage (grid-config // {
-        monitoringvpnIPv4 = "172.23.23.12";
-        stateVersion = "19.09";
-      }))
       grid-module
     ];
     config = {
+      grid.monitoringvpnIPv4 = "172.23.23.12";
       grid.publicIPv4 = "192.168.67.22";
+      grid.storage = {
+        inherit (grid-config) passValue publicStoragePort;
+      };
+      system.stateVersion = "19.09";
     };
   };
 
   storage2 = {
     imports = [
       gridlib.storage
-      (gridlib.customize-storage (grid-config // {
-        monitoringvpnIPv4 = "172.23.23.13";
-        stateVersion = "19.09";
-      }))
       grid-module
     ];
     config = {
+      grid.monitoringvpnIPv4 = "172.23.23.13";
       grid.publicIPv4 = "192.168.67.23";
+      grid.storage = {
+        inherit (grid-config) passValue publicStoragePort;
+      };
+      system.stateVersion = "19.09";
     };
   };
 
@@ -119,12 +123,12 @@ let
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
         enableSlackAlert = false;
-        monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
       grid-module
     ];
     config = {
+      grid.monitoringvpnIPv4 = "172.23.23.1";
       grid.publicIPv4 = "192.168.67.24";
     };
   };
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 950282f5573560f76355bcdcf4d6da51dacedd7d..12873a4bc010887c54f522a01115061e9875726f 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -21,6 +21,7 @@ let
     grid = {
       publicKeyPath = toString ./. + "/${grid-config.publicKeyPath}";
       privateKeyPath = toString ./. + "/${grid-config.privateKeyPath}";
+      inherit (grid-config) monitoringvpnEndpoint;
     };
     # Configure deployment management authorization for all systems in the grid.
     services.private-storage.deployment = {
@@ -33,11 +34,14 @@ let
     imports = [
       gridlib.issuer
       gridlib.hardware-aws
-      (gridlib.customize-issuer (grid-config // {
-        monitoringvpnIPv4 = "172.23.23.11";
-      }))
       grid-module
     ];
+    config = {
+      grid.monitoringvpnIPv4 = "172.23.23.11";
+      grid.issuer = {
+        inherit (grid-config) letsEncryptAdminEmail issuerDomains allowedChargeOrigins;
+      };
+    };
   };
 
   monitoring = {
@@ -52,11 +56,13 @@ let
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
         enableSlackAlert = true;
-        monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
       grid-module
     ];
+    config = {
+      grid.monitoringvpnIPv4 = "172.23.23.1";
+    };
   };
 
   defineStorageNode = name: { vpnIP, stateVersion }:
@@ -79,26 +85,27 @@ let
 
       # Get all of the configuration that is common across all storage nodes.
       gridlib.storage
-
-      # Then customize the storage system a little bit based on this node's particulars.
-      (gridlib.customize-storage (grid-config // nodecfg // {
-        monitoringvpnIPv4 = vpnIP;
-        inherit stateVersion;
-      }))
-
       # Also configure deployment management authorization
       grid-module
     ];
 
-    # And supply configuration for those hardware / network / bootloader
-    # options.  See the 100tb module for handling of this value.  The module
-    # name is quoted because `1` makes `100tb` look an awful lot like a
-    # number.
-   "100tb".config = nodecfg;
+    config = {
+      grid.monitoringvpnIPv4 = vpnIP;
+      grid.storage = {
+        inherit (grid-config) passValue publicStoragePort;
+      };
+      system.stateVersion = stateVersion;
+
+      # And supply configuration for those hardware / network / bootloader
+      # options.  See the 100tb module for handling of this value.  The module
+      # name is quoted because `1` makes `100tb` look an awful lot like a
+      # number.
+      "100tb".config = nodecfg;
 
-    # Enable statistics gathering for MegaRAID cards.
-    # TODO would be nice to enable only on machines that have such a device.
-    services.private-storage.monitoring.megacli2prom.enable = true;
+      # Enable statistics gathering for MegaRAID cards.
+      # TODO would be nice to enable only on machines that have such a device.
+      services.private-storage.monitoring.megacli2prom.enable = true;
+    };
   };
 
   # Define all of the storage nodes for this grid.
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index 334518774851c22738c93b323223f255d871a394..7f7aeccbaba1f4e8d2c9576ece080791d03fd065 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -21,6 +21,7 @@ let
     grid = {
       publicKeyPath = toString ./. + "/${grid-config.publicKeyPath}";
       privateKeyPath = toString ./. + "/${grid-config.privateKeyPath}";
+      inherit (grid-config) monitoringvpnEndpoint;
     };
     # Configure deployment management authorization for all systems in the grid.
     services.private-storage.deployment = {
@@ -33,11 +34,14 @@ let
     imports = [
       gridlib.issuer
       gridlib.hardware-aws
-      (gridlib.customize-issuer (grid-config // {
-        monitoringvpnIPv4 = "172.23.23.11";
-      }))
       grid-module
     ];
+    config = {
+      grid.monitoringvpnIPv4 = "172.23.23.11";
+      grid.issuer = {
+        inherit (grid-config) letsEncryptAdminEmail issuerDomains allowedChargeOrigins;
+      };
+    };
   };
 
   storage001 = {
@@ -45,12 +49,15 @@ let
       gridlib.storage
       gridlib.hardware-aws
       ./testing001-hardware.nix
-      (gridlib.customize-storage (grid-config // {
-        monitoringvpnIPv4 = "172.23.23.12";
-        stateVersion = "19.03";
-      }))
       grid-module
     ];
+    config = {
+      grid.monitoringvpnIPv4 = "172.23.23.12";
+      grid.storage = {
+        inherit (grid-config) passValue publicStoragePort;
+      };
+      system.stateVersion = "19.03";
+    };
   };
 
   monitoring = {
@@ -65,11 +72,13 @@ let
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
         enableSlackAlert = true;
-        monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
       grid-module
     ];
+    config = {
+      grid.monitoringvpnIPv4 = "172.23.23.1";
+    };
   };
 
   # TBD: derive these automatically:
diff --git a/morph/lib/base.nix b/morph/lib/base.nix
index 7390654ac167909149b0a6f4dfae897b8f3f43a3..9b698b9629c12c7343fbd2e9eea5d2ee599b612e 100644
--- a/morph/lib/base.nix
+++ b/morph/lib/base.nix
@@ -18,6 +18,18 @@
       corresponding private keys for the system.
       '';
     };
+    monitoringvpnIPv4 = lib.mkOption {
+      type = lib.types.str;
+      description = ''
+        The IPv4 address of this node on the monitoring VPN.
+      '';
+    };
+    monitoringvpnEndpoint = lib.mkOption {
+      type = lib.types.str;
+      description = ''
+        The domain name and port of the monitoring VPN endpoint.
+      '';
+    };
   };
 
   # Any extra NixOS modules to load on all our servers.  Note that just
diff --git a/morph/lib/customize-issuer.nix b/morph/lib/customize-issuer.nix
deleted file mode 100644
index 0686556cdf6abe79f0ac9e16586c9c219f3cddb1..0000000000000000000000000000000000000000
--- a/morph/lib/customize-issuer.nix
+++ /dev/null
@@ -1,53 +0,0 @@
-# Define a function which returns a value which fills in all the holes left by
-# ``issuer.nix``.
-{
-  # A string giving the IP address and port number (":"-separated) of the VPN
-  # server.
-  monitoringvpnEndpoint
-
-  # A string giving the VPN IPv4 address for this system.
-, monitoringvpnIPv4
-
-  # A string giving an email address to use for Let's Encrypt registration and
-  # certificate issuance.
-, letsEncryptAdminEmail
-
-  # A list of strings giving the domain names that point at this issuer
-  # system.  These will all be included in Let's Encrypt certificate.
-, issuerDomains
-
-  # A list of strings giving CORS Origins will the issuer will be configured
-  # to allow.
-, allowedChargeOrigins
-, ...
-}:
-{ config, ... }:
-let
-  inherit (config.grid) publicKeyPath privateKeyPath;
-in {
-  deployment.secrets = {
-    # ``.../monitoringvpn`` is a path on the deployment system of a directory
-    # containing a number of VPN-related secrets.  This is expected to contain
-    # a number of files named like ``<VPN IPv4 address>.key`` containing the
-    # VPN private key for the corresponding host.  It must also contain
-    # ``server.pub`` and ``preshared.key`` holding the VPN server's public key
-    # and the pre-shared key, respectively.  All of these things are used as
-    # the sources of various VPN-related morph secrets.
-    "monitoringvpn-secret-key".source = "${privateKeyPath}/monitoringvpn/${monitoringvpnIPv4}.key";
-    "monitoringvpn-preshared-key".source = "${privateKeyPath}/monitoringvpn/preshared.key";
-  };
-
-  services.private-storage.monitoring.vpn.client = {
-    enable = true;
-    ip = monitoringvpnIPv4;
-    endpoint = monitoringvpnEndpoint;
-    endpointPublicKeyFile = "${publicKeyPath}/monitoringvpn/server.pub";
-  };
-
-  services.private-storage-issuer = {
-    inherit letsEncryptAdminEmail allowedChargeOrigins;
-    domains = issuerDomains;
-  };
-
-  system.stateVersion = "19.03";
-}
diff --git a/morph/lib/customize-monitoring.nix b/morph/lib/customize-monitoring.nix
index 2899d9940d4309b81a31f96590f0d3df1d632dc4..67544de5bab466fab0927b71b493ff81935192e9 100644
--- a/morph/lib/customize-monitoring.nix
+++ b/morph/lib/customize-monitoring.nix
@@ -8,9 +8,12 @@
   # allows Grafana to show us hostnames instead of VPN IP addresses.
   hostsMap
 
-  # See ``customize-issuer.nix``.
-, monitoringvpnIPv4
+  # A string giving an email address to use for Let's Encrypt registration and
+  # certificate issuance.
 , letsEncryptAdminEmail
+
+  # A list of strings giving the domain names that point at this monitoring
+  # system.  These will all be included in Let's Encrypt certificate.
 , monitoringDomains
 
   # A list of VPN IP addresses as strings indicating which clients will be
@@ -47,7 +50,7 @@
 }:
 { config, ... }:
 let
-  inherit (config.grid) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath monitoringvpnIPv4;
 in {
   deployment.secrets = let
     # When Grafana SSO is disabled there is not necessarily any client secret
@@ -93,12 +96,8 @@ in {
           action = ["sudo" "systemctl" "restart" "grafana.service"];
         };
       };
-    monitoringvpn = {
-      "monitoringvpn-private-key".source = "${privateKeyPath}/monitoringvpn/server.key";
-      "monitoringvpn-preshared-key".source = "${privateKeyPath}/monitoringvpn/preshared.key";
-    };
     in
-      grafanaSSO // grafanaSlackUrl // monitoringvpn;
+      grafanaSSO // grafanaSlackUrl;
 
   networking.hosts = hostsMap;
 
diff --git a/morph/lib/customize-storage.nix b/morph/lib/customize-storage.nix
deleted file mode 100644
index 6a288213c3f117309b697e44304be9a7d5620bcb..0000000000000000000000000000000000000000
--- a/morph/lib/customize-storage.nix
+++ /dev/null
@@ -1,40 +0,0 @@
-# Define a function which returns a value which fills in all the holes left by
-# ``storage.nix``.
-{
-  # See ``customize-issuer.nix``
-  monitoringvpnEndpoint
-, monitoringvpnIPv4
-
-  # An integer giving the value of a single pass in byte×months.
-, passValue
-
-  # An integer giving the port number to include in Tahoe storage service
-  # advertisements and on which to listen for storage connections.
-, publicStoragePort
-
-  # A string giving the NixOS state version for the system.
-, stateVersion
-, ...
-}:
-{ config, ... }:
-let
-  inherit (config.grid) publicKeyPath privateKeyPath;
-in {
-  deployment.secrets = {
-    "monitoringvpn-secret-key".source = "${privateKeyPath}/monitoringvpn/${monitoringvpnIPv4}.key";
-    "monitoringvpn-preshared-key".source = "${privateKeyPath}/monitoringvpn/preshared.key";
-  };
-
-  services.private-storage = {
-    inherit passValue publicStoragePort;
-  };
-
-  services.private-storage.monitoring.vpn.client = {
-    enable = true;
-    ip = monitoringvpnIPv4;
-    endpoint = monitoringvpnEndpoint;
-    endpointPublicKeyFile = "${publicKeyPath}/monitoringvpn/server.pub";
-  };
-
-  system.stateVersion = stateVersion;
-}
diff --git a/morph/lib/default.nix b/morph/lib/default.nix
index a820cc559b6b2da78c06bcb84282e392c3a1ebc7..766fda5102589fbdc32e26c17fbc94731ab71a73 100644
--- a/morph/lib/default.nix
+++ b/morph/lib/default.nix
@@ -8,10 +8,7 @@
   hardware-vagrant = import ./hardware-vagrant.nix;
 
   issuer = import ./issuer.nix;
-  customize-issuer = import ./customize-issuer.nix;
-
   storage = import ./storage.nix;
-  customize-storage = import ./customize-storage.nix;
 
   monitoring = import ./monitoring.nix;
   customize-monitoring = import ./customize-monitoring.nix;
diff --git a/morph/lib/issuer.nix b/morph/lib/issuer.nix
index d3ee812e865f741b01eb811589262ae01ece824f..b2d48c2cee8cb914d7c3c9acedfcaf9ad2997178 100644
--- a/morph/lib/issuer.nix
+++ b/morph/lib/issuer.nix
@@ -1,60 +1,98 @@
-# This, along with `customize-issuer.nix, contains all of the NixOS system
-# configuration necessary to specify an "issuer"-type system.  Originally, this
-# file has all the static configuration, and `customize-issuer.nix` was a function
-# that filled in the holes. We are in the process of merging the modules, using settings
-# instead of function arguments.
-# See https://whetstone.privatestorage.io/privatestorage/PrivateStorageio/-/issues/80
-{ config, ...}:
+# This contains all of the NixOS system configuration necessary to specify an
+# "issuer"-type system.
+{ lib, config, ...}:
 let
-  inherit (config.grid) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath monitoringvpnEndpoint monitoringvpnIPv4;
+  inherit (config.grid.issuer) letsEncryptAdminEmail issuerDomains allowedChargeOrigins;
 in {
-  deployment = {
-    secrets = {
-      "ristretto-signing-key" = {
-        destination = "/run/keys/ristretto.signing-key";
-        source = "${privateKeyPath}/ristretto.signing-key";
-        owner.user = "zkapissuer";
-        owner.group = "zkapissuer";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
-      };
-      "stripe-secret-key" = {
-        destination = "/run/keys/stripe.secret-key";
-        source = "${privateKeyPath}/stripe.secret";
-        owner.user = "zkapissuer";
-        owner.group = "zkapissuer";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
-      };
-
-      "monitoringvpn-secret-key" = {
-        destination = "/run/keys/monitoringvpn/client.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";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
-      };
-    };
-  };
-
   imports = [
     ../../nixos/modules/monitoring/vpn/client.nix
     ../../nixos/modules/monitoring/exporters/node.nix
   ];
 
-  services.private-storage-issuer = {
-    enable = true;
-    tls = true;
-    ristrettoSigningKeyPath = config.deployment.secrets.ristretto-signing-key.destination;
-    stripeSecretKeyPath = config.deployment.secrets.stripe-secret-key.destination;
-    database = "SQLite3";
-    databasePath = "${config.fileSystems."zkapissuer-data".mountPoint}/vouchers.sqlite3";
+  options.grid.issuer = {
+    letsEncryptAdminEmail = lib.mkOption {
+      type = lib.types.str;
+      description = ''
+        A string giving an email address to use for Let's Encrypt registration and
+        certificate issuance.
+      '';
+    };
+
+    issuerDomains = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      description = ''
+        A list of strings giving the domain names that point at this issuer
+        system.  These will all be included in Let's Encrypt certificate.
+      '';
+    };
+
+    allowedChargeOrigins = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      description = ''
+        A list of strings giving CORS Origins will the issuer will be configured
+        to allow.
+      '';
+    };
+  };
+
+  config = {
+    deployment = {
+      secrets = {
+        "ristretto-signing-key" = {
+          destination = "/run/keys/ristretto.signing-key";
+          source = "${privateKeyPath}/ristretto.signing-key";
+          owner.user = "zkapissuer";
+          owner.group = "zkapissuer";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
+        };
+        "stripe-secret-key" = {
+          destination = "/run/keys/stripe.secret-key";
+          source = "${privateKeyPath}/stripe.secret";
+          owner.user = "zkapissuer";
+          owner.group = "zkapissuer";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
+        };
+
+        "monitoringvpn-secret-key" = {
+          destination = "/run/keys/monitoringvpn/client.key";
+          source = "${privateKeyPath}/monitoringvpn/${monitoringvpnIPv4}.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"];
+        };
+      };
+    };
+
+    services.private-storage-issuer = {
+      enable = true;
+      tls = true;
+      ristrettoSigningKeyPath = config.deployment.secrets.ristretto-signing-key.destination;
+      stripeSecretKeyPath = config.deployment.secrets.stripe-secret-key.destination;
+      database = "SQLite3";
+      databasePath = "${config.fileSystems."zkapissuer-data".mountPoint}/vouchers.sqlite3";
+      inherit letsEncryptAdminEmail allowedChargeOrigins;
+      domains = issuerDomains;
+    };
+
+    services.private-storage.monitoring.vpn.client = {
+      enable = true;
+      ip = monitoringvpnIPv4;
+      endpoint = monitoringvpnEndpoint;
+      endpointPublicKeyFile = "${publicKeyPath}/monitoringvpn/server.pub";
+    };
+
+    system.stateVersion = "19.03";
   };
 }
diff --git a/morph/lib/monitoring.nix b/morph/lib/monitoring.nix
index 89a328e89a799b445dff7180dff552350b9629cf..e10dd31c4dee7404d436285ad80c9babc350c246 100644
--- a/morph/lib/monitoring.nix
+++ b/morph/lib/monitoring.nix
@@ -1,10 +1,14 @@
 # Similar to ``issuer.nix`` but for a "monitoring"-type system.  Holes are
 # filled by ``customize-monitoring.nix``.
-{
+{ config, ...}:
+let
+  inherit (config.grid) privateKeyPath;
+in {
   deployment = {
     secrets = {
       "monitoringvpn-private-key" = {
         destination = "/run/keys/monitoringvpn/server.key";
+        source = "${privateKeyPath}/monitoringvpn/server.key";
         owner.user = "root";
         owner.group = "root";
         permissions = "0400";
@@ -12,6 +16,7 @@
       };
       "monitoringvpn-preshared-key" = {
         destination = "/run/keys/monitoringvpn/preshared.key";
+        source = "${privateKeyPath}/monitoringvpn/preshared.key";
         owner.user = "root";
         owner.group = "root";
         permissions = "0400";
diff --git a/morph/lib/storage.nix b/morph/lib/storage.nix
index 15e2373737a7ff2f1efe8cf2c41b59de606f0a1a..71e3c22371ad042c4ddbc5d8cd87db5cb05923af 100644
--- a/morph/lib/storage.nix
+++ b/morph/lib/storage.nix
@@ -1,39 +1,9 @@
-# Similar to ``issuer.nix`` but for a "storage"-type system.  Holes are filled
-# by ``customize-storage.nix``.
-{ config, ...} :
+# This contains all of the NixOS system configuration necessary to specify an
+# "storage"-type system.
+{ lib, config, ...} :
 let
-  inherit (config.grid) publicKeyPath privateKeyPath;
+  inherit (config.grid) publicKeyPath privateKeyPath monitoringvpnIPv4 monitoringvpnEndpoint;
 in {
-  deployment = {
-    secrets = {
-      "ristretto-signing-key" = {
-        destination = "/run/keys/ristretto.signing-key";
-        source = "${privateKeyPath}/ristretto.signing-key";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        # Service name here matches the name defined by our tahoe-lafs nixos
-        # module.  It would be nice to not have to hard-code it here.  Can we
-        # extract it from the tahoe-lafs nixos module somehow?
-        action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
-      };
-      "monitoringvpn-secret-key" = {
-        destination = "/run/keys/monitoringvpn/client.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";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
-      };
-    };
-  };
-
   # Any extra NixOS modules to load on this server.
   imports = [
     # Bring in our module for configuring the Tahoe-LAFS service and other
@@ -47,13 +17,72 @@ in {
     ../../nixos/modules/monitoring/exporters/tahoe.nix
   ];
 
-  services.private-storage.monitoring.tahoe.enable = true;
+  options.grid.storage = {
+    passValue = lib.mkOption {
+      type = lib.types.int;
+      description = ''
+        An integer giving the value of a single pass in byte×months.
+      '';
+    };
 
-  # Turn on the Private Storage (Tahoe-LAFS) service.
-  services.private-storage = {
-    # Yep.  Turn it on.
-    enable = true;
-    # Give it the Ristretto signing key to support authorization.
-    ristrettoSigningKeyPath = config.deployment.secrets.ristretto-signing-key.destination;
+    publicStoragePort = lib.mkOption {
+      type = lib.types.port;
+      description = ''
+        An integer giving the port number to include in Tahoe storage service
+        advertisements and on which to listen for storage connections.
+      '';
+    };
+  };
+
+  config = {
+    deployment = {
+      secrets = {
+        "ristretto-signing-key" = {
+          destination = "/run/keys/ristretto.signing-key";
+          source = "${privateKeyPath}/ristretto.signing-key";
+          owner.user = "root";
+          owner.group = "root";
+          permissions = "0400";
+          # Service name here matches the name defined by our tahoe-lafs nixos
+          # module.  It would be nice to not have to hard-code it here.  Can we
+          # extract it from the tahoe-lafs nixos module somehow?
+          action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
+        };
+        "monitoringvpn-secret-key" = {
+          destination = "/run/keys/monitoringvpn/client.key";
+          source = "${privateKeyPath}/monitoringvpn/${monitoringvpnIPv4}.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"];
+        };
+      };
+    };
+
+    services.private-storage.monitoring.tahoe.enable = true;
+
+    # Turn on the Private Storage (Tahoe-LAFS) service.
+    services.private-storage = {
+      # Yep.  Turn it on.
+      enable = true;
+      # Give it the Ristretto signing key to support authorization.
+      ristrettoSigningKeyPath = config.deployment.secrets.ristretto-signing-key.destination;
+      inherit (config.grid.storage) passValue publicStoragePort;
+    };
+
+    services.private-storage.monitoring.vpn.client = {
+      enable = true;
+      ip = monitoringvpnIPv4;
+      endpoint = monitoringvpnEndpoint;
+      endpointPublicKeyFile = "${publicKeyPath}/monitoringvpn/server.pub";
+    };
   };
 }