diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index 0e10a533b91d2c526ceefeccc71f99726681db2a..ea8d3c4185fe5f0168c013ddda41790fd9089241 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -13,7 +13,7 @@ let
   payments = let publicIPv4 = "192.168.67.21"; in {
     imports = [
       gridlib.issuer
-      (import ./virtual-hardware.nix ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
       (gridlib.customize-issuer (config // {
           monitoringvpnIPv4 = "172.23.23.11";
       }))
@@ -23,7 +23,7 @@ let
   storage1 = let publicIPv4 = "192.168.67.22"; in {
     imports = [
       gridlib.storage
-      (import ./virtual-hardware.nix ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
       (gridlib.customize-storage (config // {
         monitoringvpnIPv4 = "172.23.23.12";
         stateVersion = "19.09";
@@ -34,7 +34,7 @@ let
   storage2 = let publicIPv4 = "192.168.67.23"; in {
     imports = [
       gridlib.storage
-      (import ./virtual-hardware.nix ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
       (gridlib.customize-storage (config // {
         monitoringvpnIPv4 = "172.23.23.13";
         stateVersion = "19.09";
@@ -45,7 +45,7 @@ let
   monitoring = let publicIPv4 = "192.168.67.24"; in {
     imports = [
       gridlib.monitoring
-      (import ./virtual-hardware.nix ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
       (gridlib.customize-monitoring {
         inherit hostsMap vpnClientIPs nodeExporterTargets;
         inherit (config) domain monitoringvpnKeyDir;
diff --git a/morph/lib/customize-issuer.nix b/morph/lib/customize-issuer.nix
index 2a8faa5fe8a83a39207aedca313e42a17f702163..94599313fc2deb2d75d6532b67159b9764342803 100644
--- a/morph/lib/customize-issuer.nix
+++ b/morph/lib/customize-issuer.nix
@@ -1,12 +1,48 @@
-{ ristrettoSigningKeyPath
+# Define a function which returns a value which fills in all the holes left by
+# ``issuer.nix``.
+{
+  # A path on the deployment system to a file containing the Ristretto signing
+  # key.  This is used as the source of the Ristretto signing key morph
+  # secret.
+  ristrettoSigningKeyPath
+
+  # A path on the deployment system to a file containing the Stripe secret
+  # key.  This is used as the source of the Stripe secret key morph secret.
 , stripeSecretKeyPath
+
+  # A path on the deployment system to 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.
 , monitoringvpnKeyDir
+
+  # 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
+
 , domain
+
+  # A set mapping usernames as strings to SSH public keys as strings.  For
+  # each element of the site, the indicated user is configured on the system
+  # with the indicated SSH key as an authorized key.
 , sshUsers
+
+  # 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
 , ...
 }:
@@ -37,9 +73,8 @@
   };
 
   services.private-storage-issuer = {
-    letsEncryptAdminEmail = letsEncryptAdminEmail;
+    inherit letsEncryptAdminEmail allowedChargeOrigins;
     domains = issuerDomains;
-    allowedChargeOrigins = allowedChargeOrigins;
   };
 
   system.stateVersion = "19.03";
diff --git a/morph/lib/customize-monitoring.nix b/morph/lib/customize-monitoring.nix
index 208f5048efe5645d59f148fcf5967f5e3ea69935..75f668a76b884289a147967bda42586aff438f72 100644
--- a/morph/lib/customize-monitoring.nix
+++ b/morph/lib/customize-monitoring.nix
@@ -1,10 +1,32 @@
-{ hostsMap
+# 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
+
 , domain
+
+  # See ``customize-issuer.nix``.
 , monitoringvpnKeyDir
 , monitoringvpnIPv4
+
+  # 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 ? []
+
+  # A string giving the NixOS state version for the system.
 , stateVersion
 , ...
 }:
diff --git a/morph/lib/customize-storage.nix b/morph/lib/customize-storage.nix
index 3a9c8f6b9988999c194afc2fa253d83e1eb0b76a..449ae5f93a7f0e7cde1dfd719cbbd143fd8e54ce 100644
--- a/morph/lib/customize-storage.nix
+++ b/morph/lib/customize-storage.nix
@@ -1,11 +1,25 @@
-{ ristrettoSigningKeyPath
-, passValue
-, publicStoragePort
-, sshUsers
-, domain
+# Define a function which returns a value which fills in all the holes left by
+# ``storage.nix``.
+{
+  # See ``customize-issuer.nix``
+  ristrettoSigningKeyPath
 , monitoringvpnKeyDir
 , monitoringvpnEndpoint
 , monitoringvpnIPv4
+, sshUsers
+, domain
+
+  # 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
+
+  # XXX To be removed
+, publicIPv4
+
+  # A string giving the NixOS state version for the system.
 , stateVersion
 , ...
 }:
diff --git a/morph/lib/default.nix b/morph/lib/default.nix
index 25ae8eb2419b524240618f0a501bed6d96f08252..bdd92f4bfe52eba2e19df3ac73a087a4af4a53dc 100644
--- a/morph/lib/default.nix
+++ b/morph/lib/default.nix
@@ -1,5 +1,9 @@
-rec {
+# Gather up the grid library functionality and present it in a (somewhat)
+# coherent public interface.  Application code should prefer these names over
+# directly importing the source files in this directory.
+{
   hardware-aws = import ./issuer-aws.nix;
+  hardware-virtual = import ./hardware-virtual.nix;
 
   issuer = import ./issuer.nix;
   customize-issuer = import ./customize-issuer.nix;
diff --git a/morph/grid/local/virtual-hardware.nix b/morph/lib/hardware-virtual.nix
similarity index 100%
rename from morph/grid/local/virtual-hardware.nix
rename to morph/lib/hardware-virtual.nix
diff --git a/morph/lib/issuer.nix b/morph/lib/issuer.nix
index efba08ba1d92520398ec030a37d1df16912d4c13..417ef7965ea0120322995059fcca7a5a9afe2543 100644
--- a/morph/lib/issuer.nix
+++ b/morph/lib/issuer.nix
@@ -1,8 +1,13 @@
+# This is all of the static NixOS system configuration necessary to specify an
+# "issuer"-type system.  The configuration has various holes in it which must
+# be filled somehow.  These holes correspond to configuration which is not
+# statically known.  This value is suitable for use as a module to be imported
+# into a more complete system configuration.  It is expected that the holes
+# will be filled by a sibling module created by ``customize-issuer.nix``.
 rec {
   deployment = {
     secrets = {
       "ristretto-signing-key" = {
-        # source = ... fill this in ...
         destination = "/run/keys/ristretto.signing-key";
         owner.user = "root";
         owner.group = "root";
@@ -10,7 +15,6 @@ rec {
         action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
       };
       "stripe-secret-key" = {
-        # source = ... fill this in ...
         destination = "/run/keys/stripe.secret-key";
         owner.user = "root";
         owner.group = "root";
@@ -19,7 +23,6 @@ rec {
       };
 
       "monitoringvpn-secret-key" = {
-        # source = ... fill this in ...
         destination = "/run/keys/monitoringvpn/client.key";
         owner.user = "root";
         owner.group = "root";
@@ -27,7 +30,6 @@ rec {
         action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
       };
       "monitoringvpn-preshared-key" = {
-        # source = ... fill this in ...
         destination = "/run/keys/monitoringvpn/preshared.key";
         owner.user = "root";
         owner.group = "root";
@@ -43,15 +45,6 @@ rec {
     ../../nixos/modules/monitoring/exporters/node.nix
   ];
 
-  services.private-storage = {
-    # sshUsers = ...
-    monitoring.vpn.client = {
-      # enable = ...
-      # ip = ...
-      # endpoint = ...
-      # endpointPublicKeyFile = ...
-    };
-  };
   services.private-storage-issuer = {
     enable = true;
     tls = true;
@@ -59,10 +52,5 @@ rec {
     stripeSecretKeyPath = deployment.secrets.stripe-secret-key.destination;
     database = "SQLite3";
     databasePath = "/var/db/vouchers.sqlite3";
-    # letsEncryptAdminEmail = ...;
-    # domains = ...;
-    # allowedChargeOrigins = ...;
   };
-
-  # system.stateVersion = ...
 }
diff --git a/morph/lib/monitoring.nix b/morph/lib/monitoring.nix
index 2001dea8637a1dfa32b7789dea2d3ea2063773eb..b48820f0941694869fdda06e724ba1ae714b5993 100644
--- a/morph/lib/monitoring.nix
+++ b/morph/lib/monitoring.nix
@@ -1,8 +1,9 @@
+# Similar to ``issuer.nix`` but for a "monitoring"-type system.  Holes are
+# filled by ``customize-monitoring.nix``.
 rec {
   deployment = {
     secrets = {
       "monitoringvpn-private-key" = {
-        # source = ...;
         destination = "/run/keys/monitoringvpn/server.key";
         owner.user = "root";
         owner.group = "root";
@@ -10,7 +11,6 @@ rec {
         action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
       };
       "monitoringvpn-preshared-key" = {
-        # source = ...;
         destination = "/run/keys/monitoringvpn/preshared.key";
         owner.user = "root";
         owner.group = "root";
@@ -29,25 +29,9 @@ rec {
     # ../../nixos/modules/monitoring/server/loki.nix
   ];
 
-  services.private-storage.monitoring.vpn.server = {
-    # enable = ...;
-    # ip = ...;
-    # vpnClientIPs = ...;
-    # pubKeysPath = ...;
-  };
-
   services.private-storage.monitoring.grafana = {
     domain = "monitoring.private.storage";
     prometheusUrl = "http://localhost:9090/";
     lokiUrl = "http://localhost:3100/";
   };
-
-  services.private-storage.monitoring.prometheus = {
-    # nodeExporterTargets = ...;
-    # nginxExporterTargets = ...;
-  };
-
-  # system.stateVersion = ...;
-
-  # networking.hosts = ...;
 }
diff --git a/morph/lib/storage.nix b/morph/lib/storage.nix
index 2835e024b67150f646ecfe1d7c6d20ca7fb3ec06..1cac51b43aa38fb90a535fd34ba53363fc0cdbaa 100644
--- a/morph/lib/storage.nix
+++ b/morph/lib/storage.nix
@@ -1,8 +1,9 @@
+# Similar to ``issuer.nix`` but for a "storage"-type system.  Holes are filled
+# by ``customize-storage.nix``.
 rec {
   deployment = {
     secrets = {
       "ristretto-signing-key" = {
-        # source = ...;
         destination = "/run/keys/ristretto.signing-key";
         owner.user = "root";
         owner.group = "root";
@@ -13,7 +14,6 @@ rec {
         action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
       };
       "monitoringvpn-secret-key" = {
-        # source = ...;
         destination = "/run/keys/monitoringvpn/client.key";
         owner.user = "root";
         owner.group = "root";
@@ -21,7 +21,6 @@ rec {
         action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
       };
       "monitoringvpn-preshared-key" = {
-        # source = ...;
         destination = "/run/keys/monitoringvpn/preshared.key";
         owner.user = "root";
         owner.group = "root";
@@ -46,24 +45,7 @@ rec {
   services.private-storage = {
     # Yep.  Turn it on.
     enable = true;
-    # Get the public IPv4 address from the node configuration.
-    # inherit (cfg) publicIPv4;
-    # And the port to operate on is specified via parameter.
-    # inherit publicStoragePort;
-    # Give it the Ristretto signing key, too, to support authorization.
+    # Give it the Ristretto signing key to support authorization.
     ristrettoSigningKeyPath = deployment.secrets.ristretto-signing-key.destination;
-    # Assign the configured pass value.
-    # inherit passValue;
-    # It gets the users, too.
-    # sshUsers = ...;
-  };
-
-  # system.stateVersion = ...'
-
-  services.private-storage.monitoring.vpn.client = {
-    # enable = ...;
-    # ip = ...;
-    # endpoint = ...;
-    # endpointPublicKeyFile = ...;
   };
 }
diff --git a/morph/grid/local/vagrant-guest.nix b/morph/lib/vagrant-guest.nix
similarity index 100%
rename from morph/grid/local/vagrant-guest.nix
rename to morph/lib/vagrant-guest.nix