Merge pull request #124 from numtide/nixfmt

Switch to nixfmt
This commit is contained in:
Ramses 2024-09-30 12:01:40 +02:00 committed by GitHub
commit 2690eea57a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 837 additions and 674 deletions

View file

@ -1,6 +1,4 @@
(import
(fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2";
})
{ src = ./.; }).defaultNix
(import (fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2";
}) { src = ./.; }).defaultNix

View file

@ -1,4 +1,5 @@
{ lib, pkgs, ... }: {
{ lib, pkgs, ... }:
{
config = {
nixpkgs.hostPlatform = "x86_64-linux";
@ -56,28 +57,28 @@
};
};
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"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
wantedBy = [ "system-manager.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"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
wantedBy = [ "system-manager.target" ];
requiredBy = lib.mkIf (ix > 5) [ "service-0.service" ];
script = ''
sleep ${if ix > 5 then "2" else "1"}
'';
}
)
);
systemd.tmpfiles.rules = [ "D /var/tmp/system-manager 0755 root root -" ];
};
}

406
flake.nix
View file

@ -39,15 +39,16 @@
};
outputs =
{ self
, nixpkgs
, flake-utils
, rust-overlay
, crane
, devshell
, treefmt-nix
, pre-commit-hooks
, ...
{
self,
nixpkgs,
flake-utils,
rust-overlay,
crane,
devshell,
treefmt-nix,
pre-commit-hooks,
...
}@inputs:
{
lib = import ./nix/lib.nix {
@ -60,201 +61,228 @@
modules = [ ./examples/example.nix ];
};
}
//
(flake-utils.lib.eachSystem
// (flake-utils.lib.eachSystem
[
flake-utils.lib.system.x86_64-linux
flake-utils.lib.system.aarch64-linux
]
(system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) devshell.overlays.default ];
};
# TODO Pin the version for release
rust = pkgs.rust-bin.stable.latest;
(
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
(import rust-overlay)
devshell.overlays.default
];
};
# TODO Pin the version for release
rust = pkgs.rust-bin.stable.latest;
craneLib = (crane.mkLib pkgs).overrideToolchain rust.default;
craneLib = (crane.mkLib pkgs).overrideToolchain rust.default;
# Common derivation arguments used for all builds
commonArgs = { dbus, pkg-config }: {
src = craneLib.cleanCargoSource ./.;
buildInputs = [
dbus
];
nativeBuildInputs = [
pkg-config
];
# https://github.com/ipetkov/crane/issues/385
doNotLinkInheritedArtifacts = true;
};
# Build only the cargo dependencies
cargoArtifacts = { dbus, pkg-config }:
craneLib.buildDepsOnly ((commonArgs { inherit dbus pkg-config; }) // {
pname = "system-manager";
});
system-manager-unwrapped =
{ dbus
, pkg-config
}:
craneLib.buildPackage ((commonArgs { inherit dbus pkg-config; }) // {
pname = "system-manager";
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
});
system-manager =
{ dbus
, makeBinaryWrapper
, nix
, pkg-config
, runCommand
}:
let
unwrapped = system-manager-unwrapped { inherit dbus pkg-config; };
in
runCommand "system-manager"
# Common derivation arguments used for all builds
commonArgs =
{ dbus, pkg-config }:
{
nativeBuildInputs = [ makeBinaryWrapper ];
}
''
makeWrapper \
${unwrapped}/bin/system-manager \
$out/bin/system-manager \
--prefix PATH : ${nixpkgs.lib.makeBinPath [ nix ]}
'';
src = craneLib.cleanCargoSource ./.;
buildInputs = [
dbus
];
nativeBuildInputs = [
pkg-config
];
# https://github.com/ipetkov/crane/issues/385
doNotLinkInheritedArtifacts = true;
};
system-manager-clippy =
{ dbus
, pkg-config
}:
craneLib.cargoClippy ((commonArgs { inherit dbus pkg-config; }) // {
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
# Build only the cargo dependencies
cargoArtifacts =
{ dbus, pkg-config }:
craneLib.buildDepsOnly (
(commonArgs { inherit dbus pkg-config; })
// {
pname = "system-manager";
}
);
system-manager-test =
{ dbus
, pkg-config
}:
craneLib.cargoTest ((commonArgs { inherit dbus pkg-config; }) // {
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
});
system-manager-unwrapped =
{
dbus,
pkg-config,
}:
craneLib.buildPackage (
(commonArgs { inherit dbus pkg-config; })
// {
pname = "system-manager";
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
}
);
# treefmt-nix configuration
treefmt.config = {
projectRootFile = "flake.nix";
programs = {
nixpkgs-fmt.enable = true;
rustfmt = {
enable = true;
package = rust.rustfmt;
system-manager =
{
dbus,
makeBinaryWrapper,
nix,
pkg-config,
runCommand,
}:
let
unwrapped = system-manager-unwrapped { inherit dbus pkg-config; };
in
runCommand "system-manager"
{
nativeBuildInputs = [ makeBinaryWrapper ];
}
''
makeWrapper \
${unwrapped}/bin/system-manager \
$out/bin/system-manager \
--prefix PATH : ${nixpkgs.lib.makeBinPath [ nix ]}
'';
system-manager-clippy =
{
dbus,
pkg-config,
}:
craneLib.cargoClippy (
(commonArgs { inherit dbus pkg-config; })
// {
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
);
system-manager-test =
{
dbus,
pkg-config,
}:
craneLib.cargoTest (
(commonArgs { inherit dbus pkg-config; })
// {
cargoArtifacts = cargoArtifacts { inherit dbus pkg-config; };
}
);
# treefmt-nix configuration
treefmt.config = {
projectRootFile = "flake.nix";
programs = {
nixfmt.enable = true;
rustfmt = {
enable = true;
package = rust.rustfmt;
};
};
};
};
in
{
packages = {
# The unwrapped version takes nix from the PATH, it will fail if nix
# cannot be found.
# The wrapped version has a reference to the nix store path, so nix is
# part of its runtime closure.
system-manager-unwrapped = pkgs.callPackage system-manager-unwrapped { };
system-manager = pkgs.callPackage system-manager { };
in
{
packages = {
# The unwrapped version takes nix from the PATH, it will fail if nix
# cannot be found.
# The wrapped version has a reference to the nix store path, so nix is
# part of its runtime closure.
system-manager-unwrapped = pkgs.callPackage system-manager-unwrapped { };
system-manager = pkgs.callPackage system-manager { };
system-manager-clippy = pkgs.callPackage system-manager-clippy { };
system-manager-test = pkgs.callPackage system-manager-test { };
system-manager-clippy = pkgs.callPackage system-manager-clippy { };
system-manager-test = pkgs.callPackage system-manager-test { };
default = self.packages.${system}.system-manager;
};
devShells.default =
let
llvm = pkgs.llvmPackages_latest;
in
pkgs.devshell.mkShell {
packages = with pkgs; [
llvm.clang
pkg-config
(rust.default.override {
extensions = [ "rust-src" ];
})
(treefmt-nix.lib.mkWrapper pkgs treefmt.config)
];
env = [
{
name = "PKG_CONFIG_PATH";
value = pkgs.lib.makeSearchPath "lib/pkgconfig" [
pkgs.dbus.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";
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;
};
};
}).shellHook;
default = self.packages.${system}.system-manager;
};
checks =
let
# The Aarch64 VM tests seem to hang on garnix, we disable them for now
enableVmTests = system != flake-utils.lib.system.aarch64-linux;
in
{
inherit (self.packages.${system})
# Build the crate as part of `nix flake check` for convenience
system-manager
system-manager-clippy
system-manager-test;
} //
pkgs.lib.optionalAttrs enableVmTests (import ./test/nix/modules {
inherit system;
inherit (pkgs) lib;
inherit (inputs) nix-vm-test;
system-manager = self;
});
})
devShells.default =
let
llvm = pkgs.llvmPackages_latest;
in
pkgs.devshell.mkShell {
packages = with pkgs; [
llvm.clang
pkg-config
(rust.default.override {
extensions = [ "rust-src" ];
})
(treefmt-nix.lib.mkWrapper pkgs treefmt.config)
];
env = [
{
name = "PKG_CONFIG_PATH";
value = pkgs.lib.makeSearchPath "lib/pkgconfig" [
pkgs.dbus.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";
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;
};
};
}).shellHook;
};
checks =
let
# The Aarch64 VM tests seem to hang on garnix, we disable them for now
enableVmTests = system != flake-utils.lib.system.aarch64-linux;
in
{
inherit (self.packages.${system})
# Build the crate as part of `nix flake check` for convenience
system-manager
system-manager-clippy
system-manager-test
;
}
// pkgs.lib.optionalAttrs enableVmTests (
import ./test/nix/modules {
inherit system;
inherit (pkgs) lib;
inherit (inputs) nix-vm-test;
system-manager = self;
}
);
}
)
);
}

