Skip to content
Snippets Groups Projects
tahoe.nix 12.1 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;
  ini = pkgs.callPackage ../lib/ini.nix { };
    # 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"
    ];

    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 = "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 = "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 run). The node directory must come first,
              # and arguments which alter Twisted's behavior come afterwards.
              ExecStart = ''
                ${settings.package}/bin/tahoe run ${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:
            nameValuePair "tahoe-lafs/${node}.cfg" {
              mode = "0444";
              text = ''
                # This configuration is generated by Nix. Edit at your own
                # peril; here be dragons.

                ${ini.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.${lib.escapeShellArg 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/${lib.escapeShellArg node}";
            eliotLog = "file:${nodedir}/logs/eliot.json,rotate_length=${toString (1024 * 1024 * 32)},max_rotated_files=32";
          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 run). The node directory must come first,
              # and arguments which alter Twisted's behavior come afterwards.
              ExecStart = ''
                ${settings.package}/bin/tahoe --eliot-destination ${eliotLog} run ${nodedir} -n -l- --pidfile=${pidfile}
              # The rlimit on number of open files controls how many
              # connections a particular storage server can accept (factoring
              # in the number of non-connection files the server needs open -
              # eg for logging, reading and writing shares, etc).
              #
              # Once the maximum number of open files, as controlled by rlimit
              # is reached, service suffers dramatically. New connections
              # cannot be accepted. Shares cannot be read or written.
              #
              # The default limit on open files is fairly low, perhaps 1024
              # (2^10) or 8192 (2^13). This can easily be raised. If it is
              # raised to 2^16 then the rlimit is approximately equal to the
              # limit imposed by TCP (which only has around 2^16 ports
              # available per IP address). If we want each connection to also
              # be able to read or write a share file, a limit of 2^15 would
              # allow this. Then, we should scale the limit linearly with the
              # number of IP addresses available. If the service can be
              # reached on 2 IP addresses, allow twice as many files (2^15 * 2
              # = 2^16). If it can be reached on 3 IP addresses, (2^16 *
              # 3). etc.
              #
              # Python also sometimes wants to open files as a side effect of
              # other things going.  For example, if there's a traceback, it
              # opens the source files to read lines to put into the
              # traceback.  If random numbers are generated, /dev/urandom
              # might be opened, etc.  There is also some fixed overhead for
              # listening ports and such.  This currently doesn't factor into
              # our choice but perhaps it could somehow.
              #
              # Tahoe-LAFS has no logic to raise soft limit to hard limit so
              # make it the same.
              #
              # There is only one IPv4 address assigned to each host right
              # now. So it makes sense to have the limit be 2^15 right now.
              LimitNOFILE = 32768;
            preStart =
            let
              created = "${nodedir}.created";
              atomic = "${nodedir}.atomic";
            in ''
              if [ ! -e ${created} ]; then
                mkdir -p /var/db/tahoe-lafs/

                # Get rid of any prior partial efforts.  It might not exist.
                # Don't let this tank us.
                rm -rv ${atomic} || [ ! -e ${atomic} ]

                # Really create the node.
                tahoe create-node --hostname=localhost ${atomic}

                # Get rid of any existing partially created node directory
                # that might be in the way.
                if [ -e ${nodedir} ]; then
                  for backup in $(seq 1 100); do
                    if [ ! -e ${nodedir}.$backup ]; then
                      mv ${nodedir} ${nodedir}.$backup
                      break
                    fi
                  done
                fi

                # Move the new thing to the real location.  We don't create it
                # in-place because we might fail partway through and leave
                # inconsistent state.  Also, systemd probably created
                # logs/incidents/ already and `create-node` complains if it
                # finds these exist already.
                mv ${atomic} ${nodedir}

                # Record our complete, consistent success.
              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 ${nodedir}/tahoe.cfg
            '';
          });
        users.users = flip mapAttrs' cfg.nodes (node: _:
          nameValuePair "tahoe.${node}" {
            description = "Tahoe node user for node ${node}";
            isSystemUser = true;
          });
      })
    ];
  }