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 ];
 | |
| 
 | |
| }
 | 
