grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / programs / tsm-client.nix
bloba010bb6c6d578a36bfc5328226b1922bca19b89b
1 { config, lib, options, pkgs, ... }:  # XXX migration code for freeform settings: `options` can be removed in 2025
2 let optionsGlobal = options; in
4 let
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;
14   scalarType =
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;
28       default = name;
29       example = "mainTsmServer";
30       description = ''
31         Local name of the IBM TSM server,
32         must not contain space or more than 64 chars.
33       '';
34     };
35     options.tcpserveraddress = mkOption {
36       type = nonEmptyStr;
37       example = "tsmserver.company.com";
38       description = ''
39         Host/domain name or IP address of the IBM TSM server.
40       '';
41     };
42     options.tcpport = mkOption {
43       type = addCheck port (p: p<=32767);
44       default = 1500;  # official default
45       description = ''
46         TCP port of the IBM TSM server.
47         TSM does not support ports above 32767.
48       '';
49     };
50     options.nodename = mkOption {
51       type = nonEmptyStr;
52       example = "MY-TSM-NODE";
53       description = ''
54         Target node name on the IBM TSM server.
55       '';
56     };
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`.
64       *Caution*:
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
68     '';
69     options.passwordaccess = mkOption {
70       type = enum [ "generate" "prompt" ];
71       visible = false;
72     };
73     options.passworddir = mkOption {
74       type = nullOr path;
75       default = null;
76       example = "/home/alice/tsm-password";
77       description = ''
78         Directory that holds the TSM
79         node's password information.
80       '';
81     };
82     options.inclexcl = mkOption {
83       type = coercedTo lines
84         (pkgs.writeText "inclexcl.dsm.sys")
85         (nullOr path);
86       default = null;
87       example = ''
88         exclude.dir     /nix/store
89         include.encrypt /home/.../*
90       '';
91       description = ''
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.
95       '';
96     };
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" ])
111     ];
112   };
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"
119     '';
120     servers = mkOption {
121       type = attrsOf (submodule serverOptions);
122       default = {};
123       example.mainTsmServer = {
124         tcpserveraddress = "tsmserver.company.com";
125         nodename = "MY-TSM-NODE";
126         compression = "yes";
127       };
128       description = ''
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.
142       '';
143     };
144     defaultServername = mkOption {
145       type = nullOr servernameType;
146       default = null;
147       example = "mainTsmServer";
148       description = ''
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.
156       '';
157     };
158     dsmSysText = mkOption {
159       type = lines;
160       readOnly = true;
161       description = ''
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.
167       '';
168     };
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.
174       '';
175     };
176     wrappedPackage = mkPackageOption pkgs "tsm-client" {
177       default = null;
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.
184       '';
185     } // { readOnly = true; };
186   };
188   cfg = config.programs.tsmClient;
189   servernames = map (s: s.servername) (attrValues cfg.servers);
191   assertions =
192     [
193       {
194         assertion = allUnique (map toLower servernames);
195         message = ''
196           TSM server names
197           (option `programs.tsmClient.servers`)
198           contain duplicate name
199           (note that server names are case insensitive).
200         '';
201       }
202       {
203         assertion = (cfg.defaultServername!=null)->(elem cfg.defaultServername servernames);
204         message = ''
205           TSM default server name
206           `programs.tsmClient.defaultServername="${cfg.defaultServername}"`
207           not found in server names in
208           `programs.tsmClient.servers`.
209         '';
210       }
211     ] ++ (mapAttrsToList (name: serverCfg: {
212       assertion = all (key: null != match "[^[:space:]]+" key) (attrNames serverCfg);
213       message = ''
214         TSM server setting names in
215         `programs.tsmClient.servers.${name}.*`
216         contain spaces, but that's not allowed.
217       '';
218     }) cfg.servers) ++ (mapAttrsToList (name: serverCfg: {
219       assertion = allUnique (map toLower (attrNames serverCfg));
220       message = ''
221         TSM server setting names in
222         `programs.tsmClient.servers.${name}.*`
223         contain duplicate names
224         (note that setting names are case insensitive).
225       '';
226     }) cfg.servers)
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
236     [ ("  ${key}${
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
242     }") ];
244   makeDsmSysStanza = {servername, ... }@serverCfg:
245     let
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"
250         "extraConfig" "text"
251         "name" "server" "port" "node" "passwdDir" "includeExclude"
252       ];
253     in
254       ''
255         servername  ${servername}
256         ${concatLines (concatLists (mapAttrsToList makeDsmSysLines attrs))}
257       '';
259   dsmSysText = ''
260     ****  IBM Storage Protect (Tivoli Storage Manager)
261     ****  client system-options file "dsm.sys".
262     ****  Do not edit!
263     ****  This file is generated by NixOS configuration.
265     ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
267     ${concatLines (map makeDsmSysStanza (attrValues cfg.servers))}
268   '';
270   # XXX migration code for freeform settings, this can be removed in 2025:
271   enrichMigrationInfos = what: how: concatLists (
272     mapAttrsToList
273     (name: serverCfg: map (how (text: "In `programs.tsmClient.servers.${name}`: ${text}")) serverCfg."${what}")
274     cfg.servers
275   );
281   inherit options;
283   config = mkIf cfg.enable {
284     inherit assertions;
285     programs.tsmClient.dsmSysText = dsmSysText;
286     programs.tsmClient.wrappedPackage = cfg.package.override rec {
287       dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
288       dsmSysApi = dsmSysCli;
289     };
290     environment.systemPackages = [ cfg.wrappedPackage ];
291     # XXX migration code for freeform settings, this can be removed in 2025:
292     warnings = enrichMigrationInfos "warnings" (addText: addText);
293   };
295   meta.maintainers = [ lib.maintainers.yarny ];