288 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
let
 | 
						|
 | 
						|
  inherit (builtins) length map;
 | 
						|
  inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
 | 
						|
  inherit (lib.modules) mkDefault mkIf;
 | 
						|
  inherit (lib.options) literalExample mkEnableOption mkOption;
 | 
						|
  inherit (lib.strings) concatStringsSep optionalString toLower;
 | 
						|
  inherit (lib.types) addCheck attrsOf lines nullOr package path port str strMatching submodule;
 | 
						|
 | 
						|
  # Checks if given list of strings contains unique
 | 
						|
  # elements when compared without considering case.
 | 
						|
  # Type: checkIUnique :: [string] -> bool
 | 
						|
  # Example: checkIUnique ["foo" "Foo"] => false
 | 
						|
  checkIUnique = lst:
 | 
						|
    let
 | 
						|
      lenUniq = l: length (lib.lists.unique l);
 | 
						|
    in
 | 
						|
      lenUniq lst == lenUniq (map toLower lst);
 | 
						|
 | 
						|
  # TSM rejects servername strings longer than 64 chars.
 | 
						|
  servernameType = strMatching ".{1,64}";
 | 
						|
 | 
						|
  serverOptions = { name, config, ... }: {
 | 
						|
    options.name = mkOption {
 | 
						|
      type = servernameType;
 | 
						|
      example = "mainTsmServer";
 | 
						|
      description = ''
 | 
						|
        Local name of the IBM TSM server,
 | 
						|
        must be uncapitalized and no longer than 64 chars.
 | 
						|
        The value will be used for the
 | 
						|
        <literal>server</literal>
 | 
						|
        directive in <filename>dsm.sys</filename>.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.server = mkOption {
 | 
						|
      type = strMatching ".+";
 | 
						|
      example = "tsmserver.company.com";
 | 
						|
      description = ''
 | 
						|
        Host/domain name or IP address of the IBM TSM server.
 | 
						|
        The value will be used for the
 | 
						|
        <literal>tcpserveraddress</literal>
 | 
						|
        directive in <filename>dsm.sys</filename>.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.port = mkOption {
 | 
						|
      type = addCheck port (p: p<=32767);
 | 
						|
      default = 1500;  # official default
 | 
						|
      description = ''
 | 
						|
        TCP port of the IBM TSM server.
 | 
						|
        The value will be used for the
 | 
						|
        <literal>tcpport</literal>
 | 
						|
        directive in <filename>dsm.sys</filename>.
 | 
						|
        TSM does not support ports above 32767.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.node = mkOption {
 | 
						|
      type = strMatching ".+";
 | 
						|
      example = "MY-TSM-NODE";
 | 
						|
      description = ''
 | 
						|
        Target node name on the IBM TSM server.
 | 
						|
        The value will be used for the
 | 
						|
        <literal>nodename</literal>
 | 
						|
        directive in <filename>dsm.sys</filename>.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.genPasswd = mkEnableOption ''
 | 
						|
      automatic client password generation.
 | 
						|
      This option influences the
 | 
						|
      <literal>passwordaccess</literal>
 | 
						|
      directive in <filename>dsm.sys</filename>.
 | 
						|
      The password will be stored in the directory
 | 
						|
      given by the option <option>passwdDir</option>.
 | 
						|
      <emphasis>Caution</emphasis>:
 | 
						|
      If this option is enabled and the server forces
 | 
						|
      to renew the password (e.g. on first connection),
 | 
						|
      a random password will be generated and stored
 | 
						|
    '';
 | 
						|
    options.passwdDir = mkOption {
 | 
						|
      type = path;
 | 
						|
      example = "/home/alice/tsm-password";
 | 
						|
      description = ''
 | 
						|
        Directory that holds the TSM
 | 
						|
        node's password information.
 | 
						|
        The value will be used for the
 | 
						|
        <literal>passworddir</literal>
 | 
						|
        directive in <filename>dsm.sys</filename>.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.includeExclude = mkOption {
 | 
						|
      type = lines;
 | 
						|
      default = "";
 | 
						|
      example = ''
 | 
						|
        exclude.dir     /nix/store
 | 
						|
        include.encrypt /home/.../*
 | 
						|
      '';
 | 
						|
      description = ''
 | 
						|
        <literal>include.*</literal> and
 | 
						|
        <literal>exclude.*</literal> directives to be
 | 
						|
        used when sending files to the IBM TSM server.
 | 
						|
        The lines will be written into a file that the
 | 
						|
        <literal>inclexcl</literal>
 | 
						|
        directive in <filename>dsm.sys</filename> points to.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.extraConfig = mkOption {
 | 
						|
      # TSM option keys are case insensitive;
 | 
						|
      # we have to ensure there are no keys that
 | 
						|
      # differ only by upper and lower case.
 | 
						|
      type = addCheck
 | 
						|
        (attrsOf (nullOr str))
 | 
						|
        (attrs: checkIUnique (attrNames attrs));
 | 
						|
      default = {};
 | 
						|
      example.compression = "yes";
 | 
						|
      example.passwordaccess = null;
 | 
						|
      description = ''
 | 
						|
        Additional key-value pairs for the server stanza.
 | 
						|
        Values must be strings, or <literal>null</literal>
 | 
						|
        for the key not to be used in the stanza
 | 
						|
        (e.g. to overrule values generated by other options).
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.text = mkOption {
 | 
						|
      type = lines;
 | 
						|
      example = literalExample
 | 
						|
        ''lib.modules.mkAfter "compression no"'';
 | 
						|
      description = ''
 | 
						|
        Additional text lines for the server stanza.
 | 
						|
        This option can be used if certion configuration keys
 | 
						|
        must be used multiple times or ordered in a certain way
 | 
						|
        as the <option>extraConfig</option> option can't
 | 
						|
        control the order of lines in the resulting stanza.
 | 
						|
        Note that the <literal>server</literal>
 | 
						|
        line at the beginning of the stanza is
 | 
						|
        not part of this option's value.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    options.stanza = mkOption {
 | 
						|
      type = str;
 | 
						|
      internal = true;
 | 
						|
      visible = false;
 | 
						|
      description = "Server stanza text generated from the options.";
 | 
						|
    };
 | 
						|
    config.name = mkDefault name;
 | 
						|
    # Client system-options file directives are explained here:
 | 
						|
    # https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.8/client/c_opt_usingopts.html
 | 
						|
    config.extraConfig =
 | 
						|
      mapAttrs (lib.trivial.const mkDefault) (
 | 
						|
        {
 | 
						|
          commmethod = "v6tcpip";  # uses v4 or v6, based on dns lookup result
 | 
						|
          tcpserveraddress = config.server;
 | 
						|
          tcpport = builtins.toString config.port;
 | 
						|
          nodename = config.node;
 | 
						|
          passwordaccess = if config.genPasswd then "generate" else "prompt";
 | 
						|
          passworddir = ''"${config.passwdDir}"'';
 | 
						|
        } // optionalAttrs (config.includeExclude!="") {
 | 
						|
          inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"'';
 | 
						|
        }
 | 
						|
      );
 | 
						|
    config.text =
 | 
						|
      let
 | 
						|
        attrset = filterAttrs (k: v: v!=null) config.extraConfig;
 | 
						|
        mkLine = k: v: k + optionalString (v!="") "  ${v}";
 | 
						|
        lines = mapAttrsToList mkLine attrset;
 | 
						|
      in
 | 
						|
        concatStringsSep "\n" lines;
 | 
						|
    config.stanza = ''
 | 
						|
      server  ${config.name}
 | 
						|
      ${config.text}
 | 
						|
    '';
 | 
						|
  };
 | 
						|
 | 
						|
  options.programs.tsmClient = {
 | 
						|
    enable = mkEnableOption ''
 | 
						|
      IBM Spectrum Protect (Tivoli Storage Manager, TSM)
 | 
						|
      client command line applications with a
 | 
						|
      client system-options file "dsm.sys"
 | 
						|
    '';
 | 
						|
    servers = mkOption {
 | 
						|
      type = attrsOf (submodule [ serverOptions ]);
 | 
						|
      default = {};
 | 
						|
      example.mainTsmServer = {
 | 
						|
        server = "tsmserver.company.com";
 | 
						|
        node = "MY-TSM-NODE";
 | 
						|
        extraConfig.compression = "yes";
 | 
						|
      };
 | 
						|
      description = ''
 | 
						|
        Server definitions ("stanzas")
 | 
						|
        for the client system-options file.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    defaultServername = mkOption {
 | 
						|
      type = nullOr servernameType;
 | 
						|
      default = null;
 | 
						|
      example = "mainTsmServer";
 | 
						|
      description = ''
 | 
						|
        If multiple server stanzas are declared with
 | 
						|
        <option>programs.tsmClient.servers</option>,
 | 
						|
        this option may be used to name a default
 | 
						|
        server stanza that IBM TSM uses in the absence of
 | 
						|
        a user-defined <filename>dsm.opt</filename> file.
 | 
						|
        This option translates to a
 | 
						|
        <literal>defaultserver</literal> configuration line.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    dsmSysText = mkOption {
 | 
						|
      type = lines;
 | 
						|
      readOnly = true;
 | 
						|
      description = ''
 | 
						|
        This configuration key contains the effective text
 | 
						|
        of the client system-options file "dsm.sys".
 | 
						|
        It should not be changed, but may be
 | 
						|
        used to feed the configuration into other
 | 
						|
        TSM-depending packages used on the system.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    package = mkOption {
 | 
						|
      type = package;
 | 
						|
      default = pkgs.tsm-client;
 | 
						|
      defaultText = "pkgs.tsm-client";
 | 
						|
      example = literalExample "pkgs.tsm-client-withGui";
 | 
						|
      description = ''
 | 
						|
        The TSM client derivation to be
 | 
						|
        added to the system environment.
 | 
						|
        It will called with <literal>.override</literal>
 | 
						|
        to add paths to the client system-options file.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    wrappedPackage = mkOption {
 | 
						|
      type = package;
 | 
						|
      readOnly = true;
 | 
						|
      description = ''
 | 
						|
        The TSM client derivation, wrapped with the path
 | 
						|
        to the client system-options file "dsm.sys".
 | 
						|
        This option is to provide the effective derivation
 | 
						|
        for other modules that want to call TSM executables.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  cfg = config.programs.tsmClient;
 | 
						|
 | 
						|
  assertions = [
 | 
						|
    {
 | 
						|
      assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers);
 | 
						|
      message = ''
 | 
						|
        TSM servernames contain duplicate name
 | 
						|
        (note that case doesn't matter!)
 | 
						|
      '';
 | 
						|
    }
 | 
						|
    {
 | 
						|
      assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers);
 | 
						|
      message = "TSM defaultServername not found in list of servers";
 | 
						|
    }
 | 
						|
  ];
 | 
						|
 | 
						|
  dsmSysText = ''
 | 
						|
    ****  IBM Spectrum Protect (Tivoli Storage Manager)
 | 
						|
    ****  client system-options file "dsm.sys".
 | 
						|
    ****  Do not edit!
 | 
						|
    ****  This file is generated by NixOS configuration.
 | 
						|
 | 
						|
    ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
 | 
						|
 | 
						|
    ${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)}
 | 
						|
  '';
 | 
						|
 | 
						|
in
 | 
						|
 | 
						|
{
 | 
						|
 | 
						|
  inherit options;
 | 
						|
 | 
						|
  config = mkIf cfg.enable {
 | 
						|
    inherit assertions;
 | 
						|
    programs.tsmClient.dsmSysText = dsmSysText;
 | 
						|
    programs.tsmClient.wrappedPackage = cfg.package.override rec {
 | 
						|
      dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
 | 
						|
      dsmSysApi = dsmSysCli;
 | 
						|
    };
 | 
						|
    environment.systemPackages = [ cfg.wrappedPackage ];
 | 
						|
  };
 | 
						|
 | 
						|
  meta.maintainers = [ lib.maintainers.yarny ];
 | 
						|
 | 
						|
}
 |