Major overhaul of the nix side of things.

This commit is contained in:
r-vdp 2023-03-24 14:14:20 +01:00
parent 9aaa5e58f7
commit 9759c2da12
No known key found for this signature in database
6 changed files with 418 additions and 210 deletions

View file

@ -56,15 +56,19 @@ which should contain a `default.nix` file which functions as the entrance point.
A simple System Manager module could look something like this: A simple System Manager module could look something like this:
```nix ```nix
{ config, lib, pkgs, ... }: { config
let , lib
etcFiles = { , pkgs
, ... }:
{
config.system-manager = {
environment.etc = {
"foo.conf".text = '' "foo.conf".text = ''
launch_the_rockets = true launch_the_rockets = true
''; '';
}; };
systemd.services = {
services = {
foo = { foo = {
enable = true; enable = true;
serviceConfig = { serviceConfig = {
@ -77,15 +81,6 @@ let
''; '';
}; };
}; };
in
{
config = {
system-manager = {
etcFiles = lib.attrNames etcFiles;
services = lib.attrNames services;
};
environment.etc = etcFiles;
systemd = { inherit services; };
}; };
} }
``` ```

View file

@ -16,6 +16,7 @@ in
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
inherit (self.packages.${system}) system-manager; inherit (self.packages.${system}) system-manager;
# TODO can we call lib.evalModules directly instead of building a NixOS system?
nixosConfig = (lib.nixosSystem { nixosConfig = (lib.nixosSystem {
inherit system; inherit system;
modules = [ modules = [
@ -33,17 +34,13 @@ in
else lib.showWarnings nixosConfig.warnings drv; else lib.showWarnings nixosConfig.warnings drv;
services = services =
lib.listToAttrs lib.mapAttrs'
(map (unitName: unit:
(name: lib.nameValuePair unitName {
let
serviceName = "${name}.service";
in
lib.nameValuePair serviceName {
storePath = storePath =
''${nixosConfig.systemd.units."${serviceName}".unit}/${serviceName}''; ''${unit.unit}/${unitName}'';
}) })
nixosConfig.system-manager.services); nixosConfig.system-manager.systemd.units;
servicesPath = pkgs.writeTextFile { servicesPath = pkgs.writeTextFile {
name = "services"; name = "services";
@ -54,8 +51,6 @@ in
# TODO: handle globbing # TODO: handle globbing
etcFiles = etcFiles =
let let
isManaged = name: lib.elem name nixosConfig.system-manager.etcFiles;
addToStore = name: file: pkgs.runCommandLocal "${name}-etc-link" { } '' addToStore = name: file: pkgs.runCommandLocal "${name}-etc-link" { } ''
mkdir -p "$out/$(dirname "${file.target}")" mkdir -p "$out/$(dirname "${file.target}")"
ln -s "${file.source}" "$out/${file.target}" ln -s "${file.source}" "$out/${file.target}"
@ -68,8 +63,8 @@ in
''; '';
filteredEntries = lib.filterAttrs filteredEntries = lib.filterAttrs
(name: etcFile: etcFile.enable && isManaged name) (_name: etcFile: etcFile.enable)
nixosConfig.environment.etc; nixosConfig.system-manager.environment.etc;
srcDrvs = lib.mapAttrs addToStore filteredEntries; srcDrvs = lib.mapAttrs addToStore filteredEntries;

View file

@ -2,37 +2,10 @@
, pkgs , pkgs
, ... , ...
}: }:
let {
services = config = {
lib.listToAttrs system-manager = {
(lib.flip lib.genList 10 (ix: environment.etc = {
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"}
'';
})
);
etcFiles = {
foo = { foo = {
text = '' text = ''
This is just a test! This is just a test!
@ -40,6 +13,10 @@ let
target = "foo_test"; target = "foo_test";
}; };
"foo.conf".text = ''
launch_the_rockets = true
'';
"baz/bar/foo2" = { "baz/bar/foo2" = {
text = '' text = ''
Another test! Another test!
@ -72,14 +49,34 @@ let
source = "/run/systemd/system/"; source = "/run/systemd/system/";
}; };
}; };
in systemd.services =
{ lib.listToAttrs
config = { (lib.flip lib.genList 10 (ix:
system-manager = { lib.nameValuePair "service-${toString ix}"
etcFiles = lib.attrNames etcFiles; {
services = lib.attrNames services; 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"}
'';
})
);
}; };
environment.etc = etcFiles;
systemd = { inherit services; };
}; };
} }

118
nix/modules/etc.nix Normal file
View file

@ -0,0 +1,118 @@
{ lib
, pkgs
, ...
}:
{
options.system-manager = {
environment.etc = lib.mkOption {
default = { };
example = lib.literalExpression ''
{ example-configuration-file =
{ source = "/nix/store/.../etc/dir/file.conf.example";
mode = "0440";
};
"default/useradd".text = "GROUP=100 ...";
}
'';
description = lib.mdDoc ''
Set of files that have to be linked in {file}`/etc`.
'';
type = lib.types.attrsOf (lib.types.submodule (
{ name, config, options, ... }:
{
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc ''
Whether this /etc file should be generated. This
option allows specific /etc files to be disabled.
'';
};
target = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Name of symlink (relative to
{file}`/etc`). Defaults to the attribute
name.
'';
};
text = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.lines;
description = lib.mdDoc "Text of the file.";
};
source = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc "Path of the source file.";
};
mode = lib.mkOption {
type = lib.types.str;
default = "symlink";
example = "0600";
description = lib.mdDoc ''
If set to something else than `symlink`,
the file is copied instead of symlinked, with the given
file mode.
'';
};
uid = lib.mkOption {
default = 0;
type = lib.types.int;
description = lib.mdDoc ''
UID of created file. Only takes effect when the file is
copied (that is, the mode is not 'symlink').
'';
};
gid = lib.mkOption {
default = 0;
type = lib.types.int;
description = lib.mdDoc ''
GID of created file. Only takes effect when the file is
copied (that is, the mode is not 'symlink').
'';
};
user = lib.mkOption {
default = "+${toString config.uid}";
type = lib.types.str;
description = lib.mdDoc ''
User name of created file.
Only takes effect when the file is copied (that is, the mode is not 'symlink').
Changing this option takes precedence over `uid`.
'';
};
group = lib.mkOption {
default = "+${toString config.gid}";
type = lib.types.str;
description = lib.mdDoc ''
Group name of created file.
Only takes effect when the file is copied (that is, the mode is not 'symlink').
Changing this option takes precedence over `gid`.
'';
};
};
config = {
target = lib.mkDefault name;
source = lib.mkIf (config.text != null) (
let name' = "etc-" + baseNameOf name;
in lib.mkDerivedConfig options.text (pkgs.writeText name')
);
};
}
));
};
};
}

View file

@ -1,20 +1,14 @@
{ lib { lib
, config , config
, pkgs
, ... , ...
}: }:
{ {
imports = [
./etc.nix
./systemd.nix
];
options.system-manager = { options.system-manager = {
services = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
};
etcFiles = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
};
allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions"; allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions";
preActivationAssertions = lib.mkOption { preActivationAssertions = lib.mkOption {
@ -44,28 +38,6 @@
}; };
system.stateVersion = lib.mkDefault lib.trivial.release; system.stateVersion = lib.mkDefault lib.trivial.release;
assertions =
lib.flip map config.system-manager.etcFiles
(entry:
{
assertion = lib.hasAttr entry config.environment.etc;
message = lib.concatStringsSep " " [
"The entry ${entry} that was passed to system-manager.etcFiles"
"is not present in environment.etc"
];
}
) ++
lib.flip map config.system-manager.services
(entry:
{
assertion = lib.hasAttr entry config.systemd.services;
message = lib.concatStringsSep " " [
"The entry ${entry} that was passed to system-manager.services"
"is not present in systemd.services"
];
}
);
system-manager.preActivationAssertions = { system-manager.preActivationAssertions = {
osVersion = osVersion =
let let
@ -86,66 +58,5 @@
''; '';
}; };
}; };
# Add the system directory for systemd
system-manager.etcFiles = [ "systemd/system" ];
environment.etc =
let
allowCollisions = false;
enabledUnits =
lib.filterAttrs
(name: _: lib.elem
name
(map (name: "${name}.service") config.system-manager.services))
config.systemd.units;
in
{
"systemd/system".source = lib.mkForce (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)}
''
);
};
}; };
} }

