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

18
flake.lock generated
View file

@ -121,6 +121,23 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-nonflake": {
"flake": false,
"locked": {
"lastModified": 1679437018,
"narHash": "sha256-vOuiDPLHSEo/7NkiWtxpHpHgoXoNmrm+wkXZ6a072Fc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "19cf008bb18e47b6e3b4e16e32a9a4bdd4b45f7e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1678872516, "lastModified": 1678872516,
@ -169,6 +186,7 @@
"devshell": "devshell", "devshell": "devshell",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-nonflake": "nixpkgs-nonflake",
"pre-commit-hooks": "pre-commit-hooks", "pre-commit-hooks": "pre-commit-hooks",
"rust-overlay": "rust-overlay", "rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"

240
flake.nix
View file

@ -1,6 +1,12 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# We re-use the systemd lib from NixOS, this input allows to import the needed modules.
# TODO: is there a better way to do this?
nixpkgs-nonflake = {
url = "github:NixOS/nixpkgs/nixos-unstable";
flake = false;
};
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
devshell = { devshell = {
url = "github:numtide/devshell"; url = "github:numtide/devshell";
@ -41,6 +47,7 @@
outputs = outputs =
{ self { self
, nixpkgs , nixpkgs
, nixpkgs-nonflake
, flake-utils , flake-utils
, rust-overlay , rust-overlay
, crane , crane
@ -49,137 +56,140 @@
, pre-commit-hooks , pre-commit-hooks
, ,
}: }:
(flake-utils.lib.eachDefaultSystem (system: (flake-utils.lib.eachSystem
let (with flake-utils.lib.system; [ x86_64-linux aarch64-linux ])
pkgs = import nixpkgs { (system:
inherit system; let
overlays = [ (import rust-overlay) devshell.overlays.default ]; pkgs = import nixpkgs {
}; inherit system;
# TODO Pin the version for release overlays = [ (import rust-overlay) devshell.overlays.default ];
rust = pkgs.rust-bin.stable.latest; };
llvm = pkgs.llvmPackages_latest; # TODO Pin the version for release
rust = pkgs.rust-bin.stable.latest;
llvm = pkgs.llvmPackages_latest;
craneLib = (crane.mkLib pkgs).overrideToolchain rust.default; craneLib = (crane.mkLib pkgs).overrideToolchain rust.default;
# Common derivation arguments used for all builds # Common derivation arguments used for all builds
commonArgs = { commonArgs = {
src = craneLib.cleanCargoSource ./.; src = craneLib.cleanCargoSource ./.;
buildInputs = with pkgs; [ buildInputs = with pkgs; [
dbus dbus
]; ];
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config
]; ];
}; };
# Build only the cargo dependencies # Build only the cargo dependencies
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
pname = "system-manager"; pname = "system-manager";
}); });
system-manager = craneLib.buildPackage (commonArgs // { system-manager = craneLib.buildPackage (commonArgs // {
pname = "system-manager"; pname = "system-manager";
inherit cargoArtifacts; inherit cargoArtifacts;
}); });
system-manager-clippy = craneLib.cargoClippy (commonArgs // { system-manager-clippy = craneLib.cargoClippy (commonArgs // {
inherit cargoArtifacts; inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings"; cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}); });
# treefmt-nix configuration # treefmt-nix configuration
treefmt.config = { treefmt.config = {
projectRootFile = "flake.nix"; projectRootFile = "flake.nix";
programs = { programs = {
nixpkgs-fmt.enable = true; nixpkgs-fmt.enable = true;
rustfmt = { rustfmt = {
enable = true; enable = true;
package = rust.rustfmt; package = rust.rustfmt;
};
}; };
}; };
}; in
in {
{ packages = {
packages = { inherit system-manager;
inherit system-manager; default = self.packages.${system}.system-manager;
default = self.packages.${system}.system-manager; };
};
devShells.default = pkgs.devshell.mkShell { devShells.default = pkgs.devshell.mkShell {
packages = with pkgs; [ packages = with pkgs; [
llvm.clang llvm.clang
openssl openssl
pkg-config pkg-config
(rust.default.override { (rust.default.override {
extensions = [ "rust-src" ]; extensions = [ "rust-src" ];
}) })
(treefmt-nix.lib.mkWrapper pkgs treefmt.config) (treefmt-nix.lib.mkWrapper pkgs treefmt.config)
]; ];
env = [ env = [
{ {
name = "PKG_CONFIG_PATH"; name = "PKG_CONFIG_PATH";
value = pkgs.lib.makeSearchPath "lib/pkgconfig" [ value = pkgs.lib.makeSearchPath "lib/pkgconfig" [
pkgs.dbus.dev pkgs.dbus.dev
pkgs.systemdMinimal.dev pkgs.systemdMinimal.dev
];
}
{
name = "LIBCLANG_PATH";
value = "${llvm.libclang}/lib";
}
{
# for rust-analyzer
name = "RUST_SRC_PATH";
value = "${rust.rust-src}";
}
{
name = "RUST_BACKTRACE";
value = "1";
}
{
name = "RUSTFLAGS";
value =
let
getLib = pkg: "${pkgs.lib.getLib pkg}/lib";
in
pkgs.lib.concatStringsSep " " [
"-L${getLib pkgs.systemdMinimal} -lsystemd"
]; ];
} }
{ {
name = "DEVSHELL_NO_MOTD"; name = "LIBCLANG_PATH";
value = "1"; value = "${llvm.libclang}/lib";
} }
]; {
devshell.startup.pre-commit.text = (pre-commit-hooks.lib.${system}.run { # for rust-analyzer
src = ./.; name = "RUST_SRC_PATH";
hooks = { value = "${rust.rust-src}";
check-format = { }
enable = true; {
entry = "treefmt --fail-on-change"; name = "RUST_BACKTRACE";
value = "1";
}
{
name = "RUSTFLAGS";
value =
let
getLib = pkg: "${pkgs.lib.getLib pkg}/lib";
in
pkgs.lib.concatStringsSep " " [
"-L${getLib pkgs.systemdMinimal} -lsystemd"
];
}
{
name = "DEVSHELL_NO_MOTD";
value = "1";
}
];
devshell.startup.pre-commit.text = (pre-commit-hooks.lib.${system}.run {
src = ./.;
hooks = {
check-format = {
enable = true;
entry = "treefmt --fail-on-change";
};
cargo-clippy = {
enable = true;
description = "Lint Rust code.";
entry = "cargo-clippy --workspace -- -D warnings";
files = "\\.rs$";
pass_filenames = false;
};
}; };
cargo-clippy = { }).shellHook;
enable = true; };
description = "Lint Rust code.";
entry = "cargo-clippy --workspace -- -D warnings";
files = "\\.rs$";
pass_filenames = false;
};
};
}).shellHook;
};
checks = { checks = {
inherit inherit
# Build the crate as part of `nix flake check` for convenience # Build the crate as part of `nix flake check` for convenience
system-manager system-manager
system-manager-clippy; system-manager-clippy;
}; };
})) }))
// //
{ {
lib = import ./nix/lib.nix { lib = import ./nix/lib.nix {
inherit nixpkgs self; inherit nixpkgs self;
nixosModules = "${nixpkgs-nonflake}/nixos";
}; };
systemConfigs.default = self.lib.makeSystemConfig { systemConfigs.default = self.lib.makeSystemConfig {

View file

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

View file

@ -4,79 +4,77 @@
}: }:
{ {
config = { config = {
system-manager = { environment.etc = {
environment.etc = { foo = {
foo = { text = ''
text = '' This is just a test!
This is just a test!
'';
target = "foo_test";
};
"foo.conf".text = ''
launch_the_rockets = true
''; '';
target = "foo_test";
"baz/bar/foo2" = { };
text = ''
Another test! "foo.conf".text = ''
''; launch_the_rockets = true
mode = "symlink"; '';
};
"baz/bar/foo2" = {
foo3 = { text = ''
text = "boo!"; Another test!
mode = "0700"; '';
user = "root"; mode = "symlink";
group = "root"; };
};
foo3 = {
"a/nested/example/foo3" = { text = "boo!";
text = "boo!"; mode = "0700";
mode = "0764"; user = "root";
user = "root"; group = "root";
group = "root"; };
};
"a/nested/example/foo3" = {
"a/nested/example2/foo3" = { text = "boo!";
text = "boo!"; mode = "0764";
mode = "0764"; user = "root";
user = "root"; group = "root";
group = "root"; };
};
"a/nested/example2/foo3" = {
out-of-store = { text = "boo!";
source = "/run/systemd/system/"; 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 { environment.etc = lib.mkOption {
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
@ -100,7 +100,6 @@
Changing this option takes precedence over `gid`. Changing this option takes precedence over `gid`.
''; '';
}; };
}; };
config = { config = {
@ -110,7 +109,6 @@
in lib.mkDerivedConfig options.text (pkgs.writeText name') in lib.mkDerivedConfig options.text (pkgs.writeText name')
); );
}; };
} }
)); ));
}; };

