Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 65 additions & 23 deletions crates/system-manager-engine/src/activate/etc_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,34 +228,35 @@ fn list_static_entries(config_entries: &EtcFilesConfig) -> anyhow::Result<Vec<Et
let dir_content = fs::read_dir(&dir.absolute_path)?;
for file in dir_content {
let file = file?;
let canon_path = fs::canonicalize(file.path()).context(format!(
"Failed to get the canonical path of {}",
file.path().display()
))?;
if canon_path.is_dir() {
log::debug!("{} is a dir", canon_path.display());
let dirname = file.file_name();
let mut path_from_root = dir.path_from_root.clone();
path_from_root.push(dirname);
dirs_to_visit.push(DirToVisit {
absolute_path: canon_path,
path_from_root,
});
} else {
log::debug!("{} is a file", file.path().display());
let target = dir.path_from_root.clone().join(file.file_name());
// Is this file entry available in the config? If so, inherit replace_existing.
let file_path = file.path();
let target_path = dir.path_from_root.clone().join(file.file_name());
// .wants/.requires symlinks use relative targets (e.g. ../unit.service)
// that may not resolve inside the Nix store because the target unit
// can come from the host system (asDropin strategy). Read the symlink
// target directly and store it as the source instead of canonicalizing.
let target_is_systemd_dep =
is_inside_systemd_dependency_dir(&PathBuf::from("/etc").join(&target_path));
if target_is_systemd_dep && file_path.is_symlink() {
let link_target = fs::read_link(&file_path).context(format!(
"Failed to read symlink target of {}",
file_path.display()
))?;
log::debug!(
"{} is a dependency symlink -> {}",
file_path.display(),
link_target.display()
);
let replace_existing = config_entries
.entries
.iter()
.find(|e| e.1.target == target)
.find(|e| e.1.target == target_path)
.map(|e| e.1.replace_existing)
.unwrap_or(false);
let etc_file = EtcFile {
source: StorePath {
store_path: canon_path,
store_path: link_target,
},
target: PathBuf::from("/etc").join(target),
target: PathBuf::from("/etc").join(target_path),
uid: 0,
gid: 0,
group: "".to_string(),
Expand All @@ -264,12 +265,53 @@ fn list_static_entries(config_entries: &EtcFilesConfig) -> anyhow::Result<Vec<Et
replace_existing,
};
log::debug!(
"add file: {:?}, path_from_root: {:?}, absolute_path: {:?}",
"add dependency symlink: {:?}, path_from_root: {:?}",
etc_file,
dir.path_from_root,
dir.absolute_path
dir.path_from_root
);
files.push(etc_file);
} else {
let canon_path = fs::canonicalize(&file_path).context(format!(
"Failed to get the canonical path of {}",
file_path.display()
))?;
if canon_path.is_dir() {
log::debug!("{} is a dir", canon_path.display());
let dirname = file.file_name();
let mut path_from_root = dir.path_from_root.clone();
path_from_root.push(dirname);
dirs_to_visit.push(DirToVisit {
absolute_path: canon_path,
path_from_root,
});
} else {
log::debug!("{} is a file", file_path.display());
let replace_existing = config_entries
.entries
.iter()
.find(|e| e.1.target == target_path)
.map(|e| e.1.replace_existing)
.unwrap_or(false);
let etc_file = EtcFile {
source: StorePath {
store_path: canon_path,
},
target: PathBuf::from("/etc").join(target_path),
uid: 0,
gid: 0,
group: "".to_string(),
user: "".to_string(),
mode: "symlink".to_string(),
replace_existing,
};
log::debug!(
"add file: {:?}, path_from_root: {:?}, absolute_path: {:?}",
etc_file,
dir.path_from_root,
dir.absolute_path
);
files.push(etc_file);
}
}
}
i += 1;
Expand Down
18 changes: 17 additions & 1 deletion nix/modules/environment.nix
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@
Setting a variable to `null` does nothing. You can override a
variable set by another module to `null` to unset it.

Unlike [](#opt-environment.variables), session variables will
append the existing value of the variable using the
`''${parameter:+word}` shell expansion. For example, setting
`XDG_DATA_DIRS` to `"/nix/share"` will produce
`export XDG_DATA_DIRS="/nix/share''${XDG_DATA_DIRS:+:''$XDG_DATA_DIRS}"`,
which preserves any pre-existing value.

Note: unlike NixOS, system-manager does not manage PAM on the
host, so these variables are not injected by pam_env into
non-shell sessions (e.g. graphical logins).
Expand All @@ -125,6 +132,15 @@
config =
let
pathDir = "/run/system-manager/sw";

sessionVarNames = builtins.attrNames config.environment.sessionVariables;

exportLine =
k: v:
if builtins.elem k sessionVarNames then
"export ${k}=\"" + v + "$\{" + k + ":+:$" + k + "}\""
else
"export ${k}=\"" + v + "\"";
in
{
environment = {
Expand All @@ -138,7 +154,7 @@

etc = {
"profile.d/system-manager-path.sh".source = pkgs.writeText "system-manager-path.sh" ''
${lib.concatLines (lib.mapAttrsToList (k: v: ''export ${k}="${v}"'') config.environment.variables)}
${lib.concatLines (lib.mapAttrsToList exportLine config.environment.variables)}
export PATH=${pathDir}/bin:''${PATH}
if [ -d "/etc/profiles/per-user/$USER/bin" ]; then
export PATH="/etc/profiles/per-user/$USER/bin:$PATH"
Expand Down
24 changes: 22 additions & 2 deletions nix/modules/systemd.nix
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding that feature ! Could you add an extra container test for that ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for sure. I've pushed a commit that adds an extra test.

That said, to merge this, we might also need to update the documentation. Previously, the docs stated that we define boot with type raw. I'm not sure exactly how it should be rephrased now, so I'll leave that to you if that's alright.

Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,21 @@ in

for package in $packages
do
for hook in $package/lib/systemd/system/*
for hook in $package/etc/systemd/system/* $package/lib/systemd/system/*
do
ln -s $hook $out/
done
done

for i in ${toString (lib.mapAttrsToList (n: v: v.unit) enabledUnits)}; do
for i in ${
toString (
lib.mapAttrsToList (n: v: v.unit) (
lib.filterAttrs (
n: v: (lib.attrByPath [ "overrideStrategy" ] "asDropinIfExists" v) == "asDropinIfExists"
) enabledUnits
)
)
}; do
fn=$(basename $i/*)
if [ -e $out/$fn ]; then
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
Expand All @@ -273,6 +281,18 @@ in
fi
done

for i in ${
toString (
lib.mapAttrsToList (n: v: v.unit) (
lib.filterAttrs (n: v: v ? overrideStrategy && v.overrideStrategy == "asDropin") enabledUnits
)
)
}; do
fn=$(basename $i/*)
mkdir -p $out/$fn.d
ln -s $i/$fn $out/$fn.d/overrides.conf
done

${lib.concatStrings (
lib.mapAttrsToList (
name: unit:
Expand Down
62 changes: 58 additions & 4 deletions nix/modules/upstream/nixpkgs/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@
nixosModulesPath,
lib,
pkgs,
config,
...
}:
let
modulesTypeDesc = ''
This can either be a list of modules, or an attrset. In an
attrset, names that are set to `true` represent modules that will
be included. Note that setting these names to `false` does not
prevent the module from being loaded.
'';
kernelModulesConf = pkgs.writeText "system-manager.conf" ''
${lib.concatStringsSep "\n" config.boot.kernelModules}
'';
attrNamesToTrue = lib.types.coercedTo (lib.types.listOf lib.types.str) (
enabledList: lib.genAttrs enabledList (_attrName: true)
) (lib.types.attrsOf lib.types.bool);
in
{
imports = [
./dhparams.nix
Expand All @@ -13,6 +28,7 @@
./programs/ssh.nix
./security-wrappers.nix
./security/sudo.nix
./sysctl.nix
./userborn.nix
./users-groups.nix
../sops-nix.nix
Expand All @@ -38,11 +54,27 @@
options =
# We need to ignore a bunch of options that are used in NixOS modules but
# that don't apply to system-manager configs.
# TODO: can we print an informational message for things like kernel modules
# to inform users that they need to be enabled in the host system?
{
boot = lib.mkOption {
type = lib.types.raw;
boot = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problems is that we now implement a subset of the boot options. It doesn't cost us a lot more to stub more of boot options here. Maybe we should just copying the options defined in https://github.com/NixOS/nixpkgs/blob/80bdc1e5ce51f56b19791b52b2901187931f5353/nixos/modules/system/boot/kernel.nix) ? I guess importing that upstream file directly would have been a problem ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think importing it directly wouldn't give us much benefit anyway as a lot of those options don't really make sense in our context, and we'd still need to define extra stubs to make everything work properly.

kernelModules = lib.mkOption {
type = attrNamesToTrue;
default = { };
description = ''
The set of kernel modules to be loaded in the second stage of
the boot process.

${modulesTypeDesc}
'';
apply = mods: lib.attrNames (lib.filterAttrs (_: v: v) mods);
};

kernelPackages = lib.mkOption {
type = lib.types.raw;
default = {
kernel.version = "stub";
};
description = "Stub kernel packages for compatibility; not actively used in system-manager.";
};
};

# nixos/modules/services/system/userborn.nix still depends on activation scripts
Expand Down Expand Up @@ -70,4 +102,26 @@
defaultText = lib.literalExpression "pkgs.glibcLocales";
};
};

config = {
# Create /etc/modules-load.d/system-manager.conf, which is read by
# systemd-modules-load.service to load required kernel modules.
environment.etc = lib.mkIf (config.boot.kernelModules != [ ]) {
"modules-load.d/system-manager.conf".source = kernelModulesConf;
Comment thread
yuxqiu marked this conversation as resolved.
};

systemd.services = {
systemd-modules-load = lib.mkIf (config.boot.kernelModules != [ ]) {
overrideStrategy = "asDropin";
wantedBy = [
"system-manager.target"
"multi-user.target"
];
restartTriggers = [ kernelModulesConf ];
serviceConfig = {
SuccessExitStatus = "0 1";
};
};
};
};
}
82 changes: 82 additions & 0 deletions nix/modules/upstream/nixpkgs/sysctl.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
config,
lib,
...
}:
let

sysctlOption = lib.mkOptionType {
name = "sysctl option value";
check =
val:
let
checkType = x: lib.isBool x || lib.isString x || lib.isInt x || x == null;
in
checkType val || (val._type or "" == "override" && checkType val.content);
merge = loc: defs: lib.mergeOneOption loc defs;
};

in
{

options = {

boot.kernel.sysctl = lib.mkOption {
type =
let
highestValueType = lib.types.ints.unsigned // {
merge = loc: defs: lib.foldl (a: b: if b.value == null then null else lib.max a b.value) 0 defs;
};
in
lib.types.submodule {
freeformType = lib.types.attrsOf sysctlOption;
options = {
"net.core.rmem_max" = lib.mkOption {
type = lib.types.nullOr highestValueType;
default = null;
description = "The maximum receive socket buffer size in bytes. In case of conflicting values, the highest will be used.";
};

"net.core.wmem_max" = lib.mkOption {
type = lib.types.nullOr highestValueType;
default = null;
description = "The maximum send socket buffer size in bytes. In case of conflicting values, the highest will be used.";
};
};
};
default = { };
example = lib.literalExpression ''
{ "net.ipv4.tcp_syncookies" = false; "vm.swappiness" = 60; }
'';
description = ''
Runtime parameters of the Linux kernel, as set by
{manpage}`sysctl(8)`. Note that sysctl
parameters names must be enclosed in quotes
(e.g. `"vm.swappiness"` instead of
`vm.swappiness`). The value of each
parameter may be a string, integer, boolean, or null
(signifying the option will not appear at all).
'';

};

};

config = {

environment.etc = lib.mkIf (config.boot.kernel.sysctl != { }) {
"sysctl.d/60-system-manager.conf".text = lib.concatStrings (
lib.mapAttrsToList (
n: v: lib.optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
) config.boot.kernel.sysctl
);
};

systemd.services.systemd-sysctl = lib.mkIf (config.boot.kernel.sysctl != { }) {
overrideStrategy = "asDropin";
wantedBy = [ "system-manager.target" ];
restartTriggers = [ config.environment.etc."sysctl.d/60-system-manager.conf".source ];
};

};
}
Loading