diff options
Diffstat (limited to 'modules/networkd/systemd-lib.nix')
| -rw-r--r-- | modules/networkd/systemd-lib.nix | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/modules/networkd/systemd-lib.nix b/modules/networkd/systemd-lib.nix new file mode 100644 index 00000000..2dbf1503 --- /dev/null +++ b/modules/networkd/systemd-lib.nix | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | { config, lib, pkgs }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | cfg = config.systemd; | ||
| 7 | lndir = "${pkgs.xorg.lndir}/bin/lndir"; | ||
| 8 | in rec { | ||
| 9 | |||
| 10 | shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s); | ||
| 11 | |||
| 12 | mkPathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""]; | ||
| 13 | |||
| 14 | makeUnit = name: unit: | ||
| 15 | if unit.enable then | ||
| 16 | pkgs.runCommand "unit-${mkPathSafeName name}" | ||
| 17 | { preferLocalBuild = true; | ||
| 18 | allowSubstitutes = false; | ||
| 19 | inherit (unit) text; | ||
| 20 | } | ||
| 21 | '' | ||
| 22 | mkdir -p $out | ||
| 23 | echo -n "$text" > $out/${shellEscape name} | ||
| 24 | '' | ||
| 25 | else | ||
| 26 | pkgs.runCommand "unit-${mkPathSafeName name}-disabled" | ||
| 27 | { preferLocalBuild = true; | ||
| 28 | allowSubstitutes = false; | ||
| 29 | } | ||
| 30 | '' | ||
| 31 | mkdir -p $out | ||
| 32 | ln -s /dev/null $out/${shellEscape name} | ||
| 33 | ''; | ||
| 34 | |||
| 35 | boolValues = [true false "yes" "no"]; | ||
| 36 | |||
| 37 | digits = map toString (range 0 9); | ||
| 38 | |||
| 39 | isByteFormat = s: | ||
| 40 | let | ||
| 41 | l = reverseList (stringToCharacters s); | ||
| 42 | suffix = head l; | ||
| 43 | nums = tail l; | ||
| 44 | in elem suffix (["K" "M" "G" "T"] ++ digits) | ||
| 45 | && all (num: elem num digits) nums; | ||
| 46 | |||
| 47 | assertByteFormat = name: group: attr: | ||
| 48 | optional (attr ? ${name} && ! isByteFormat attr.${name}) | ||
| 49 | "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT]."; | ||
| 50 | |||
| 51 | hexChars = stringToCharacters "0123456789abcdefABCDEF"; | ||
| 52 | |||
| 53 | isMacAddress = s: stringLength s == 17 | ||
| 54 | && flip all (splitString ":" s) (bytes: | ||
| 55 | all (byte: elem byte hexChars) (stringToCharacters bytes) | ||
| 56 | ); | ||
| 57 | |||
| 58 | assertMacAddress = name: group: attr: | ||
| 59 | optional (attr ? ${name} && ! isMacAddress attr.${name}) | ||
| 60 | "Systemd ${group} field `${name}' must be a valid mac address."; | ||
| 61 | |||
| 62 | isPort = i: i >= 0 && i <= 65535; | ||
| 63 | |||
| 64 | assertPort = name: group: attr: | ||
| 65 | optional (attr ? ${name} && ! isPort attr.${name}) | ||
| 66 | "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number."; | ||
| 67 | |||
| 68 | assertValueOneOf = name: values: group: attr: | ||
| 69 | optional (attr ? ${name} && !elem attr.${name} values) | ||
| 70 | "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'."; | ||
| 71 | |||
| 72 | assertHasField = name: group: attr: | ||
| 73 | optional (!(attr ? ${name})) | ||
| 74 | "Systemd ${group} field `${name}' must exist."; | ||
| 75 | |||
| 76 | assertRange = name: min: max: group: attr: | ||
| 77 | optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name})) | ||
| 78 | "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]"; | ||
| 79 | |||
| 80 | assertMinimum = name: min: group: attr: | ||
| 81 | optional (attr ? ${name} && attr.${name} < min) | ||
| 82 | "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}"; | ||
| 83 | |||
| 84 | assertOnlyFields = fields: group: attr: | ||
| 85 | let badFields = filter (name: ! elem name fields) (attrNames attr); in | ||
| 86 | optional (badFields != [ ]) | ||
| 87 | "Systemd ${group} has extra fields [${concatStringsSep " " badFields}]."; | ||
| 88 | |||
| 89 | assertInt = name: group: attr: | ||
| 90 | optional (attr ? ${name} && !isInt attr.${name}) | ||
| 91 | "Systemd ${group} field `${name}' is not an integer"; | ||
| 92 | |||
| 93 | checkUnitConfig = group: checks: attrs: let | ||
| 94 | # We're applied at the top-level type (attrsOf unitOption), so the actual | ||
| 95 | # unit options might contain attributes from mkOverride and mkIf that we need to | ||
| 96 | # convert into single values before checking them. | ||
| 97 | defs = mapAttrs (const (v: | ||
| 98 | if v._type or "" == "override" then v.content | ||
| 99 | else if v._type or "" == "if" then v.content | ||
| 100 | else v | ||
| 101 | )) attrs; | ||
| 102 | errors = concatMap (c: c group defs) checks; | ||
| 103 | in if errors == [] then true | ||
| 104 | else builtins.trace (concatStringsSep "\n" errors) false; | ||
| 105 | |||
| 106 | toOption = x: | ||
| 107 | if x == true then "true" | ||
| 108 | else if x == false then "false" | ||
| 109 | else toString x; | ||
| 110 | |||
| 111 | attrsToSection = as: | ||
| 112 | concatStrings (concatLists (mapAttrsToList (name: value: | ||
| 113 | map (x: '' | ||
| 114 | ${name}=${toOption x} | ||
| 115 | '') | ||
| 116 | (if isList value then value else [value])) | ||
| 117 | as)); | ||
| 118 | |||
| 119 | generateUnits = generateUnits' true; | ||
| 120 | |||
| 121 | generateUnits' = allowCollisions: type: units: upstreamUnits: upstreamWants: | ||
| 122 | pkgs.runCommand "${type}-units" | ||
| 123 | { preferLocalBuild = true; | ||
| 124 | allowSubstitutes = false; | ||
| 125 | } '' | ||
| 126 | mkdir -p $out | ||
| 127 | |||
| 128 | # Copy the upstream systemd units we're interested in. | ||
| 129 | for i in ${toString upstreamUnits}; do | ||
| 130 | fn=${cfg.package}/example/systemd/${type}/$i | ||
| 131 | if ! [ -e $fn ]; then echo "missing $fn"; false; fi | ||
| 132 | if [ -L $fn ]; then | ||
| 133 | target="$(readlink "$fn")" | ||
| 134 | if [ ''${target:0:3} = ../ ]; then | ||
| 135 | ln -s "$(readlink -f "$fn")" $out/ | ||
| 136 | else | ||
| 137 | cp -pd $fn $out/ | ||
| 138 | fi | ||
| 139 | else | ||
| 140 | ln -s $fn $out/ | ||
| 141 | fi | ||
| 142 | done | ||
| 143 | |||
| 144 | # Copy .wants links, but only those that point to units that | ||
| 145 | # we're interested in. | ||
| 146 | for i in ${toString upstreamWants}; do | ||
| 147 | fn=${cfg.package}/example/systemd/${type}/$i | ||
| 148 | if ! [ -e $fn ]; then echo "missing $fn"; false; fi | ||
| 149 | x=$out/$(basename $fn) | ||
| 150 | mkdir $x | ||
| 151 | for i in $fn/*; do | ||
| 152 | y=$x/$(basename $i) | ||
| 153 | cp -pd $i $y | ||
| 154 | if ! [ -e $y ]; then rm $y; fi | ||
| 155 | done | ||
| 156 | done | ||
| 157 | |||
| 158 | # Symlink all units provided listed in systemd.packages. | ||
| 159 | packages="${toString cfg.packages}" | ||
| 160 | |||
| 161 | # Filter duplicate directories | ||
| 162 | declare -A unique_packages | ||
| 163 | for k in $packages ; do unique_packages[$k]=1 ; done | ||
| 164 | |||
| 165 | for i in ''${!unique_packages[@]}; do | ||
| 166 | for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do | ||
| 167 | if ! [[ "$fn" =~ .wants$ ]]; then | ||
| 168 | if [[ -d "$fn" ]]; then | ||
| 169 | targetDir="$out/$(basename "$fn")" | ||
| 170 | mkdir -p "$targetDir" | ||
| 171 | ${lndir} "$fn" "$targetDir" | ||
| 172 | else | ||
| 173 | ln -s $fn $out/ | ||
| 174 | fi | ||
| 175 | fi | ||
| 176 | done | ||
| 177 | done | ||
| 178 | |||
| 179 | # Symlink all units defined by systemd.units. If these are also | ||
| 180 | # provided by systemd or systemd.packages, then add them as | ||
| 181 | # <unit-name>.d/overrides.conf, which makes them extend the | ||
| 182 | # upstream unit. | ||
| 183 | for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do | ||
| 184 | fn=$(basename $i/*) | ||
| 185 | if [ -e $out/$fn ]; then | ||
| 186 | if [ "$(readlink -f $i/$fn)" = /dev/null ]; then | ||
| 187 | ln -sfn /dev/null $out/$fn | ||
| 188 | else | ||
| 189 | ${if allowCollisions then '' | ||
| 190 | mkdir -p $out/$fn.d | ||
| 191 | ln -s $i/$fn $out/$fn.d/overrides.conf | ||
| 192 | '' else '' | ||
| 193 | echo "Found multiple derivations configuring $fn!" | ||
| 194 | exit 1 | ||
| 195 | ''} | ||
| 196 | fi | ||
| 197 | else | ||
| 198 | ln -fs $i/$fn $out/ | ||
| 199 | fi | ||
| 200 | done | ||
| 201 | |||
| 202 | # Create service aliases from aliases option. | ||
| 203 | ${concatStrings (mapAttrsToList (name: unit: | ||
| 204 | concatMapStrings (name2: '' | ||
| 205 | ln -sfn '${name}' $out/'${name2}' | ||
| 206 | '') unit.aliases) units)} | ||
| 207 | |||
| 208 | # Create .wants and .requires symlinks from the wantedBy and | ||
| 209 | # requiredBy options. | ||
| 210 | ${concatStrings (mapAttrsToList (name: unit: | ||
| 211 | concatMapStrings (name2: '' | ||
| 212 | mkdir -p $out/'${name2}.wants' | ||
| 213 | ln -sfn '../${name}' $out/'${name2}.wants'/ | ||
| 214 | '') unit.wantedBy) units)} | ||
| 215 | |||
| 216 | ${concatStrings (mapAttrsToList (name: unit: | ||
| 217 | concatMapStrings (name2: '' | ||
| 218 | mkdir -p $out/'${name2}.requires' | ||
| 219 | ln -sfn '../${name}' $out/'${name2}.requires'/ | ||
| 220 | '') unit.requiredBy) units)} | ||
| 221 | |||
| 222 | ${optionalString (type == "system") '' | ||
| 223 | # Stupid misc. symlinks. | ||
| 224 | ln -s ${cfg.defaultUnit} $out/default.target | ||
| 225 | ln -s ${cfg.ctrlAltDelUnit} $out/ctrl-alt-del.target | ||
| 226 | ln -s rescue.target $out/kbrequest.target | ||
| 227 | |||
| 228 | mkdir -p $out/getty.target.wants/ | ||
| 229 | ln -s ../autovt@tty1.service $out/getty.target.wants/ | ||
| 230 | |||
| 231 | ln -s ../local-fs.target ../remote-fs.target \ | ||
| 232 | ../nss-lookup.target ../nss-user-lookup.target ../swap.target \ | ||
| 233 | $out/multi-user.target.wants/ | ||
| 234 | ''} | ||
| 235 | ''; # */ | ||
| 236 | |||
| 237 | } | ||