192
nix/modules/systemd.nix Normal file
View file

@ -0,0 +1,192 @@
{ lib
, config
, pkgs
, utils
, ...
}:
let
cfg = config.system-manager.systemd;
inherit (utils) systemdUtils;
systemd-lib = utils.systemdUtils.lib;
in
{
options.system-manager.systemd = {
units = lib.mkOption {
description = lib.mdDoc "Definition of systemd units.";
default = { };
type = systemdUtils.types.units;
};
packages = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.package;
example = lib.literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
description = lib.mdDoc "Packages providing systemd units and hooks.";
};
targets = lib.mkOption {
default = { };
type = systemdUtils.types.targets;
description = lib.mdDoc "Definition of systemd target units.";
};
services = lib.mkOption {
default = { };
type = systemdUtils.types.services;
description = lib.mdDoc "Definition of systemd service units.";
};
sockets = lib.mkOption {
default = { };
type = systemdUtils.types.sockets;
description = lib.mdDoc "Definition of systemd socket units.";
};
timers = lib.mkOption {
default = { };
type = systemdUtils.types.timers;
description = lib.mdDoc "Definition of systemd timer units.";
};
paths = lib.mkOption {
default = { };
type = systemdUtils.types.paths;
description = lib.mdDoc "Definition of systemd path units.";
};
mounts = lib.mkOption {
default = [ ];
type = systemdUtils.types.mounts;
description = lib.mdDoc ''
Definition of systemd mount units.
This is a list instead of an attrSet, because systemd mandates the names to be derived from
the 'where' attribute.
'';
};
automounts = lib.mkOption {
default = [ ];
type = systemdUtils.types.automounts;
description = lib.mdDoc ''
Definition of systemd automount units.
This is a list instead of an attrSet, because systemd mandates the names to be derived from
the 'where' attribute.
'';
};
slices = lib.mkOption {
default = { };
type = systemdUtils.types.slices;
description = lib.mdDoc "Definition of slice configurations.";
};
generators = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
default = { };
example = { systemd-gpt-auto-generator = "/dev/null"; };
description = lib.mdDoc ''
Definition of systemd generators.
For each `NAME = VALUE` pair of the attrSet, a link is generated from
`/etc/systemd/system-generators/NAME` to `VALUE`.
'';
};
shutdown = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
default = { };
description = lib.mdDoc ''
Definition of systemd shutdown executables.
For each `NAME = VALUE` pair of the attrSet, a link is generated from
`/etc/systemd/system-shutdown/NAME` to `VALUE`.
'';
};
};
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"
{
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)}
'';
};
};
};
}