at 25.11-pre 12 kB view raw
1{ 2 config, 3 lib, 4 options, 5 pkgs, 6 ... 7}: # XXX migration code for freeform settings: `options` can be removed in 2025 8let 9 optionsGlobal = options; 10in 11 12let 13 14 inherit (lib.attrsets) 15 attrNames 16 attrValues 17 mapAttrsToList 18 removeAttrs 19 ; 20 inherit (lib.lists) 21 all 22 allUnique 23 concatLists 24 concatMap 25 elem 26 isList 27 map 28 ; 29 inherit (lib.modules) mkDefault mkIf; 30 inherit (lib.options) mkEnableOption mkOption mkPackageOption; 31 inherit (lib.strings) 32 concatLines 33 match 34 optionalString 35 toLower 36 ; 37 inherit (lib.trivial) isInt; 38 inherit (lib.types) 39 addCheck 40 attrsOf 41 coercedTo 42 either 43 enum 44 int 45 lines 46 listOf 47 nonEmptyStr 48 nullOr 49 oneOf 50 path 51 port 52 singleLineStr 53 strMatching 54 submodule 55 ; 56 57 scalarType = 58 # see the option's description below for the 59 # handling/transformation of each possible type 60 oneOf [ 61 (enum [ 62 true 63 null 64 ]) 65 int 66 path 67 singleLineStr 68 ]; 69 70 # TSM rejects servername strings longer than 64 chars. 71 servernameType = strMatching "[^[:space:]]{1,64}"; 72 73 serverOptions = 74 { name, config, ... }: 75 { 76 freeformType = attrsOf (either scalarType (listOf scalarType)); 77 # Client system-options file directives are explained here: 78 # https://www.ibm.com/docs/en/storage-protect/8.1.25?topic=commands-processing-options 79 options.servername = mkOption { 80 type = servernameType; 81 default = name; 82 example = "mainTsmServer"; 83 description = '' 84 Local name of the IBM TSM server, 85 must not contain space or more than 64 chars. 86 ''; 87 }; 88 options.tcpserveraddress = mkOption { 89 type = nonEmptyStr; 90 example = "tsmserver.company.com"; 91 description = '' 92 Host/domain name or IP address of the IBM TSM server. 93 ''; 94 }; 95 options.tcpport = mkOption { 96 type = addCheck port (p: p <= 32767); 97 default = 1500; # official default 98 description = '' 99 TCP port of the IBM TSM server. 100 TSM does not support ports above 32767. 101 ''; 102 }; 103 options.nodename = mkOption { 104 type = nonEmptyStr; 105 example = "MY-TSM-NODE"; 106 description = '' 107 Target node name on the IBM TSM server. 108 ''; 109 }; 110 options.genPasswd = mkEnableOption '' 111 automatic client password generation. 112 This option does *not* cause a line in 113 {file}`dsm.sys` by itself, but generates a 114 corresponding `passwordaccess` directive. 115 The password will be stored in the directory 116 given by the option {option}`passworddir`. 117 *Caution*: 118 If this option is enabled and the server forces 119 to renew the password (e.g. on first connection), 120 a random password will be generated and stored 121 ''; 122 options.passwordaccess = mkOption { 123 type = enum [ 124 "generate" 125 "prompt" 126 ]; 127 visible = false; 128 }; 129 options.passworddir = mkOption { 130 type = nullOr path; 131 default = null; 132 example = "/home/alice/tsm-password"; 133 description = '' 134 Directory that holds the TSM 135 node's password information. 136 ''; 137 }; 138 options.inclexcl = mkOption { 139 type = coercedTo lines (pkgs.writeText "inclexcl.dsm.sys") (nullOr path); 140 default = null; 141 example = '' 142 exclude.dir /nix/store 143 include.encrypt /home/.../* 144 ''; 145 description = '' 146 Text lines with `include.*` and `exclude.*` directives 147 to be used when sending files to the IBM TSM server, 148 or an absolute path pointing to a file with such lines. 149 ''; 150 }; 151 config.commmethod = mkDefault "v6tcpip"; # uses v4 or v6, based on dns lookup result 152 config.passwordaccess = if config.genPasswd then "generate" else "prompt"; 153 # XXX migration code for freeform settings, these can be removed in 2025: 154 options.warnings = optionsGlobal.warnings; 155 options.assertions = optionsGlobal.assertions; 156 imports = 157 let 158 inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule; 159 in 160 [ 161 (mkRemovedOptionModule [ "extraConfig" ] 162 "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`." 163 ) 164 (mkRemovedOptionModule [ "text" ] 165 "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`." 166 ) 167 (mkRenamedOptionModule [ "name" ] [ "servername" ]) 168 (mkRenamedOptionModule [ "server" ] [ "tcpserveraddress" ]) 169 (mkRenamedOptionModule [ "port" ] [ "tcpport" ]) 170 (mkRenamedOptionModule [ "node" ] [ "nodename" ]) 171 (mkRenamedOptionModule [ "passwdDir" ] [ "passworddir" ]) 172 (mkRenamedOptionModule [ "includeExclude" ] [ "inclexcl" ]) 173 ]; 174 }; 175 176 options.programs.tsmClient = { 177 enable = mkEnableOption '' 178 IBM Storage Protect (Tivoli Storage Manager, TSM) 179 client command line applications with a 180 client system-options file "dsm.sys" 181 ''; 182 servers = mkOption { 183 type = attrsOf (submodule serverOptions); 184 default = { }; 185 example.mainTsmServer = { 186 tcpserveraddress = "tsmserver.company.com"; 187 nodename = "MY-TSM-NODE"; 188 compression = "yes"; 189 }; 190 description = '' 191 Server definitions ("stanzas") 192 for the client system-options file. 193 The name of each entry will be used for 194 the internal `servername` by default. 195 Each attribute will be transformed into a line 196 with a key-value pair within the server's stanza. 197 Integers as values will be 198 canonically turned into strings. 199 The boolean value `true` will be turned 200 into a line with just the attribute's name. 201 The value `null` will not generate a line. 202 A list as values generates an entry for 203 each value, according to the rules above. 204 ''; 205 }; 206 defaultServername = mkOption { 207 type = nullOr servernameType; 208 default = null; 209 example = "mainTsmServer"; 210 description = '' 211 If multiple server stanzas are declared with 212 {option}`programs.tsmClient.servers`, 213 this option may be used to name a default 214 server stanza that IBM TSM uses in the absence of 215 a user-defined {file}`dsm.opt` file. 216 This option translates to a 217 `defaultserver` configuration line. 218 ''; 219 }; 220 dsmSysText = mkOption { 221 type = lines; 222 readOnly = true; 223 description = '' 224 This configuration key contains the effective text 225 of the client system-options file "dsm.sys". 226 It should not be changed, but may be 227 used to feed the configuration into other 228 TSM-depending packages used on the system. 229 ''; 230 }; 231 package = mkPackageOption pkgs "tsm-client" { 232 example = "tsm-client-withGui"; 233 extraDescription = '' 234 It will be used with `.override` 235 to add paths to the client system-options file. 236 ''; 237 }; 238 wrappedPackage = 239 mkPackageOption pkgs "tsm-client" { 240 default = null; 241 extraDescription = '' 242 This option is to provide the effective derivation, 243 wrapped with the path to the 244 client system-options file "dsm.sys". 245 It should not be changed, but exists 246 for other modules that want to call TSM executables. 247 ''; 248 } 249 // { 250 readOnly = true; 251 }; 252 }; 253 254 cfg = config.programs.tsmClient; 255 servernames = map (s: s.servername) (attrValues cfg.servers); 256 257 assertions = 258 [ 259 { 260 assertion = allUnique (map toLower servernames); 261 message = '' 262 TSM server names 263 (option `programs.tsmClient.servers`) 264 contain duplicate name 265 (note that server names are case insensitive). 266 ''; 267 } 268 { 269 assertion = (cfg.defaultServername != null) -> (elem cfg.defaultServername servernames); 270 message = '' 271 TSM default server name 272 `programs.tsmClient.defaultServername="${cfg.defaultServername}"` 273 not found in server names in 274 `programs.tsmClient.servers`. 275 ''; 276 } 277 ] 278 ++ (mapAttrsToList (name: serverCfg: { 279 assertion = all (key: null != match "[^[:space:]]+" key) (attrNames serverCfg); 280 message = '' 281 TSM server setting names in 282 `programs.tsmClient.servers.${name}.*` 283 contain spaces, but that's not allowed. 284 ''; 285 }) cfg.servers) 286 ++ (mapAttrsToList (name: serverCfg: { 287 assertion = allUnique (map toLower (attrNames serverCfg)); 288 message = '' 289 TSM server setting names in 290 `programs.tsmClient.servers.${name}.*` 291 contain duplicate names 292 (note that setting names are case insensitive). 293 ''; 294 }) cfg.servers) 295 # XXX migration code for freeform settings, this can be removed in 2025: 296 ++ (enrichMigrationInfos "assertions" ( 297 addText: 298 { assertion, message }: 299 { 300 inherit assertion; 301 message = addText message; 302 } 303 )); 304 305 makeDsmSysLines = 306 key: value: 307 # Turn a key-value pair from the server options attrset 308 # into zero (value==null), one (scalar value) or 309 # more (value is list) configuration stanza lines. 310 if isList value then 311 concatMap (makeDsmSysLines key) value 312 # recurse into list 313 else if value == null then 314 [ ] 315 # skip `null` value 316 else 317 [ 318 ( 319 " ${key}${ 320 if value == true then 321 "" 322 # just output key if value is `true` 323 else if isInt value then 324 " ${builtins.toString value}" 325 else if path.check value then 326 " \"${value}\"" 327 # enclose path in ".." 328 else if singleLineStr.check value then 329 " ${value}" 330 else 331 throw "assertion failed: cannot convert type" # should never happen 332 }" 333 ) 334 ]; 335 336 makeDsmSysStanza = 337 { servername, ... }@serverCfg: 338 let 339 # drop special values that should not go into server config block 340 attrs = removeAttrs serverCfg [ 341 "servername" 342 "genPasswd" 343 # XXX migration code for freeform settings, these can be removed in 2025: 344 "assertions" 345 "warnings" 346 "extraConfig" 347 "text" 348 "name" 349 "server" 350 "port" 351 "node" 352 "passwdDir" 353 "includeExclude" 354 ]; 355 in 356 '' 357 servername ${servername} 358 ${concatLines (concatLists (mapAttrsToList makeDsmSysLines attrs))} 359 ''; 360 361 dsmSysText = '' 362 **** IBM Storage Protect (Tivoli Storage Manager) 363 **** client system-options file "dsm.sys". 364 **** Do not edit! 365 **** This file is generated by NixOS configuration. 366 367 ${optionalString (cfg.defaultServername != null) "defaultserver ${cfg.defaultServername}"} 368 369 ${concatLines (map makeDsmSysStanza (attrValues cfg.servers))} 370 ''; 371 372 # XXX migration code for freeform settings, this can be removed in 2025: 373 enrichMigrationInfos = 374 what: how: 375 concatLists ( 376 mapAttrsToList ( 377 name: serverCfg: 378 map (how (text: "In `programs.tsmClient.servers.${name}`: ${text}")) serverCfg."${what}" 379 ) cfg.servers 380 ); 381 382in 383 384{ 385 386 inherit options; 387 388 config = mkIf cfg.enable { 389 inherit assertions; 390 programs.tsmClient.dsmSysText = dsmSysText; 391 programs.tsmClient.wrappedPackage = cfg.package.override rec { 392 dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText; 393 dsmSysApi = dsmSysCli; 394 }; 395 environment.systemPackages = [ cfg.wrappedPackage ]; 396 # XXX migration code for freeform settings, this can be removed in 2025: 397 warnings = enrichMigrationInfos "warnings" (addText: addText); 398 }; 399 400 meta.maintainers = [ lib.maintainers.yarny ]; 401 402}