summary refs log tree commit diff
path: root/nixos/modules/services/backup/bacula.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/backup/bacula.nix')
-rw-r--r--nixos/modules/services/backup/bacula.nix578
1 files changed, 578 insertions, 0 deletions
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
new file mode 100644
index 00000000000..59890204234
--- /dev/null
+++ b/nixos/modules/services/backup/bacula.nix
@@ -0,0 +1,578 @@
+{ config, lib, pkgs, ... }:
+
+
+# TODO: test configuration when building nixexpr (use -t parameter)
+# TODO: support sqlite3 (it's deprecate?) and mysql
+
+with lib;
+
+let
+  libDir = "/var/lib/bacula";
+
+  fd_cfg = config.services.bacula-fd;
+  fd_conf = pkgs.writeText "bacula-fd.conf"
+    ''
+      Client {
+        Name = "${fd_cfg.name}";
+        FDPort = ${toString fd_cfg.port};
+        WorkingDirectory = "${libDir}";
+        Pid Directory = "/run";
+        ${fd_cfg.extraClientConfig}
+      }
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Director {
+        Name = "${name}";
+        Password = "${value.password}";
+        Monitor = "${value.monitor}";
+      }
+      '') fd_cfg.director)}
+
+      Messages {
+        Name = Standard;
+        syslog = all, !skipped, !restored
+        ${fd_cfg.extraMessagesConfig}
+      }
+    '';
+
+  sd_cfg = config.services.bacula-sd;
+  sd_conf = pkgs.writeText "bacula-sd.conf"
+    ''
+      Storage {
+        Name = "${sd_cfg.name}";
+        SDPort = ${toString sd_cfg.port};
+        WorkingDirectory = "${libDir}";
+        Pid Directory = "/run";
+        ${sd_cfg.extraStorageConfig}
+      }
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Autochanger {
+        Name = "${name}";
+        Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)};
+        Changer Device =  "${value.changerDevice}";
+        Changer Command = "${value.changerCommand}";
+        ${value.extraAutochangerConfig}
+      }
+      '') sd_cfg.autochanger)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Device {
+        Name = "${name}";
+        Archive Device = "${value.archiveDevice}";
+        Media Type = "${value.mediaType}";
+        ${value.extraDeviceConfig}
+      }
+      '') sd_cfg.device)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Director {
+        Name = "${name}";
+        Password = "${value.password}";
+        Monitor = "${value.monitor}";
+      }
+      '') sd_cfg.director)}
+
+      Messages {
+        Name = Standard;
+        syslog = all, !skipped, !restored
+        ${sd_cfg.extraMessagesConfig}
+      }
+    '';
+
+  dir_cfg = config.services.bacula-dir;
+  dir_conf = pkgs.writeText "bacula-dir.conf"
+    ''
+    Director {
+      Name = "${dir_cfg.name}";
+      Password = "${dir_cfg.password}";
+      DirPort = ${toString dir_cfg.port};
+      Working Directory = "${libDir}";
+      Pid Directory = "/run/";
+      QueryFile = "${pkgs.bacula}/etc/query.sql";
+      ${dir_cfg.extraDirectorConfig}
+    }
+
+    Catalog {
+      Name = "PostgreSQL";
+      dbname = "bacula";
+      user = "bacula";
+    }
+
+    Messages {
+      Name = Standard;
+      syslog = all, !skipped, !restored
+      ${dir_cfg.extraMessagesConfig}
+    }
+
+    ${dir_cfg.extraConfig}
+    '';
+
+  directorOptions = {...}:
+  {
+    options = {
+      password = mkOption {
+        type = types.str;
+        # TODO: required?
+        description = ''
+          Specifies the password that must be supplied for the default Bacula
+          Console to be authorized. The same password must appear in the
+          Director resource of the Console configuration file. For added
+          security, the password is never passed across the network but instead
+          a challenge response hash code created with the password. This
+          directive is required. If you have either /dev/random or bc on your
+          machine, Bacula will generate a random password during the
+          configuration process, otherwise it will be left blank and you must
+          manually supply it.
+
+          The password is plain text. It is not generated through any special
+          process but as noted above, it is better to use random text for
+          security reasons.
+        '';
+      };
+
+      monitor = mkOption {
+        type = types.enum [ "no" "yes" ];
+        default = "no";
+        example = "yes";
+        description = ''
+          If Monitor is set to <literal>no</literal>, this director will have
+          full access to this Storage daemon. If Monitor is set to
+          <literal>yes</literal>, this director will only be able to fetch the
+          current status of this Storage daemon.
+
+          Please note that if this director is being used by a Monitor, we
+          highly recommend to set this directive to yes to avoid serious
+          security problems.
+        '';
+      };
+    };
+  };
+
+  autochangerOptions = {...}:
+  {
+    options = {
+      changerDevice = mkOption {
+        type = types.str;
+        description = ''
+          The specified name-string must be the generic SCSI device name of the
+          autochanger that corresponds to the normal read/write Archive Device
+          specified in the Device resource. This generic SCSI device name
+          should be specified if you have an autochanger or if you have a
+          standard tape drive and want to use the Alert Command (see below).
+          For example, on Linux systems, for an Archive Device name of
+          <literal>/dev/nst0</literal>, you would specify
+          <literal>/dev/sg0</literal> for the Changer Device name.  Depending
+          on your exact configuration, and the number of autochangers or the
+          type of autochanger, what you specify here can vary. This directive
+          is optional. See the Using AutochangersAutochangersChapter chapter of
+          this manual for more details of using this and the following
+          autochanger directives.
+          '';
+      };
+
+      changerCommand = mkOption {
+        type = types.str;
+        description = ''
+          The name-string specifies an external program to be called that will
+          automatically change volumes as required by Bacula. Normally, this
+          directive will be specified only in the AutoChanger resource, which
+          is then used for all devices. However, you may also specify the
+          different Changer Command in each Device resource. Most frequently,
+          you will specify the Bacula supplied mtx-changer script as follows:
+
+          <literal>"/path/mtx-changer %c %o %S %a %d"</literal>
+
+          and you will install the mtx on your system (found in the depkgs
+          release). An example of this command is in the default bacula-sd.conf
+          file. For more details on the substitution characters that may be
+          specified to configure your autochanger please see the
+          AutochangersAutochangersChapter chapter of this manual. For FreeBSD
+          users, you might want to see one of the several chio scripts in
+          examples/autochangers.
+          '';
+        default = "/etc/bacula/mtx-changer %c %o %S %a %d";
+      };
+
+      devices = mkOption {
+        description = "";
+        type = types.listOf types.str;
+      };
+
+      extraAutochangerConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Autochanger directive.
+        '';
+        example = ''
+
+        '';
+      };
+    };
+  };
+
+
+  deviceOptions = {...}:
+  {
+    options = {
+      archiveDevice = mkOption {
+        # TODO: required?
+        type = types.str;
+        description = ''
+          The specified name-string gives the system file name of the storage
+          device managed by this storage daemon. This will usually be the
+          device file name of a removable storage device (tape drive), for
+          example <literal>/dev/nst0</literal> or
+          <literal>/dev/rmt/0mbn</literal>. For a DVD-writer, it will be for
+          example <literal>/dev/hdc</literal>. It may also be a directory name
+          if you are archiving to disk storage. In this case, you must supply
+          the full absolute path to the directory. When specifying a tape
+          device, it is preferable that the "non-rewind" variant of the device
+          file name be given.
+        '';
+      };
+
+      mediaType = mkOption {
+        # TODO: required?
+        type = types.str;
+        description = ''
+          The specified name-string names the type of media supported by this
+          device, for example, <literal>DLT7000</literal>. Media type names are
+          arbitrary in that you set them to anything you want, but they must be
+          known to the volume database to keep track of which storage daemons
+          can read which volumes. In general, each different storage type
+          should have a unique Media Type associated with it. The same
+          name-string must appear in the appropriate Storage resource
+          definition in the Director's configuration file.
+
+          Even though the names you assign are arbitrary (i.e. you choose the
+          name you want), you should take care in specifying them because the
+          Media Type is used to determine which storage device Bacula will
+          select during restore. Thus you should probably use the same Media
+          Type specification for all drives where the Media can be freely
+          interchanged. This is not generally an issue if you have a single
+          Storage daemon, but it is with multiple Storage daemons, especially
+          if they have incompatible media.
+
+          For example, if you specify a Media Type of <literal>DDS-4</literal>
+          then during the restore, Bacula will be able to choose any Storage
+          Daemon that handles <literal>DDS-4</literal>. If you have an
+          autochanger, you might want to name the Media Type in a way that is
+          unique to the autochanger, unless you wish to possibly use the
+          Volumes in other drives. You should also ensure to have unique Media
+          Type names if the Media is not compatible between drives. This
+          specification is required for all devices.
+
+          In addition, if you are using disk storage, each Device resource will
+          generally have a different mount point or directory. In order for
+          Bacula to select the correct Device resource, each one must have a
+          unique Media Type.
+        '';
+      };
+
+      extraDeviceConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Device directive.
+        '';
+        example = ''
+          LabelMedia = yes
+          Random Access = no
+          AutomaticMount = no
+          RemovableMedia = no
+          MaximumOpenWait = 60
+          AlwaysOpen = no
+        '';
+      };
+    };
+  };
+
+in {
+  options = {
+    services.bacula-fd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Bacula File Daemon.
+        '';
+      };
+
+      name = mkOption {
+        default = "${config.networking.hostName}-fd";
+        defaultText = literalExpression ''"''${config.networking.hostName}-fd"'';
+        type = types.str;
+        description = ''
+          The client name that must be used by the Director when connecting.
+          Generally, it is a good idea to use a name related to the machine so
+          that error messages can be easily identified if you have multiple
+          Clients. This directive is required.
+        '';
+      };
+
+      port = mkOption {
+        default = 9102;
+        type = types.int;
+        description = ''
+          This specifies the port number on which the Client listens for
+          Director connections. It must agree with the FDPort specified in
+          the Client resource of the Director's configuration file.
+        '';
+      };
+
+      director = mkOption {
+        default = {};
+        description = ''
+          This option defines director resources in Bacula File Daemon.
+        '';
+        type = with types; attrsOf (submodule directorOptions);
+      };
+
+      extraClientConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Client directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+    };
+
+    services.bacula-sd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Bacula Storage Daemon.
+        '';
+      };
+
+      name = mkOption {
+        default = "${config.networking.hostName}-sd";
+        defaultText = literalExpression ''"''${config.networking.hostName}-sd"'';
+        type = types.str;
+        description = ''
+          Specifies the Name of the Storage daemon.
+        '';
+      };
+
+      port = mkOption {
+        default = 9103;
+        type = types.int;
+        description = ''
+          Specifies port number on which the Storage daemon listens for
+          Director connections.
+        '';
+      };
+
+      director = mkOption {
+        default = {};
+        description = ''
+          This option defines Director resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule directorOptions);
+      };
+
+      device = mkOption {
+        default = {};
+        description = ''
+          This option defines Device resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule deviceOptions);
+      };
+
+      autochanger = mkOption {
+        default = {};
+        description = ''
+          This option defines Autochanger resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule autochangerOptions);
+      };
+
+      extraStorageConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Storage directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+
+    };
+
+    services.bacula-dir = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Bacula Director Daemon.
+        '';
+      };
+
+      name = mkOption {
+        default = "${config.networking.hostName}-dir";
+        defaultText = literalExpression ''"''${config.networking.hostName}-dir"'';
+        type = types.str;
+        description = ''
+          The director name used by the system administrator. This directive is
+          required.
+        '';
+      };
+
+      port = mkOption {
+        default = 9101;
+        type = types.int;
+        description = ''
+          Specify the port (a positive integer) on which the Director daemon
+          will listen for Bacula Console connections. This same port number
+          must be specified in the Director resource of the Console
+          configuration file. The default is 9101, so normally this directive
+          need not be specified. This directive should not be used if you
+          specify DirAddresses (N.B plural) directive.
+        '';
+      };
+
+      password = mkOption {
+        # TODO: required?
+        type = types.str;
+        description = ''
+           Specifies the password that must be supplied for a Director.
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+
+      extraDirectorConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration to be passed in Director directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration for Bacula Director Daemon.
+        '';
+        example = ''
+          TODO
+        '';
+      };
+    };
+  };
+
+  config = mkIf (fd_cfg.enable || sd_cfg.enable || dir_cfg.enable) {
+    systemd.services.bacula-fd = mkIf fd_cfg.enable {
+      after = [ "network.target" ];
+      description = "Bacula File Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+    };
+
+    systemd.services.bacula-sd = mkIf sd_cfg.enable {
+      after = [ "network.target" ];
+      description = "Bacula Storage Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+    };
+
+    services.postgresql.enable = dir_cfg.enable == true;
+
+    systemd.services.bacula-dir = mkIf dir_cfg.enable {
+      after = [ "network.target" "postgresql.service" ];
+      description = "Bacula Director Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+      preStart = ''
+        if ! test -e "${libDir}/db-created"; then
+            ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole bacula
+            #${pkgs.postgresql}/bin/createdb --owner bacula bacula
+
+            # populate DB
+            ${pkgs.bacula}/etc/create_bacula_database postgresql
+            ${pkgs.bacula}/etc/make_bacula_tables postgresql
+            ${pkgs.bacula}/etc/grant_bacula_privileges postgresql
+            touch "${libDir}/db-created"
+        else
+            ${pkgs.bacula}/etc/update_bacula_tables postgresql || true
+        fi
+      '';
+    };
+
+    environment.systemPackages = [ pkgs.bacula ];
+
+    users.users.bacula = {
+      group = "bacula";
+      uid = config.ids.uids.bacula;
+      home = "${libDir}";
+      createHome = true;
+      description = "Bacula Daemons user";
+      shell = "${pkgs.bash}/bin/bash";
+    };
+
+    users.groups.bacula.gid = config.ids.gids.bacula;
+  };
+}