Add modular services, system.services
This commit is contained in:
@@ -1838,6 +1838,8 @@
|
||||
./system/boot/uvesafb.nix
|
||||
./system/boot/zram-as-tmp.nix
|
||||
./system/etc/etc-activation.nix
|
||||
./system/service/systemd/system.nix
|
||||
./system/service/systemd/user.nix
|
||||
./tasks/auto-upgrade.nix
|
||||
./tasks/bcache.nix
|
||||
./tasks/cpu-freq.nix
|
||||
|
||||
28
nixos/modules/system/service/README.md
Normal file
28
nixos/modules/system/service/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
# Modular Services
|
||||
|
||||
This directory defines a modular service infrastructure for NixOS.
|
||||
See the [Modular Services chapter] in the manual [[source]](../../doc/manual/development/modular-services.md).
|
||||
|
||||
[Modular Services chapter]: https://nixos.org/manual/nixos/unstable/#modular-services
|
||||
|
||||
# Design decision log
|
||||
|
||||
- `system.services.<name>`. Alternatives considered
|
||||
- `systemServices`: similar to does not allow importing a composition of services into `system`. Not sure if that's a good idea in the first place, but I've kept the possibility open.
|
||||
- `services.abstract`: used in https://github.com/NixOS/nixpkgs/pull/267111, but too weird. Service modules should fit naturally into the configuration system.
|
||||
Also "abstract" is wrong, because it has submodules - in other words, evalModules results, concrete services - not abstract at all.
|
||||
- `services.modular`: only slightly better than `services.abstract`, but still weird
|
||||
|
||||
- No `daemon.*` options. https://github.com/NixOS/nixpkgs/pull/267111/files#r1723206521
|
||||
|
||||
- For now, do not add an `enable` option, because it's ambiguous. Does it disable at the Nix level (not generate anything) or at the systemd level (generate a service that is disabled)?
|
||||
|
||||
- Move all process options into a `process` option tree. Putting this at the root is messy, because we also have sub-services at that level. Those are rather distinct. Grouping them "by kind" should raise fewer questions.
|
||||
|
||||
- `modules/system/service/systemd/system.nix` has `system` twice. Not great, but
|
||||
- they have different meanings
|
||||
1. These are system-provided modules, provided by the configuration manager
|
||||
2. `systemd/system` configures SystemD _system units_.
|
||||
- This reserves `modules/service` for actual service modules, at least until those are lifted out of NixOS, potentially
|
||||
|
||||
58
nixos/modules/system/service/portable/service.nix
Normal file
58
nixos/modules/system/service/portable/service.nix
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
pathOrStr = types.coercedTo types.path (x: "${x}") types.str;
|
||||
program =
|
||||
types.coercedTo (
|
||||
types.package
|
||||
// {
|
||||
# require mainProgram for this conversion
|
||||
check = v: v.type or null == "derivation" && v ? meta.mainProgram;
|
||||
}
|
||||
) lib.getExe pathOrStr
|
||||
// {
|
||||
description = "main program, path or command";
|
||||
descriptionClass = "conjunction";
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submoduleWith {
|
||||
modules = [
|
||||
./service.nix
|
||||
];
|
||||
}
|
||||
);
|
||||
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 = {
|
||||
executable = mkOption {
|
||||
type = program;
|
||||
description = ''
|
||||
The path to the executable that will be run when the service is started.
|
||||
'';
|
||||
};
|
||||
args = lib.mkOption {
|
||||
type = types.listOf pathOrStr;
|
||||
description = ''
|
||||
Arguments to pass to the `executable`.
|
||||
'';
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
93
nixos/modules/system/service/portable/test.nix
Normal file
93
nixos/modules/system/service/portable/test.nix
Normal file
@@ -0,0 +1,93 @@
|
||||
# Run:
|
||||
# nix-instantiate --eval nixos/modules/system/service/portable/test.nix
|
||||
let
|
||||
lib = import ../../../../../lib;
|
||||
|
||||
inherit (lib) mkOption types;
|
||||
|
||||
dummyPkg =
|
||||
name:
|
||||
derivation {
|
||||
system = "dummy";
|
||||
name = name;
|
||||
builder = "/bin/false";
|
||||
};
|
||||
|
||||
exampleConfig = {
|
||||
_file = "${__curPos.file}:${toString __curPos.line}";
|
||||
services = {
|
||||
service1 = {
|
||||
process = {
|
||||
executable = "/usr/bin/echo"; # *giggles*
|
||||
args = [ "hello" ];
|
||||
};
|
||||
};
|
||||
service2 = {
|
||||
process = {
|
||||
# No meta.mainProgram, because it's supposedly an executable script _file_,
|
||||
# not a directory with a bin directory containing the main program.
|
||||
executable = dummyPkg "cowsay.sh";
|
||||
args = [ "world" ];
|
||||
};
|
||||
};
|
||||
service3 = {
|
||||
process = {
|
||||
executable = dummyPkg "cowsay-ng" // {
|
||||
meta.mainProgram = "cowsay";
|
||||
};
|
||||
args = [ "!" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
exampleEval = lib.evalModules {
|
||||
modules = [
|
||||
{
|
||||
options.services = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submoduleWith {
|
||||
class = "service";
|
||||
modules = [
|
||||
./service.nix
|
||||
];
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
exampleConfig
|
||||
];
|
||||
};
|
||||
|
||||
test =
|
||||
assert
|
||||
exampleEval.config == {
|
||||
services = {
|
||||
service1 = {
|
||||
process = {
|
||||
executable = "/usr/bin/echo";
|
||||
args = [ "hello" ];
|
||||
};
|
||||
services = { };
|
||||
};
|
||||
service2 = {
|
||||
process = {
|
||||
executable = "${dummyPkg "cowsay.sh"}";
|
||||
args = [ "world" ];
|
||||
};
|
||||
services = { };
|
||||
};
|
||||
service3 = {
|
||||
process = {
|
||||
executable = "${dummyPkg "cowsay-ng"}/bin/cowsay";
|
||||
args = [ "!" ];
|
||||
};
|
||||
services = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
"ok";
|
||||
|
||||
in
|
||||
test
|
||||
79
nixos/modules/system/service/systemd/service.nix
Normal file
79
nixos/modules/system/service/systemd/service.nix
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
systemdPackage,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../portable/service.nix
|
||||
(lib.mkAliasOptionModule [ "systemd" "service" ] [ "systemd" "services" "" ])
|
||||
(lib.mkAliasOptionModule [ "systemd" "socket" ] [ "systemd" "sockets" "" ])
|
||||
];
|
||||
options = {
|
||||
systemd.services = mkOption {
|
||||
description = ''
|
||||
This module configures systemd services, with the notable difference that their unit names will be prefixed with the abstract service name.
|
||||
|
||||
This option's value is not suitable for reading, but you can define a module here that interacts with just the unit configuration in the host system configuration.
|
||||
|
||||
Note that this option contains _deferred_ modules.
|
||||
This means that the module has not been combined with the system configuration yet, no values can be read from this option.
|
||||
What you can do instead is define a module that reads from the module arguments (such as `config`) that are available when the module is merged into the system configuration.
|
||||
'';
|
||||
type = types.lazyAttrsOf (
|
||||
types.deferredModuleWith {
|
||||
staticModules = [
|
||||
# TODO: Add modules for the purpose of generating documentation?
|
||||
];
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
systemd.sockets = mkOption {
|
||||
description = ''
|
||||
Declares systemd socket units. Names will be prefixed by the service name / path.
|
||||
|
||||
See {option}`systemd.services`.
|
||||
'';
|
||||
type = types.lazyAttrsOf types.deferredModule;
|
||||
default = { };
|
||||
};
|
||||
|
||||
# Also import systemd logic into sub-services
|
||||
# extends the portable `services` option
|
||||
services = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submoduleWith {
|
||||
class = "service";
|
||||
modules = [
|
||||
./service.nix
|
||||
];
|
||||
specialArgs = {
|
||||
inherit systemdPackage;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
config = {
|
||||
# Note that this is the systemd.services option above, not the system one.
|
||||
systemd.services."" = {
|
||||
# TODO description;
|
||||
wantedBy = lib.mkDefault [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = lib.mkDefault "simple";
|
||||
Restart = lib.mkDefault "always";
|
||||
RestartSec = lib.mkDefault "5";
|
||||
ExecStart = [
|
||||
(systemdPackage.functions.escapeSystemdExecArgs (
|
||||
[ config.process.executable ] ++ config.process.args
|
||||
))
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
68
nixos/modules/system/service/systemd/system.nix
Normal file
68
nixos/modules/system/service/systemd/system.nix
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) concatMapAttrs mkOption types;
|
||||
|
||||
dash =
|
||||
before: after:
|
||||
if after == "" then
|
||||
before
|
||||
else if before == "" then
|
||||
after
|
||||
else
|
||||
"${before}-${after}";
|
||||
|
||||
makeUnits =
|
||||
unitType: prefix: service:
|
||||
concatMapAttrs (unitName: unitModule: {
|
||||
"${dash prefix unitName}" =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ unitModule ];
|
||||
};
|
||||
}) service.systemd.${unitType}
|
||||
// concatMapAttrs (
|
||||
subServiceName: subService: makeUnits unitType (dash prefix subServiceName) subService
|
||||
) service.services;
|
||||
in
|
||||
{
|
||||
# First half of the magic: mix systemd logic into the otherwise abstract services
|
||||
options = {
|
||||
system.services = mkOption {
|
||||
description = ''
|
||||
A collection of NixOS [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) that are configured as systemd services.
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submoduleWith {
|
||||
class = "service";
|
||||
modules = [
|
||||
./service.nix
|
||||
];
|
||||
specialArgs = {
|
||||
# perhaps: features."systemd" = { };
|
||||
inherit pkgs;
|
||||
systemdPackage = config.systemd.package;
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
visible = "shallow";
|
||||
};
|
||||
};
|
||||
|
||||
# Second half of the magic: siphon units that were defined in isolation to the system
|
||||
config = {
|
||||
systemd.services = concatMapAttrs (
|
||||
serviceName: topLevelService: makeUnits "services" serviceName topLevelService
|
||||
) config.system.services;
|
||||
|
||||
systemd.sockets = concatMapAttrs (
|
||||
serviceName: topLevelService: makeUnits "sockets" serviceName topLevelService
|
||||
) config.system.services;
|
||||
};
|
||||
}
|
||||
89
nixos/modules/system/service/systemd/test.nix
Normal file
89
nixos/modules/system/service/systemd/test.nix
Normal file
@@ -0,0 +1,89 @@
|
||||
# Run:
|
||||
# nix-build -A nixosTests.modularService
|
||||
|
||||
{
|
||||
evalSystem,
|
||||
runCommand,
|
||||
hello,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
machine = evalSystem (
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
# Test input
|
||||
|
||||
system.services.foo = {
|
||||
process = {
|
||||
executable = hello;
|
||||
args = [
|
||||
"--greeting"
|
||||
"hoi"
|
||||
];
|
||||
};
|
||||
};
|
||||
system.services.bar = {
|
||||
process = {
|
||||
executable = hello;
|
||||
args = [
|
||||
"--greeting"
|
||||
"hoi"
|
||||
];
|
||||
};
|
||||
systemd.service = {
|
||||
serviceConfig.X-Bar = "lol crossbar whatever";
|
||||
};
|
||||
services.db = {
|
||||
process = {
|
||||
executable = hello;
|
||||
args = [
|
||||
"--greeting"
|
||||
"Hi, I'm a database, would you believe it"
|
||||
];
|
||||
};
|
||||
systemd.service = {
|
||||
serviceConfig.RestartSec = "42";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# irrelevant stuff
|
||||
system.stateVersion = "25.05";
|
||||
fileSystems."/".device = "/test/dummy";
|
||||
boot.loader.grub.enable = false;
|
||||
}
|
||||
);
|
||||
|
||||
inherit (machine.config.system.build) toplevel;
|
||||
in
|
||||
runCommand "test-modular-service-systemd-units"
|
||||
{
|
||||
passthru = {
|
||||
inherit
|
||||
machine
|
||||
toplevel
|
||||
;
|
||||
};
|
||||
}
|
||||
''
|
||||
echo ${toplevel}/etc/systemd/system/foo.service:
|
||||
cat -n ${toplevel}/etc/systemd/system/foo.service
|
||||
(
|
||||
set -x
|
||||
grep -F 'ExecStart=${hello}/bin/hello --greeting hoi' ${toplevel}/etc/systemd/system/foo.service >/dev/null
|
||||
|
||||
grep -F 'ExecStart=${hello}/bin/hello --greeting hoi' ${toplevel}/etc/systemd/system/bar.service >/dev/null
|
||||
grep -F 'X-Bar=lol crossbar whatever' ${toplevel}/etc/systemd/system/bar.service >/dev/null
|
||||
|
||||
grep 'ExecStart=${hello}/bin/hello --greeting .*database.*' ${toplevel}/etc/systemd/system/bar-db.service >/dev/null
|
||||
grep -F 'RestartSec=42' ${toplevel}/etc/systemd/system/bar-db.service >/dev/null
|
||||
|
||||
[[ ! -e ${toplevel}/etc/systemd/system/foo.socket ]]
|
||||
[[ ! -e ${toplevel}/etc/systemd/system/bar.socket ]]
|
||||
[[ ! -e ${toplevel}/etc/systemd/system/bar-db.socket ]]
|
||||
)
|
||||
echo 🐬👍
|
||||
touch $out
|
||||
''
|
||||
3
nixos/modules/system/service/systemd/user.nix
Normal file
3
nixos/modules/system/service/systemd/user.nix
Normal file
@@ -0,0 +1,3 @@
|
||||
# TBD, analogous to system.nix but for user units
|
||||
{
|
||||
}
|
||||
@@ -89,6 +89,16 @@ let
|
||||
featureFlags.minimalModules = { };
|
||||
};
|
||||
evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
|
||||
evalSystem =
|
||||
module:
|
||||
import ../lib/eval-config.nix {
|
||||
system = null;
|
||||
modules = [
|
||||
../modules/misc/nixpkgs/read-only.nix
|
||||
{ nixpkgs.pkgs = pkgs; }
|
||||
module
|
||||
];
|
||||
};
|
||||
|
||||
inherit
|
||||
(rec {
|
||||
@@ -891,6 +901,9 @@ in
|
||||
mjolnir = runTest ./matrix/mjolnir.nix;
|
||||
mobilizon = runTest ./mobilizon.nix;
|
||||
mod_perl = runTest ./mod_perl.nix;
|
||||
modularService = pkgs.callPackage ../modules/system/service/systemd/test.nix {
|
||||
inherit evalSystem;
|
||||
};
|
||||
molly-brown = runTest ./molly-brown.nix;
|
||||
mollysocket = runTest ./mollysocket.nix;
|
||||
monado = runTest ./monado.nix;
|
||||
|
||||
Reference in New Issue
Block a user