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 loaOf 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 = loaOf (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 ]; | ||
|  | 
 | ||
|  | } |