1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.snapper;
7in
8
9{
10 options.services.snapper = {
11
12 snapshotInterval = mkOption {
13 type = types.str;
14 default = "hourly";
15 description = ''
16 Snapshot interval.
17
18 The format is described in
19 <citerefentry><refentrytitle>systemd.time</refentrytitle>
20 <manvolnum>7</manvolnum></citerefentry>.
21 '';
22 };
23
24 cleanupInterval = mkOption {
25 type = types.str;
26 default = "1d";
27 description = ''
28 Cleanup interval.
29
30 The format is described in
31 <citerefentry><refentrytitle>systemd.time</refentrytitle>
32 <manvolnum>7</manvolnum></citerefentry>.
33 '';
34 };
35
36 filters = mkOption {
37 type = types.nullOr types.lines;
38 default = null;
39 description = ''
40 Global display difference filter. See man:snapper(8) for more details.
41 '';
42 };
43
44 configs = mkOption {
45 default = { };
46 example = literalExample {
47 "home" = {
48 subvolume = "/home";
49 extraConfig = ''
50 ALLOW_USERS="alice"
51 '';
52 };
53 };
54
55 description = ''
56 Subvolume configuration
57 '';
58
59 type = types.attrsOf (types.submodule {
60 options = {
61 subvolume = mkOption {
62 type = types.path;
63 description = ''
64 Path of the subvolume or mount point.
65 This path is a subvolume and has to contain a subvolume named
66 .snapshots.
67 See also man:snapper(8) section PERMISSIONS.
68 '';
69 };
70
71 fstype = mkOption {
72 type = types.enum [ "btrfs" ];
73 default = "btrfs";
74 description = ''
75 Filesystem type. Only btrfs is stable and tested.
76 '';
77 };
78
79 extraConfig = mkOption {
80 type = types.lines;
81 default = "";
82 description = ''
83 Additional configuration next to SUBVOLUME and FSTYPE.
84 See man:snapper-configs(5).
85 '';
86 };
87 };
88 });
89 };
90 };
91
92 config = mkIf (cfg.configs != {}) (let
93 documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ];
94 in {
95
96 environment = {
97
98 systemPackages = [ pkgs.snapper ];
99
100 # Note: snapper/config-templates/default is only needed for create-config
101 # which is not the NixOS way to configure.
102 etc = {
103
104 "sysconfig/snapper".text = ''
105 SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
106 '';
107
108 }
109 // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
110 text = ''
111 ${subvolume.extraConfig}
112 FSTYPE="${subvolume.fstype}"
113 SUBVOLUME="${subvolume.subvolume}"
114 '';
115 })) cfg.configs)
116 // (lib.optionalAttrs (cfg.filters != null) {
117 "snapper/filters/default.txt".text = cfg.filters;
118 });
119
120 };
121
122 services.dbus.packages = [ pkgs.snapper ];
123
124 systemd.services.snapper-timeline = {
125 description = "Timeline of Snapper Snapshots";
126 inherit documentation;
127 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
128 };
129
130 systemd.timers.snapper-timeline = {
131 description = "Timeline of Snapper Snapshots";
132 inherit documentation;
133 wantedBy = [ "basic.target" ];
134 timerConfig.OnCalendar = cfg.snapshotInterval;
135 };
136
137 systemd.services.snapper-cleanup = {
138 description = "Cleanup of Snapper Snapshots";
139 inherit documentation;
140 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
141 };
142
143 systemd.timers.snapper-cleanup = {
144 description = "Cleanup of Snapper Snapshots";
145 inherit documentation;
146 wantedBy = [ "basic.target" ];
147 timerConfig.OnBootSec = "10m";
148 timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
149 };
150 });
151}
152