288 lines
9.4 KiB
Nix
288 lines
9.4 KiB
Nix
|
{ 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 ];
|
||
|
|
||
|
}
|