typioca: 2.7.0 -> 2.8.0
[NixPkgs.git] / nixos / modules / programs / tsm-client.nix
blob41560544c2c702a55072c00319d7bcd00a4b346f
1 { config, lib, pkgs, ... }:
3 let
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
16   checkIUnique = lst:
17     let
18       lenUniq = l: length (lib.lists.unique l);
19     in
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
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   };
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   };
242   cfg = config.programs.tsmClient;
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   ];
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.
264     ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
266     ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)}
267   '';
273   inherit options;
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   };
285   meta.maintainers = [ lib.maintainers.yarny ];