aboutsummaryrefslogtreecommitdiff
path: root/modules/backup/policy.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/backup/policy.nix')
-rw-r--r--modules/backup/policy.nix301
1 files changed, 301 insertions, 0 deletions
diff --git a/modules/backup/policy.nix b/modules/backup/policy.nix
new file mode 100644
index 0000000..5486cac
--- /dev/null
+++ b/modules/backup/policy.nix
@@ -0,0 +1,301 @@
+{
+ pkgs,
+ lib,
+ config,
+ mkInstanceServices,
+ ...
+}:
+let
+
+ # kopia policy json definition
+ compressionType = lib.types.enum [
+ "none"
+ "deflate-best-compression"
+ "deflate-best-speed"
+ "deflate-default"
+ "gzip"
+ "gzip-best-compression"
+ "gzip-best-speed"
+ "pgzip"
+ "pgzip-best-compression"
+ "pgzip-best-speed"
+ "s2-better"
+ "s2-default"
+ "s2-parallel-4"
+ "s2-parallel-8"
+ "zstd"
+ "zstd-better-compression"
+ "zstd-fastest"
+ ];
+
+ policyType = lib.mkOption {
+ type = lib.types.nullOr (
+ lib.types.submodule {
+ options = {
+ retention = {
+ keepLatest = lib.mkOption {
+ type = lib.types.int;
+ default = 5;
+ description = "Number of latest snapshots to keep.";
+ };
+ keepHourly = lib.mkOption {
+ type = lib.types.int;
+ default = 48;
+ description = "Number of hourly snapshots to keep.";
+ };
+ keepDaily = lib.mkOption {
+ type = lib.types.int;
+ default = 7;
+ description = "Number of daily snapshots to keep.";
+ };
+ keepWeekly = lib.mkOption {
+ type = lib.types.int;
+ default = 4;
+ description = "Number of weekly snapshots to keep.";
+ };
+ keepMonthly = lib.mkOption {
+ type = lib.types.int;
+ default = 3;
+ description = "Number of monthly snapshots to keep.";
+ };
+ keepAnnual = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = "Number of yearly snapshots to keep.";
+ };
+ };
+
+ files = {
+ ignoreDotFiles = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [
+ ".gitignore"
+ ".kopiaignore"
+ ];
+ description = "List of files to source ignore lists from.";
+ };
+ noParentDotFiles = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = false;
+ description = "Do not use parent ignore dot files.";
+ };
+ ignoreCacheDirs = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = false;
+ description = "Ignore cache directories.";
+ };
+ maxFileSize = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Maximum file size to include in backup.";
+ };
+ oneFileSystem = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = null;
+ description = "Stay in parent filesystem when finding files.";
+ };
+ };
+
+ errorHandling = {
+ ignoreFileErrors = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = null;
+ description = "Ignore errors reading ignore files.";
+ };
+ ignoreDirectoryErrors = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = null;
+ description = "Ignore errors reading directories.";
+ };
+ ignoreUnknownTypes = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = null;
+ description = "Ignore unknown file types.";
+ };
+ };
+
+ compression = {
+ compressorName = lib.mkOption {
+ type = compressionType;
+ default = "none";
+ description = "Name of the compressor to use.";
+ };
+
+ onlyCompress = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ ];
+ description = "List of file extensions to compress.";
+ };
+
+ noParentOnlyCompress = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Do not use parent only compress list.";
+ };
+
+ neverCompress = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ ];
+ description = "List of file extensions to never compress.";
+ };
+
+ noParentNeverCompress = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Do not use parent never compress list.";
+ };
+
+ minSize = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = "Minimum file size to compress.";
+ };
+
+ maxSize = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = "Maximum file size to compress.";
+ };
+ };
+
+ metadataCompression = {
+ compressorName = lib.mkOption {
+ type = compressionType;
+ default = "zstd-fastest";
+ description = "Name of the compressor to use.";
+ };
+ };
+
+ splitter = {
+ algorithm = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Name of the splitter algorithm to use.";
+ };
+ };
+
+ # FIXME: add action definition afterward (maybe implement it during implement at zfs, btrfs snapshot)
+
+ osSnapshots = {
+ volumeShadowCopy = {
+ enable = lib.mkOption {
+ type = lib.types.nullOr (
+ lib.types.enum [
+ "never"
+ "always"
+ "when-available"
+ "inherit"
+ ]
+ );
+ default = null;
+ description = "Enable volume shadow copy";
+ };
+ };
+ };
+
+ logging = {
+ directories = {
+ snapshotted = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Log detail when a directory is snapshotted";
+ };
+ ignored = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Log detail when a directory is ignored";
+ };
+ };
+
+ entries = {
+ snapshotted = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Log detail when an entry is snapshotted";
+ };
+ ignored = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Log detail when an entry is ignored";
+ };
+ };
+ };
+
+ upload = {
+ # maxParallelSnapshots - GUI only
+ maxParallelFileReads = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Maximum number of parallel file reads(GUI Only)";
+ };
+ parallelUploadAboveSize = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Use parallel uploads above size(GUI Only)";
+ };
+ };
+ };
+ }
+ );
+ default = null;
+ };
+
+ instanceType = lib.types.submodule {
+ options = {
+ policy = policyType;
+ };
+ };
+
+ # application logic
+ jsonFormat = (pkgs.formats.json { });
+
+ # generate policy name for policy file generation
+ mkPolicyName =
+ user: hostname: path:
+ "${user}@${hostname}${if path != "" then ":${path}" else ""}";
+
+ mkPolicyFile = policy: (jsonFormat.generate "kopia-policy.json" policy);
+
+ mkInstancePolicyService =
+ name: instance:
+ let
+ policyName = mkPolicyName instance.user config.networking.hostName instance.path;
+ policyFile = mkPolicyFile (
+ {
+ "${policyName}" = instance.policy;
+ }
+ // lib.optionalAttrs (config.services.kopia.globalPolicy != null) {
+ "(global)" = config.services.kopia.globalPolicy;
+ }
+ );
+ in
+ (lib.attrsets.nameValuePair "kopia-policy-${name}" {
+ description = "Kopia policy setup";
+ wants = [ "kopia-repository-${name}.service" ];
+ wantedBy = [ "kopia-snapshot-${name}.service" ];
+ after = [ "kopia-repository-${name}.service" ];
+ before = [ "kopia-snapshot-${name}.service" ];
+ script = ''
+ ${pkgs.kopia}/bin/kopia policy import --from-file=${policyFile}
+ '';
+ serviceConfig = {
+ Type = "oneshot";
+ User = instance.user;
+ WorkingDirectory = "~";
+ SetLoginEnvironment = true;
+ };
+ });
+in
+{
+ options.services.kopia = {
+ globalPolicy = policyType;
+
+ instances = lib.mkOption {
+ type = lib.types.attrsOf instanceType;
+ };
+ };
+
+ config = lib.mkIf config.services.kopia.enable {
+ systemd.services = mkInstanceServices config.services.kopia.instances mkInstancePolicyService;
+ };
+}