1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.snapper;
7in
8
9{
10 options.services.snapper = {
11
12 snapshotRootOnBoot = mkOption {
13 type = types.bool;
14 default = false;
15 description = ''
16 Whether to snapshot root on boot
17 '';
18 };
19
20 snapshotInterval = mkOption {
21 type = types.str;
22 default = "hourly";
23 description = ''
24 Snapshot interval.
25
26 The format is described in
27 <citerefentry><refentrytitle>systemd.time</refentrytitle>
28 <manvolnum>7</manvolnum></citerefentry>.
29 '';
30 };
31
32 cleanupInterval = mkOption {
33 type = types.str;
34 default = "1d";
35 description = ''
36 Cleanup interval.
37
38 The format is described in
39 <citerefentry><refentrytitle>systemd.time</refentrytitle>
40 <manvolnum>7</manvolnum></citerefentry>.
41 '';
42 };
43
44 filters = mkOption {
45 type = types.nullOr types.lines;
46 default = null;
47 description = ''
48 Global display difference filter. See man:snapper(8) for more details.
49 '';
50 };
51
52 configs = mkOption {
53 default = { };
54 example = literalExpression ''
55 {
56 home = {
57 subvolume = "/home";
58 extraConfig = '''
59 ALLOW_USERS="alice"
60 TIMELINE_CREATE=yes
61 TIMELINE_CLEANUP=yes
62 ''';
63 };
64 }
65 '';
66
67 description = ''
68 Subvolume configuration
69 '';
70
71 type = types.attrsOf (types.submodule {
72 options = {
73 subvolume = mkOption {
74 type = types.path;
75 description = ''
76 Path of the subvolume or mount point.
77 This path is a subvolume and has to contain a subvolume named
78 .snapshots.
79 See also man:snapper(8) section PERMISSIONS.
80 '';
81 };
82
83 fstype = mkOption {
84 type = types.enum [ "btrfs" ];
85 default = "btrfs";
86 description = ''
87 Filesystem type. Only btrfs is stable and tested.
88 '';
89 };
90
91 extraConfig = mkOption {
92 type = types.lines;
93 default = "";
94 description = ''
95 Additional configuration next to SUBVOLUME and FSTYPE.
96 See man:snapper-configs(5).
97 '';
98 };
99 };
100 });
101 };
102 };
103
104 config = mkIf (cfg.configs != {}) (let
105 documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ];
106 in {
107
108 environment = {
109
110 systemPackages = [ pkgs.snapper ];
111
112 # Note: snapper/config-templates/default is only needed for create-config
113 # which is not the NixOS way to configure.
114 etc = {
115
116 "sysconfig/snapper".text = ''
117 SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
118 '';
119
120 }
121 // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
122 text = ''
123 ${subvolume.extraConfig}
124 FSTYPE="${subvolume.fstype}"
125 SUBVOLUME="${subvolume.subvolume}"
126 '';
127 })) cfg.configs)
128 // (lib.optionalAttrs (cfg.filters != null) {
129 "snapper/filters/default.txt".text = cfg.filters;
130 });
131
132 };
133
134 services.dbus.packages = [ pkgs.snapper ];
135
136 systemd.services.snapperd = {
137 description = "DBus interface for snapper";
138 inherit documentation;
139 serviceConfig = {
140 Type = "dbus";
141 BusName = "org.opensuse.Snapper";
142 ExecStart = "${pkgs.snapper}/bin/snapperd";
143 CapabilityBoundingSet = "CAP_DAC_OVERRIDE CAP_FOWNER CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_SYS_ADMIN CAP_SYS_MODULE CAP_IPC_LOCK CAP_SYS_NICE";
144 LockPersonality = true;
145 NoNewPrivileges = false;
146 PrivateNetwork = true;
147 ProtectHostname = true;
148 RestrictAddressFamilies = "AF_UNIX";
149 RestrictRealtime = true;
150 };
151 };
152
153 systemd.services.snapper-timeline = {
154 description = "Timeline of Snapper Snapshots";
155 inherit documentation;
156 requires = [ "local-fs.target" ];
157 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
158 startAt = cfg.snapshotInterval;
159 };
160
161 systemd.services.snapper-cleanup = {
162 description = "Cleanup of Snapper Snapshots";
163 inherit documentation;
164 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
165 };
166
167 systemd.timers.snapper-cleanup = {
168 description = "Cleanup of Snapper Snapshots";
169 inherit documentation;
170 wantedBy = [ "timers.target" ];
171 requires = [ "local-fs.target" ];
172 timerConfig.OnBootSec = "10m";
173 timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
174 };
175
176 systemd.services.snapper-boot = lib.optionalAttrs cfg.snapshotRootOnBoot {
177 description = "Take snapper snapshot of root on boot";
178 inherit documentation;
179 serviceConfig.ExecStart = "${pkgs.snapper}/bin/snapper --config root create --cleanup-algorithm number --description boot";
180 serviceConfig.type = "oneshot";
181 requires = [ "local-fs.target" ];
182 wantedBy = [ "multi-user.target" ];
183 unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
184 };
185
186 });
187}