{ lib, config, pkgs, ... }: with lib; let cfg = config.local; in { options.local = with lib.types; { snapperSubvols = mkOption { type = attrsOf str; default = { }; }; fs.btrfs = mkOption { default = [ ]; type = attrsOf (submodule { options = { device = mkOption { type = str; }; subvol = mkOption { type = str; }; ssd = mkOption { type = bool; }; snapper = mkOption { type = nullOr str; default = null; }; }; }); }; }; config = { environment.systemPackages = optional (cfg.snapperSubvols != { }) pkgs.local.btclone; fileSystems = let inherit (cfg) fs; btrfs = { device, subvol, ssd, ... }: { inherit device; fsType = "btrfs"; options = [ "noatime" "compress=zstd" "subvol=${subvol}" ] ++ optional ssd "ssd"; }; in mapAttrs (_: btrfs) cfg.fs.btrfs; local.snapperSubvols = let snapperEntry = path: opts: { name = opts.snapper; value = path; }; validEntry = _: opts: opts.snapper != null; in mapAttrs' snapperEntry (filterAttrs validEntry cfg.fs.btrfs); services.snapper.configs = let snapperConfig = _: subvolume: { SUBVOLUME = subvolume; # btrfs qgroup for space aware cleanup algorithms QGROUP = ""; # fraction of the filesystems space the snapshots may use SPACE_LIMIT = "0.5"; # fraction of the filesystems space that should be free FREE_LIMIT = "0.2"; # users and groups allowed to work with config ALLOW_USERS = [ ]; ALLOW_GROUPS = [ ]; # sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots # directory SYNC_ACL = "no"; # start comparing pre- and post-snapshot in background after creating # post-snapshot BACKGROUND_COMPARISON = "yes"; # run daily number cleanup NUMBER_CLEANUP = "yes"; # limit for number cleanup NUMBER_MIN_AGE = "1800"; NUMBER_LIMIT = "100"; NUMBER_LIMIT_IMPORTANT = "10"; # create hourly snapshots TIMELINE_CREATE = true; # cleanup hourly snapshots after some time TIMELINE_CLEANUP = true; # limits for timeline cleanup TIMELINE_MIN_AGE = "1800"; TIMELINE_LIMIT_HOURLY = "24"; TIMELINE_LIMIT_DAILY = "7"; TIMELINE_LIMIT_WEEKLY = "4"; TIMELINE_LIMIT_MONTHLY = "12"; TIMELINE_LIMIT_YEARLY = "10"; # cleanup empty pre-post-pairs EMPTY_PRE_POST_CLEANUP = "yes"; # limits for empty pre-post-pair cleanup EMPTY_PRE_POST_MIN_AGE = "1800"; }; in mapAttrs snapperConfig cfg.snapperSubvols; }; }