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