8 cfg = config.services.snapper;
14 lib.concatMapStringsSep " " (lib.escape [
19 else if v == true then
21 else if v == false then
23 else if lib.isString v then
28 mkKeyValue = k: v: "${k}=${mkValue v}";
30 # "it's recommended to always specify the filesystem type" -- man snapper-configs
31 defaultOf = k: if k == "FSTYPE" then null else configOptions.${k}.default or null;
33 safeStr = lib.types.strMatching "[^\n\"]*" // {
34 description = "string without line breaks or quotes";
35 descriptionClass = "conjunction";
38 intOrNumberOrRange = lib.types.either lib.types.ints.unsigned (
39 lib.types.strMatching "[[:digit:]]+(\-[[:digit:]]+)?"
41 description = "string containing either a number or a range";
42 descriptionClass = "conjunction";
47 SUBVOLUME = lib.mkOption {
48 type = lib.types.path;
50 Path of the subvolume or mount point.
51 This path is a subvolume and has to contain a subvolume named
53 See also man:snapper(8) section PERMISSIONS.
57 FSTYPE = lib.mkOption {
58 type = lib.types.enum [ "btrfs" ];
61 Filesystem type. Only btrfs is stable and tested.
65 ALLOW_GROUPS = lib.mkOption {
66 type = lib.types.listOf safeStr;
69 List of groups allowed to operate with the config.
71 Also see the PERMISSIONS section in man:snapper(8).
75 ALLOW_USERS = lib.mkOption {
76 type = lib.types.listOf safeStr;
78 example = [ "alice" ];
80 List of users allowed to operate with the config. "root" is always
83 Also see the PERMISSIONS section in man:snapper(8).
87 TIMELINE_CLEANUP = lib.mkOption {
88 type = lib.types.bool;
91 Defines whether the timeline cleanup algorithm should be run for the config.
95 TIMELINE_CREATE = lib.mkOption {
96 type = lib.types.bool;
99 Defines whether hourly snapshots should be created.
103 TIMELINE_LIMIT_HOURLY = lib.mkOption {
104 type = intOrNumberOrRange;
107 Limits for timeline cleanup.
111 TIMELINE_LIMIT_DAILY = lib.mkOption {
112 type = intOrNumberOrRange;
115 Limits for timeline cleanup.
119 TIMELINE_LIMIT_WEEKLY = lib.mkOption {
120 type = intOrNumberOrRange;
123 Limits for timeline cleanup.
127 TIMELINE_LIMIT_MONTHLY = lib.mkOption {
128 type = intOrNumberOrRange;
131 Limits for timeline cleanup.
135 TIMELINE_LIMIT_QUARTERLY = lib.mkOption {
136 type = intOrNumberOrRange;
139 Limits for timeline cleanup.
143 TIMELINE_LIMIT_YEARLY = lib.mkOption {
144 type = intOrNumberOrRange;
147 Limits for timeline cleanup.
154 options.services.snapper = {
156 snapshotRootOnBoot = lib.mkOption {
157 type = lib.types.bool;
160 Whether to snapshot root on boot
164 snapshotInterval = lib.mkOption {
165 type = lib.types.str;
170 The format is described in
171 {manpage}`systemd.time(7)`.
175 persistentTimer = lib.mkOption {
177 type = lib.types.bool;
180 Set the `Persistent` option for the
181 {manpage}`systemd.timer(5)`
182 which triggers the snapshot immediately if the last trigger
183 was missed (e.g. if the system was powered down).
187 cleanupInterval = lib.mkOption {
188 type = lib.types.str;
193 The format is described in
194 {manpage}`systemd.time(7)`.
198 filters = lib.mkOption {
199 type = lib.types.nullOr lib.types.lines;
202 Global display difference filter. See man:snapper(8) for more details.
206 configs = lib.mkOption {
208 example = lib.literalExpression ''
212 ALLOW_USERS = [ "alice" ];
213 TIMELINE_CREATE = true;
214 TIMELINE_CLEANUP = true;
220 Subvolume configuration. Any option mentioned in man:snapper-configs(5)
221 is valid here, even if NixOS doesn't document it.
224 type = lib.types.attrsOf (
225 lib.types.submodule {
226 freeformType = lib.types.attrsOf (
228 (lib.types.listOf safeStr)
235 options = configOptions;
241 config = lib.mkIf (cfg.configs != { }) (
245 "man:snapper-configs(5)"
251 systemPackages = [ pkgs.snapper ];
253 # Note: snapper/config-templates/default is only needed for create-config
254 # which is not the NixOS way to configure.
258 "sysconfig/snapper".text = ''
259 SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
264 lib.nameValuePair "snapper/configs/${name}" ({
265 text = lib.generators.toKeyValue { inherit mkKeyValue; } (
266 lib.filterAttrs (k: v: v != defaultOf k) subvolume
270 // (lib.optionalAttrs (cfg.filters != null) { "snapper/filters/default.txt".text = cfg.filters; });
273 services.dbus.packages = [ pkgs.snapper ];
275 systemd.services.snapperd = {
276 description = "DBus interface for snapper";
277 inherit documentation;
280 BusName = "org.opensuse.Snapper";
281 ExecStart = "${pkgs.snapper}/bin/snapperd";
282 CapabilityBoundingSet = "CAP_DAC_OVERRIDE CAP_FOWNER CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_SYS_ADMIN CAP_SYS_MODULE CAP_IPC_LOCK CAP_SYS_NICE";
283 LockPersonality = true;
284 NoNewPrivileges = false;
285 PrivateNetwork = true;
286 ProtectHostname = true;
287 RestrictAddressFamilies = "AF_UNIX";
288 RestrictRealtime = true;
292 systemd.services.snapper-timeline = {
293 description = "Timeline of Snapper Snapshots";
294 inherit documentation;
295 requires = [ "local-fs.target" ];
296 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
299 systemd.timers.snapper-timeline = {
300 wantedBy = [ "timers.target" ];
302 Persistent = cfg.persistentTimer;
303 OnCalendar = cfg.snapshotInterval;
307 systemd.services.snapper-cleanup = {
308 description = "Cleanup of Snapper Snapshots";
309 inherit documentation;
310 serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
313 systemd.timers.snapper-cleanup = {
314 description = "Cleanup of Snapper Snapshots";
315 inherit documentation;
316 wantedBy = [ "timers.target" ];
317 requires = [ "local-fs.target" ];
318 timerConfig.OnBootSec = "10m";
319 timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
322 systemd.services.snapper-boot = lib.mkIf cfg.snapshotRootOnBoot {
323 description = "Take snapper snapshot of root on boot";
324 inherit documentation;
325 serviceConfig.ExecStart = "${pkgs.snapper}/bin/snapper --config root create --cleanup-algorithm number --description boot";
326 serviceConfig.Type = "oneshot";
327 requires = [ "local-fs.target" ];
328 wantedBy = [ "multi-user.target" ];
329 unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
332 assertions = lib.concatMap (
335 sub = cfg.configs.${name};
339 assertion = !(sub ? extraConfig);
341 The option definition `services.snapper.configs.${name}.extraConfig' no longer has any effect; please remove it.
342 The contents of this option should be migrated to attributes on `services.snapper.configs.${name}'.
349 assertion = !(lib.hasAttr attr sub);
351 The option definition `services.snapper.configs.${name}.${attr}' has been renamed to `services.snapper.configs.${name}.${lib.toUpper attr}'.
358 ) (lib.attrNames cfg.configs);
362 meta.maintainers = with lib.maintainers; [ Djabx ];