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