nixpkgs/nixos/modules/system/boot/systemd-lib.nix
aszlig 0e7c945e15
nixos/systemd: Allow to override serviceConfig
This has been reported by @qknight in his Stack Overflow question:

https://stackoverflow.com/q/50678639

The correct way to override a single value would be to use something
like this:

systemd.services.nagios.serviceConfig.Restart = lib.mkForce "no";

However, this doesn't work because the check is applied for the attrsOf
type and thus the attribute values might still contain the attribute set
created by mkOverride.

The unitOption type however did already account for this, but at this
stage it's already too late.

So now the actual value is unpacked while checking the values of the
attribute set, which should allow us to override values in
serviceConfig.

Signed-off-by: aszlig <aszlig@nix.build>
Cc: @edolstra, @qknight
2018-06-04 15:34:21 +02:00

210 lines
6.7 KiB
Nix

{ config, lib, pkgs }:
with lib;
let
cfg = config.systemd;
lndir = "${pkgs.xorg.lndir}/bin/lndir";
in rec {
shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s);
makeUnit = name: unit:
let
pathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""] name;
in
if unit.enable then
pkgs.runCommand "unit-${pathSafeName}"
{ preferLocalBuild = true;
allowSubstitutes = false;
inherit (unit) text;
}
''
mkdir -p $out
echo -n "$text" > $out/${shellEscape name}
''
else
pkgs.runCommand "unit-${pathSafeName}-disabled"
{ preferLocalBuild = true;
allowSubstitutes = false;
}
''
mkdir -p $out
ln -s /dev/null $out/${shellEscape name}
'';
boolValues = [true false "yes" "no"];
digits = map toString (range 0 9);
isByteFormat = s:
let
l = reverseList (stringToCharacters s);
suffix = head l;
nums = tail l;
in elem suffix (["K" "M" "G" "T"] ++ digits)
&& all (num: elem num digits) nums;
assertByteFormat = name: group: attr:
optional (attr ? ${name} && ! isByteFormat attr.${name})
"Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
hexChars = stringToCharacters "0123456789abcdefABCDEF";
isMacAddress = s: stringLength s == 17
&& flip all (splitString ":" s) (bytes:
all (byte: elem byte hexChars) (stringToCharacters bytes)
);
assertMacAddress = name: group: attr:
optional (attr ? ${name} && ! isMacAddress attr.${name})
"Systemd ${group} field `${name}' must be a valid mac address.";
assertValueOneOf = name: values: group: attr:
optional (attr ? ${name} && !elem attr.${name} values)
"Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
assertHasField = name: group: attr:
optional (!(attr ? ${name}))
"Systemd ${group} field `${name}' must exist.";
assertRange = name: min: max: group: attr:
optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
"Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
assertOnlyFields = fields: group: attr:
let badFields = filter (name: ! elem name fields) (attrNames attr); in
optional (badFields != [ ])
"Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
checkUnitConfig = group: checks: attrs: let
# We're applied at the top-level type (attrsOf unitOption), so the actual
# unit options might contain attributes from mkOverride that we need to
# convert into single values before checking them.
defs = mapAttrs (const (v:
if v._type or "" == "override" then v.content else v
)) attrs;
errors = concatMap (c: c group defs) checks;
in if errors == [] then true
else builtins.trace (concatStringsSep "\n" errors) false;
toOption = x:
if x == true then "true"
else if x == false then "false"
else toString x;
attrsToSection = as:
concatStrings (concatLists (mapAttrsToList (name: value:
map (x: ''
${name}=${toOption x}
'')
(if isList value then value else [value]))
as));
generateUnits = type: units: upstreamUnits: upstreamWants:
pkgs.runCommand "${type}-units"
{ preferLocalBuild = true;
allowSubstitutes = false;
} ''
mkdir -p $out
# Copy the upstream systemd units we're interested in.
for i in ${toString upstreamUnits}; do
fn=${cfg.package}/example/systemd/${type}/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
if [ -L $fn ]; then
target="$(readlink "$fn")"
if [ ''${target:0:3} = ../ ]; then
ln -s "$(readlink -f "$fn")" $out/
else
cp -pd $fn $out/
fi
else
ln -s $fn $out/
fi
done
# Copy .wants links, but only those that point to units that
# we're interested in.
for i in ${toString upstreamWants}; do
fn=${cfg.package}/example/systemd/${type}/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
x=$out/$(basename $fn)
mkdir $x
for i in $fn/*; do
y=$x/$(basename $i)
cp -pd $i $y
if ! [ -e $y ]; then rm $y; fi
done
done
# Symlink all units provided listed in systemd.packages.
for i in ${toString cfg.packages}; do
for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
if ! [[ "$fn" =~ .wants$ ]]; then
if [[ -d "$fn" ]]; then
targetDir="$out/$(basename "$fn")"
mkdir -p "$targetDir"
${lndir} "$fn" "$targetDir"
else
ln -s $fn $out/
fi
fi
done
done
# Symlink all units defined by systemd.units. If these are also
# provided by systemd or systemd.packages, then add them as
# <unit-name>.d/overrides.conf, which makes them extend the
# upstream unit.
for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
fn=$(basename $i/*)
if [ -e $out/$fn ]; then
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
ln -sfn /dev/null $out/$fn
else
mkdir -p $out/$fn.d
ln -s $i/$fn $out/$fn.d/overrides.conf
fi
else
ln -fs $i/$fn $out/
fi
done
# Create service aliases from aliases option.
${concatStrings (mapAttrsToList (name: unit:
concatMapStrings (name2: ''
ln -sfn '${name}' $out/'${name2}'
'') unit.aliases) units)}
# Create .wants and .requires symlinks from the wantedBy and
# requiredBy options.
${concatStrings (mapAttrsToList (name: unit:
concatMapStrings (name2: ''
mkdir -p $out/'${name2}.wants'
ln -sfn '../${name}' $out/'${name2}.wants'/
'') unit.wantedBy) units)}
${concatStrings (mapAttrsToList (name: unit:
concatMapStrings (name2: ''
mkdir -p $out/'${name2}.requires'
ln -sfn '../${name}' $out/'${name2}.requires'/
'') unit.requiredBy) units)}
${optionalString (type == "system") ''
# Stupid misc. symlinks.
ln -s ${cfg.defaultUnit} $out/default.target
ln -s ${cfg.ctrlAltDelUnit} $out/ctrl-alt-del.target
ln -s rescue.target $out/kbrequest.target
mkdir -p $out/getty.target.wants/
ln -s ../autovt@tty1.service $out/getty.target.wants/
ln -s ../local-fs.target ../remote-fs.target \
../nss-lookup.target ../nss-user-lookup.target ../swap.target \
$out/multi-user.target.wants/
''}
''; # */
}