1# Management of static files in /etc.
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8
9 etc' = filter (f: f.enable) (attrValues config.environment.etc);
10
11 etc = pkgs.runCommandLocal "etc" {
12 # This is needed for the systemd module
13 passthru.targets = map (x: x.target) etc';
14 } /* sh */ ''
15 set -euo pipefail
16
17 makeEtcEntry() {
18 src="$1"
19 target="$2"
20 mode="$3"
21 user="$4"
22 group="$5"
23
24 if [[ "$src" = *'*'* ]]; then
25 # If the source name contains '*', perform globbing.
26 mkdir -p "$out/etc/$target"
27 for fn in $src; do
28 ln -s "$fn" "$out/etc/$target/"
29 done
30 else
31
32 mkdir -p "$out/etc/$(dirname "$target")"
33 if ! [ -e "$out/etc/$target" ]; then
34 ln -s "$src" "$out/etc/$target"
35 else
36 echo "duplicate entry $target -> $src"
37 if [ "$(readlink "$out/etc/$target")" != "$src" ]; then
38 echo "mismatched duplicate entry $(readlink "$out/etc/$target") <-> $src"
39 ret=1
40
41 continue
42 fi
43 fi
44
45 if [ "$mode" != symlink ]; then
46 echo "$mode" > "$out/etc/$target.mode"
47 echo "$user" > "$out/etc/$target.uid"
48 echo "$group" > "$out/etc/$target.gid"
49 fi
50 fi
51 }
52
53 mkdir -p "$out/etc"
54 ${concatMapStringsSep "\n" (etcEntry: escapeShellArgs [
55 "makeEtcEntry"
56 # Force local source paths to be added to the store
57 "${etcEntry.source}"
58 etcEntry.target
59 etcEntry.mode
60 etcEntry.user
61 etcEntry.group
62 ]) etc'}
63 '';
64
65in
66
67{
68
69 imports = [ ../build.nix ];
70
71 ###### interface
72
73 options = {
74
75 environment.etc = mkOption {
76 default = {};
77 example = literalExpression ''
78 { example-configuration-file =
79 { source = "/nix/store/.../etc/dir/file.conf.example";
80 mode = "0440";
81 };
82 "default/useradd".text = "GROUP=100 ...";
83 }
84 '';
85 description = lib.mdDoc ''
86 Set of files that have to be linked in {file}`/etc`.
87 '';
88
89 type = with types; attrsOf (submodule (
90 { name, config, options, ... }:
91 { options = {
92
93 enable = mkOption {
94 type = types.bool;
95 default = true;
96 description = lib.mdDoc ''
97 Whether this /etc file should be generated. This
98 option allows specific /etc files to be disabled.
99 '';
100 };
101
102 target = mkOption {
103 type = types.str;
104 description = lib.mdDoc ''
105 Name of symlink (relative to
106 {file}`/etc`). Defaults to the attribute
107 name.
108 '';
109 };
110
111 text = mkOption {
112 default = null;
113 type = types.nullOr types.lines;
114 description = lib.mdDoc "Text of the file.";
115 };
116
117 source = mkOption {
118 type = types.path;
119 description = lib.mdDoc "Path of the source file.";
120 };
121
122 mode = mkOption {
123 type = types.str;
124 default = "symlink";
125 example = "0600";
126 description = lib.mdDoc ''
127 If set to something else than `symlink`,
128 the file is copied instead of symlinked, with the given
129 file mode.
130 '';
131 };
132
133 uid = mkOption {
134 default = 0;
135 type = types.int;
136 description = lib.mdDoc ''
137 UID of created file. Only takes effect when the file is
138 copied (that is, the mode is not 'symlink').
139 '';
140 };
141
142 gid = mkOption {
143 default = 0;
144 type = types.int;
145 description = lib.mdDoc ''
146 GID of created file. Only takes effect when the file is
147 copied (that is, the mode is not 'symlink').
148 '';
149 };
150
151 user = mkOption {
152 default = "+${toString config.uid}";
153 type = types.str;
154 description = lib.mdDoc ''
155 User name of created file.
156 Only takes effect when the file is copied (that is, the mode is not 'symlink').
157 Changing this option takes precedence over `uid`.
158 '';
159 };
160
161 group = mkOption {
162 default = "+${toString config.gid}";
163 type = types.str;
164 description = lib.mdDoc ''
165 Group name of created file.
166 Only takes effect when the file is copied (that is, the mode is not 'symlink').
167 Changing this option takes precedence over `gid`.
168 '';
169 };
170
171 };
172
173 config = {
174 target = mkDefault name;
175 source = mkIf (config.text != null) (
176 let name' = "etc-" + baseNameOf name;
177 in mkDerivedConfig options.text (pkgs.writeText name')
178 );
179 };
180
181 }));
182
183 };
184
185 };
186
187
188 ###### implementation
189
190 config = {
191
192 system.build.etc = etc;
193 system.build.etcActivationCommands =
194 ''
195 # Set up the statically computed bits of /etc.
196 echo "setting up /etc..."
197 ${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl ${./setup-etc.pl} ${etc}/etc
198 '';
199 };
200
201}