fc614c37c6
most modules can be evaluated for their documentation in a very restricted environment that doesn't include all of nixpkgs. this evaluation can then be cached and reused for subsequent builds, merging only documentation that has changed into the cached set. since nixos ships with a large number of modules of which only a few are used in any given config this can save evaluation a huge percentage of nixos options available in any given config. in tests of this caching, despite having to copy most of nixos/, saves about 80% of the time needed to build the system manual, or about two second on the machine used for testing. build time for a full system config shrank from 9.4s to 7.4s, while turning documentation off entirely shortened the build to 7.1s.
204 lines
5.9 KiB
Nix
204 lines
5.9 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";
|
|
};
|
|
|
|
commonOptions = {
|
|
hourly = mkOption {
|
|
description = "Number of hourly snapshots.";
|
|
type = with types; nullOr ints.unsigned;
|
|
default = null;
|
|
};
|
|
|
|
daily = mkOption {
|
|
description = "Number of daily snapshots.";
|
|
type = with types; nullOr ints.unsigned;
|
|
default = null;
|
|
};
|
|
|
|
monthly = mkOption {
|
|
description = "Number of monthly snapshots.";
|
|
type = with types; nullOr ints.unsigned;
|
|
default = null;
|
|
};
|
|
|
|
yearly = mkOption {
|
|
description = "Number of yearly snapshots.";
|
|
type = with types; nullOr ints.unsigned;
|
|
default = null;
|
|
};
|
|
|
|
autoprune = mkOption {
|
|
description = "Whether to automatically prune old snapshots.";
|
|
type = with types; nullOr bool;
|
|
default = null;
|
|
};
|
|
|
|
autosnap = mkOption {
|
|
description = "Whether to automatically take snapshots.";
|
|
type = with types; nullOr bool;
|
|
default = null;
|
|
};
|
|
};
|
|
|
|
datasetOptions = rec {
|
|
use_template = mkOption {
|
|
description = "Names of the templates to use for this dataset.";
|
|
type = types.listOf (types.str // {
|
|
check = (types.enum (attrNames cfg.templates)).check;
|
|
description = "configured template name";
|
|
});
|
|
default = [ ];
|
|
};
|
|
useTemplate = use_template;
|
|
|
|
recursive = mkOption {
|
|
description = ''
|
|
Whether to recursively snapshot dataset children.
|
|
You can also set this to <literal>"zfs"</literal> to handle datasets
|
|
recursively in an atomic way without the possibility to
|
|
override settings for child datasets.
|
|
'';
|
|
type = with types; oneOf [ bool (enum [ "zfs" ]) ];
|
|
default = false;
|
|
};
|
|
|
|
process_children_only = mkOption {
|
|
description = "Whether to only snapshot child datasets if recursing.";
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
processChildrenOnly = process_children_only;
|
|
};
|
|
|
|
# Extract unique dataset names
|
|
datasets = unique (attrNames cfg.datasets);
|
|
|
|
# Function to build "zfs allow" and "zfs unallow" commands for the
|
|
# filesystems we've delegated permissions to.
|
|
buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
|
|
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
|
|
"-+/run/booted-system/sw/bin/zfs"
|
|
zfsAction
|
|
"sanoid"
|
|
(concatStringsSep "," permissions)
|
|
dataset
|
|
];
|
|
|
|
configFile =
|
|
let
|
|
mkValueString = v:
|
|
if builtins.isList v then concatStringsSep "," v
|
|
else generators.mkValueStringDefault { } v;
|
|
|
|
mkKeyValue = k: v:
|
|
if v == null then ""
|
|
else if k == "processChildrenOnly" then ""
|
|
else if k == "useTemplate" then ""
|
|
else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
|
|
in
|
|
generators.toINI { inherit mkKeyValue; } cfg.settings;
|
|
|
|
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, ... }: {
|
|
freeformType = datasetSettingsType;
|
|
options = commonOptions // datasetOptions;
|
|
config.use_template = mkAliasDefinitions (mkDefault options.useTemplate or { });
|
|
config.process_children_only = mkAliasDefinitions (mkDefault options.processChildrenOnly or { });
|
|
}));
|
|
default = { };
|
|
description = "Datasets to snapshot.";
|
|
};
|
|
|
|
templates = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
freeformType = datasetSettingsType;
|
|
options = commonOptions;
|
|
});
|
|
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) cfg.templates)
|
|
(mapAttrs (d: v: v) cfg.datasets)
|
|
];
|
|
|
|
systemd.services.sanoid = {
|
|
description = "Sanoid snapshot service";
|
|
serviceConfig = {
|
|
ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets);
|
|
ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets);
|
|
ExecStart = lib.escapeShellArgs ([
|
|
"${pkgs.sanoid}/bin/sanoid"
|
|
"--cron"
|
|
"--configdir"
|
|
(pkgs.writeTextDir "sanoid.conf" configFile)
|
|
] ++ cfg.extraArgs);
|
|
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 ];
|
|
}
|