at 23.11-pre 9.5 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 5 inherit (builtins) length map; 6 inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs; 7 inherit (lib.modules) mkDefault mkIf; 8 inherit (lib.options) literalExpression mkEnableOption mkOption; 9 inherit (lib.strings) concatLines optionalString toLower; 10 inherit (lib.types) addCheck attrsOf lines nonEmptyStr nullOr package path port str strMatching submodule; 11 12 # Checks if given list of strings contains unique 13 # elements when compared without considering case. 14 # Type: checkIUnique :: [string] -> bool 15 # Example: checkIUnique ["foo" "Foo"] => false 16 checkIUnique = lst: 17 let 18 lenUniq = l: length (lib.lists.unique l); 19 in 20 lenUniq lst == lenUniq (map toLower lst); 21 22 # TSM rejects servername strings longer than 64 chars. 23 servernameType = strMatching ".{1,64}"; 24 25 serverOptions = { name, config, ... }: { 26 options.name = mkOption { 27 type = servernameType; 28 example = "mainTsmServer"; 29 description = lib.mdDoc '' 30 Local name of the IBM TSM server, 31 must be uncapitalized and no longer than 64 chars. 32 The value will be used for the 33 `server` 34 directive in {file}`dsm.sys`. 35 ''; 36 }; 37 options.server = mkOption { 38 type = nonEmptyStr; 39 example = "tsmserver.company.com"; 40 description = lib.mdDoc '' 41 Host/domain name or IP address of the IBM TSM server. 42 The value will be used for the 43 `tcpserveraddress` 44 directive in {file}`dsm.sys`. 45 ''; 46 }; 47 options.port = mkOption { 48 type = addCheck port (p: p<=32767); 49 default = 1500; # official default 50 description = lib.mdDoc '' 51 TCP port of the IBM TSM server. 52 The value will be used for the 53 `tcpport` 54 directive in {file}`dsm.sys`. 55 TSM does not support ports above 32767. 56 ''; 57 }; 58 options.node = mkOption { 59 type = nonEmptyStr; 60 example = "MY-TSM-NODE"; 61 description = lib.mdDoc '' 62 Target node name on the IBM TSM server. 63 The value will be used for the 64 `nodename` 65 directive in {file}`dsm.sys`. 66 ''; 67 }; 68 options.genPasswd = mkEnableOption (lib.mdDoc '' 69 automatic client password generation. 70 This option influences the 71 `passwordaccess` 72 directive in {file}`dsm.sys`. 73 The password will be stored in the directory 74 given by the option {option}`passwdDir`. 75 *Caution*: 76 If this option is enabled and the server forces 77 to renew the password (e.g. on first connection), 78 a random password will be generated and stored 79 ''); 80 options.passwdDir = mkOption { 81 type = path; 82 example = "/home/alice/tsm-password"; 83 description = lib.mdDoc '' 84 Directory that holds the TSM 85 node's password information. 86 The value will be used for the 87 `passworddir` 88 directive in {file}`dsm.sys`. 89 ''; 90 }; 91 options.includeExclude = mkOption { 92 type = lines; 93 default = ""; 94 example = '' 95 exclude.dir /nix/store 96 include.encrypt /home/.../* 97 ''; 98 description = lib.mdDoc '' 99 `include.*` and 100 `exclude.*` directives to be 101 used when sending files to the IBM TSM server. 102 The lines will be written into a file that the 103 `inclexcl` 104 directive in {file}`dsm.sys` points to. 105 ''; 106 }; 107 options.extraConfig = mkOption { 108 # TSM option keys are case insensitive; 109 # we have to ensure there are no keys that 110 # differ only by upper and lower case. 111 type = addCheck 112 (attrsOf (nullOr str)) 113 (attrs: checkIUnique (attrNames attrs)); 114 default = {}; 115 example.compression = "yes"; 116 example.passwordaccess = null; 117 description = lib.mdDoc '' 118 Additional key-value pairs for the server stanza. 119 Values must be strings, or `null` 120 for the key not to be used in the stanza 121 (e.g. to overrule values generated by other options). 122 ''; 123 }; 124 options.text = mkOption { 125 type = lines; 126 example = literalExpression 127 ''lib.modules.mkAfter "compression no"''; 128 description = lib.mdDoc '' 129 Additional text lines for the server stanza. 130 This option can be used if certion configuration keys 131 must be used multiple times or ordered in a certain way 132 as the {option}`extraConfig` option can't 133 control the order of lines in the resulting stanza. 134 Note that the `server` 135 line at the beginning of the stanza is 136 not part of this option's value. 137 ''; 138 }; 139 options.stanza = mkOption { 140 type = str; 141 internal = true; 142 visible = false; 143 description = lib.mdDoc "Server stanza text generated from the options."; 144 }; 145 config.name = mkDefault name; 146 # Client system-options file directives are explained here: 147 # https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=commands-processing-options 148 config.extraConfig = 149 mapAttrs (lib.trivial.const mkDefault) ( 150 { 151 commmethod = "v6tcpip"; # uses v4 or v6, based on dns lookup result 152 tcpserveraddress = config.server; 153 tcpport = builtins.toString config.port; 154 nodename = config.node; 155 passwordaccess = if config.genPasswd then "generate" else "prompt"; 156 passworddir = ''"${config.passwdDir}"''; 157 } // optionalAttrs (config.includeExclude!="") { 158 inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"''; 159 } 160 ); 161 config.text = 162 let 163 attrset = filterAttrs (k: v: v!=null) config.extraConfig; 164 mkLine = k: v: k + optionalString (v!="") " ${v}"; 165 lines = mapAttrsToList mkLine attrset; 166 in 167 concatLines lines; 168 config.stanza = '' 169 server ${config.name} 170 ${config.text} 171 ''; 172 }; 173 174 options.programs.tsmClient = { 175 enable = mkEnableOption (lib.mdDoc '' 176 IBM Spectrum Protect (Tivoli Storage Manager, TSM) 177 client command line applications with a 178 client system-options file "dsm.sys" 179 ''); 180 servers = mkOption { 181 type = attrsOf (submodule [ serverOptions ]); 182 default = {}; 183 example.mainTsmServer = { 184 server = "tsmserver.company.com"; 185 node = "MY-TSM-NODE"; 186 extraConfig.compression = "yes"; 187 }; 188 description = lib.mdDoc '' 189 Server definitions ("stanzas") 190 for the client system-options file. 191 ''; 192 }; 193 defaultServername = mkOption { 194 type = nullOr servernameType; 195 default = null; 196 example = "mainTsmServer"; 197 description = lib.mdDoc '' 198 If multiple server stanzas are declared with 199 {option}`programs.tsmClient.servers`, 200 this option may be used to name a default 201 server stanza that IBM TSM uses in the absence of 202 a user-defined {file}`dsm.opt` file. 203 This option translates to a 204 `defaultserver` configuration line. 205 ''; 206 }; 207 dsmSysText = mkOption { 208 type = lines; 209 readOnly = true; 210 description = lib.mdDoc '' 211 This configuration key contains the effective text 212 of the client system-options file "dsm.sys". 213 It should not be changed, but may be 214 used to feed the configuration into other 215 TSM-depending packages used on the system. 216 ''; 217 }; 218 package = mkOption { 219 type = package; 220 default = pkgs.tsm-client; 221 defaultText = literalExpression "pkgs.tsm-client"; 222 example = literalExpression "pkgs.tsm-client-withGui"; 223 description = lib.mdDoc '' 224 The TSM client derivation to be 225 added to the system environment. 226 It will be used with `.override` 227 to add paths to the client system-options file. 228 ''; 229 }; 230 wrappedPackage = mkOption { 231 type = package; 232 readOnly = true; 233 description = lib.mdDoc '' 234 The TSM client derivation, wrapped with the path 235 to the client system-options file "dsm.sys". 236 This option is to provide the effective derivation 237 for other modules that want to call TSM executables. 238 ''; 239 }; 240 }; 241 242 cfg = config.programs.tsmClient; 243 244 assertions = [ 245 { 246 assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers); 247 message = '' 248 TSM servernames contain duplicate name 249 (note that case doesn't matter!) 250 ''; 251 } 252 { 253 assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers); 254 message = "TSM defaultServername not found in list of servers"; 255 } 256 ]; 257 258 dsmSysText = '' 259 **** IBM Spectrum Protect (Tivoli Storage Manager) 260 **** client system-options file "dsm.sys". 261 **** Do not edit! 262 **** This file is generated by NixOS configuration. 263 264 ${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"} 265 266 ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)} 267 ''; 268 269in 270 271{ 272 273 inherit options; 274 275 config = mkIf cfg.enable { 276 inherit assertions; 277 programs.tsmClient.dsmSysText = dsmSysText; 278 programs.tsmClient.wrappedPackage = cfg.package.override rec { 279 dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText; 280 dsmSysApi = dsmSysCli; 281 }; 282 environment.systemPackages = [ cfg.wrappedPackage ]; 283 }; 284 285 meta.maintainers = [ lib.maintainers.yarny ]; 286 287}