{ config, lib, pkgs }: with lib; let cfg = config.systemd; lndir = "${pkgs.buildPackages.xorg.lndir}/bin/lndir"; in rec { shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s); mkPathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""]; # a type for options that take a unit name unitNameType = types.strMatching "[a-zA-Z0-9@%:_.\\-]+[.](service|socket|device|mount|automount|swap|target|path|timer|scope|slice)"; makeUnit = name: unit: if unit.enable then pkgs.runCommand "unit-${mkPathSafeName name}" { preferLocalBuild = true; allowSubstitutes = false; inherit (unit) text; } '' mkdir -p $out echo -n "$text" > $out/${shellEscape name} '' else pkgs.runCommand "unit-${mkPathSafeName name}-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."; isPort = i: i >= 0 && i <= 65535; assertPort = name: group: attr: optional (attr ? ${name} && ! isPort attr.${name}) "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number."; assertValueOneOf = name: values: group: attr: optional (attr ? ${name} && !elem attr.${name} values) "Systemd ${group} field `${name}' cannot have value `${toString 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}]"; assertMinimum = name: min: group: attr: optional (attr ? ${name} && attr.${name} < min) "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}"; assertOnlyFields = fields: group: attr: let badFields = filter (name: ! elem name fields) (attrNames attr); in optional (badFields != [ ]) "Systemd ${group} has extra fields [${concatStringsSep " " badFields}]."; assertInt = name: group: attr: optional (attr ? ${name} && !isInt attr.${name}) "Systemd ${group} field `${name}' is not an integer"; 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 and mkIf that we need to # convert into single values before checking them. defs = mapAttrs (const (v: if v._type or "" == "override" then v.content else if v._type or "" == "if" 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 = generateUnits' true; generateUnits' = allowCollisions: 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. packages="${toString cfg.packages}" # Filter duplicate directories declare -A unique_packages for k in $packages ; do unique_packages[$k]=1 ; done for i in ''${!unique_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 # .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 ${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 # 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 ../remote-fs.target $out/multi-user.target.wants/ ''} ''; # */ }