1 { config, lib, pkgs, ... }:
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;
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
18 lenUniq = l: length (lib.lists.unique l);
20 lenUniq lst == lenUniq (map toLower lst);
22 # TSM rejects servername strings longer than 64 chars.
23 servernameType = strMatching ".{1,64}";
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
34 directive in {file}`dsm.sys`.
37 options.server = mkOption {
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
44 directive in {file}`dsm.sys`.
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
54 directive in {file}`dsm.sys`.
55 TSM does not support ports above 32767.
58 options.node = mkOption {
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
65 directive in {file}`dsm.sys`.
68 options.genPasswd = mkEnableOption (lib.mdDoc ''
69 automatic client password generation.
70 This option influences the
72 directive in {file}`dsm.sys`.
73 The password will be stored in the directory
74 given by the option {option}`passwdDir`.
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
80 options.passwdDir = mkOption {
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
88 directive in {file}`dsm.sys`.
91 options.includeExclude = mkOption {
95 exclude.dir /nix/store
96 include.encrypt /home/.../*
98 description = lib.mdDoc ''
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
104 directive in {file}`dsm.sys` points to.
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.
112 (attrsOf (nullOr str))
113 (attrs: checkIUnique (attrNames attrs));
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).
124 options.text = mkOption {
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.
139 options.stanza = mkOption {
143 description = lib.mdDoc "Server stanza text generated from the options.";
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
149 mapAttrs (lib.trivial.const mkDefault) (
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}"'';
163 attrset = filterAttrs (k: v: v!=null) config.extraConfig;
164 mkLine = k: v: k + optionalString (v!="") " ${v}";
165 lines = mapAttrsToList mkLine attrset;
169 server ${config.name}
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"
181 type = attrsOf (submodule [ serverOptions ]);
183 example.mainTsmServer = {
184 server = "tsmserver.company.com";
185 node = "MY-TSM-NODE";
186 extraConfig.compression = "yes";
188 description = lib.mdDoc ''
189 Server definitions ("stanzas")
190 for the client system-options file.
193 defaultServername = mkOption {
194 type = nullOr servernameType;
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.
207 dsmSysText = mkOption {
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.
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.
230 wrappedPackage = mkOption {
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.
242 cfg = config.programs.tsmClient;
246 assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers);
248 TSM servernames contain duplicate name
249 (note that case doesn't matter!)
253 assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers);
254 message = "TSM defaultServername not found in list of servers";
259 **** IBM Spectrum Protect (Tivoli Storage Manager)
260 **** client system-options file "dsm.sys".
262 **** This file is generated by NixOS configuration.
264 ${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"}
266 ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)}
275 config = mkIf cfg.enable {
277 programs.tsmClient.dsmSysText = dsmSysText;
278 programs.tsmClient.wrappedPackage = cfg.package.override rec {
279 dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
280 dsmSysApi = dsmSysCli;
282 environment.systemPackages = [ cfg.wrappedPackage ];
285 meta.maintainers = [ lib.maintainers.yarny ];