{ 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: { inherit subvolume; extraConfig = '' # 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="yes" # cleanup hourly snapshots after some time TIMELINE_CLEANUP="yes" # 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; }; }