View file

@ -8,35 +8,54 @@
./systemd.nix ./systemd.nix
]; ];
options.system-manager = { options = {
allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions"; 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 { warnings = lib.mkOption {
type = with lib.types; attrsOf (submodule ({ name, ... }: { internal = true;
options = { default = [ ];
enable = lib.mkEnableOption "the assertion"; 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 { system-manager = {
type = lib.types.str; allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions";
default = name;
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 { default = { };
type = lib.types.str; };
};
};
}));
default = { };
}; };
}; };
config = { config = {
# Avoid some standard NixOS assertions
boot = {
loader.grub.enable = false;
initrd.enable = false;
};
system.stateVersion = lib.mkDefault lib.trivial.release;
system-manager.preActivationAssertions = { system-manager.preActivationAssertions = {
osVersion = osVersion =

View file

@ -6,13 +6,31 @@
}: }:
let let
cfg = config.system-manager.systemd; cfg = config.systemd;
inherit (utils) systemdUtils; inherit (utils) systemdUtils;
systemd-lib = utils.systemdUtils.lib; systemd-lib = utils.systemdUtils.lib;
in 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 { units = lib.mkOption {
description = lib.mdDoc "Definition of systemd units."; description = lib.mdDoc "Definition of systemd units.";
@ -106,87 +124,85 @@ in
}; };
config = { config = {
system-manager = { systemd = {
systemd = { timers =
timers = lib.mapAttrs
lib.mapAttrs (name: service:
(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; wantedBy = [ "timers.target" ];
allowSubstitutes = false; timerConfig.OnCalendar = service.startAt;
} })
'' (lib.filterAttrs (name: service: service.enable && service.startAt != [ ]) cfg.services);
mkdir -p $out
for i in ${toString (lib.mapAttrsToList (n: v: v.unit) enabledUnits)}; do units =
fn=$(basename $i/*) lib.mapAttrs' (n: v: lib.nameValuePair "${n}.path" (systemd-lib.pathToUnit n v)) cfg.paths
if [ -e $out/$fn ]; then // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.service" (systemd-lib.serviceToUnit n v)) cfg.services
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.slice" (systemd-lib.sliceToUnit n v)) cfg.slices
ln -sfn /dev/null $out/$fn // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.socket" (systemd-lib.socketToUnit n v)) cfg.sockets
else // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.target" (systemd-lib.targetToUnit n v)) cfg.targets
${if allowCollisions then '' // lib.mapAttrs' (n: v: lib.nameValuePair "${n}.timer" (systemd-lib.timerToUnit n v)) cfg.timers
mkdir -p $out/$fn.d // lib.listToAttrs (map
ln -s $i/$fn $out/$fn.d/overrides.conf (v:
'' else '' let n = utils.escapeSystemdPath v.where;
echo "Found multiple derivations configuring $fn!" in lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit n v))
exit 1 cfg.mounts)
''} // lib.listToAttrs (map
fi (v:
else let n = utils.escapeSystemdPath v.where;
ln -fs $i/$fn $out/ in lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit n v))
fi cfg.automounts);
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)}
'';
};
}; };
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)}
'';
};
}; };
} }