This commit brings a module that installs the IBM Spectrum Protect (Tivoli Storage Manager) command-line client together with its system-wide client system-options file `dsm.sys`.wip/yesman
parent
fe0bfb3fec
commit
f5b873f43c
@ -0,0 +1,287 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
let |
||||
|
||||
inherit (builtins) length map; |
||||
inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs; |
||||
inherit (lib.modules) mkDefault mkIf; |
||||
inherit (lib.options) literalExample mkEnableOption mkOption; |
||||
inherit (lib.strings) concatStringsSep optionalString toLower; |
||||
inherit (lib.types) addCheck attrsOf lines loaOf nullOr package path port str strMatching submodule; |
||||
|
||||
# Checks if given list of strings contains unique |
||||
# elements when compared without considering case. |
||||
# Type: checkIUnique :: [string] -> bool |
||||
# Example: checkIUnique ["foo" "Foo"] => false |
||||
checkIUnique = lst: |
||||
let |
||||
lenUniq = l: length (lib.lists.unique l); |
||||
in |
||||
lenUniq lst == lenUniq (map toLower lst); |
||||
|
||||
# TSM rejects servername strings longer than 64 chars. |
||||
servernameType = strMatching ".{1,64}"; |
||||
|
||||
serverOptions = { name, config, ... }: { |
||||
options.name = mkOption { |
||||
type = servernameType; |
||||
example = "mainTsmServer"; |
||||
description = '' |
||||
Local name of the IBM TSM server, |
||||
must be uncapitalized and no longer than 64 chars. |
||||
The value will be used for the |
||||
<literal>server</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
''; |
||||
}; |
||||
options.server = mkOption { |
||||
type = strMatching ".+"; |
||||
example = "tsmserver.company.com"; |
||||
description = '' |
||||
Host/domain name or IP address of the IBM TSM server. |
||||
The value will be used for the |
||||
<literal>tcpserveraddress</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
''; |
||||
}; |
||||
options.port = mkOption { |
||||
type = addCheck port (p: p<=32767); |
||||
default = 1500; # official default |
||||
description = '' |
||||
TCP port of the IBM TSM server. |
||||
The value will be used for the |
||||
<literal>tcpport</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
TSM does not support ports above 32767. |
||||
''; |
||||
}; |
||||
options.node = mkOption { |
||||
type = strMatching ".+"; |
||||
example = "MY-TSM-NODE"; |
||||
description = '' |
||||
Target node name on the IBM TSM server. |
||||
The value will be used for the |
||||
<literal>nodename</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
''; |
||||
}; |
||||
options.genPasswd = mkEnableOption '' |
||||
automatic client password generation. |
||||
This option influences the |
||||
<literal>passwordaccess</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
The password will be stored in the directory |
||||
given by the option <option>passwdDir</option>. |
||||
<emphasis>Caution</emphasis>: |
||||
If this option is enabled and the server forces |
||||
to renew the password (e.g. on first connection), |
||||
a random password will be generated and stored |
||||
''; |
||||
options.passwdDir = mkOption { |
||||
type = path; |
||||
example = "/home/alice/tsm-password"; |
||||
description = '' |
||||
Directory that holds the TSM |
||||
node's password information. |
||||
The value will be used for the |
||||
<literal>passworddir</literal> |
||||
directive in <filename>dsm.sys</filename>. |
||||
''; |
||||
}; |
||||
options.includeExclude = mkOption { |
||||
type = lines; |
||||
default = ""; |
||||
example = '' |
||||
exclude.dir /nix/store |
||||
include.encrypt /home/.../* |
||||
''; |
||||
description = '' |
||||
<literal>include.*</literal> and |
||||
<literal>exclude.*</literal> directives to be |
||||
used when sending files to the IBM TSM server. |
||||
The lines will be written into a file that the |
||||
<literal>inclexcl</literal> |
||||
directive in <filename>dsm.sys</filename> points to. |
||||
''; |
||||
}; |
||||
options.extraConfig = mkOption { |
||||
# TSM option keys are case insensitive; |
||||
# we have to ensure there are no keys that |
||||
# differ only by upper and lower case. |
||||
type = addCheck |
||||
(attrsOf (nullOr str)) |
||||
(attrs: checkIUnique (attrNames attrs)); |
||||
default = {}; |
||||
example.compression = "yes"; |
||||
example.passwordaccess = null; |
||||
description = '' |
||||
Additional key-value pairs for the server stanza. |
||||
Values must be strings, or <literal>null</literal> |
||||
for the key not to be used in the stanza |
||||
(e.g. to overrule values generated by other options). |
||||
''; |
||||
}; |
||||
options.text = mkOption { |
||||
type = lines; |
||||
example = literalExample |
||||
''lib.modules.mkAfter "compression no"''; |
||||
description = '' |
||||
Additional text lines for the server stanza. |
||||
This option can be used if certion configuration keys |
||||
must be used multiple times or ordered in a certain way |
||||
as the <option>extraConfig</option> option can't |
||||
control the order of lines in the resulting stanza. |
||||
Note that the <literal>server</literal> |
||||
line at the beginning of the stanza is |
||||
not part of this option's value. |
||||
''; |
||||
}; |
||||
options.stanza = mkOption { |
||||
type = str; |
||||
internal = true; |
||||
visible = false; |
||||
description = "Server stanza text generated from the options."; |
||||
}; |
||||
config.name = mkDefault name; |
||||
# Client system-options file directives are explained here: |
||||
# https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.8/client/c_opt_usingopts.html |
||||
config.extraConfig = |
||||
mapAttrs (lib.trivial.const mkDefault) ( |
||||
{ |
||||
commmethod = "v6tcpip"; # uses v4 or v6, based on dns lookup result |
||||
tcpserveraddress = config.server; |
||||
tcpport = builtins.toString config.port; |
||||
nodename = config.node; |
||||
passwordaccess = if config.genPasswd then "generate" else "prompt"; |
||||
passworddir = ''"${config.passwdDir}"''; |
||||
} // optionalAttrs (config.includeExclude!="") { |
||||
inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"''; |
||||
} |
||||
); |
||||
config.text = |
||||
let |
||||
attrset = filterAttrs (k: v: v!=null) config.extraConfig; |
||||
mkLine = k: v: k + optionalString (v!="") " ${v}"; |
||||
lines = mapAttrsToList mkLine attrset; |
||||
in |
||||
concatStringsSep "\n" lines; |
||||
config.stanza = '' |
||||
server ${config.name} |
||||
${config.text} |
||||
''; |
||||
}; |
||||
|
||||
options.programs.tsmClient = { |
||||
enable = mkEnableOption '' |
||||
IBM Spectrum Protect (Tivoli Storage Manager, TSM) |
||||
client command line applications with a |
||||
client system-options file "dsm.sys" |
||||
''; |
||||
servers = mkOption { |
||||
type = loaOf (submodule [ serverOptions ]); |
||||
default = {}; |
||||
example.mainTsmServer = { |
||||
server = "tsmserver.company.com"; |
||||
node = "MY-TSM-NODE"; |
||||
extraConfig.compression = "yes"; |
||||
}; |
||||
description = '' |
||||
Server definitions ("stanzas") |
||||
for the client system-options file. |
||||
''; |
||||
}; |
||||
defaultServername = mkOption { |
||||
type = nullOr servernameType; |
||||
default = null; |
||||
example = "mainTsmServer"; |
||||
description = '' |
||||
If multiple server stanzas are declared with |
||||
<option>programs.tsmClient.servers</option>, |
||||
this option may be used to name a default |
||||
server stanza that IBM TSM uses in the absence of |
||||
a user-defined <filename>dsm.opt</filename> file. |
||||
This option translates to a |
||||
<literal>defaultserver</literal> configuration line. |
||||
''; |
||||
}; |
||||
dsmSysText = mkOption { |
||||
type = lines; |
||||
readOnly = true; |
||||
description = '' |
||||
This configuration key contains the effective text |
||||
of the client system-options file "dsm.sys". |
||||
It should not be changed, but may be |
||||
used to feed the configuration into other |
||||
TSM-depending packages used on the system. |
||||
''; |
||||
}; |
||||
package = mkOption { |
||||
type = package; |
||||
default = pkgs.tsm-client; |
||||
defaultText = "pkgs.tsm-client"; |
||||
example = literalExample "pkgs.tsm-client-withGui"; |
||||
description = '' |
||||
The TSM client derivation to be |
||||
added to the system environment. |
||||
It will called with <literal>.override</literal> |
||||
to add paths to the client system-options file. |
||||
''; |
||||
}; |
||||
wrappedPackage = mkOption { |
||||
type = package; |
||||
readOnly = true; |
||||
description = '' |
||||
The TSM client derivation, wrapped with the path |
||||
to the client system-options file "dsm.sys". |
||||
This option is to provide the effective derivation |
||||
for other modules that want to call TSM executables. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
cfg = config.programs.tsmClient; |
||||
|
||||
assertions = [ |
||||
{ |
||||
assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers); |
||||
message = '' |
||||
TSM servernames contain duplicate name |
||||
(note that case doesn't matter!) |
||||
''; |
||||
} |
||||
{ |
||||
assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers); |
||||
message = "TSM defaultServername not found in list of servers"; |
||||
} |
||||
]; |
||||
|
||||
dsmSysText = '' |
||||
**** IBM Spectrum Protect (Tivoli Storage Manager) |
||||
**** client system-options file "dsm.sys". |
||||
**** Do not edit! |
||||
**** This file is generated by NixOS configuration. |
||||
|
||||
${optionalString (cfg.defaultServername!=null) "defaultserver ${cfg.defaultServername}"} |
||||
|
||||
${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)} |
||||
''; |
||||
|
||||
in |
||||
|
||||
{ |
||||
|
||||
inherit options; |
||||
|
||||
config = mkIf cfg.enable { |
||||
inherit assertions; |
||||
programs.tsmClient.dsmSysText = dsmSysText; |
||||
programs.tsmClient.wrappedPackage = cfg.package.override rec { |
||||
dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText; |
||||
dsmSysApi = dsmSysCli; |
||||
}; |
||||
environment.systemPackages = [ cfg.wrappedPackage ]; |
||||
}; |
||||
|
||||
meta.maintainers = [ lib.maintainers.yarny ]; |
||||
|
||||
} |
Loading…
Reference in new issue