View file

@ -1,7 +1,7 @@
{ nixpkgs # The nixpkgs flake
, self # The system-manager flake
, nixos # The path to the nixos dir from nixpkgs
,
{
nixpkgs, # The nixpkgs flake
self, # The system-manager flake
nixos, # The path to the nixos dir from nixpkgs
}:
let
inherit (nixpkgs) lib;
@ -11,45 +11,64 @@ in
# reporting in module-system errors.
# Usage example:
# { _file = "${printAttrPos (builtins.unsafeGetAttrPos "a" { a = null; })}: inline module"; }
printAttrPos = { file, line, column }: "${file}:${toString line}:${toString column}";
printAttrPos =
{
file,
line,
column,
}:
"${file}:${toString line}:${toString column}";
makeSystemConfig =
{ modules
, extraSpecialArgs ? { }
{
modules,
extraSpecialArgs ? { },
}:
let
# Module that sets additional module arguments
extraArgsModule = { lib, config, pkgs, ... }: {
_file = "${self.lib.printAttrPos (builtins.unsafeGetAttrPos "a" { a = null; })}: inline module";
_module.args = {
pkgs = nixpkgs.legacyPackages.${config.nixpkgs.hostPlatform};
utils = import "${nixos}/lib/utils.nix" {
inherit lib config pkgs;
extraArgsModule =
{
lib,
config,
pkgs,
...
}:
{
_file = "${self.lib.printAttrPos (builtins.unsafeGetAttrPos "a" { a = null; })}: inline module";
_module.args = {
pkgs = nixpkgs.legacyPackages.${config.nixpkgs.hostPlatform};
utils = import "${nixos}/lib/utils.nix" {
inherit lib config pkgs;
};
# Pass the wrapped system-manager binary down
inherit (self.packages.${config.nixpkgs.hostPlatform}) system-manager;
};
# Pass the wrapped system-manager binary down
inherit (self.packages.${config.nixpkgs.hostPlatform}) system-manager;
};
};
config = (lib.evalModules {
specialArgs = { nixosModulesPath = "${nixos}/modules"; } // extraSpecialArgs;
modules = [
extraArgsModule
./modules
] ++ modules;
}).config;
config =
(lib.evalModules {
specialArgs = {
nixosModulesPath = "${nixos}/modules";
} // extraSpecialArgs;
modules = [
extraArgsModule
./modules
] ++ modules;
}).config;
# Get the system as it was defined in the modules.
system = config.nixpkgs.hostPlatform;
pkgs = nixpkgs.legacyPackages.${system};
returnIfNoAssertions = drv:
returnIfNoAssertions =
drv:
let
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 config.warnings drv;
if failedAssertions != [ ] then
throw "\nFailed assertions:\n${lib.concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
else
lib.showWarnings config.warnings drv;
servicesPath = pkgs.writeTextFile {
name = "services";
@ -72,45 +91,50 @@ in
toplevel =
let
scripts = lib.mapAttrsToList
(_: script: linkFarmBinEntryFromDrv script)
config.build.scripts;
scripts = lib.mapAttrsToList (_: script: linkFarmBinEntryFromDrv script) config.build.scripts;
entries = [
(linkFarmEntryFromDrv servicesPath)
(linkFarmEntryFromDrv etcPath)
] ++ scripts;
addPassthru = drv: drv.overrideAttrs (prevAttrs: {
passthru = (prevAttrs.passthru or { }) // {
inherit config;
};
});
addPassthru =
drv:
drv.overrideAttrs (prevAttrs: {
passthru = (prevAttrs.passthru or { }) // {
inherit config;
};
});
in
addPassthru (pkgs.linkFarm "system-manager" entries);
in
returnIfNoAssertions toplevel;
mkTestPreamble =
{ node
, profile
, action
}: ''
{
node,
profile,
action,
}:
''
${node}.succeed("${profile}/bin/${action} 2>&1 | tee /tmp/output.log")
${node}.succeed("! grep -F 'ERROR' /tmp/output.log")
'';
activateProfileSnippet = { node, profile }:
activateProfileSnippet =
{ node, profile }:
self.lib.mkTestPreamble {
inherit node profile;
action = "activate";
};
deactivateProfileSnippet = { node, profile }:
deactivateProfileSnippet =
{ node, profile }:
self.lib.mkTestPreamble {
inherit node profile;
action = "deactivate";
};
prepopulateProfileSnippet = { node, profile }:
prepopulateProfileSnippet =
{ node, profile }:
self.lib.mkTestPreamble {
inherit node profile;
action = "prepopulate";

View file

@ -1,8 +1,9 @@
{ lib
, config
, pkgs
, system-manager
, ...
{
lib,
config,
pkgs,
system-manager,
...
}:
{
imports = [
@ -31,7 +32,12 @@
type = types.listOf types.unspecified;
internal = true;
default = [ ];
example = [{ assertion = false; message = "you can't enable this for that reason"; }];
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
@ -95,20 +101,27 @@
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";
type =
with lib.types;
attrsOf (
submodule (
{ name, ... }:
{
options = {
enable = lib.mkEnableOption "the assertion";
name = lib.mkOption {
type = types.str;
default = name;
};
name = lib.mkOption {
type = types.str;
default = name;
};
script = lib.mkOption {
type = types.str;
};
};
}));
script = lib.mkOption {
type = types.str;
};
};
}
)
);
default = { };
};
};
@ -140,17 +153,22 @@
system-manager.preActivationAssertions = {
osVersion =
let
supportedIds = [ "nixos" "ubuntu" ];
supportedIds = [
"nixos"
"ubuntu"
];
in
{
enable = !config.system-manager.allowAnyDistro;
script = ''
source /etc/os-release
${lib.concatStringsSep "\n" (lib.flip map supportedIds (supportedId: ''
if [ $ID = "${supportedId}" ]; then
exit 0
fi
''))}
${lib.concatStringsSep "\n" (
lib.flip map supportedIds (supportedId: ''
if [ $ID = "${supportedId}" ]; then
exit 0
fi
'')
)}
echo "This OS is not currently supported."
echo "Supported OSs are: ${lib.concatStringsSep ", " supportedIds}"
exit 1
@ -184,27 +202,27 @@
preActivationAssertionScript =
let
mkAssertion = { name, script, ... }: ''
# ${name}
mkAssertion =
{ name, script, ... }:
''
# ${name}
echo -e "Evaluating pre-activation assertion ${name}...\n"
(
set +e
${script}
)
assertion_result=$?
if [ $assertion_result -ne 0 ]; then
failed_assertions+=${name}
fi
'';
mkAssertions = assertions:
lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: mkAssertion) (
lib.filterAttrs (name: cfg: cfg.enable)
assertions
echo -e "Evaluating pre-activation assertion ${name}...\n"
(
set +e
${script}
)
assertion_result=$?
if [ $assertion_result -ne 0 ]; then
failed_assertions+=${name}
fi
'';
mkAssertions =
assertions:
lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: mkAssertion) (lib.filterAttrs (name: cfg: cfg.enable) assertions)
);
in
pkgs.writeShellScript "preActivationAssertions" ''
@ -230,43 +248,40 @@
# TODO: handle globbing
etc =
let
addToStore = name: file: pkgs.runCommandLocal "${name}-etc-link" { } ''
mkdir -p "$out/$(dirname "${file.target}")"
ln -s "${file.source}" "$out/${file.target}"
addToStore =
name: file:
pkgs.runCommandLocal "${name}-etc-link" { } ''
mkdir -p "$out/$(dirname "${file.target}")"
ln -s "${file.source}" "$out/${file.target}"
if [ "${file.mode}" != symlink ]; then
echo "${file.mode}" > "$out/${file.target}.mode"
echo "${file.user}" > "$out/${file.target}.uid"
echo "${file.group}" > "$out/${file.target}.gid"
fi
'';
if [ "${file.mode}" != symlink ]; then
echo "${file.mode}" > "$out/${file.target}.mode"
echo "${file.user}" > "$out/${file.target}.uid"
echo "${file.group}" > "$out/${file.target}.gid"
fi
'';
filteredEntries = lib.filterAttrs
(_name: etcFile: etcFile.enable)
config.environment.etc;
filteredEntries = lib.filterAttrs (_name: etcFile: etcFile.enable) config.environment.etc;
srcDrvs = lib.mapAttrs addToStore filteredEntries;
entries = lib.mapAttrs
(name: file: file // { source = "${srcDrvs.${name}}"; })
filteredEntries;
entries = lib.mapAttrs (name: file: file // { source = "${srcDrvs.${name}}"; }) filteredEntries;
staticEnv = pkgs.buildEnv {
name = "etc-static-env";
paths = lib.attrValues srcDrvs;
};
in
{ inherit entries staticEnv; };
{
inherit entries staticEnv;
};
services =
lib.mapAttrs'
(unitName: unit:
lib.nameValuePair unitName {
storePath =
''${unit.unit}/${unitName}'';
})
(lib.filterAttrs (_: unit: unit.enable)
config.systemd.units);
services = lib.mapAttrs' (
unitName: unit:
lib.nameValuePair unitName {
storePath = ''${unit.unit}/${unitName}'';
}
) (lib.filterAttrs (_: unit: unit.enable) config.systemd.units);
};
};
}

