ae9994806c
Fixes several correctness bugs primarily around modular services' recent reload/notification options (#535695) and adds compliance coverage to guard them. Fixes: - `lib/services/service.nix`: - the reload-conflict assertion had inverted polarity, so it fired on the default configuration - the `mkIf` guard on `process.reloadCommand` had a misplaced paren, applying `!= null` to the `mkIf` result rather than to the condition - `process.reloadSignal` derives `process.reloadCommand`, so the assertion could not check `reloadCommand != null` -- that fired on every signal-only service. The command is now derived at `mkDefault` priority and the assertion is gated on `options.process.reloadCommand.highestPrio`, firing only when the user also set `reloadCommand` explicitly. - change `notificationProtocol` to a sub-module type - `nixos/modules/system/service/systemd/service.nix`: - `systemd.mainExecReload`'s default ran `escapeSystemdExecArgs` (a list escaper) on the `nullOr str` `process.reloadCommand`; this threw `expected a list but found a string` and would have mangled `$MAINPID`. It now uses `process.reloadCommand` verbatim. - the `Type` default read a non-existent `config.serviceManager.notificationProtocol` instead of `config.notificationProtocol`. Tests: Extend the modular-service compliance suite to guard the above: - Portable (manager-agnostic) eval assertions: `reloadSignal` derives `reloadCommand`, the conflict assertion does not fire on signal-only services but does when both are set explicitly, and `notificationProtocol.systemd`/`.s6` default to `false`. - systemd-specific eval assertions: `serviceConfig.Type` (simple/notify) and `serviceConfig.ExecReload` are asserted on the resolved host units. This directly guards the `mainExecReload` fix, which threw before it. - Runtime reload compliance test: a nested reloadable sub-service is started and reloaded, asserting the service observed the reload (recorded a SIGHUP marker). `callReload` receives the service's name path (the list of names from the top-level service down to the target sub-service); each integration joins it per its own unit-naming convention (NixOS dash-joins to the systemd unit name, e.g. `reload-inner.service`). Keeping it a path list rather than a read-only submodule option keeps the suite manager-agnostic. - `doc/build-helpers/testers.chapter.md`: document `callReload`. Follow-up to #535695. Signed-off-by: cinereal <cinereal@riseup.net> Assisted-by: Claude:claude-opus-4-8
108 lines
3.5 KiB
Nix
108 lines
3.5 KiB
Nix
# 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;
|
|
pathOrStr = types.coercedTo types.path (x: "${x}") types.str;
|
|
in
|
|
{
|
|
# https://nixos.org/manual/nixos/unstable/#modular-services
|
|
_class = "service";
|
|
imports = [
|
|
../../modules/generic/meta-maintainers.nix
|
|
../../nixos/modules/misc/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`.
|
|
'';
|
|
};
|
|
|
|
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"
|
|
);
|
|
};
|
|
}
|