Major overhaul of the nix side of things, part II.

This commit is contained in:
r-vdp 2023-03-24 16:03:20 +01:00
parent 9759c2da12
commit ce4cf7149d
No known key found for this signature in database
7 changed files with 371 additions and 303 deletions

View file

@ -1,5 +1,6 @@
{ nixpkgs
, self
{ nixpkgs # The nixpkgs flake
, self # The system-manager flake
, nixosModules # The path to the nixos modules dir from nixpkgs
,
}:
let
@ -16,22 +17,30 @@ in
pkgs = nixpkgs.legacyPackages.${system};
inherit (self.packages.${system}) system-manager;
# TODO can we call lib.evalModules directly instead of building a NixOS system?
nixosConfig = (lib.nixosSystem {
inherit system;
# Module that sets additional module arguments
extraArgsModule = { lib, config, pkgs, ... }: {
_module.args = {
pkgs = nixpkgs.legacyPackages.${system};
utils = import "${nixosModules}/lib/utils.nix" {
inherit lib config pkgs;
};
};
};
config = (lib.evalModules {
modules = [
extraArgsModule
./modules/system-manager.nix
] ++ modules;
specialArgs = extraSpecialArgs;
}).config;
returnIfNoAssertions = drv:
let
failedAssertions = map (x: x.message) (lib.filter (x: !x.assertion) nixosConfig.assertions);
failedAssertions = map (x: x.message) (lib.filter (x: !x.assertion) config.assertions);
in
if failedAssertions != [ ]
then throw "\nFailed assertions:\n${lib.concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
else lib.showWarnings nixosConfig.warnings drv;
else lib.showWarnings config.warnings drv;
services =
lib.mapAttrs'
@ -40,7 +49,7 @@ in
storePath =
''${unit.unit}/${unitName}'';
})
nixosConfig.system-manager.systemd.units;
config.systemd.units;
servicesPath = pkgs.writeTextFile {
name = "services";
@ -64,7 +73,7 @@ in
filteredEntries = lib.filterAttrs
(_name: etcFile: etcFile.enable)
nixosConfig.system-manager.environment.etc;
config.environment.etc;
srcDrvs = lib.mapAttrs addToStore filteredEntries;
@ -131,7 +140,7 @@ in
declare -a failed_assertions=()
${mkAssertions nixosConfig.system-manager.preActivationAssertions}
${mkAssertions config.system-manager.preActivationAssertions}
if [ ''${#failed_assertions[@]} -ne 0 ]; then
for failed_assertion in ''${failed_assertions[@]}; do

View file

@ -4,79 +4,77 @@
}:
{
config = {
system-manager = {
environment.etc = {
foo = {
text = ''
This is just a test!
'';
target = "foo_test";
};
"foo.conf".text = ''
launch_the_rockets = true
environment.etc = {
foo = {
text = ''
This is just a test!
'';
"baz/bar/foo2" = {
text = ''
Another test!
'';
mode = "symlink";
};
foo3 = {
text = "boo!";
mode = "0700";
user = "root";
group = "root";
};
"a/nested/example/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
"a/nested/example2/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
out-of-store = {
source = "/run/systemd/system/";
};
target = "foo_test";
};
"foo.conf".text = ''
launch_the_rockets = true
'';
"baz/bar/foo2" = {
text = ''
Another test!
'';
mode = "symlink";
};
foo3 = {
text = "boo!";
mode = "0700";
user = "root";
group = "root";
};
"a/nested/example/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
"a/nested/example2/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
out-of-store = {
source = "/run/systemd/system/";
};
systemd.services =
lib.listToAttrs
(lib.flip lib.genList 10 (ix:
lib.nameValuePair "service-${toString ix}"
{
enable = true;
description = "service-${toString ix}";
wants = [ "network-online.target" ];
after = [
"network-online.target"
"avahi-daemon.service"
"chrony.service"
"nss-lookup.target"
"tinc.service"
"pulseaudio.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = "${lib.getBin pkgs.coreutils}/bin/true";
};
wantedBy = [ "multi-user.target" ];
requiredBy = lib.mkIf (ix > 5) [ "service-0.service" ];
script = ''
sleep ${if ix > 5 then "2" else "1"}
'';
})
);
};
systemd.services =
lib.listToAttrs
(lib.flip lib.genList 10 (ix:
lib.nameValuePair "service-${toString ix}"
{
enable = true;
description = "service-${toString ix}";
wants = [ "network-online.target" ];
after = [
"network-online.target"
"avahi-daemon.service"
"chrony.service"
"nss-lookup.target"
"tinc.service"
"pulseaudio.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = "${lib.getBin pkgs.coreutils}/bin/true";
};
wantedBy = [ "multi-user.target" ];
requiredBy = lib.mkIf (ix > 5) [ "service-0.service" ];
script = ''
sleep ${if ix > 5 then "2" else "1"}
'';
})
);
};
}

View file

@ -3,7 +3,7 @@
, ...
}:
{
options.system-manager = {
options = {
environment.etc = lib.mkOption {
default = { };
example = lib.literalExpression ''
@ -100,7 +100,6 @@
Changing this option takes precedence over `gid`.
'';
};
};
config = {
@ -110,7 +109,6 @@
in lib.mkDerivedConfig options.text (pkgs.writeText name')
);
};
}
));
};

View file

@ -8,35 +8,54 @@
./systemd.nix
];
options.system-manager = {
allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions";
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.unspecified;
internal = true;
default = [ ];
example = [{ assertion = false; message = "you can't enable this for that reason"; }];
description = lib.mdDoc ''
This option allows modules to express conditions that must
hold for the evaluation of the system configuration to
succeed, along with associated error messages for the user.
'';
};
preActivationAssertions = lib.mkOption {
type = with lib.types; attrsOf (submodule ({ name, ... }: {
options = {
enable = lib.mkEnableOption "the assertion";
warnings = lib.mkOption {
internal = true;
default = [ ];
type = lib.types.listOf lib.types.str;
example = [ "The `foo' service is deprecated and will go away soon!" ];
description = lib.mdDoc ''
This option allows modules to show warnings to users during
the evaluation of the system configuration.
'';
};
name = lib.mkOption {
type = lib.types.str;
default = name;
system-manager = {
allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions";
preActivationAssertions = lib.mkOption {
type = with lib.types; attrsOf (submodule ({ name, ... }: {
options = {
enable = lib.mkEnableOption "the assertion";
name = lib.mkOption {
type = lib.types.str;
default = name;
};
script = lib.mkOption {
type = lib.types.str;
};
};
script = lib.mkOption {
type = lib.types.str;
};
};
}));
default = { };
}));
default = { };
};
};
};
config = {
# Avoid some standard NixOS assertions
boot = {
loader.grub.enable = false;
initrd.enable = false;
};
system.stateVersion = lib.mkDefault lib.trivial.release;
system-manager.preActivationAssertions = {
osVersion =

View file

@ -6,13 +6,31 @@
}:
let
cfg = config.system-manager.systemd;
cfg = config.systemd;
inherit (utils) systemdUtils;
systemd-lib = utils.systemdUtils.lib;
in
{
options.system-manager.systemd = {
options.systemd = {
# TODO: this is a bit dirty.
# The value here gets added to the PATH of every service.
# We could consider copying the systemd lib from NixOS and removing the bits
# that are not relevant to us, like this option.
package = lib.mkOption {
type = lib.types.oneOf [ lib.types.str lib.types.path lib.types.package ];
default = pkgs.systemdMinimal;
};
globalEnvironment = lib.mkOption {
type = with lib.types; attrsOf (nullOr (oneOf [ str path package ]));
default = { };
example = { TZ = "CET"; };
description = lib.mdDoc ''
Environment variables passed to *all* systemd units.
'';
};
units = lib.mkOption {
description = lib.mdDoc "Definition of systemd units.";
@ -106,87 +124,85 @@ in
};
config = {
system-manager = {
systemd = {
timers =
lib.mapAttrs
(name: service:
{
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = service.startAt;
})
(lib.filterAttrs (name: service: service.enable && service.startAt != [ ]) cfg.services);
units =
lib.mapAttrs' (n: v: lib.nameValuePair "${n}.path" (systemd-lib.pathToUnit n v)) cfg.paths
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.service" (systemd-lib.serviceToUnit n v)) cfg.services
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.slice" (systemd-lib.sliceToUnit n v)) cfg.slices
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.socket" (systemd-lib.socketToUnit n v)) cfg.sockets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.target" (systemd-lib.targetToUnit n v)) cfg.targets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.timer" (systemd-lib.timerToUnit n v)) cfg.timers
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit n v))
cfg.mounts)
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit n v))
cfg.automounts);
};
environment.etc =
let
allowCollisions = false;
enabledUnits = cfg.units;
in
{
"systemd/system".source = pkgs.runCommand "system-manager-units"
systemd = {
timers =
lib.mapAttrs
(name: service:
{
preferLocalBuild = true;
allowSubstitutes = false;
}
''
mkdir -p $out
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = service.startAt;
})
(lib.filterAttrs (name: service: service.enable && service.startAt != [ ]) cfg.services);
for i in ${toString (lib.mapAttrsToList (n: v: v.unit) enabledUnits)}; do
fn=$(basename $i/*)
if [ -e $out/$fn ]; then
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
ln -sfn /dev/null $out/$fn
else
${if allowCollisions then ''
mkdir -p $out/$fn.d
ln -s $i/$fn $out/$fn.d/overrides.conf
'' else ''
echo "Found multiple derivations configuring $fn!"
exit 1
''}
fi
else
ln -fs $i/$fn $out/
fi
done
${lib.concatStrings (
lib.mapAttrsToList (name: unit:
lib.concatMapStrings (name2: ''
mkdir -p $out/'${name2}.wants'
ln -sfn '../${name}' $out/'${name2}.wants'/
'') (unit.wantedBy or [])
) enabledUnits)}
${lib.concatStrings (
lib.mapAttrsToList (name: unit:
lib.concatMapStrings (name2: ''
mkdir -p $out/'${name2}.requires'
ln -sfn '../${name}' $out/'${name2}.requires'/
'') (unit.requiredBy or [])
) enabledUnits)}
'';
};
units =
lib.mapAttrs' (n: v: lib.nameValuePair "${n}.path" (systemd-lib.pathToUnit n v)) cfg.paths
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.service" (systemd-lib.serviceToUnit n v)) cfg.services
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.slice" (systemd-lib.sliceToUnit n v)) cfg.slices
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.socket" (systemd-lib.socketToUnit n v)) cfg.sockets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.target" (systemd-lib.targetToUnit n v)) cfg.targets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.timer" (systemd-lib.timerToUnit n v)) cfg.timers
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit n v))
cfg.mounts)
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit n v))
cfg.automounts);
};
environment.etc =
let
allowCollisions = false;
enabledUnits = lib.filterAttrs (_: unit: unit.enable) cfg.units;
in
{
"systemd/system".source = pkgs.runCommand "system-manager-units"
{
preferLocalBuild = true;
allowSubstitutes = false;
}
''
mkdir -p $out
for i in ${toString (lib.mapAttrsToList (n: v: v.unit) enabledUnits)}; do
fn=$(basename $i/*)
if [ -e $out/$fn ]; then
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
ln -sfn /dev/null $out/$fn
else
${if allowCollisions then ''
mkdir -p $out/$fn.d
ln -s $i/$fn $out/$fn.d/overrides.conf
'' else ''
echo "Found multiple derivations configuring $fn!"
exit 1
''}
fi
else
ln -fs $i/$fn $out/
fi
done
${lib.concatStrings (
lib.mapAttrsToList (name: unit:
lib.concatMapStrings (name2: ''
mkdir -p $out/'${name2}.wants'
ln -sfn '../${name}' $out/'${name2}.wants'/
'') (unit.wantedBy or [])
) enabledUnits)}
${lib.concatStrings (
lib.mapAttrsToList (name: unit:
lib.concatMapStrings (name2: ''
mkdir -p $out/'${name2}.requires'
ln -sfn '../${name}' $out/'${name2}.requires'/
'') (unit.requiredBy or [])
) enabledUnits)}
'';
};
};
}