Hi @Pierot, for sure.
BTW, I recommend using nixos 24.05, with that update, there’s an issue regarding btrfs-snapraid, so I’m using a forked version of the original program to manage it, already included in the config here
I ended up with this configuration:
(Sorry I can’t just link to my config on GH since it has some private keys, so here’s a copy of the files)
configuration.nix:
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
./storage.nix
./samba.nix
./nixarr.nix
./network-services.nix
./remote-monitoring.nix
];
nixpkgs.overlays = [
(
final: prev: {
snapraid-btrfs = prev.callPackage ./pkgs/snapraid-btrfs.nix {};
snapraid-btrfs-runner = prev.callPackage ./pkgs/snapraid-btrfs-runner.nix {};
}
)
];
# Use the systemd-boot EFI boot loader.
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub = {
enable = true;
efiSupport = true;
mirroredBoots = [
{
devices = [ "nodev" ];
path = "/boot1";
}
{
devices = [ "nodev" ];
path = "/boot2";
}
];
};
fileSystems."/boot1".options = [ "nofail" ];
fileSystems."/boot2".options = [ "nofail" ];
# Enable ZSH
environment.shells = with pkgs; [ zsh ];
users.defaultUserShell = pkgs.zsh;
programs.zsh.enable = true;
networking.hostName = "nixos"; # Define your hostname.
# Pick only one of the below networking options.
# Enable networking
networking.networkmanager.enable = true;
networking.useDHCP = false;
# Set your time zone.
time.timeZone = "America/Mexico_City";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Configure keymap in X11
services.xserver = {
xkb = {
layout = "us";
variant = "";
};
};
users.groups = {
nas = { };
nasReadOnly = { };
};
# Define a user account. Don't forget to set a password with ‘passwd’.
users.users = {
someUser = {
isNormalUser = true;
description = "Machine owner";
extraGroups = [ "networkmanager" "wheel" "docker" "nasReadOnly"];
packages = with pkgs; [];
};
};
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
vim
wget
git
curl
smartmontools
];
# List services that you want to enable:
# Enable the OpenSSH daemon.
services.openssh.enable = true;
system.stateVersion = "24.05"; # Did you read the comment?
# Install Docker
virtualisation.docker.enable = true;
# Disable sudo password
security.sudo.extraRules = [{
users = ["someUser"];
commands = [{
command = "ALL";
options = [ "NOPASSWD" ];
}];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
}
storage.nix:
# Where we got info to build this from:
# https://wiki.selfhosted.show/tools/snapraid-btrfs/
# https://www.joshkasuboski.com/posts/nixos-nas/#setting-it-up
# https://discourse.nixos.org/t/setting-up-snapraid-btrfs-on-nixos/46476
{
config,
lib,
pkgs,
modulesPath,
...
}: let
disks = [
{
type = "parity";
name = "parity1";
uuid = "d6d85354-15d7-49f9-83ea-7b99f13c386b";
}
{
type = "data";
name = "disk1";
uuid = "5662f7cc-9946-439f-b948-e8eb872ee8ab";
}
{
type = "data";
name = "disk2";
uuid = "d755b1cf-54d7-4f34-b6cc-e70a189e028f";
}
{
type = "data";
name = "disk3";
uuid = "04f194ff-d0fc-431d-8394-b2fafe012b28";
}
];
snapraidDataDisks = builtins.listToAttrs (lib.lists.imap0 (i: d: {
name = "${d.name}";
value = "/mnt/${d.name}";
})
dataDisks);
parityDisks = builtins.filter (d: d.type == "parity") disks;
dataDisks = builtins.filter (d: d.type == "data") disks;
parityFs = builtins.listToAttrs (builtins.map (d: {
name = "/mnt/${d.name}";
value = {
device = "/dev/disk/by-uuid/${d.uuid}";
fsType = "ext4";
};
})
parityDisks);
dataFs = builtins.listToAttrs (builtins.map (d: {
name = "/mnt/${d.name}";
value = {
device = "/dev/disk/by-uuid/${d.uuid}";
fsType = "btrfs";
options = [ "subvol=data" ];
};
})
dataDisks);
snapraidContentFs = builtins.listToAttrs (builtins.map (d: {
name = "/mnt/snapraid-content/${d.name}";
value = {
device = "/dev/disk/by-uuid/${d.uuid}";
fsType = "btrfs";
options = [ "subvol=content" ];
};
})
dataDisks);
snapshotFs = builtins.listToAttrs (builtins.map (d: {
name = "/mnt/${d.name}/.snapshots";
value = {
device = "/dev/disk/by-uuid/${d.uuid}";
fsType = "btrfs";
options = [ "subvol=.snapshots" ];
};
})
dataDisks);
contentFiles =
builtins.map (d: "/mnt/snapraid-content/${d.name}/snapraid.content") dataDisks;
parityFiles = builtins.map (p: "/mnt/${p.name}/snapraid.parity") parityDisks;
snapperConfigs = builtins.listToAttrs (builtins.map (d: {
name = "${d.name}";
value = {
SUBVOLUME = "/mnt/${d.name}";
ALLOW_GROUPS = ["wheel"];
SYNC_ACL = true;
};
})
dataDisks);
in {
environment.systemPackages = with pkgs; [
mergerfs
snapraid-btrfs
snapraid-btrfs-runner
];
fileSystems = {
"/mnt/storage" = {
device = lib.strings.concatMapStringsSep ":" (d: "/mnt/${d.name}") dataDisks;
fsType = "fuse.mergerfs";
options = ["defaults" "nofail" "nonempty" "allow_other" "use_ino" "cache.files=partial" "category.create=lus" "moveonenospc=true" "dropcacheonclose=true" "minfreespace=100G" "fsname=mergerfs"];
};
}
// parityFs
// dataFs
// snapraidContentFs
// snapshotFs;
services.snapraid = {
inherit contentFiles parityFiles;
enable = true;
sync.interval = "";
scrub.interval = "";
dataDisks = snapraidDataDisks;
exclude = [
"*.unrecoverable"
"/tmp/"
"/lost+found/"
"downloads/"
"appdata/"
"*.!sync"
"/.snapshots/"
"/media/.state/"
"/media/torrents/"
];
};
services.snapper = {
configs = snapperConfigs;
};
systemd.services.snapraid-btrfs-sync = {
description = "Run the snapraid-btrfs sync with the runner";
startAt = [ "15:00" "03:00" ];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
ExecStart = "+${pkgs.snapraid-btrfs-runner}/bin/snapraid-btrfs-runner";
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 = "AF_UNIX";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
CapabilityBoundingSet = "";
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadOnlyPaths = ["/etc/snapraid.conf" "/etc/snapper"];
ReadWritePaths =
# sync requires access to directories containing content files
# to remove them if they are stale
let
contentDirs = builtins.map builtins.dirOf contentFiles;
in
lib.unique (
builtins.attrValues snapraidDataDisks ++ parityFiles ++ contentDirs
);
};
};
}
pkgs/snapraid-btrfs.nix
{
symlinkJoin,
fetchFromGitHub,
writeScriptBin,
makeWrapper,
coreutils,
gnugrep,
gawk,
gnused,
snapraid,
snapper,
}: let
name = "snapraid-btrfs";
deps = [coreutils gnugrep gawk gnused snapraid snapper];
script =
(
writeScriptBin name
# NOTE: Original version from automorphism88
# unfortunately, it breaks with new snapper version 0.11.1
# (builtins.readFile ((fetchFromGitHub {
# owner = "automorphism88";
# repo = "snapraid-btrfs";
# rev = "8cdbf54100c2b630ee9fcea11b14f58a894b4bf3";
# sha256 = "IQgL55SMwViOnl3R8rQ9oGsanpFOy4esENKTwl8qsgo=";
# })
# + "/snapraid-btrfs"))
# NOTE: Forked version from D34DC3N73R to fix snapper 0.11.1 compatibility
(builtins.readFile ((fetchFromGitHub {
owner = "D34DC3N73R";
repo = "snapraid-btrfs";
rev = "ea9a1cfbfbe1cefcae9c038e1a4962d4bc2de843";
sha256 = "sha256-+UCBGlGFqRKgFjCt1GdOSxaayTONfwisxdnZEwxOnSY=";
})
+ "/snapraid-btrfs"))
)
.overrideAttrs (old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
in
symlinkJoin {
inherit name;
paths = [script] ++ deps;
buildInputs = [makeWrapper];
postBuild = "wrapProgram $out/bin/${name} --set PATH $out/bin";
}
pkgs/snapraid-btrfs-runner.nix
{
symlinkJoin,
fetchFromGitHub,
writeScriptBin,
writeTextFile,
makeWrapper,
python311,
snapraid,
snapraid-btrfs,
snapper
}: let
name = "snapraid-btrfs-runner";
deps = [python311 config snapraid snapraid-btrfs snapper];
src = fetchFromGitHub {
owner = "fmoledina";
repo = "snapraid-btrfs-runner";
rev = "afb83c67c61fdf3769aab95dba6385184066e119";
sha256 = "M8LXxsc7jEn5GsiXAKykmFUgsij2aOIenw1Dx+/5Rww=";
};
config = writeTextFile {
name = "snapraid-btrfs-runner.conf";
text = ''
[snapraid-btrfs]
; path to the snapraid-btrfs executable (e.g. /usr/bin/snapraid-btrfs)
executable = ${snapraid-btrfs}/bin/snapraid-btrfs
; optional: specify snapper-configs and/or snapper-configs-file as specified in snapraid-btrfs
; only one instance of each can be specified in this config
snapper-configs =
snapper-configs-file =
; specify whether snapraid-btrfs should run the pool command after the sync, and optionally specify pool-dir
pool = false
pool-dir =
; specify whether snapraid-btrfs-runner should automatically clean up all but the last snapraid-btrfs sync snapshot after a successful sync
cleanup = true
[snapper]
; path to snapper executable (e.g. /usr/bin/snapper)
executable = ${snapper}/bin/snapper
[snapraid]
; path to the snapraid executable (e.g. /usr/bin/snapraid)
executable = ${snapraid}/bin/snapraid
; path to the snapraid config to be used
config = /etc/snapraid.conf
; abort operation if there are more deletes than this, set to -1 to disable
deletethreshold = 40
; if you want touch to be ran each time
touch = false
[logging]
; logfile to write to, leave empty to disable
file =
; maximum logfile size in KiB, leave empty for infinite
maxsize = 5000
[email]
; when to send an email, comma-separated list of [success, error]
sendon =
; set to false to get full programm output via email
short = false
subject = [SnapRAID] Status Report:
from =
to =
; maximum email size in KiB
maxsize = 500
[smtp]
host = somesmtphost
; leave empty for default port
port = 587
; set to "true" to activate
ssl = false
tls = true
user = someuser
password = somepassword
[scrub]
; set to true to run scrub after sync
enabled = false
; plan can be 0-100 percent, new, bad, or full
plan = 12
; only used for percent scrub plan
older-than = 10
'';
destination = "/etc/${name}";
};
script =
(
writeScriptBin name
(builtins.readFile (src + "/snapraid-btrfs-runner.py"))
)
.overrideAttrs (old: {
buildCommand = "${old.buildCommand}\n patchShebangs $out";
});
in
symlinkJoin {
inherit name;
paths = [script] ++ deps;
buildInputs = [makeWrapper python311];
postBuild = "wrapProgram $out/bin/${name} --add-flags '-c ${config}/etc/snapraid-btrfs-runner' --set PATH $out/bin";
}
NOTE: I’ve configured an SMTP service to email me when something goes wrong, I’ve removed my user/keys so pay attention if there’s an error regarding that part of the configuration