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