From b53364715d2149df7e326b07624555753de91300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20D=C3=B6rfler?= Date: Sat, 3 Mar 2018 01:28:20 +0000 Subject: [PATCH] Added basic support for borgbackup --- default.nix | 134 ++++++++++++++++++++++++++++++++++++- mail-server/borgbackup.nix | 78 +++++++++++++++++++++ 2 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 mail-server/borgbackup.nix diff --git a/default.nix b/default.nix index 168bbaf..77dca63 100644 --- a/default.nix +++ b/default.nix @@ -477,10 +477,139 @@ in description = '' The configuration used for monitoring via monit. Use a mail address that you actively check and set it via 'set alert ...'. - ''; - }; + ''; + }; + }; + + borgbackup = { + enable = mkEnableOption "backup via borgbackup"; + + repoLocation = mkOption { + type = types.string; + default = "/var/borgbackup"; + description = '' + The location where borg saves the backups. + This can be a local path or a remote location such as user@host:/path/to/repo. + It is exported and thus available as an environment variable to cmdPreexec and cmdPostexec. + ''; + }; + + startAt = mkOption { + type = types.string; + default = "hourly"; + description = "When or how often the backup should run. Must be in the format described in systemd.time 7."; + }; + + user = mkOption { + type = types.string; + default = "virtualMail"; + description = "The user borg and its launch script is run as."; + }; + + group = mkOption { + type = types.string; + default = "virtualMail"; + description = "The group borg and its launch script is run as."; + }; + + compression = { + method = mkOption { + type = types.nullOr (types.enum ["none" "lz4" "zstd" "zlib" "lzma"]); + default = null; + description = "Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4."; }; + level = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Denotes the level of compression used by borg. + Most methods accept levels from 0 to 9 but zstd which accepts values from 1 to 22. + If null the decision is left up to borg. + ''; + }; + + auto = mkOption { + type = types.bool; + default = false; + description = "Leaves it to borg to determine whether an individual file should be compressed."; + }; + }; + + encryption = { + method = mkOption { + type = types.enum [ + "none" + "authenticated" + "authenticated-blake2" + "repokey" + "keyfile" + "repokey-blake2" + "keyfile-blake2" + ]; + default = "none"; + description = '' + The backup can be encrypted by choosing any other value than 'none'. + When using encryption the password / passphrase must be provided in passphraseFile. + ''; + }; + + passphraseFile = mkOption { + type = types.nullOr types.path; + default = null; + }; + }; + + name = mkOption { + type = types.string; + default = "{hostname}-{user}-{now}"; + description = '' + The name of the individual backups as used by borg. + Certain placeholders will be replaced by borg. + ''; + }; + + locations = mkOption { + type = types.listOf types.path; + default = [cfg.mailDirectory]; + description = "The locations that are to be backed up by borg."; + }; + + extraArgumentsForInit = mkOption { + type = types.listOf types.string; + default = ["--critical"]; + description = "Additional arguments to add to the borg init command line."; + }; + + extraArgumentsForCreate = mkOption { + type = types.listOf types.string; + default = [ ]; + description = "Additional arguments to add to the borg create command line e.g. '--stats'."; + }; + + cmdPreexec = mkOption { + type = types.nullOr types.string; + default = null; + description = '' + The command to be executed before each backup operation. + This is called prior to borg init in the same script that runs borg init and create and cmdPostexec. + Example: + export BORG_RSH="ssh -i /path/to/private/key" + ''; + }; + + cmdPostexec = mkOption { + type = types.nullOr types.string; + default = null; + description = '' + The command to be executed after each backup operation. + This is called after borg create completed successfully and in the same script that runs + cmdPreexec, borg init and create. + ''; + }; + + }; + backup = { enable = mkEnableOption "backup via rsnapshot"; @@ -542,6 +671,7 @@ in }; imports = [ + ./mail-server/borgbackup.nix ./mail-server/rsnapshot.nix ./mail-server/clamav.nix ./mail-server/monit.nix diff --git a/mail-server/borgbackup.nix b/mail-server/borgbackup.nix new file mode 100644 index 0000000..3c60031 --- /dev/null +++ b/mail-server/borgbackup.nix @@ -0,0 +1,78 @@ +# nixos-mailserver: a simple mail server +# Copyright (C) 2016-2018 Robin Raymond +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +{ config, pkgs, lib, ... }: + +let + cfg = config.mailserver.borgbackup; + + methodFragment = lib.optional (cfg.compression.method != null) cfg.compression.method; + autoFragment = + if cfg.compression.auto && cfg.compression.method == null + then throw "compression.method must be set when using auto." + else lib.optional cfg.compression.auto "auto"; + levelFragment = + if cfg.compression.level != null && cfg.compression.method == null + then throw "compression.method must be set when using compression.level." + else lib.optional (cfg.compression.level != null) (toString cfg.compression.level); + compressionFragment = lib.concatStringsSep "," (lib.flatten [autoFragment methodFragment levelFragment]); + compression = lib.optionalString (compressionFragment != "") "--compression ${compressionFragment}"; + + encryptionFragment = cfg.encryption.method; + passphraseFile = lib.escapeShellArg cfg.encryption.passphraseFile; + passphraseFragment = lib.optionalString (cfg.encryption.method != "none") + (if cfg.encryption.passphraseFile != null then ''env BORG_PASSPHRASE="$(cat ${passphraseFile})"'' + else throw "passphraseFile must be set when using encryption."); + + locations = lib.escapeShellArgs cfg.locations; + name = lib.escapeShellArg cfg.name; + + repoLocation = lib.escapeShellArg cfg.repoLocation; + + extraInitArgs = lib.escapeShellArgs cfg.extraArgumentsForInit; + extraCreateArgs = lib.escapeShellArgs cfg.extraArgumentsForCreate; + + cmdPreexec = lib.optionalString (cfg.cmdPreexec != null) cfg.cmdPreexec; + cmdPostexec = lib.optionalString (cfg.cmdPostexec != null) cfg.cmdPostexec; + + borgScript = '' + export BORG_REPO=${repoLocation} + ${cmdPreexec} + ${passphraseFragment} ${pkgs.borgbackup}/bin/borg init ${extraInitArgs} --encryption ${encryptionFragment} || true + ${passphraseFragment} ${pkgs.borgbackup}/bin/borg create ${extraCreateArgs} ${compression} ::${name} ${locations} + ${cmdPostexec} + ''; +in { + config = lib.mkIf config.mailserver.borgbackup.enable { + environment.systemPackages = with pkgs; [ + borgbackup + ]; + + systemd.services.borgbackup = { + description = "borgbackup"; + unitConfig.Documentation = "man:borgbackup"; + script = borgScript; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + CPUSchedulingPolicy = "idle"; + IOSchedulingClass = "idle"; + ProtectSystem = "full"; + }; + startAt = cfg.startAt; + }; + }; +}