Casual Nixpkgs contributions

Hi all,

apparently today I’m here exactly 1 year.
Let’s celebrate. I’ve managed to create a NixOS service for seaweedfs, the package of which the inclusion was done by @raboof

I’d say it’s 99% ready, but since I’ve never created a NixOS service before, I’m looking forward to feedback. I will update this post accordingly

{ config, lib, pkgs, ... }:
with lib;
let

  seaweedfs = pkgs.seaweedfs;

  user = "seaweedfs";
  group = "seaweedfs";
  cfg = config.services.seaweedfs;
  enabledVolumes = filterAttrs (_: v: v.enable) cfg.volumes;
  anyEnabled = cfg.master.enable || cfg.filer.enable || cfg.webdav.enable
    || enabledVolumes != { };

  mkCmdLineArguments = mapAttrsToList (option: value:
    if isBool value then
      "-${option}"
    else
      "-${option}=${
        if isList value then
          builtins.concatStringsSep "," value
        else
          toString value
      }");

  mkWeedExec = subcmd: options:
    (toString ([ "${seaweedfs}/bin/weed" subcmd ] ++ mkCmdLineArguments
      ((removeAttrs options [ "enable" "extraConfig" ])
        // (if options ? "extraConfig" then
          (removeAttrs options.extraConfig (builtins.attrNames options))
        else
          { }))));

  mkExtraConfigOption = subcmd:
    mkOption {
      default = { };
      type = with types; attrs;
      description = ''
        Additional configuration, see output of 'weed ${subcmd} --help' for attributes.
        Do not define settings for flags for which explicit configuration options exist, these will be ignored.
      '';
    };

  mkPortOption = defaultPort:
    mkOption {
      default = defaultPort;
      type = with types; uniq port;
      description = "Http listen port";
    };

  mkServerListOption = subcmd:
    mkOption {
      default = [ "localhost:${toString cfg.${subcmd}.port}" ];
      type = with types; listOf str;
      description = "List of ${subcmd} servers (host/ip:port)";
    };

  mkVolumeService = id: options:
    nameValuePair "seaweedfs-volume-${id}" {
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ]
        ++ optional cfg.master.enable "seaweedfs-master.service";
      description = "SeaweedFS volume - ${id}";
      unitConfig.ConditionPathIsDirectory = options.dir;
      serviceConfig = {
        User = user;
        Group = group;
        ExecStart = mkWeedExec "volume" options;
        KillSignal = "SIGTERM";
      };
    };

  mkVolumeOptions = { id, ... }: {
    options = {
      enable = mkEnableOption "SeaweedFS volume server";
      port = mkPortOption 8080;
      mserver = mkServerListOption "master";
      extraConfig = mkExtraConfigOption "volume";

      dir = mkOption {
        default = [ "/tmp" ];
        type = with types; listOf path;
        description = ''
          One or more directories to store data files.
          These must exist before the volume server service is started,
          and must be owned by ${user}:${group}.'';
      };
    };
  };

in {

  ###### interface

  options = {
    services.seaweedfs = {

      master = {
        enable = mkEnableOption "SeaweedFS master server";
        port = mkPortOption 9333;
        extraConfig = mkExtraConfigOption "master";

      };

      filer = {
        enable = mkEnableOption "SeaweedFS file server";
        port = mkPortOption 8888;
        master = mkServerListOption "master";
        extraConfig = mkExtraConfigOption "filer";

      };

      volumes = mkOption {
        default = { };
        type = with types; attrsOf (submodule mkVolumeOptions);
      };

      webdav = {
        enable = mkEnableOption "SeaweedFS webdav server";
        port = mkPortOption 7333;
        filer = mkServerListOption "filer";
        extraConfig = mkExtraConfigOption "webdav";

      };
    };
  };

  ###### implementation

  config = mkIf anyEnabled {
    environment.systemPackages = [ seaweedfs ];
    users.users.${user} = {
      description = "SeaweedFS user";
      isSystemUser = true;
      group = group;
    };
    users.groups.${group} = { };
    systemd.services = (mapAttrs' mkVolumeService enabledVolumes) // {
      seaweedfs-master = mkIf cfg.master.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ];
        description = "SeaweedFS master";
        serviceConfig = rec {
          User = user;
          Group = group;
          StateDirectory = "seaweedfs/master";
          ExecStart = (mkWeedExec "master" cfg.master)
            + " -mdir=/var/lib/${StateDirectory}";
          KillSignal = "SIGTERM";
        };
      };

      seaweedfs-filer = mkIf cfg.filer.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ]
          ++ optional cfg.master.enable "seaweedfs-master.service";
        description = "SeaweedFS filer";
        serviceConfig = rec {
          User = user;
          Group = group;
          WorkingDirectory = "/var/lib/${StateDirectory}";
          StateDirectory = "seaweedfs/filer";
          ExecStart = mkWeedExec "filer" cfg.filer;
          KillSignal = "SIGTERM";
        };
      };

      seaweedfs-webdav = mkIf cfg.webdav.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ]
          ++ optional cfg.filer.enable "seaweedfs-filer.service";
        description = "SeaweedFS webdav";
        serviceConfig = {
          User = user;
          Group = group;
          ExecStart = mkWeedExec "webdav" cfg.webdav;
          KillSignal = "SIGTERM";
        };
      };
    };
  };
}

Here’s an example how to use it:

...
  services.seaweedfs.master.enable = true;
  services.seaweedfs.master.extraConfig.resumeState = true;
  services.seaweedfs.filer.enable = true;
  services.seaweedfs.webdav.enable = true;
  services.seaweedfs.webdav.extraConfig = {
    disk = "hdd";
    cacheCapacityMB = 100;
  };
  services.seaweedfs.volumes = {
    default = {
      enable = true;
      dir = [ "/seaweed-volume-sda" "/mnt/backup/seaweedfs/volume-sdb" ];
      #  mserver = [ "localhost:${config.services.seaweedfs.master.port}" ];
    };
  };

  networking.firewall.allowedTCPPorts = [
    config.services.seaweedfs.master.port # HTTP
    19333 # gRPC
    config.services.seaweedfs.volumes.default.port
    18080
    config.services.seaweedfs.filer.port
    18888
  ];
...
1 Like