Skip to content
Snippets Groups Projects
tahoe.nix 9.96 KiB
Newer Older
# 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;
          });
      })
    ];
  }