diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d8e8dd37af76..80c8618df193 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -139,6 +139,7 @@
./programs/sway.nix
./programs/thefuck.nix
./programs/tmux.nix
+ ./programs/tsm-client.nix
./programs/udevil.nix
./programs/venus.nix
./programs/vim.nix
@@ -210,6 +211,7 @@
./services/backup/restic-rest-server.nix
./services/backup/rsnapshot.nix
./services/backup/tarsnap.nix
+ ./services/backup/tsm.nix
./services/backup/znapzend.nix
./services/cluster/hadoop/default.nix
./services/cluster/kubernetes/addons/dns.nix
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
new file mode 100644
index 000000000000..eb6f12475286
--- /dev/null
+++ b/nixos/modules/programs/tsm-client.nix
@@ -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
+ server
+ directive in dsm.sys.
+ '';
+ };
+ 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
+ tcpserveraddress
+ directive in dsm.sys.
+ '';
+ };
+ 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
+ tcpport
+ directive in dsm.sys.
+ 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
+ nodename
+ directive in dsm.sys.
+ '';
+ };
+ options.genPasswd = mkEnableOption ''
+ automatic client password generation.
+ This option influences the
+ passwordaccess
+ directive in dsm.sys.
+ The password will be stored in the directory
+ given by the option .
+ Caution:
+ 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
+ passworddir
+ directive in dsm.sys.
+ '';
+ };
+ options.includeExclude = mkOption {
+ type = lines;
+ default = "";
+ example = ''
+ exclude.dir /nix/store
+ include.encrypt /home/.../*
+ '';
+ description = ''
+ include.* and
+ exclude.* directives to be
+ used when sending files to the IBM TSM server.
+ The lines will be written into a file that the
+ inclexcl
+ directive in dsm.sys 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 null
+ 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 can't
+ control the order of lines in the resulting stanza.
+ Note that the server
+ 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
+ ,
+ this option may be used to name a default
+ server stanza that IBM TSM uses in the absence of
+ a user-defined dsm.opt file.
+ This option translates to a
+ defaultserver 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 .override
+ 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 ];
+
+}
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 000000000000..3b2bb37491b5
--- /dev/null
+++ b/nixos/modules/services/backup/tsm.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+let
+
+ inherit (lib.attrsets) hasAttr;
+ inherit (lib.modules) mkDefault mkIf;
+ inherit (lib.options) mkEnableOption mkOption;
+ inherit (lib.types) nullOr strMatching;
+
+ options.services.tsmBackup = {
+ enable = mkEnableOption ''
+ automatic backups with the
+ IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
+ This also enables
+
+ '';
+ command = mkOption {
+ type = strMatching ".+";
+ default = "backup";
+ example = "incr";
+ description = ''
+ The actual command passed to the
+ dsmc executable to start the backup.
+ '';
+ };
+ servername = mkOption {
+ type = strMatching ".+";
+ example = "mainTsmServer";
+ description = ''
+ Create a systemd system service
+ tsm-backup.service that starts
+ a backup based on the given servername's stanza.
+ Note that this server's
+ will default to
+ /var/lib/tsm-backup/password
+ (but may be overridden);
+ also, the service will use
+ /var/lib/tsm-backup as
+ HOME when calling
+ dsmc.
+ '';
+ };
+ autoTime = mkOption {
+ type = nullOr (strMatching ".+");
+ default = null;
+ example = "12:00";
+ description = ''
+ The backup service will be invoked
+ automatically at the given date/time,
+ which must be in the format described in
+ systemd.time5.
+ The default null
+ disables automatic backups.
+ '';
+ };
+ };
+
+ cfg = config.services.tsmBackup;
+ cfgPrg = config.programs.tsmClient;
+
+ assertions = [
+ {
+ assertion = hasAttr cfg.servername cfgPrg.servers;
+ message = "TSM service servername not found in list of servers";
+ }
+ {
+ assertion = cfgPrg.servers.${cfg.servername}.genPasswd;
+ message = "TSM service requires automatic password generation";
+ }
+ ];
+
+in
+
+{
+
+ inherit options;
+
+ config = mkIf cfg.enable {
+ inherit assertions;
+ programs.tsmClient.enable = true;
+ programs.tsmClient.servers."${cfg.servername}".passwdDir =
+ mkDefault "/var/lib/tsm-backup/password";
+ systemd.services.tsm-backup = {
+ description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup";
+ # DSM_LOG needs a trailing slash to have it treated as a directory.
+ # `/var/log` would be littered with TSM log files otherwise.
+ environment.DSM_LOG = "/var/log/tsm-backup/";
+ # TSM needs a HOME dir to store certificates.
+ environment.HOME = "/var/lib/tsm-backup";
+ # for exit status description see
+ # https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html
+ serviceConfig.SuccessExitStatus = "4 8";
+ # The `-se` option must come after the command.
+ # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
+ serviceConfig.ExecStart =
+ "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
+ serviceConfig.LogsDirectory = "tsm-backup";
+ serviceConfig.StateDirectory = "tsm-backup";
+ serviceConfig.StateDirectoryMode = "0750";
+ startAt = mkIf (cfg.autoTime!=null) cfg.autoTime;
+ };
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/pkgs/tools/backup/tsm-client/default.nix b/pkgs/tools/backup/tsm-client/default.nix
new file mode 100644
index 000000000000..a1f7b1aba84e
--- /dev/null
+++ b/pkgs/tools/backup/tsm-client/default.nix
@@ -0,0 +1,165 @@
+{ lib
+, stdenv
+, autoPatchelfHook
+, buildEnv
+, fetchurl
+, makeWrapper
+, procps
+, zlib
+# optional packages that enable certain features
+, acl ? null # EXT2/EXT3/XFS ACL support
+, jdk8 ? null # Java GUI
+, lvm2 ? null # LVM image backup and restore functions
+# path to `dsm.sys` configuration files
+, dsmSysCli ? "/etc/tsm-client/cli.dsm.sys"
+, dsmSysApi ? "/etc/tsm-client/api.dsm.sys"
+}:
+
+
+# For an explanation of optional packages
+# (features provided by them, version limits), see
+# https://www-01.ibm.com/support/docview.wss?uid=swg21052223#Version%208.1
+
+
+# IBM Tivoli Storage Manager Client uses a system-wide
+# client system-options file `dsm.sys` and expects it
+# to be located in a directory within the package.
+# Note that the command line client and the API use
+# different "dms.sys" files (located in different directories).
+# Since these files contain settings to be altered by the
+# admin user (e.g. TSM server name), we create symlinks
+# in place of the files that the client attempts to open.
+# Use the arguments `dsmSysCli` and `dsmSysApi` to
+# provide the location of the configuration files for
+# the command-line interface and the API, respectively.
+#
+# While the command-line interface contains wrappers
+# that help the executables find the configuration file,
+# packages that link against the API have to
+# set the environment variable `DSMI_DIR` to
+# point to this derivations `/dsmi_dir` directory symlink.
+# Other environment variables might be necessary,
+# depending on local configuration or usage; see:
+# https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_cfg_sapiunix.html
+
+
+# The newest version of TSM client should be discoverable
+# by going the the `downloadPage` (see `meta` below),
+# there to "Client Latest Downloads",
+# "IBM Spectrum Protect Client Downloads and READMEs",
+# then to "Linux x86_64 Ubuntu client" (as of 2019-07-15).
+
+
+let
+
+ meta = {
+ homepage = https://www.ibm.com/us-en/marketplace/data-protection-and-recovery;
+ downloadPage = https://www-01.ibm.com/support/docview.wss?uid=swg21239415;
+ platforms = [ "x86_64-linux" ];
+ license = lib.licenses.unfree;
+ maintainers = [ lib.maintainers.yarny ];
+ description = "IBM Spectrum Protect (Tivoli Storage Manager) CLI and API";
+ longDescription = ''
+ IBM Spectrum Protect (Tivoli Storage Manager) provides
+ a single point of control for backup and recovery.
+ This package contains the client software, that is,
+ a command line client and linkable libraries.
+
+ Note that the software requires a system-wide
+ client system-options file (commonly named "dsm.sys").
+ This package allows to use separate files for
+ the command-line interface and for the linkable API.
+ The location of those files can
+ be provided as build parameters.
+ '';
+ };
+
+ unwrapped = stdenv.mkDerivation rec {
+ name = "tsm-client-${version}-unwrapped";
+ version = "8.1.8.0";
+ src = fetchurl {
+ url = "ftp://public.dhe.ibm.com/storage/tivoli-storage-management/maintenance/client/v8r1/Linux/LinuxX86_DEB/BA/v818/${version}-TIV-TSMBAC-LinuxX86_DEB.tar";
+ sha256 = "0c1d0jm0i7qjd314nhj2vj8fs7sncm1x2n4d6dg4049jniyvjhpk";
+ };
+ inherit meta;
+
+ nativeBuildInputs = [
+ autoPatchelfHook
+ ];
+ buildInputs = [
+ stdenv.cc.cc
+ zlib
+ ];
+ runtimeDependencies = [
+ lvm2
+ ];
+ sourceRoot = ".";
+
+ postUnpack = ''
+ for debfile in *.deb
+ do
+ ar -x "$debfile"
+ tar --xz --extract --file=data.tar.xz
+ rm data.tar.xz
+ done
+ '';
+
+ installPhase = ''
+ runHook preInstall
+ mkdir --parents $out
+ mv --target-directory=$out usr/* opt
+ runHook postInstall
+ '';
+
+ # Fix relative symlinks after `/usr` was moved up one level
+ preFixup = ''
+ for link in $out/lib/* $out/bin/*
+ do
+ target=$(readlink "$link")
+ if [ "$(cut -b -6 <<< "$target")" != "../../" ]
+ then
+ echo "cannot fix this symlink: $link -> $target"
+ exit 1
+ fi
+ ln --symbolic --force --no-target-directory "$out/$(cut -b 7- <<< "$target")" "$link"
+ done
+ '';
+ };
+
+in
+
+buildEnv {
+ name = "tsm-client-${unwrapped.version}";
+ inherit meta;
+ passthru = { inherit unwrapped; };
+ paths = [ unwrapped ];
+ buildInputs = [ makeWrapper ];
+ pathsToLink = [
+ "/"
+ "/bin"
+ "/opt/tivoli/tsm/client/ba/bin"
+ "/opt/tivoli/tsm/client/api/bin64"
+ ];
+ # * Provide top-level symlinks `dsm_dir` and `dsmi_dir`
+ # to the so-called "installation directories"
+ # * Add symlinks to the "installation directories"
+ # that point to the `dsm.sys` configuration files
+ # * Drop the Java GUI executable unless `jdk` is present
+ # * Create wrappers for the command-line interface to
+ # prepare `PATH` and `DSM_DIR` environment variables
+ postBuild = ''
+ ln --symbolic --no-target-directory opt/tivoli/tsm/client/ba/bin $out/dsm_dir
+ ln --symbolic --no-target-directory opt/tivoli/tsm/client/api/bin64 $out/dsmi_dir
+ ln --symbolic --no-target-directory "${dsmSysCli}" $out/dsm_dir/dsm.sys
+ ln --symbolic --no-target-directory "${dsmSysApi}" $out/dsmi_dir/dsm.sys
+ ${lib.strings.optionalString (jdk8==null) "rm $out/bin/dsmj"}
+ for bin in $out/bin/*
+ do
+ target=$(readlink "$bin")
+ rm "$bin"
+ makeWrapper "$target" "$bin" \
+ --prefix PATH : "$out/dsm_dir:${lib.strings.makeBinPath [ procps acl jdk8 ]}" \
+ --set DSM_DIR $out/dsm_dir
+ done
+ '';
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index bde0167c94d0..e881e2f758fd 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -2682,6 +2682,9 @@ in
teamocil = callPackage ../tools/misc/teamocil { };
+ tsm-client = callPackage ../tools/backup/tsm-client { jdk8 = null; };
+ tsm-client-withGui = callPackage ../tools/backup/tsm-client { };
+
tridactyl-native = callPackage ../tools/networking/tridactyl-native { };
trompeloeil = callPackage ../development/libraries/trompeloeil { };