diff --git a/nixos/modules/private-storage.nix b/nixos/modules/private-storage.nix
index cf8cbca1c1ca2dd7568c7ec94ac31ce5e8060634..31b550066cb737100ad45c5f703f90e80b1870ef 100644
--- a/nixos/modules/private-storage.nix
+++ b/nixos/modules/private-storage.nix
@@ -7,7 +7,22 @@ let
   };
   cfg = config.services.private-storage;
 in
-{ imports = [ ];
+{
+
+  # Upstream tahoe-lafs module conflicts with ours (since ours is a
+  # copy/paste/edit of upstream's...).  Disable
+  # it.
+  #
+  # https://nixos.org/nixos/manual/#sec-replace-modules
+  disabledModules =
+  [ "services/network-filesystems/tahoe.nix"
+  ];
+
+  # Load our tahoe-lafs module.
+  imports =
+  [ ./tahoe.nix
+  ];
+
   options =
   { services.private-storage.enable = lib.mkEnableOption "private storage service";
     services.private-storage.tahoe.package = lib.mkOption
@@ -22,8 +37,14 @@ in
   config = lib.mkIf cfg.enable
   { services.tahoe.nodes."alpha" =
     { package = config.services.private-storage.tahoe.package;
-      nickname = "alpha";
-      storage.enable = true;
+      sections =
+      { node =
+        { nickname = "alpha";
+        };
+        storage =
+        { enable = true;
+        };
+      };
     };
   };
 }
diff --git a/nixos/modules/tahoe.nix b/nixos/modules/tahoe.nix
new file mode 100644
index 0000000000000000000000000000000000000000..aa48a59a9b110aa3bed2aae74776e2bf27714c98
--- /dev/null
+++ b/nixos/modules/tahoe.nix
@@ -0,0 +1,251 @@
+# Copy/pasted from nixos/modules/services/network-filesystems/tahoe.nix :/ We
+# require control over additional configuration options compared to upstream
+# and it's not clear how to do this without duplicating everything.
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.tahoe;
+in
+  {
+    options.services.tahoe = {
+      introducers = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule {
+          options = {
+            nickname = mkOption {
+              type = types.str;
+              description = ''
+                The nickname of this Tahoe introducer.
+              '';
+            };
+            tub.port = mkOption {
+              default = 3458;
+              type = types.int;
+              description = ''
+                The port on which the introducer will listen.
+              '';
+            };
+            tub.location = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The external location that the introducer should listen on.
+
+                If specified, the port should be included.
+              '';
+            };
+            package = mkOption {
+              default = pkgs.tahoelafs;
+              defaultText = "pkgs.tahoelafs";
+              type = types.package;
+              example = literalExample "pkgs.tahoelafs";
+              description = ''
+                The package to use for the Tahoe LAFS daemon.
+              '';
+            };
+          };
+        });
+        description = ''
+          The Tahoe introducers.
+        '';
+      };
+      nodes = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule {
+          options = {
+            sections = mkOption {
+              type = types.attrs;
+              description = ''
+                Top-level configuration sections.
+              '';
+              default = {
+                "node" = { };
+                "client" = { };
+                "storage" = { };
+              };
+            };
+            package = mkOption {
+              default = pkgs.tahoelafs;
+              defaultText = "pkgs.tahoelafs";
+              type = types.package;
+              example = literalExample "pkgs.tahoelafs";
+              description = ''
+                The package to use for the Tahoe LAFS daemon.
+              '';
+            };
+          };
+        });
+        description = ''
+          The Tahoe nodes.
+        '';
+      };
+    };
+    config = mkMerge [
+      (mkIf (cfg.introducers != {}) {
+        environment = {
+          etc = flip mapAttrs' cfg.introducers (node: settings:
+            nameValuePair "tahoe-lafs/introducer-${node}.cfg" {
+              mode = "0444";
+              text = ''
+                # This configuration is generated by Nix. Edit at your own
+                # peril; here be dragons.
+
+                [node]
+                nickname = ${settings.nickname}
+                tub.port = ${toString settings.tub.port}
+                ${optionalString (settings.tub.location != null)
+                  "tub.location = ${settings.tub.location}"}
+              '';
+            });
+          # Actually require Tahoe, so that we will have it installed.
+          systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
+            settings.package
+          );
+        };
+        # Open up the firewall.
+        # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.introducers
+        #   (node: settings: settings.tub.port);
+        systemd.services = flip mapAttrs' cfg.introducers (node: settings:
+          let
+            pidfile = "/run/tahoe.introducer-${node}.pid";
+            # This is a directory, but it has no trailing slash. Tahoe commands
+            # get antsy when there's a trailing slash.
+            nodedir = "/var/db/tahoe-lafs/introducer-${node}";
+          in nameValuePair "tahoe.introducer-${node}" {
+            description = "Tahoe LAFS node ${node}";
+            wantedBy = [ "multi-user.target" ];
+            path = [ settings.package ];
+            restartTriggers = [
+              config.environment.etc."tahoe-lafs/introducer-${node}.cfg".source ];
+            serviceConfig = {
+              Type = "simple";
+              PIDFile = pidfile;
+              # Believe it or not, Tahoe is very brittle about the order of
+              # arguments to $(tahoe start). The node directory must come first,
+              # and arguments which alter Twisted's behavior come afterwards.
+              ExecStart = ''
+                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+              '';
+            };
+            preStart = ''
+              if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+                mkdir -p /var/db/tahoe-lafs
+                tahoe create-introducer ${lib.escapeShellArg nodedir}
+              fi
+
+              # Tahoe has created a predefined tahoe.cfg which we must now
+              # scribble over.
+              # XXX I thought that a symlink would work here, but it doesn't, so
+              # we must do this on every prestart. Fixes welcome.
+              # rm ${nodedir}/tahoe.cfg
+              # ln -s /etc/tahoe-lafs/introducer-${node}.cfg ${nodedir}/tahoe.cfg
+              cp /etc/tahoe-lafs/introducer-"${node}".cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+            '';
+          });
+        users.users = flip mapAttrs' cfg.introducers (node: _:
+          nameValuePair "tahoe.introducer-${node}" {
+            description = "Tahoe node user for introducer ${node}";
+            isSystemUser = true;
+          });
+      })
+      (mkIf (cfg.nodes != {}) {
+        environment = {
+          etc = flip mapAttrs' cfg.nodes (node: settings:
+            let
+              # Map a function over an attrset and concatenate the string results.
+              #
+              # concatMapAttrsToList (n: v: "${n} = ${v}\n") { a = "b"; c = "d"; } -> "a = b\nc = d\n"
+              concatMapAttrsToList = f: a:
+                lib.strings.concatStrings (lib.attrsets.mapAttrsToList f a);
+
+              # Generate one line of configuration defining one item in one section.
+              #
+              # oneConfigItemText "foo" "bar" -> "foo = bar\n"
+              oneConfigItemText = name: value:
+                "${name} = ${builtins.toString value}\n";
+
+              # Generate all lines of configuration defining all items in one section.
+              #
+              # allConfigItemsText { foo = "bar"; baz = "quux"; } -> "foo = bar\nbaz = quux"
+              allConfigItemsText = items:
+                concatMapAttrsToList oneConfigItemText items;
+
+              # Generate all lines of configuration for one section, header
+              # and items included.
+              #
+              # oneConfigSectionText "foo" { bar = "baz"; } -> "[foo]\nbar = baz\n"
+              oneConfigSectionText = name: value: ''
+                [${name}]
+                ${allConfigItemsText value}
+              '';
+
+              # Generate all lines of configuration for all sections, headers
+              # and items included.
+              #
+              # allConfigSectionsText { foo = { bar = "baz"; }; } -> "[foo]\nbar = baz\n"
+              allConfigSectionsText = sections:
+                concatMapAttrsToList oneConfigSectionText sections;
+            in
+            nameValuePair "tahoe-lafs/${node}.cfg" {
+              mode = "0444";
+              text = ''
+                # This configuration is generated by Nix. Edit at your own
+                # peril; here be dragons.
+
+                ${allConfigSectionsText settings.sections}
+                '';
+            });
+          # Actually require Tahoe, so that we will have it installed.
+          systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
+            settings.package
+          );
+        };
+        # Open up the firewall.
+        # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.nodes
+        #   (node: settings: settings.tub.port);
+        systemd.services = flip mapAttrs' cfg.nodes (node: settings:
+          let
+            pidfile = "/run/tahoe.${node}.pid";
+            # This is a directory, but it has no trailing slash. Tahoe commands
+            # get antsy when there's a trailing slash.
+            nodedir = "/var/db/tahoe-lafs/${node}";
+          in nameValuePair "tahoe.${node}" {
+            description = "Tahoe LAFS node ${node}";
+            wantedBy = [ "multi-user.target" ];
+            path = [ settings.package ];
+            restartTriggers = [
+              config.environment.etc."tahoe-lafs/${node}.cfg".source ];
+            serviceConfig = {
+              Type = "simple";
+              PIDFile = pidfile;
+              # Believe it or not, Tahoe is very brittle about the order of
+              # arguments to $(tahoe start). The node directory must come first,
+              # and arguments which alter Twisted's behavior come afterwards.
+              ExecStart = ''
+                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+              '';
+            };
+            preStart = ''
+              if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+                mkdir -p /var/db/tahoe-lafs
+                tahoe create-node --hostname=localhost ${lib.escapeShellArg nodedir}
+              fi
+
+              # Tahoe has created a predefined tahoe.cfg which we must now
+              # scribble over.
+              # XXX I thought that a symlink would work here, but it doesn't, so
+              # we must do this on every prestart. Fixes welcome.
+              # rm ${nodedir}/tahoe.cfg
+              # ln -s /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
+              cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+            '';
+          });
+        users.users = flip mapAttrs' cfg.nodes (node: _:
+          nameValuePair "tahoe.${node}" {
+            description = "Tahoe node user for node ${node}";
+            isSystemUser = true;
+          });
+      })
+    ];
+  }