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 | } | ||