mirror of
https://github.com/LnL7/nix-darwin.git
synced 2024-12-14 11:57:34 +00:00
fix(launchd): improve StartCalendarInterval
Stricter launchd -> StartCalendarInterval type: - Verify that the integers passed to `Minute`, `Hour`, etc. are within range. - When provided, the value for StartCalendarInterval must be a non-empty list of calendar intervals and must not contain duplicates entries (throw an error otherwise). - For increased flexibility and backwards-compatibility, allow an attrset to be passed as well (which will be type-checked and is functionally equivalent to passing a singleton list). Allowing an attrset or list is precisely in-line with what `launchd.plist(5)` accepts for StartCalendarInterval. Migrate `nix.gc.interval` and `nix.optimise.interval` over to use this new type, and update their defaults to run weekly instead of daily. Create `modules/launchd/types.nix` file for easier/modular use of launchd types needed in multiple files. Documentation: - Update and improve wording/documentation of launchd's `StartCalendarInterval`. - Improve wording/documentation of `nix.gc.interval` and `nix.optimise.interval` ("time interval" can be misleading as it's actually a "calendar interval"; e.g. `{ Hour = 3; Minute = 15;}` runs daily, not every 3.25 hours).
This commit is contained in:
parent
c0d5b8c54d
commit
861af0fc94
4 changed files with 146 additions and 57 deletions
|
@ -2,6 +2,10 @@
|
|||
|
||||
with lib;
|
||||
|
||||
let
|
||||
launchdTypes = import ./types.nix { inherit config lib; };
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
Label = mkOption {
|
||||
|
@ -344,55 +348,21 @@ with lib;
|
|||
default = null;
|
||||
example = [{ Hour = 2; Minute = 30; }];
|
||||
description = ''
|
||||
This optional key causes the job to be started every calendar interval as specified. Missing arguments
|
||||
are considered to be wildcard. The semantics are much like `crontab(5)`. Unlike cron which skips job
|
||||
invocations when the computer is asleep, launchd will start the job the next time the computer wakes
|
||||
This optional key causes the job to be started every calendar interval as specified. The semantics are
|
||||
much like {manpage}`crontab(5)`: Missing attributes are considered to be wildcard. Unlike cron which skips
|
||||
job invocations when the computer is asleep, launchd will start the job the next time the computer wakes
|
||||
up. If multiple intervals transpire before the computer is woken, those events will be coalesced into
|
||||
one event upon wake from sleep.
|
||||
one event upon waking from sleep.
|
||||
|
||||
::: {.important}
|
||||
The list must not be empty and must not contain duplicate entries (attrsets which compare equally).
|
||||
:::
|
||||
|
||||
::: {.caution}
|
||||
Since missing attrs become wildcards, an empty attrset effectively means "every minute".
|
||||
:::
|
||||
'';
|
||||
type = types.nullOr (types.listOf (types.submodule {
|
||||
options = {
|
||||
Minute = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
The minute on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Hour = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
The hour on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Day = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
The day on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Weekday = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
The weekday on which this job will be run (0 and 7 are Sunday).
|
||||
'';
|
||||
};
|
||||
|
||||
Month = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
The month on which this job will be run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
type = types.nullOr launchdTypes.StartCalendarInterval;
|
||||
};
|
||||
|
||||
StandardInPath = mkOption {
|
||||
|
@ -886,6 +856,5 @@ with lib;
|
|||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
};
|
||||
config = {};
|
||||
}
|
||||
|
|
110
modules/launchd/types.nix
Normal file
110
modules/launchd/types.nix
Normal file
|
@ -0,0 +1,110 @@
|
|||
{ lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) imap1 types mkOption showOption optionDescriptionPhrase mergeDefinitions;
|
||||
inherit (builtins) map filter length deepSeq throw toString concatLists;
|
||||
inherit (lib.options) showDefs;
|
||||
wildcardText = lib.literalMD "`*`";
|
||||
|
||||
/**
|
||||
A type of list which does not allow duplicate elements. The base/inner
|
||||
list type to use (e.g. `types.listOf` or `types.nonEmptyListOf`) is passed
|
||||
via argument `listType`, which must be the final type and not a function.
|
||||
|
||||
NOTE: The extra check for duplicates is quadratic and strict, so use this
|
||||
type sparingly and only:
|
||||
|
||||
* when needed, and
|
||||
* when the list is expected to be recursively short (e.g. < 10 elements)
|
||||
and shallow (i.e. strict evaluation of the list won't take too long)
|
||||
|
||||
The implementation of this function is similar to that of
|
||||
`types.nonEmptyListOf`.
|
||||
*/
|
||||
types'.uniqueList = listType: listType // {
|
||||
description = "unique ${types.optionDescriptionPhrase (class: class == "noun") listType}";
|
||||
substSubModules = m: types'.uniqueList (listType.substSubModules m);
|
||||
# This has been taken from the implementation of `types.listOf`, but has
|
||||
# been modified to throw on duplicates. This check cannot be done in the
|
||||
# `check` fn as this check is deep/strict, and because `check` runs
|
||||
# prior to merging.
|
||||
merge = loc: defs:
|
||||
let
|
||||
# Each element of `dupes` is a list. When there are duplicates,
|
||||
# later lists will be duplicates of earlier lists, so just throw on
|
||||
# the first set of duplicates found so that we don't have duplicate
|
||||
# error msgs.
|
||||
checked = filter (li:
|
||||
if length li > 1
|
||||
then throw "The option `${showOption loc}' contains duplicate entries after merging:\n${showDefs li}"
|
||||
else false) dupes;
|
||||
dupes = map (def: filter (def': def'.value == def.value) merged) merged;
|
||||
merged = filter (x: x ? value) (concatLists (imap1 (n: def:
|
||||
imap1 (m: el:
|
||||
let
|
||||
inherit (def) file;
|
||||
loc' = loc ++ ["[definition ${toString n}-entry ${toString m}]"];
|
||||
in
|
||||
(mergeDefinitions
|
||||
loc'
|
||||
listType.nestedTypes.elemType
|
||||
[{ inherit file; value = el; }]
|
||||
).optionalValue // {inherit loc' file;}
|
||||
) def.value
|
||||
) defs));
|
||||
in
|
||||
deepSeq checked (map (x: x.value) merged);
|
||||
};
|
||||
in {
|
||||
StartCalendarInterval = let
|
||||
CalendarIntervalEntry = types.submodule {
|
||||
options = {
|
||||
Minute = mkOption {
|
||||
type = types.nullOr (types.ints.between 0 59);
|
||||
default = null;
|
||||
defaultText = wildcardText;
|
||||
description = ''
|
||||
The minute on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Hour = mkOption {
|
||||
type = types.nullOr (types.ints.between 0 23);
|
||||
default = null;
|
||||
defaultText = wildcardText;
|
||||
description = ''
|
||||
The hour on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Day = mkOption {
|
||||
type = types.nullOr (types.ints.between 1 31);
|
||||
default = null;
|
||||
defaultText = wildcardText;
|
||||
description = ''
|
||||
The day on which this job will be run.
|
||||
'';
|
||||
};
|
||||
|
||||
Weekday = mkOption {
|
||||
type = types.nullOr (types.ints.between 0 7);
|
||||
default = null;
|
||||
defaultText = wildcardText;
|
||||
description = ''
|
||||
The weekday on which this job will be run (0 and 7 are Sunday).
|
||||
'';
|
||||
};
|
||||
|
||||
Month = mkOption {
|
||||
type = types.nullOr (types.ints.between 1 12);
|
||||
default = null;
|
||||
defaultText = wildcardText;
|
||||
description = ''
|
||||
The month on which this job will be run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
types.either CalendarIntervalEntry (types'.uniqueList (types.nonEmptyListOf CalendarIntervalEntry));
|
||||
}
|
|
@ -6,6 +6,7 @@ with lib;
|
|||
|
||||
let
|
||||
cfg = config.nix.gc;
|
||||
launchdTypes = import ../../launchd/types.nix { inherit config lib; };
|
||||
in
|
||||
|
||||
{
|
||||
|
@ -35,9 +36,13 @@ in
|
|||
};
|
||||
|
||||
interval = mkOption {
|
||||
type = types.attrs;
|
||||
default = { Hour = 3; Minute = 15; };
|
||||
description = "The time interval at which the garbage collector will run.";
|
||||
type = launchdTypes.StartCalendarInterval;
|
||||
default = [{ Weekday = 7; Hour = 3; Minute = 15; }];
|
||||
description = ''
|
||||
The calendar interval at which the garbage collector will run.
|
||||
See the {option}`serviceConfig.StartCalendarInterval` option of
|
||||
the {option}`launchd` module for more info.
|
||||
'';
|
||||
};
|
||||
|
||||
options = mkOption {
|
||||
|
@ -63,7 +68,7 @@ in
|
|||
command = "${config.nix.package}/bin/nix-collect-garbage ${cfg.options}";
|
||||
environment.NIX_REMOTE = optionalString config.nix.useDaemon "daemon";
|
||||
serviceConfig.RunAtLoad = false;
|
||||
serviceConfig.StartCalendarInterval = [ cfg.interval ];
|
||||
serviceConfig.StartCalendarInterval = cfg.interval;
|
||||
serviceConfig.UserName = cfg.user;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ let
|
|||
;
|
||||
|
||||
cfg = config.nix.optimise;
|
||||
launchdTypes = import ../../launchd/types.nix { inherit config lib; };
|
||||
in
|
||||
|
||||
{
|
||||
|
@ -41,9 +42,13 @@ in
|
|||
};
|
||||
|
||||
interval = mkOption {
|
||||
type = types.attrs;
|
||||
default = { Hour = 3; Minute = 15; };
|
||||
description = "The time interval at which the optimiser will run.";
|
||||
type = launchdTypes.StartCalendarInterval;
|
||||
default = [{ Weekday = 7; Hour = 4; Minute = 15; }];
|
||||
description = ''
|
||||
The calendar interval at which the optimiser will run.
|
||||
See the {option}`serviceConfig.StartCalendarInterval` option of
|
||||
the {option}`launchd` module for more info.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -63,7 +68,7 @@ in
|
|||
"/bin/wait4path ${config.nix.package} && exec ${config.nix.package}/bin/nix-store --optimise"
|
||||
];
|
||||
RunAtLoad = false;
|
||||
StartCalendarInterval = [ cfg.interval ];
|
||||
StartCalendarInterval = cfg.interval;
|
||||
UserName = cfg.user;
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue