1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
# Non-module arguments
# These are separate from the module arguments to avoid implicit dependencies.
# This makes service modules self-contains, allowing mixing of Nixpkgs versions.
{ pkgs }:
# The module
{
lib,
config,
options,
...
}:
let
inherit (lib) mkEnableOption mkOption types;
# Paths are interpolated rather than `toString`ed on purpose: interpolation
# copies the path into the store, so the resulting argument still resolves on
# the machine that runs the service. `toString` would yield the path of the
# source tree the configuration was evaluated from, which is not there at
# runtime.
pathOrStr = types.coercedTo types.path (x: "${x}") types.str;
# `argv` and `flags` share a single `lib.mkOrder` space, so flags need a
# priority. This one sits between `lib.modules.defaultOrderPriority` (1000,
# what an unadorned `argv` definition gets) and `lib.mkAfter` (1500): plain
# flags follow plain `argv` entries, while `lib.mkAfter` on `argv` still lands
# after the flags. See the `flags` option description.
unadornedFlagPriority = 1250;
# `attrListWith` re-emits every flag wrapped in `lib.mkOrder`, using
# `lib.modules.defaultOrderPriority` for flags that carried no ordering
# property of their own. Rewrite exactly that priority; anything else is an
# explicit `lib.mkOrder` from the user and is passed through verbatim.
atFlagPriority =
def:
if def.value._type or null == "order" && def.value.priority == lib.modules.defaultOrderPriority then
def // { value = lib.mkOrder unadornedFlagPriority def.value.content; }
else
def;
in
{
# https://nixos.org/manual/nixos/unstable/#modular-services
_class = "service";
imports = [
../modules/generic/meta-maintainers.nix
../modules/generic/assertions.nix
(lib.modules.importApply ./config-data.nix { inherit pkgs; })
];
options = {
services = mkOption {
type = types.attrsOf (
types.submoduleWith {
modules = [
(lib.modules.importApply ./service.nix { inherit pkgs; })
];
}
);
description = ''
A collection of [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) that are configured in one go.
You could consider the sub-service relationship to be an ownership relation.
It **does not** automatically create any other relationship between services (e.g. systemd slices), unless perhaps such a behavior is explicitly defined and enabled in another option.
'';
default = { };
visible = "shallow";
};
process = {
argv = mkOption {
type = types.listOf pathOrStr;
example = lib.literalExpression ''[ (lib.getExe config.package) "--nobackground" ]'';
description = ''
Command filename and arguments for starting this service.
This is a raw command-line that should not contain any shell escaping.
If expansion of environmental variables is required then use
a shell script or `importas` from `pkgs.execline`.
When `flags` are set, the arguments rendered from them are merged into
`argv`. See `flags` for how the two are ordered against each other.
'';
};
flagFormat = mkOption {
type = types.functionTo (types.attrsOf types.anything);
default = name: {
option = name;
sep = null;
explicitBool = false;
};
description = ''
Function mapping flag names to option format specs
for `lib.cli.toCommandLine`.
Receives the flag name and returns `{ option, sep, explicitBool, formatArg? }`.
'';
example = lib.literalExpression ''
name: {
option = name;
sep = "=";
explicitBool = false;
}
'';
};
flags = mkOption {
type = types.attrListWith {
elemType = types.nullOr (
types.oneOf [
types.bool
types.int
# `pathOrStr`, not `types.path`: `lib.cli.toCommandLine` renders
# values with `lib.generators.mkValueStringDefault`, which has no
# case for paths and would abort.
pathOrStr
]
);
asAttrs = true;
};
default = { };
description = ''
Flags to pass to the service process.
The key is the flag name (e.g. `"--port"`), the value is the flag value.
Each `name = value` pair is rendered via `lib.cli.toCommandLine`
using `flagFormat`.
- `null`: the flag is omitted (regardless of `flagFormat`)
- bool: rendered per `flagFormat.explicitBool`
- `explicitBool = false` (default): `true` emits the bare flag,
`false` is omitted
- `explicitBool = true`: both `true` and `false` are rendered as
explicit arguments via `flagFormat.formatArg`
- string / path / int: rendered as the option's argument, joined to the
option name per `flagFormat.sep` and stringified by
`flagFormat.formatArg`
To pass the same flag multiple times, use the list form with
repeated keys, e.g.
`[ { "--host" = "a"; } { "--host" = "b"; } ]`.
The rendered arguments are merged into `argv`, so `argv` and `flags`
share a single `lib.mkOrder` space:
- A flag with no ordering property of its own is placed at priority
1250, between `lib.modules.defaultOrderPriority` (1000, which is
what an unadorned `argv` definition gets) and `lib.mkAfter` (1500).
Plain flags therefore follow the command name and any other plain
`argv` arguments.
- `lib.mkAfter` on `argv` still lands after the flags, which is how
trailing positional arguments are expressed.
- `lib.mkOrder` on a flag is honoured verbatim against `argv`, so a
sub-command can be placed between two groups of flags.
Because 1250 is substituted for flags that carry no ordering property,
`lib.mkOrder 1000` on a flag is indistinguishable from leaving that
flag unadorned. To order a flag around plain `argv` entries, pick a
priority next to 1000, such as 999 or 1001.
'';
example = lib.literalExpression ''
{
"--port" = "8080";
"--verbose" = true;
# ordered ahead of the unadorned flags above
"--config" = lib.mkOrder 1100 "/etc/foo.conf";
}
# or, for repeated flags:
[
{ "--host" = "localhost"; }
{ "--host" = "0.0.0.0"; }
]
'';
};
reloadSignal = mkOption {
type = types.nullOr types.str;
default = null;
example = "HUP";
description = ''
Configures the reload signal to send to the service manager.
'';
};
reloadCommand = mkOption {
type = types.nullOr types.str;
default = null;
example = lib.literalExpression ''"''${pkgs.coreutils}/bin/kill -HUP $MAINPID"'';
description = ''
Command used for reloading in the underlying service manager to reload.
'';
};
};
notificationProtocol = mkOption {
type = types.submodule {
options = {
systemd = mkEnableOption "Whether the service supports systemd-notify.";
s6 = mkEnableOption "Whether the service supports s6-notify.";
};
};
description = ''
Notification protocol that this service supports with the underlying service manager.
'';
};
};
config = {
assertions = [
{
# `reloadSignal` derives `reloadCommand` at `mkDefault` priority below, so a
# conflict only exists when the user *also* set `reloadCommand` explicitly.
# An explicit (non-`mkDefault`) definition has `defaultOverridePriority`.
assertion =
!(
config.process.reloadSignal != null
&& options.process.reloadCommand.highestPrio <= lib.modules.defaultOverridePriority
);
message = "reloadSignal conflicts with reloadCommand. Please either use reloadSignal or reloadCommand.";
}
];
process.reloadCommand = lib.mkIf (config.process.reloadSignal != null) (
lib.mkDefault "${pkgs.coreutils}/bin/kill -${config.process.reloadSignal} $MAINPID"
);
process.argv = lib.modules.mapDefinitionValue (
attr: lib.cli.toCommandLine config.process.flagFormat attr
) (lib.mkMerge (map atFlagPriority options.process.flags.valueMeta.definitions));
};
}
|