214 lines
6.1 KiB
Nix
214 lines
6.1 KiB
Nix
|
{ config, lib, pkgs, ... }:
|
||
|
|
||
|
with lib;
|
||
|
|
||
|
let
|
||
|
cfg = config.services.sanoid;
|
||
|
|
||
|
datasetSettingsType = with types;
|
||
|
(attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // {
|
||
|
description = "dataset/template options";
|
||
|
};
|
||
|
|
||
|
# Default values from https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf
|
||
|
|
||
|
commonOptions = {
|
||
|
hourly = mkOption {
|
||
|
description = "Number of hourly snapshots.";
|
||
|
type = types.ints.unsigned;
|
||
|
default = 48;
|
||
|
};
|
||
|
|
||
|
daily = mkOption {
|
||
|
description = "Number of daily snapshots.";
|
||
|
type = types.ints.unsigned;
|
||
|
default = 90;
|
||
|
};
|
||
|
|
||
|
monthly = mkOption {
|
||
|
description = "Number of monthly snapshots.";
|
||
|
type = types.ints.unsigned;
|
||
|
default = 6;
|
||
|
};
|
||
|
|
||
|
yearly = mkOption {
|
||
|
description = "Number of yearly snapshots.";
|
||
|
type = types.ints.unsigned;
|
||
|
default = 0;
|
||
|
};
|
||
|
|
||
|
autoprune = mkOption {
|
||
|
description = "Whether to automatically prune old snapshots.";
|
||
|
type = types.bool;
|
||
|
default = true;
|
||
|
};
|
||
|
|
||
|
autosnap = mkOption {
|
||
|
description = "Whether to automatically take snapshots.";
|
||
|
type = types.bool;
|
||
|
default = true;
|
||
|
};
|
||
|
|
||
|
settings = mkOption {
|
||
|
description = ''
|
||
|
Free-form settings for this template/dataset. See
|
||
|
<link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
|
||
|
for allowed values.
|
||
|
'';
|
||
|
type = datasetSettingsType;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
commonConfig = config: {
|
||
|
settings = {
|
||
|
hourly = mkDefault config.hourly;
|
||
|
daily = mkDefault config.daily;
|
||
|
monthly = mkDefault config.monthly;
|
||
|
yearly = mkDefault config.yearly;
|
||
|
autoprune = mkDefault config.autoprune;
|
||
|
autosnap = mkDefault config.autosnap;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
datasetOptions = {
|
||
|
useTemplate = mkOption {
|
||
|
description = "Names of the templates to use for this dataset.";
|
||
|
type = (types.listOf (types.enum (attrNames cfg.templates))) // {
|
||
|
description = "list of template names";
|
||
|
};
|
||
|
default = [];
|
||
|
};
|
||
|
|
||
|
recursive = mkOption {
|
||
|
description = "Whether to recursively snapshot dataset children.";
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
};
|
||
|
|
||
|
processChildrenOnly = mkOption {
|
||
|
description = "Whether to only snapshot child datasets if recursing.";
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
datasetConfig = config: {
|
||
|
settings = {
|
||
|
use_template = mkDefault config.useTemplate;
|
||
|
recursive = mkDefault config.recursive;
|
||
|
process_children_only = mkDefault config.processChildrenOnly;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
# Extract pool names from configured datasets
|
||
|
pools = unique (map (d: head (builtins.match "([^/]+).*" d)) (attrNames cfg.datasets));
|
||
|
|
||
|
configFile = let
|
||
|
mkValueString = v:
|
||
|
if builtins.isList v then concatStringsSep "," v
|
||
|
else generators.mkValueStringDefault {} v;
|
||
|
|
||
|
mkKeyValue = k: v: if v == null then ""
|
||
|
else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
|
||
|
in generators.toINI { inherit mkKeyValue; } cfg.settings;
|
||
|
|
||
|
configDir = pkgs.writeTextDir "sanoid.conf" configFile;
|
||
|
|
||
|
in {
|
||
|
|
||
|
# Interface
|
||
|
|
||
|
options.services.sanoid = {
|
||
|
enable = mkEnableOption "Sanoid ZFS snapshotting service";
|
||
|
|
||
|
interval = mkOption {
|
||
|
type = types.str;
|
||
|
default = "hourly";
|
||
|
example = "daily";
|
||
|
description = ''
|
||
|
Run sanoid at this interval. The default is to run hourly.
|
||
|
|
||
|
The format is described in
|
||
|
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||
|
<manvolnum>7</manvolnum></citerefentry>.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
datasets = mkOption {
|
||
|
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||
|
options = commonOptions // datasetOptions;
|
||
|
config = mkMerge [ (commonConfig config) (datasetConfig config) ];
|
||
|
}));
|
||
|
default = {};
|
||
|
description = "Datasets to snapshot.";
|
||
|
};
|
||
|
|
||
|
templates = mkOption {
|
||
|
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||
|
options = commonOptions;
|
||
|
config = commonConfig config;
|
||
|
}));
|
||
|
default = {};
|
||
|
description = "Templates for datasets.";
|
||
|
};
|
||
|
|
||
|
settings = mkOption {
|
||
|
type = types.attrsOf datasetSettingsType;
|
||
|
description = ''
|
||
|
Free-form settings written directly to the config file. See
|
||
|
<link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
|
||
|
for allowed values.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
extraArgs = mkOption {
|
||
|
type = types.listOf types.str;
|
||
|
default = [];
|
||
|
example = [ "--verbose" "--readonly" "--debug" ];
|
||
|
description = ''
|
||
|
Extra arguments to pass to sanoid. See
|
||
|
<link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/>
|
||
|
for allowed options.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
# Implementation
|
||
|
|
||
|
config = mkIf cfg.enable {
|
||
|
services.sanoid.settings = mkMerge [
|
||
|
(mapAttrs' (d: v: nameValuePair ("template_" + d) v.settings) cfg.templates)
|
||
|
(mapAttrs (d: v: v.settings) cfg.datasets)
|
||
|
];
|
||
|
|
||
|
systemd.services.sanoid = {
|
||
|
description = "Sanoid snapshot service";
|
||
|
serviceConfig = {
|
||
|
ExecStartPre = map (pool: lib.escapeShellArgs [
|
||
|
"+/run/booted-system/sw/bin/zfs" "allow"
|
||
|
"sanoid" "snapshot,mount,destroy" pool
|
||
|
]) pools;
|
||
|
ExecStart = lib.escapeShellArgs ([
|
||
|
"${pkgs.sanoid}/bin/sanoid"
|
||
|
"--cron"
|
||
|
"--configdir" configDir
|
||
|
] ++ cfg.extraArgs);
|
||
|
ExecStopPost = map (pool: lib.escapeShellArgs [
|
||
|
"+/run/booted-system/sw/bin/zfs" "unallow" "sanoid" pool
|
||
|
]) pools;
|
||
|
User = "sanoid";
|
||
|
Group = "sanoid";
|
||
|
DynamicUser = true;
|
||
|
RuntimeDirectory = "sanoid";
|
||
|
CacheDirectory = "sanoid";
|
||
|
};
|
||
|
# Prevents missing snapshots during DST changes
|
||
|
environment.TZ = "UTC";
|
||
|
after = [ "zfs.target" ];
|
||
|
startAt = cfg.interval;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
meta.maintainers = with maintainers; [ lopsided98 ];
|
||
|
}
|