nixpkgs/nixos/modules/services/backup/snapraid.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

240 lines
7.6 KiB
Nix
Raw Normal View History

2021-07-14 16:40:05 +00:00
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.services.snapraid;
2021-07-14 16:40:05 +00:00
in
{
imports = [
# Should have never been on the top-level.
(mkRenamedOptionModule [ "snapraid" ] [ "services" "snapraid" ])
];
options.services.snapraid = with types; {
2021-07-14 16:40:05 +00:00
enable = mkEnableOption (lib.mdDoc "SnapRAID");
dataDisks = mkOption {
default = { };
example = {
d1 = "/mnt/disk1/";
d2 = "/mnt/disk2/";
d3 = "/mnt/disk3/";
};
description = lib.mdDoc "SnapRAID data disks.";
2021-07-14 16:40:05 +00:00
type = attrsOf str;
};
parityFiles = mkOption {
default = [ ];
example = [
"/mnt/diskp/snapraid.parity"
"/mnt/diskq/snapraid.2-parity"
"/mnt/diskr/snapraid.3-parity"
"/mnt/disks/snapraid.4-parity"
"/mnt/diskt/snapraid.5-parity"
"/mnt/disku/snapraid.6-parity"
];
description = lib.mdDoc "SnapRAID parity files.";
2021-07-14 16:40:05 +00:00
type = listOf str;
};
contentFiles = mkOption {
default = [ ];
example = [
"/var/snapraid.content"
"/mnt/disk1/snapraid.content"
"/mnt/disk2/snapraid.content"
];
description = lib.mdDoc "SnapRAID content list files.";
2021-07-14 16:40:05 +00:00
type = listOf str;
};
exclude = mkOption {
default = [ ];
example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
description = lib.mdDoc "SnapRAID exclude directives.";
2021-07-14 16:40:05 +00:00
type = listOf str;
};
touchBeforeSync = mkOption {
default = true;
example = false;
description = lib.mdDoc
"Whether {command}`snapraid touch` should be run before {command}`snapraid sync`.";
2021-07-14 16:40:05 +00:00
type = bool;
};
sync.interval = mkOption {
default = "01:00";
example = "daily";
description = lib.mdDoc "How often to run {command}`snapraid sync`.";
2021-07-14 16:40:05 +00:00
type = str;
};
scrub = {
interval = mkOption {
default = "Mon *-*-* 02:00:00";
example = "weekly";
description = lib.mdDoc "How often to run {command}`snapraid scrub`.";
2021-07-14 16:40:05 +00:00
type = str;
};
plan = mkOption {
default = 8;
example = 5;
description = lib.mdDoc
"Percent of the array that should be checked by {command}`snapraid scrub`.";
2021-07-14 16:40:05 +00:00
type = int;
};
olderThan = mkOption {
default = 10;
example = 20;
description = lib.mdDoc
2021-07-14 16:40:05 +00:00
"Number of days since data was last scrubbed before it can be scrubbed again.";
type = int;
};
};
extraConfig = mkOption {
default = "";
example = ''
nohidden
blocksize 256
hashsize 16
autosave 500
pool /pool
'';
description = lib.mdDoc "Extra config options for SnapRAID.";
2021-07-14 16:40:05 +00:00
type = lines;
};
};
config =
let
nParity = builtins.length cfg.parityFiles;
mkPrepend = pre: s: pre + s;
in
mkIf cfg.enable {
assertions = [
{
assertion = nParity <= 6;
message = "You can have no more than six SnapRAID parity files.";
}
{
assertion = builtins.length cfg.contentFiles >= nParity + 1;
message =
"There must be at least one SnapRAID content file for each SnapRAID parity file plus one.";
}
];
environment = {
systemPackages = with pkgs; [ snapraid ];
etc."snapraid.conf" = {
text = with cfg;
let
prependData = mkPrepend "data ";
prependContent = mkPrepend "content ";
prependExclude = mkPrepend "exclude ";
in
concatStringsSep "\n"
(map prependData
((mapAttrsToList (name: value: name + " " + value)) dataDisks)
++ zipListsWith (a: b: a + b)
([ "parity " ] ++ map (i: toString i + "-parity ") (range 2 6))
parityFiles ++ map prependContent contentFiles
++ map prependExclude exclude) + "\n" + extraConfig;
};
};
systemd.services = with cfg; {
snapraid-scrub = {
description = "Scrub the SnapRAID array";
startAt = scrub.interval;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.snapraid}/bin/snapraid scrub -p ${
toString scrub.plan
} -o ${toString scrub.olderThan}";
Nice = 19;
IOSchedulingPriority = 7;
CPUSchedulingPolicy = "batch";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = "none";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
CapabilityBoundingSet = "CAP_DAC_OVERRIDE";
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadWritePaths =
# scrub requires access to directories containing content files
# to remove them if they are stale
let
contentDirs = map dirOf contentFiles;
in
unique (
attrValues dataDisks ++ contentDirs
);
};
unitConfig.After = "snapraid-sync.service";
};
snapraid-sync = {
description = "Synchronize the state of the SnapRAID array";
startAt = sync.interval;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.snapraid}/bin/snapraid sync";
Nice = 19;
IOSchedulingPriority = 7;
CPUSchedulingPolicy = "batch";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = "none";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
CapabilityBoundingSet = "CAP_DAC_OVERRIDE" +
lib.optionalString cfg.touchBeforeSync " CAP_FOWNER";
2021-07-14 16:40:05 +00:00
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadWritePaths =
# sync requires access to directories containing content files
# to remove them if they are stale
let
contentDirs = map dirOf contentFiles;
snapraid: fix split parity files SnapRAID has a feature where you can specify "split" parity files. This is useful when you're using 16tb or bigger ext4-formatted disks for parity. ext4 doesn't support files bigger than 16tb so this "split parity file" can be used to specify two parity files on a single parity disk and SnapRAID will automatically use the subsequent file when the current cannot grow anymore (hits 16TB). You specify these split parity files by separating them with commas in the "parity" config option. This mostly already works except when it comes to the scheduled systemd sync job where it specifies ReadWritePaths. If you specify a parity with multiple files you'll get an error when the systemd job runs: Failed to set up mount namespacing: /run/systemd/unit-root/mnt/parity1/snapraid1.parity,/mnt/parity1/snapraid2.parity: No such file or directory Essentially, when the parity file paths are passed into ReadWritePaths, they're always treated as a single path. This change makes sure to split the paths if they contain a comma. The big concern for this change is if it would break users who have commas in their actual parity file paths. This won't be an issue because SnapRAID itself blindly splits on commas for parity files, so legitimate commas in a parity file path wouldn't work in SnapRAID anyway. See here: https://github.com/amadvance/snapraid/blob/978d812153736c06763192f0f1d4b34e54ad633f/cmdline/state.c#L692 SnapRAID doc for split parity files: https://www.snapraid.it/manual#7.1
2023-11-23 01:51:11 +00:00
# Multiple "split" parity files can be specified in a single
# "parityFile", separated by a comma.
# https://www.snapraid.it/manual#7.1
splitParityFiles = map (s: splitString "," s) parityFiles;
2021-07-14 16:40:05 +00:00
in
unique (
snapraid: fix split parity files SnapRAID has a feature where you can specify "split" parity files. This is useful when you're using 16tb or bigger ext4-formatted disks for parity. ext4 doesn't support files bigger than 16tb so this "split parity file" can be used to specify two parity files on a single parity disk and SnapRAID will automatically use the subsequent file when the current cannot grow anymore (hits 16TB). You specify these split parity files by separating them with commas in the "parity" config option. This mostly already works except when it comes to the scheduled systemd sync job where it specifies ReadWritePaths. If you specify a parity with multiple files you'll get an error when the systemd job runs: Failed to set up mount namespacing: /run/systemd/unit-root/mnt/parity1/snapraid1.parity,/mnt/parity1/snapraid2.parity: No such file or directory Essentially, when the parity file paths are passed into ReadWritePaths, they're always treated as a single path. This change makes sure to split the paths if they contain a comma. The big concern for this change is if it would break users who have commas in their actual parity file paths. This won't be an issue because SnapRAID itself blindly splits on commas for parity files, so legitimate commas in a parity file path wouldn't work in SnapRAID anyway. See here: https://github.com/amadvance/snapraid/blob/978d812153736c06763192f0f1d4b34e54ad633f/cmdline/state.c#L692 SnapRAID doc for split parity files: https://www.snapraid.it/manual#7.1
2023-11-23 01:51:11 +00:00
attrValues dataDisks ++ splitParityFiles ++ contentDirs
2021-07-14 16:40:05 +00:00
);
} // optionalAttrs touchBeforeSync {
ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch";
};
};
};
};
}