1 { config, lib, options, pkgs, ... }: # XXX migration code for freeform settings: `options` can be removed in 2025
2 let optionsGlobal = options; in
6 inherit (lib.attrsets) attrNames attrValues mapAttrsToList removeAttrs;
7 inherit (lib.lists) all allUnique concatLists concatMap elem isList map;
8 inherit (lib.modules) mkDefault mkIf;
9 inherit (lib.options) mkEnableOption mkOption mkPackageOption;
10 inherit (lib.strings) concatLines match optionalString toLower;
11 inherit (lib.trivial) isInt;
12 inherit (lib.types) addCheck attrsOf coercedTo either enum int lines listOf nonEmptyStr nullOr oneOf path port singleLineStr strMatching submodule;
15 # see the option's description below for the
16 # handling/transformation of each possible type
17 oneOf [ (enum [ true null ]) int path singleLineStr ];
19 # TSM rejects servername strings longer than 64 chars.
20 servernameType = strMatching "[^[:space:]]{1,64}";
22 serverOptions = { name, config, ... }: {
23 freeformType = attrsOf (either scalarType (listOf scalarType));
24 # Client system-options file directives are explained here:
25 # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=commands-processing-options
26 options.servername = mkOption {
27 type = servernameType;
29 example = "mainTsmServer";
31 Local name of the IBM TSM server,
32 must not contain space or more than 64 chars.
35 options.tcpserveraddress = mkOption {
37 example = "tsmserver.company.com";
39 Host/domain name or IP address of the IBM TSM server.
42 options.tcpport = mkOption {
43 type = addCheck port (p: p<=32767);
44 default = 1500; # official default
46 TCP port of the IBM TSM server.
47 TSM does not support ports above 32767.
50 options.nodename = mkOption {
52 example = "MY-TSM-NODE";
54 Target node name on the IBM TSM server.
57 options.genPasswd = mkEnableOption ''
58 automatic client password generation.
59 This option does *not* cause a line in
60 {file}`dsm.sys` by itself, but generates a
61 corresponding `passwordaccess` directive.
62 The password will be stored in the directory
63 given by the option {option}`passworddir`.
65 If this option is enabled and the server forces
66 to renew the password (e.g. on first connection),
67 a random password will be generated and stored
69 options.passwordaccess = mkOption {
70 type = enum [ "generate" "prompt" ];
73 options.passworddir = mkOption {
76 example = "/home/alice/tsm-password";
78 Directory that holds the TSM
79 node's password information.
82 options.inclexcl = mkOption {
83 type = coercedTo lines
84 (pkgs.writeText "inclexcl.dsm.sys")
88 exclude.dir /nix/store
89 include.encrypt /home/.../*
92 Text lines with `include.*` and `exclude.*` directives
93 to be used when sending files to the IBM TSM server,
94 or an absolute path pointing to a file with such lines.
97 config.commmethod = mkDefault "v6tcpip"; # uses v4 or v6, based on dns lookup result
98 config.passwordaccess = if config.genPasswd then "generate" else "prompt";
99 # XXX migration code for freeform settings, these can be removed in 2025:
100 options.warnings = optionsGlobal.warnings;
101 options.assertions = optionsGlobal.assertions;
102 imports = let inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule; in [
103 (mkRemovedOptionModule [ "extraConfig" ] "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`.")
104 (mkRemovedOptionModule [ "text" ] "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`.")
105 (mkRenamedOptionModule [ "name" ] [ "servername" ])
106 (mkRenamedOptionModule [ "server" ] [ "tcpserveraddress" ])
107 (mkRenamedOptionModule [ "port" ] [ "tcpport" ])
108 (mkRenamedOptionModule [ "node" ] [ "nodename" ])
109 (mkRenamedOptionModule [ "passwdDir" ] [ "passworddir" ])
110 (mkRenamedOptionModule [ "includeExclude" ] [ "inclexcl" ])
114 options.programs.tsmClient = {
115 enable = mkEnableOption ''
116 IBM Storage Protect (Tivoli Storage Manager, TSM)
117 client command line applications with a
118 client system-options file "dsm.sys"
121 type = attrsOf (submodule serverOptions);
123 example.mainTsmServer = {
124 tcpserveraddress = "tsmserver.company.com";
125 nodename = "MY-TSM-NODE";
129 Server definitions ("stanzas")
130 for the client system-options file.
131 The name of each entry will be used for
132 the internal `servername` by default.
133 Each attribute will be transformed into a line
134 with a key-value pair within the server's stanza.
135 Integers as values will be
136 canonically turned into strings.
137 The boolean value `true` will be turned
138 into a line with just the attribute's name.
139 The value `null` will not generate a line.
140 A list as values generates an entry for
141 each value, according to the rules above.
144 defaultServername = mkOption {
145 type = nullOr servernameType;
147 example = "mainTsmServer";
149 If multiple server stanzas are declared with
150 {option}`programs.tsmClient.servers`,
151 this option may be used to name a default
152 server stanza that IBM TSM uses in the absence of
153 a user-defined {file}`dsm.opt` file.
154 This option translates to a
155 `defaultserver` configuration line.
158 dsmSysText = mkOption {
162 This configuration key contains the effective text
163 of the client system-options file "dsm.sys".
164 It should not be changed, but may be
165 used to feed the configuration into other
166 TSM-depending packages used on the system.
169 package = mkPackageOption pkgs "tsm-client" {
170 example = "tsm-client-withGui";
171 extraDescription = ''
172 It will be used with `.override`
173 to add paths to the client system-options file.
176 wrappedPackage = mkPackageOption pkgs "tsm-client" {
178 extraDescription = ''
179 This option is to provide the effective derivation,
180 wrapped with the path to the
181 client system-options file "dsm.sys".
182 It should not be changed, but exists
183 for other modules that want to call TSM executables.
185 } // { readOnly = true; };
188 cfg = config.programs.tsmClient;
189 servernames = map (s: s.servername) (attrValues cfg.servers);
194 assertion = allUnique (map toLower servernames);
197 (option `programs.tsmClient.servers`)
198 contain duplicate name
199 (note that server names are case insensitive).
203 assertion = (cfg.defaultServername!=null)->(elem cfg.defaultServername servernames);
205 TSM default server name
206 `programs.tsmClient.defaultServername="${cfg.defaultServername}"`
207 not found in server names in
208 `programs.tsmClient.servers`.
211 ] ++ (mapAttrsToList (name: serverCfg: {
212 assertion = all (key: null != match "[^[:space:]]+" key) (attrNames serverCfg);
214 TSM server setting names in
215 `programs.tsmClient.servers.${name}.*`
216 contain spaces, but that's not allowed.
218 }) cfg.servers) ++ (mapAttrsToList (name: serverCfg: {
219 assertion = allUnique (map toLower (attrNames serverCfg));
221 TSM server setting names in
222 `programs.tsmClient.servers.${name}.*`
223 contain duplicate names
224 (note that setting names are case insensitive).
227 # XXX migration code for freeform settings, this can be removed in 2025:
228 ++ (enrichMigrationInfos "assertions" (addText: { assertion, message }: { inherit assertion; message = addText message; }));
230 makeDsmSysLines = key: value:
231 # Turn a key-value pair from the server options attrset
232 # into zero (value==null), one (scalar value) or
233 # more (value is list) configuration stanza lines.
234 if isList value then concatMap (makeDsmSysLines key) value else # recurse into list
235 if value == null then [ ] else # skip `null` value
237 if value == true then "" else # just output key if value is `true`
238 if isInt value then " ${builtins.toString value}" else
239 if path.check value then " \"${value}\"" else # enclose path in ".."
240 if singleLineStr.check value then " ${value}" else
241 throw "assertion failed: cannot convert type" # should never happen
244 makeDsmSysStanza = {servername, ... }@serverCfg:
246 # drop special values that should not go into server config block
247 attrs = removeAttrs serverCfg [ "servername" "genPasswd"
248 # XXX migration code for freeform settings, these can be removed in 2025:
249 "assertions" "warnings"
251 "name" "server" "port" "node" "passwdDir" "includeExclude"
255 servername ${servername}
256 ${concatLines (concatLists (mapAttrsToList makeDsmSysLines attrs))}
260 **** IBM Storage Protect (Tivoli Storage Manager)
261 **** client system-options file "dsm.sys".
263 **** This file is generated by NixOS configuration.
265 ${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"}
267 ${concatLines (map makeDsmSysStanza (attrValues cfg.servers))}
270 # XXX migration code for freeform settings, this can be removed in 2025:
271 enrichMigrationInfos = what: how: concatLists (
273 (name: serverCfg: map (how (text: "In `programs.tsmClient.servers.${name}`: ${text}")) serverCfg."${what}")
283 config = mkIf cfg.enable {
285 programs.tsmClient.dsmSysText = dsmSysText;
286 programs.tsmClient.wrappedPackage = cfg.package.override rec {
287 dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
288 dsmSysApi = dsmSysCli;
290 environment.systemPackages = [ cfg.wrappedPackage ];
291 # XXX migration code for freeform settings, this can be removed in 2025:
292 warnings = enrichMigrationInfos "warnings" (addText: addText);
295 meta.maintainers = [ lib.maintainers.yarny ];