fix snapper
This commit is contained in:
parent
4edccaa438
commit
b8220b11a7
3 changed files with 380 additions and 8 deletions
359
extras/snapper.nix
Normal file
359
extras/snapper.nix
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.services.snapper;
|
||||||
|
|
||||||
|
mkValue = v:
|
||||||
|
if lib.isList v
|
||||||
|
then "\"${
|
||||||
|
lib.concatMapStringsSep " " (lib.escape [
|
||||||
|
"\\"
|
||||||
|
" "
|
||||||
|
])
|
||||||
|
v
|
||||||
|
}\""
|
||||||
|
else if v == true
|
||||||
|
then "yes"
|
||||||
|
else if v == false
|
||||||
|
then "no"
|
||||||
|
else if lib.isString v
|
||||||
|
then "\"${v}\""
|
||||||
|
else builtins.toJSON v;
|
||||||
|
|
||||||
|
mkKeyValue = k: v: "${k}=${mkValue v}";
|
||||||
|
|
||||||
|
# "it's recommended to always specify the filesystem type" -- man snapper-configs
|
||||||
|
defaultOf = k:
|
||||||
|
if k == "FSTYPE"
|
||||||
|
then null
|
||||||
|
else configOptions.${k}.default or null;
|
||||||
|
|
||||||
|
safeStr =
|
||||||
|
lib.types.strMatching "[^\n\"]*"
|
||||||
|
// {
|
||||||
|
description = "string without line breaks or quotes";
|
||||||
|
descriptionClass = "conjunction";
|
||||||
|
};
|
||||||
|
|
||||||
|
intOrNumberOrRange = lib.types.either lib.types.ints.unsigned (
|
||||||
|
lib.types.strMatching "[[:digit:]]+(\-[[:digit:]]+)?"
|
||||||
|
// {
|
||||||
|
description = "string containing either a number or a range";
|
||||||
|
descriptionClass = "conjunction";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
configOptions = {
|
||||||
|
SUBVOLUME = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
Path of the subvolume or mount point.
|
||||||
|
This path is a subvolume and has to contain a subvolume named
|
||||||
|
.snapshots.
|
||||||
|
See also man:snapper(8) section PERMISSIONS.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
FSTYPE = lib.mkOption {
|
||||||
|
type = lib.types.enum ["btrfs" "bcachefs"];
|
||||||
|
default = "btrfs";
|
||||||
|
description = ''
|
||||||
|
Filesystem type. Only btrfs is stable and tested.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ALLOW_GROUPS = lib.mkOption {
|
||||||
|
type = lib.types.listOf safeStr;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
List of groups allowed to operate with the config.
|
||||||
|
|
||||||
|
Also see the PERMISSIONS section in man:snapper(8).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ALLOW_USERS = lib.mkOption {
|
||||||
|
type = lib.types.listOf safeStr;
|
||||||
|
default = [];
|
||||||
|
example = ["alice"];
|
||||||
|
description = ''
|
||||||
|
List of users allowed to operate with the config. "root" is always
|
||||||
|
implicitly included.
|
||||||
|
|
||||||
|
Also see the PERMISSIONS section in man:snapper(8).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_CLEANUP = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Defines whether the timeline cleanup algorithm should be run for the config.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_CREATE = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Defines whether hourly snapshots should be created.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_HOURLY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 10;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_DAILY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 10;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_WEEKLY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 0;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_MONTHLY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 10;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_QUARTERLY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 0;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
TIMELINE_LIMIT_YEARLY = lib.mkOption {
|
||||||
|
type = intOrNumberOrRange;
|
||||||
|
default = 10;
|
||||||
|
description = ''
|
||||||
|
Limits for timeline cleanup.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options.services.snapper = {
|
||||||
|
snapshotRootOnBoot = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to snapshot root on boot
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshotInterval = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "hourly";
|
||||||
|
description = ''
|
||||||
|
Snapshot interval.
|
||||||
|
|
||||||
|
The format is described in
|
||||||
|
{manpage}`systemd.time(7)`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
persistentTimer = lib.mkOption {
|
||||||
|
default = false;
|
||||||
|
type = lib.types.bool;
|
||||||
|
example = true;
|
||||||
|
description = ''
|
||||||
|
Set the `Persistent` option for the
|
||||||
|
{manpage}`systemd.timer(5)`
|
||||||
|
which triggers the snapshot immediately if the last trigger
|
||||||
|
was missed (e.g. if the system was powered down).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanupInterval = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "1d";
|
||||||
|
description = ''
|
||||||
|
Cleanup interval.
|
||||||
|
|
||||||
|
The format is described in
|
||||||
|
{manpage}`systemd.time(7)`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
filters = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.lines;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Global display difference filter. See man:snapper(8) for more details.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
configs = lib.mkOption {
|
||||||
|
default = {};
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
home = {
|
||||||
|
SUBVOLUME = "/home";
|
||||||
|
ALLOW_USERS = [ "alice" ];
|
||||||
|
TIMELINE_CREATE = true;
|
||||||
|
TIMELINE_CLEANUP = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
Subvolume configuration. Any option mentioned in man:snapper-configs(5)
|
||||||
|
is valid here, even if NixOS doesn't document it.
|
||||||
|
'';
|
||||||
|
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
freeformType = lib.types.attrsOf (
|
||||||
|
lib.types.oneOf [
|
||||||
|
(lib.types.listOf safeStr)
|
||||||
|
lib.types.bool
|
||||||
|
safeStr
|
||||||
|
lib.types.number
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
options = configOptions;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf (cfg.configs != {}) (
|
||||||
|
let
|
||||||
|
documentation = [
|
||||||
|
"man:snapper(8)"
|
||||||
|
"man:snapper-configs(5)"
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
environment = {
|
||||||
|
systemPackages = [pkgs.snapper];
|
||||||
|
|
||||||
|
# Note: snapper/config-templates/default is only needed for create-config
|
||||||
|
# which is not the NixOS way to configure.
|
||||||
|
etc =
|
||||||
|
{
|
||||||
|
"sysconfig/snapper".text = ''
|
||||||
|
SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
// (lib.mapAttrs' (
|
||||||
|
name: subvolume:
|
||||||
|
lib.nameValuePair "snapper/configs/${name}" {
|
||||||
|
text = lib.generators.toKeyValue {inherit mkKeyValue;} (
|
||||||
|
lib.filterAttrs (k: v: v != defaultOf k) subvolume
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cfg.configs)
|
||||||
|
// (lib.optionalAttrs (cfg.filters != null) {"snapper/filters/default.txt".text = cfg.filters;});
|
||||||
|
};
|
||||||
|
|
||||||
|
services.dbus.packages = [pkgs.snapper];
|
||||||
|
|
||||||
|
systemd.services.snapperd = {
|
||||||
|
description = "DBus interface for snapper";
|
||||||
|
inherit documentation;
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "dbus";
|
||||||
|
BusName = "org.opensuse.Snapper";
|
||||||
|
ExecStart = "${pkgs.snapper}/bin/snapperd";
|
||||||
|
CapabilityBoundingSet = "CAP_DAC_OVERRIDE CAP_FOWNER CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_SYS_ADMIN CAP_SYS_MODULE CAP_IPC_LOCK CAP_SYS_NICE";
|
||||||
|
LockPersonality = true;
|
||||||
|
NoNewPrivileges = false;
|
||||||
|
PrivateNetwork = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
RestrictAddressFamilies = "AF_UNIX";
|
||||||
|
RestrictRealtime = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.snapper-timeline = {
|
||||||
|
description = "Timeline of Snapper Snapshots";
|
||||||
|
inherit documentation;
|
||||||
|
requires = ["local-fs.target"];
|
||||||
|
serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.snapper-timeline = {
|
||||||
|
wantedBy = ["timers.target"];
|
||||||
|
timerConfig = {
|
||||||
|
Persistent = cfg.persistentTimer;
|
||||||
|
OnCalendar = cfg.snapshotInterval;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.snapper-cleanup = {
|
||||||
|
description = "Cleanup of Snapper Snapshots";
|
||||||
|
inherit documentation;
|
||||||
|
serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.snapper-cleanup = {
|
||||||
|
description = "Cleanup of Snapper Snapshots";
|
||||||
|
inherit documentation;
|
||||||
|
wantedBy = ["timers.target"];
|
||||||
|
requires = ["local-fs.target"];
|
||||||
|
timerConfig.OnBootSec = "10m";
|
||||||
|
timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.snapper-boot = lib.mkIf cfg.snapshotRootOnBoot {
|
||||||
|
description = "Take snapper snapshot of root on boot";
|
||||||
|
inherit documentation;
|
||||||
|
serviceConfig.ExecStart = "${pkgs.snapper}/bin/snapper --config root create --cleanup-algorithm number --description boot";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
requires = ["local-fs.target"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
|
||||||
|
};
|
||||||
|
|
||||||
|
assertions = lib.concatMap (
|
||||||
|
name: let
|
||||||
|
sub = cfg.configs.${name};
|
||||||
|
in
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assertion = !(sub ? extraConfig);
|
||||||
|
message = ''
|
||||||
|
The option definition `services.snapper.configs.${name}.extraConfig' no longer has any effect; please remove it.
|
||||||
|
The contents of this option should be migrated to attributes on `services.snapper.configs.${name}'.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ map
|
||||||
|
(attr: {
|
||||||
|
assertion = !(lib.hasAttr attr sub);
|
||||||
|
message = ''
|
||||||
|
The option definition `services.snapper.configs.${name}.${attr}' has been renamed to `services.snapper.configs.${name}.${lib.toUpper attr}'.
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
[
|
||||||
|
"fstype"
|
||||||
|
"subvolume"
|
||||||
|
]
|
||||||
|
) (lib.attrNames cfg.configs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [Djabx];
|
||||||
|
}
|
||||||
|
|
@ -91,8 +91,8 @@
|
||||||
user = "tao";
|
user = "tao";
|
||||||
dataDir = "/home/tao/sync";
|
dataDir = "/home/tao/sync";
|
||||||
configDir = "/home/tao/.config/syncthing";
|
configDir = "/home/tao/.config/syncthing";
|
||||||
overrideDevices = false;
|
overrideDevices = true;
|
||||||
overrideFolders = false;
|
overrideFolders = true;
|
||||||
openDefaultPorts = true;
|
openDefaultPorts = true;
|
||||||
settings = {
|
settings = {
|
||||||
devices = {
|
devices = {
|
||||||
|
|
@ -129,17 +129,30 @@
|
||||||
path = "/home/tao/sync";
|
path = "/home/tao/sync";
|
||||||
devices = devs;
|
devices = devs;
|
||||||
};
|
};
|
||||||
"work" = {
|
# "work" = {
|
||||||
path = "/home/tao/work";
|
# path = "/home/tao/work";
|
||||||
devices = devs;
|
# devices = devs;
|
||||||
};
|
# };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# nixpkgs.overlays = [
|
||||||
|
# (final: prev: {
|
||||||
|
# options.services.snapper.configs = prev.options.services.snapper.configs.overrideAttrs (old: {
|
||||||
|
# configOptions.FSTYPE = lib.mkOption {
|
||||||
|
# type = lib.types.enum ["btrfs" "bcachefs"];
|
||||||
|
# };
|
||||||
|
# });
|
||||||
|
# })
|
||||||
|
# ];
|
||||||
|
|
||||||
|
disabledModules = ["services/misc/snapper.nix"];
|
||||||
|
imports = [../extras/snapper.nix];
|
||||||
services.snapper.configs = {
|
services.snapper.configs = {
|
||||||
home = {
|
home = {
|
||||||
SUBVOLUME = "/home";
|
SUBVOLUME = "/home";
|
||||||
|
FSTYPE = "bcachefs";
|
||||||
ALLOW_USERS = ["tao"];
|
ALLOW_USERS = ["tao"];
|
||||||
TIMELINE_CREATE = true;
|
TIMELINE_CREATE = true;
|
||||||
TIMELINE_CLEANUP = true;
|
TIMELINE_CLEANUP = true;
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@
|
||||||
ALLOW_USERS = ["vy"];
|
ALLOW_USERS = ["vy"];
|
||||||
TIMELINE_CREATE = true;
|
TIMELINE_CREATE = true;
|
||||||
TIMELINE_CLEANUP = true;
|
TIMELINE_CLEANUP = true;
|
||||||
TIMELINE_LIMIT_HOURLY = "5";
|
TIMELINE_LIMIT_HOURLY = 5;
|
||||||
TIMELINE_LIMIT_DAILY = "7";
|
TIMELINE_LIMIT_DAILY = 7;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
services.snapper.snapshotInterval = "*:0/5";
|
services.snapper.snapshotInterval = "*:0/5";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue