1{ config, lib, pkgs }:
2
3with lib;
4
5let cfg = config.systemd; in
6
7rec {
8
9 shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s);
10
11 makeUnit = name: unit:
12 let
13 pathSafeName = lib.replaceChars ["@" ":" "\\"] ["-" "-" "-"] name;
14 in
15 if unit.enable then
16 pkgs.runCommand "unit-${pathSafeName}"
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-${pathSafeName}-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
63 assertValueOneOf = name: values: group: attr:
64 optional (attr ? ${name} && !elem attr.${name} values)
65 "Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
66
67 assertHasField = name: group: attr:
68 optional (!(attr ? ${name}))
69 "Systemd ${group} field `${name}' must exist.";
70
71 assertRange = name: min: max: group: attr:
72 optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
73 "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
74
75 assertOnlyFields = fields: group: attr:
76 let badFields = filter (name: ! elem name fields) (attrNames attr); in
77 optional (badFields != [ ])
78 "Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
79
80 checkUnitConfig = group: checks: v:
81 let errors = concatMap (c: c group v) checks; in
82 if errors == [] then true
83 else builtins.trace (concatStringsSep "\n" errors) false;
84
85 toOption = x:
86 if x == true then "true"
87 else if x == false then "false"
88 else toString x;
89
90 attrsToSection = as:
91 concatStrings (concatLists (mapAttrsToList (name: value:
92 map (x: ''
93 ${name}=${toOption x}
94 '')
95 (if isList value then value else [value]))
96 as));
97
98 generateUnits = type: units: upstreamUnits: upstreamWants:
99 pkgs.runCommand "${type}-units"
100 { preferLocalBuild = true;
101 allowSubstitutes = false;
102 } ''
103 mkdir -p $out
104
105 # Copy the upstream systemd units we're interested in.
106 for i in ${toString upstreamUnits}; do
107 fn=${cfg.package}/example/systemd/${type}/$i
108 if ! [ -e $fn ]; then echo "missing $fn"; false; fi
109 if [ -L $fn ]; then
110 target="$(readlink "$fn")"
111 if [ ''${target:0:3} = ../ ]; then
112 ln -s "$(readlink -f "$fn")" $out/
113 else
114 cp -pd $fn $out/
115 fi
116 else
117 ln -s $fn $out/
118 fi
119 done
120
121 # Copy .wants links, but only those that point to units that
122 # we're interested in.
123 for i in ${toString upstreamWants}; do
124 fn=${cfg.package}/example/systemd/${type}/$i
125 if ! [ -e $fn ]; then echo "missing $fn"; false; fi
126 x=$out/$(basename $fn)
127 mkdir $x
128 for i in $fn/*; do
129 y=$x/$(basename $i)
130 cp -pd $i $y
131 if ! [ -e $y ]; then rm $y; fi
132 done
133 done
134
135 # Symlink all units provided listed in systemd.packages.
136 for i in ${toString cfg.packages}; do
137 for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
138 if ! [[ "$fn" =~ .wants$ ]]; then
139 ln -s $fn $out/
140 fi
141 done
142 done
143
144 # Symlink all units defined by systemd.units. If these are also
145 # provided by systemd or systemd.packages, then add them as
146 # <unit-name>.d/overrides.conf, which makes them extend the
147 # upstream unit.
148 for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
149 fn=$(basename $i/*)
150 if [ -e $out/$fn ]; then
151 if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
152 ln -sfn /dev/null $out/$fn
153 else
154 mkdir $out/$fn.d
155 ln -s $i/$fn $out/$fn.d/overrides.conf
156 fi
157 else
158 ln -fs $i/$fn $out/
159 fi
160 done
161
162 # Created .wants and .requires symlinks from the wantedBy and
163 # requiredBy options.
164 ${concatStrings (mapAttrsToList (name: unit:
165 concatMapStrings (name2: ''
166 mkdir -p $out/'${name2}.wants'
167 ln -sfn '../${name}' $out/'${name2}.wants'/
168 '') unit.wantedBy) units)}
169
170 ${concatStrings (mapAttrsToList (name: unit:
171 concatMapStrings (name2: ''
172 mkdir -p $out/'${name2}.requires'
173 ln -sfn '../${name}' $out/'${name2}.requires'/
174 '') unit.requiredBy) units)}
175
176 ${optionalString (type == "system") ''
177 # Stupid misc. symlinks.
178 ln -s ${cfg.defaultUnit} $out/default.target
179
180 ln -s rescue.target $out/kbrequest.target
181
182 mkdir -p $out/getty.target.wants/
183 ln -s ../autovt@tty1.service $out/getty.target.wants/
184
185 ln -s ../local-fs.target ../remote-fs.target ../network.target \
186 ../nss-lookup.target ../nss-user-lookup.target ../swap.target \
187 $out/multi-user.target.wants/
188 ''}
189 ''; # */
190
191}