View file

@ -1,4 +1,9 @@
{ lib, config, pkgs, ... }:
{
lib,
config,
pkgs,
...
}:
{
options.environment = {
@ -24,10 +29,9 @@
];
etc = {
"profile.d/system-manager-path.sh".source =
pkgs.writeText "system-manager-path.sh" ''
export PATH=${pathDir}/bin/:''${PATH}
'';
"profile.d/system-manager-path.sh".source = pkgs.writeText "system-manager-path.sh" ''
export PATH=${pathDir}/bin/:''${PATH}
'';
# TODO: figure out how to properly add fish support. We could start by
# looking at what NixOS and HM do to set up the fish env.

View file

@ -1,6 +1,7 @@
{ lib
, pkgs
, ...
{
lib,
pkgs,
...
}:
{
options = {
@ -18,99 +19,108 @@
Set of files that have to be linked in {file}`/etc`.
'';
type = lib.types.attrsOf (lib.types.submodule (
{ name, config, options, ... }:
{
options = {
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.
'';
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`.
'';
};
};
target = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Name of symlink (relative to
{file}`/etc`). Defaults to the attribute
name.
'';
config = {
target = lib.mkDefault name;
source = lib.mkIf (config.text != null) (
let
name' = "etc-" + baseNameOf name;
in
lib.mkDerivedConfig options.text (pkgs.writeText 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,8 +1,9 @@
{ lib
, config
, pkgs
, utils
, ...
{
lib,
config,
pkgs,
utils,
...
}:
let
@ -19,14 +20,28 @@ in
# 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 ];
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 ]));
type =
with lib.types;
attrsOf (
nullOr (oneOf [
str
path
package
])
);
default = { };
example = { TZ = "CET"; };
example = {
TZ = "CET";
};
description = lib.mdDoc ''
Environment variables passed to *all* systemd units.
'';
@ -104,7 +119,9 @@ in
generators = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
default = { };
example = { systemd-gpt-auto-generator = "/dev/null"; };
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
@ -129,14 +146,10 @@ in
wantedBy = [ "default.target" ];
};
timers =
lib.mapAttrs
(name: service:
{
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = service.startAt;
})
(lib.filterAttrs (name: service: service.enable && service.startAt != [ ]) cfg.services);
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 v)) cfg.paths
@ -145,16 +158,24 @@ in
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.socket" (systemd-lib.socketToUnit v)) cfg.sockets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.target" (systemd-lib.targetToUnit v)) cfg.targets
// lib.mapAttrs' (n: v: lib.nameValuePair "${n}.timer" (systemd-lib.timerToUnit v)) cfg.timers
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit v))
cfg.mounts)
// lib.listToAttrs (map
(v:
let n = utils.escapeSystemdPath v.where;
in lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit v))
cfg.automounts);
// lib.listToAttrs (
map (
v:
let
n = utils.escapeSystemdPath v.where;
in
lib.nameValuePair "${n}.mount" (systemd-lib.mountToUnit v)
) cfg.mounts
)
// lib.listToAttrs (
map (
v:
let
n = utils.escapeSystemdPath v.where;
in
lib.nameValuePair "${n}.automount" (systemd-lib.automountToUnit v)
) cfg.automounts
);
};
environment.etc =
@ -164,49 +185,59 @@ in
enabledUnits = lib.filterAttrs (_: unit: unit.enable) cfg.units;
in
{
"systemd/system".source = pkgs.runCommand "system-manager-units"
{
preferLocalBuild = true;
allowSubstitutes = false;
}
''
mkdir -p $out
"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
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
${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
''}
ln -fs $i/$fn $out/
fi
else
ln -fs $i/$fn $out/
fi
done
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}.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)}
'';
${lib.concatStrings (
lib.mapAttrsToList (
name: unit:
lib.concatMapStrings (name2: ''
mkdir -p $out/'${name2}.requires'
ln -sfn '../${name}' $out/'${name2}.requires'/
'') (unit.requiredBy or [ ])
) enabledUnits
)}
'';
};
};
}

View file

@ -1,19 +1,21 @@
{ nixosModulesPath
, lib
, ...
{
nixosModulesPath,
lib,
...
}:
{
imports = [
./nginx.nix
] ++
# List of imported NixOS modules
# TODO: how will we manage this in the long term?
map (path: nixosModulesPath + path) [
"/misc/meta.nix"
"/security/acme/"
"/services/web-servers/nginx/"
];
imports =
[
./nginx.nix
]
++
# List of imported NixOS modules
# TODO: how will we manage this in the long term?
map (path: nixosModulesPath + path) [
"/misc/meta.nix"
"/security/acme/"
"/services/web-servers/nginx/"
];
options =
# We need to ignore a bunch of options that are used in NixOS modules but

View file

@ -5,4 +5,3 @@ let
};
in
(import compat { src = ./.; }).shellNix.default

View file

@ -1,42 +1,55 @@
{ lib
, system-manager
, system
, nix-vm-test
{
lib,
system-manager,
system,
nix-vm-test,
}:
let
forEachUbuntuImage =
name:
{ modules
, testScriptFunction
, extraPathsToRegister ? [ ]
, projectTest ? test: test.sandboxed
{
modules,
testScriptFunction,
extraPathsToRegister ? [ ],
projectTest ? test: test.sandboxed,
}:
let
ubuntu = nix-vm-test.lib.${system}.ubuntu;
in
lib.listToAttrs (lib.flip map (lib.attrNames ubuntu.images)
(imageVersion:
let
toplevel = (system-manager.lib.makeSystemConfig {
modules = modules ++ [
({ lib, pkgs, ... }: {
options.hostPkgs = lib.mkOption { type = lib.types.raw; readOnly = true; };
config.hostPkgs = pkgs;
})
];
});
inherit (toplevel.config) hostPkgs;
in
lib.nameValuePair "ubuntu-${imageVersion}-${name}"
(projectTest
(ubuntu.${imageVersion} {
testScript = testScriptFunction { inherit toplevel hostPkgs; };
extraPathsToRegister = extraPathsToRegister ++ [
toplevel
];
sharedDirs = { };
}))
lib.listToAttrs (
lib.flip map (lib.attrNames ubuntu.images) (
imageVersion:
let
toplevel = (
system-manager.lib.makeSystemConfig {
modules = modules ++ [
(
{ lib, pkgs, ... }:
{
options.hostPkgs = lib.mkOption {
type = lib.types.raw;
readOnly = true;
};
config.hostPkgs = pkgs;
}
)
];
}
);
inherit (toplevel.config) hostPkgs;
in
lib.nameValuePair "ubuntu-${imageVersion}-${name}" (
projectTest (
ubuntu.${imageVersion} {
testScript = testScriptFunction { inherit toplevel hostPkgs; };
extraPathsToRegister = extraPathsToRegister ++ [
toplevel
];
sharedDirs = { };
}
)
)
)
);
@ -44,90 +57,100 @@ let
# and one that cannot.
# The id parameter is a string that can be used to force reloading the services
# between two configs by changing their contents.
testModule = id: { lib, pkgs, ... }: {
systemd.services = {
has-reload = {
enable = true;
description = "service-reload";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = ''
${lib.getBin pkgs.coreutils}/bin/true
testModule =
id:
{ lib, pkgs, ... }:
{
systemd.services = {
has-reload = {
enable = true;
description = "service-reload";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = ''
${lib.getBin pkgs.coreutils}/bin/true
'';
};
wantedBy = [ "system-manager.target" ];
script = ''
echo "I can be reloaded (id: ${id})"
'';
};
has-no-reload = {
enable = true;
description = "service-no-reload";
serviceConfig.Type = "simple";
wantedBy = [ "system-manager.target" ];
script = ''
while true; do
echo "I cannot be reloaded (id: ${id})"
done
'';
};
wantedBy = [ "system-manager.target" ];
script = ''
echo "I can be reloaded (id: ${id})"
'';
};
has-no-reload = {
enable = true;
description = "service-no-reload";
serviceConfig.Type = "simple";
wantedBy = [ "system-manager.target" ];
script = ''
while true; do
echo "I cannot be reloaded (id: ${id})"
done
'';
};
};
};
newConfig = system-manager.lib.makeSystemConfig {
modules = [
(testModule "new")
({ lib, pkgs, ... }: {
config = {
nixpkgs.hostPlatform = system;
(
{ lib, pkgs, ... }:
{
config = {
nixpkgs.hostPlatform = system;
services.nginx.enable = false;
services.nginx.enable = false;
environment = {
etc = {
foo_new = {
text = ''
This is just a test!
environment = {
etc = {
foo_new = {
text = ''
This is just a test!
'';
};
};
systemPackages = [
pkgs.fish
];
};
systemd.services = {
new-service = {
enable = true;
description = "new-service";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = "${lib.getBin pkgs.coreutils}/bin/true";
};
wantedBy = [
"system-manager.target"
"default.target"
];
script = ''
sleep 2
'';
};
};
systemPackages = [
pkgs.fish
];
};
systemd.services = {
new-service = {
enable = true;
description = "new-service";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecReload = "${lib.getBin pkgs.coreutils}/bin/true";
};
wantedBy = [ "system-manager.target" "default.target" ];
script = ''
sleep 2
'';
};
};
};
})
}
)
];
};
in
forEachUbuntuImage "example"
{
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction = { toplevel, ... }: ''
forEachUbuntuImage "example" {
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction =
{ toplevel, ... }:
''
# Start all machines in parallel
start_all()
@ -138,7 +161,10 @@ forEachUbuntuImage "example"
vm.succeed("grep -F 'Error while creating file in /etc: Unmanaged path already exists in filesystem, please remove it and run system-manager again: /etc/foo_test' /tmp/output.log")
vm.succeed("rm /etc/foo_test")
${system-manager.lib.activateProfileSnippet { node = "vm"; profile = toplevel; }}
${system-manager.lib.activateProfileSnippet {
node = "vm";
profile = toplevel;
}}
vm.wait_for_unit("system-manager.target")
vm.succeed("systemctl status service-9.service")
@ -150,7 +176,10 @@ forEachUbuntuImage "example"
vm.succeed("test -d /var/tmp/system-manager")
${system-manager.lib.activateProfileSnippet { node = "vm"; profile = newConfig; }}
${system-manager.lib.activateProfileSnippet {
node = "vm";
profile = newConfig;
}}
vm.succeed("systemctl status new-service.service")
vm.fail("systemctl status service-9.service")
vm.fail("test -f /etc/a/nested/example/foo3")
@ -177,89 +206,111 @@ forEachUbuntuImage "example"
vm.fail("test -f /etc/baz/bar/foo2")
vm.succeed("test -f /etc/foo_new")
${system-manager.lib.deactivateProfileSnippet { node = "vm"; profile = newConfig; }}
${system-manager.lib.deactivateProfileSnippet {
node = "vm";
profile = newConfig;
}}
vm.fail("systemctl status new-service.service")
vm.fail("test -f /etc/foo_new")
#vm.fail("test -f /var/tmp/system-manager/foo1")
'';
}
//
forEachUbuntuImage "prepopulate" {
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction =
{ toplevel, ... }:
''
# Start all machines in parallel
start_all()
vm.wait_for_unit("default.target")
${system-manager.lib.prepopulateProfileSnippet {
node = "vm";
profile = toplevel;
}}
vm.systemctl("daemon-reload")
# Simulate a reboot, to check that the services defined with
# system-manager start correctly after a reboot.
# TODO: can we find an easy way to really reboot the VM and not
# loose the root FS state?
vm.systemctl("isolate rescue.target")
# We need to send a return character to dismiss the rescue-mode prompt
vm.send_key("ret")
vm.systemctl("isolate default.target")
vm.wait_for_unit("system-manager.target")
vm.succeed("systemctl status service-9.service")
vm.succeed("test -f /etc/baz/bar/foo2")
vm.succeed("test -f /etc/a/nested/example/foo3")
vm.succeed("test -f /etc/foo.conf")
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
${system-manager.lib.activateProfileSnippet {
node = "vm";
profile = newConfig;
}}
vm.succeed("systemctl status new-service.service")
vm.fail("systemctl status service-9.service")
vm.fail("test -f /etc/a/nested/example/foo3")
vm.fail("test -f /etc/baz/bar/foo2")
vm.succeed("test -f /etc/foo_new")
${system-manager.lib.deactivateProfileSnippet {
node = "vm";
profile = newConfig;
}}
vm.fail("systemctl status new-service.service")
vm.fail("test -f /etc/foo_new")
'';
}
//
forEachUbuntuImage "prepopulate" {
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction = { toplevel, ... }: ''
# Start all machines in parallel
start_all()
forEachUbuntuImage "system-path" {
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction =
{ toplevel, hostPkgs, ... }:
''
# Start all machines in parallel
start_all()
vm.wait_for_unit("default.target")
vm.wait_for_unit("default.target")
vm.fail("bash --login -c '$(which rg)'")
vm.fail("bash --login -c '$(which fd)'")
${system-manager.lib.prepopulateProfileSnippet { node = "vm"; profile = toplevel; }}
vm.systemctl("daemon-reload")
${system-manager.lib.activateProfileSnippet {
node = "vm";
profile = toplevel;
}}
# Simulate a reboot, to check that the services defined with
# system-manager start correctly after a reboot.
# TODO: can we find an easy way to really reboot the VM and not
# loose the root FS state?
vm.systemctl("isolate rescue.target")
# We need to send a return character to dismiss the rescue-mode prompt
vm.send_key("ret")
vm.systemctl("isolate default.target")
vm.wait_for_unit("system-manager.target")
vm.wait_for_unit("system-manager.target")
vm.wait_for_unit("system-manager-path.service")
vm.succeed("systemctl status service-9.service")
vm.succeed("test -f /etc/baz/bar/foo2")
vm.succeed("test -f /etc/a/nested/example/foo3")
vm.succeed("test -f /etc/foo.conf")
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
#vm.fail("bash --login -c '$(which fish)'")
vm.succeed("bash --login -c 'realpath $(which rg) | grep -F ${hostPkgs.ripgrep}/bin/rg'")
vm.succeed("bash --login -c 'realpath $(which fd) | grep -F ${hostPkgs.fd}/bin/fd'")
${system-manager.lib.activateProfileSnippet { node = "vm"; profile = newConfig; }}
vm.succeed("systemctl status new-service.service")
vm.fail("systemctl status service-9.service")
vm.fail("test -f /etc/a/nested/example/foo3")
vm.fail("test -f /etc/baz/bar/foo2")
vm.succeed("test -f /etc/foo_new")
${system-manager.lib.activateProfileSnippet {
node = "vm";
profile = newConfig;
}}
${system-manager.lib.deactivateProfileSnippet { node = "vm"; profile = newConfig; }}
vm.fail("systemctl status new-service.service")
vm.fail("test -f /etc/foo_new")
'';
}
//
forEachUbuntuImage "system-path" {
modules = [
(testModule "old")
../../../examples/example.nix
];
extraPathsToRegister = [ newConfig ];
testScriptFunction = { toplevel, hostPkgs, ... }: ''
# Start all machines in parallel
start_all()
vm.wait_for_unit("default.target")
vm.fail("bash --login -c '$(which rg)'")
vm.fail("bash --login -c '$(which fd)'")
${system-manager.lib.activateProfileSnippet { node = "vm"; profile = toplevel; }}
vm.wait_for_unit("system-manager.target")
vm.wait_for_unit("system-manager-path.service")
#vm.fail("bash --login -c '$(which fish)'")
vm.succeed("bash --login -c 'realpath $(which rg) | grep -F ${hostPkgs.ripgrep}/bin/rg'")
vm.succeed("bash --login -c 'realpath $(which fd) | grep -F ${hostPkgs.fd}/bin/fd'")
${system-manager.lib.activateProfileSnippet { node = "vm"; profile = newConfig; }}
vm.fail("bash --login -c '$(which rg)'")
vm.fail("bash --login -c '$(which fd)'")
vm.succeed("bash --login -c 'realpath $(which fish) | grep -F ${hostPkgs.fish}/bin/fish'")
'';
}
vm.fail("bash --login -c '$(which rg)'")
vm.fail("bash --login -c '$(which fd)'")
vm.succeed("bash --login -c 'realpath $(which fish) | grep -F ${hostPkgs.fish}/bin/fish'")
'';
}