1{ config, lib, pkgs, ... }:
2
3let
4
5 inherit (lib.options) literalExample mkEnableOption mkOption;
6 inherit (lib.types) bool enum ints lines attrsOf nullOr path str submodule;
7 inherit (lib.modules) mkDefault mkIf mkMerge;
8
9 commonDescr = ''
10 Values can be either strings or integers
11 (which will be added to the config file verbatimly)
12 or lists thereof
13 (which will be translated to multiple
14 lines with the same configuration key).
15 Boolean values are translated to "Yes" or "No".
16 The default contains some reasonable
17 configuration to yield an operational system.
18 '';
19
20 str1 = lib.types.addCheck str (s: s!=""); # non-empty string
21
22 configAttrType =
23 # Options in HylaFAX configuration files can be
24 # booleans, strings, integers, or list thereof
25 # representing multiple config directives with the same key.
26 # This type definition resolves all
27 # those types into a list of strings.
28 let
29 inherit (lib.types) attrsOf coercedTo int listOf;
30 innerType = coercedTo bool (x: if x then "Yes" else "No")
31 (coercedTo int (toString) str);
32 in
33 attrsOf (coercedTo innerType lib.singleton (listOf innerType));
34
35 cfg = config.services.hylafax;
36
37 modemConfigOptions = { name, config, ... }: {
38 options = {
39 name = mkOption {
40 type = str1;
41 example = "ttyS1";
42 description = ''
43 Name of modem device,
44 will be searched for in <filename>/dev</filename>.
45 '';
46 };
47 type = mkOption {
48 type = str1;
49 example = "cirrus";
50 description = ''
51 Name of modem configuration file,
52 will be searched for in <filename>config</filename>
53 in the spooling area directory.
54 '';
55 };
56 config = mkOption {
57 type = configAttrType;
58 example = {
59 AreaCode = "49";
60 LocalCode = "30";
61 FAXNumber = "123456";
62 LocalIdentifier = "LostInBerlin";
63 };
64 description = ''
65 Attribute set of values for the given modem.
66 ${commonDescr}
67 Options defined here override options in
68 <option>commonModemConfig</option> for this modem.
69 '';
70 };
71 };
72 config.name = mkDefault name;
73 config.config.Include = [ "config/${config.type}" ];
74 };
75
76 defaultConfig =
77 let
78 inherit (config.security) wrapperDir;
79 inherit (config.services.mail.sendmailSetuidWrapper) program;
80 mkIfDefault = cond: value: mkIf cond (mkDefault value);
81 noWrapper = config.services.mail.sendmailSetuidWrapper==null;
82 # If a sendmail setuid wrapper exists,
83 # we add the path to the default configuration file.
84 # Otherwise, we use `false` to provoke
85 # an error if hylafax tries to use it.
86 c.sendmailPath = mkMerge [
87 (mkIfDefault noWrapper "${pkgs.coreutils}/bin/false")
88 (mkIfDefault (!noWrapper) "${wrapperDir}/${program}")
89 ];
90 importDefaultConfig = file:
91 lib.attrsets.mapAttrs
92 (lib.trivial.const mkDefault)
93 (import file { inherit pkgs; });
94 c.commonModemConfig = importDefaultConfig ./modem-default.nix;
95 c.faxqConfig = importDefaultConfig ./faxq-default.nix;
96 c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
97 in
98 c;
99
100 localConfig =
101 let
102 c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
103 c.faxqConfig = lib.attrsets.mapAttrs
104 (lib.trivial.const (v: mkIf (v!=null) v))
105 {
106 AreaCode = cfg.areaCode;
107 CountryCode = cfg.countryCode;
108 LongDistancePrefix = cfg.longDistancePrefix;
109 InternationalPrefix = cfg.internationalPrefix;
110 };
111 c.commonModemConfig = c.faxqConfig;
112 in
113 c;
114
115in
116
117
118{
119
120
121 options.services.hylafax = {
122
123 enable = mkEnableOption "HylaFAX server";
124
125 autostart = mkOption {
126 type = bool;
127 default = true;
128 example = false;
129 description = ''
130 Autostart the HylaFAX queue manager at system start.
131 If this is <literal>false</literal>, the queue manager
132 will still be started if there are pending
133 jobs or if a user tries to connect to it.
134 '';
135 };
136
137 countryCode = mkOption {
138 type = nullOr str1;
139 default = null;
140 example = "49";
141 description = "Country code for server and all modems.";
142 };
143
144 areaCode = mkOption {
145 type = nullOr str1;
146 default = null;
147 example = "30";
148 description = "Area code for server and all modems.";
149 };
150
151 longDistancePrefix = mkOption {
152 type = nullOr str;
153 default = null;
154 example = "0";
155 description = "Long distance prefix for server and all modems.";
156 };
157
158 internationalPrefix = mkOption {
159 type = nullOr str;
160 default = null;
161 example = "00";
162 description = "International prefix for server and all modems.";
163 };
164
165 spoolAreaPath = mkOption {
166 type = path;
167 default = "/var/spool/fax";
168 description = ''
169 The spooling area will be created/maintained
170 at the location given here.
171 '';
172 };
173
174 userAccessFile = mkOption {
175 type = path;
176 default = "/etc/hosts.hfaxd";
177 description = ''
178 The <filename>hosts.hfaxd</filename>
179 file entry in the spooling area
180 will be symlinked to the location given here.
181 This file must exist and be
182 readable only by the <literal>uucp</literal> user.
183 See hosts.hfaxd(5) for details.
184 This configuration permits access for all users:
185 <literal>
186 environment.etc."hosts.hfaxd" = {
187 mode = "0600";
188 user = "uucp";
189 text = ".*";
190 };
191 </literal>
192 Note that host-based access can be controlled with
193 <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
194 by default, only 127.0.0.1 is permitted to connect.
195 '';
196 };
197
198 sendmailPath = mkOption {
199 type = path;
200 example = literalExample "''${pkgs.postfix}/bin/sendmail";
201 # '' ; # fix vim
202 description = ''
203 Path to <filename>sendmail</filename> program.
204 The default uses the local sendmail wrapper
205 (see <option>config.services.mail.sendmailSetuidWrapper</option>),
206 otherwise the <filename>false</filename>
207 binary to cause an error if used.
208 '';
209 };
210
211 hfaxdConfig = mkOption {
212 type = configAttrType;
213 example.RecvqProtection = "0400";
214 description = ''
215 Attribute set of lines for the global
216 hfaxd config file <filename>etc/hfaxd.conf</filename>.
217 ${commonDescr}
218 '';
219 };
220
221 faxqConfig = mkOption {
222 type = configAttrType;
223 example = {
224 InternationalPrefix = "00";
225 LongDistancePrefix = "0";
226 };
227 description = ''
228 Attribute set of lines for the global
229 faxq config file <filename>etc/config</filename>.
230 ${commonDescr}
231 '';
232 };
233
234 commonModemConfig = mkOption {
235 type = configAttrType;
236 example = {
237 InternationalPrefix = "00";
238 LongDistancePrefix = "0";
239 };
240 description = ''
241 Attribute set of default values for
242 modem config files <filename>etc/config.*</filename>.
243 ${commonDescr}
244 Think twice before changing
245 paths of fax-processing scripts.
246 '';
247 };
248
249 modems = mkOption {
250 type = attrsOf (submodule [ modemConfigOptions ]);
251 default = {};
252 example.ttyS1 = {
253 type = "cirrus";
254 config = {
255 FAXNumber = "123456";
256 LocalIdentifier = "Smith";
257 };
258 };
259 description = ''
260 Description of installed modems.
261 At least on modem must be defined
262 to enable the HylaFAX server.
263 '';
264 };
265
266 spoolExtraInit = mkOption {
267 type = lines;
268 default = "";
269 example = "chmod 0755 . # everyone may read my faxes";
270 description = ''
271 Additional shell code that is executed within the
272 spooling area directory right after its setup.
273 '';
274 };
275
276 faxcron.enable.spoolInit = mkEnableOption ''
277 Purge old files from the spooling area with
278 <filename>faxcron</filename>
279 each time the spooling area is initialized.
280 '';
281 faxcron.enable.frequency = mkOption {
282 type = nullOr str1;
283 default = null;
284 example = "daily";
285 description = ''
286 Purge old files from the spooling area with
287 <filename>faxcron</filename> with the given frequency
288 (see systemd.time(7)).
289 '';
290 };
291 faxcron.infoDays = mkOption {
292 type = ints.positive;
293 default = 30;
294 description = ''
295 Set the expiration time for data in the
296 remote machine information directory in days.
297 '';
298 };
299 faxcron.logDays = mkOption {
300 type = ints.positive;
301 default = 30;
302 description = ''
303 Set the expiration time for
304 session trace log files in days.
305 '';
306 };
307 faxcron.rcvDays = mkOption {
308 type = ints.positive;
309 default = 7;
310 description = ''
311 Set the expiration time for files in
312 the received facsimile queue in days.
313 '';
314 };
315
316 faxqclean.enable.spoolInit = mkEnableOption ''
317 Purge old files from the spooling area with
318 <filename>faxqclean</filename>
319 each time the spooling area is initialized.
320 '';
321 faxqclean.enable.frequency = mkOption {
322 type = nullOr str1;
323 default = null;
324 example = "daily";
325 description = ''
326 Purge old files from the spooling area with
327 <filename>faxcron</filename> with the given frequency
328 (see systemd.time(7)).
329 '';
330 };
331 faxqclean.archiving = mkOption {
332 type = enum [ "never" "as-flagged" "always" ];
333 default = "as-flagged";
334 example = "always";
335 description = ''
336 Enable or suppress job archiving:
337 <literal>never</literal> disables job archiving,
338 <literal>as-flagged</literal> archives jobs that
339 have been flagged for archiving by sendfax,
340 <literal>always</literal> forces archiving of all jobs.
341 See also sendfax(1) and faxqclean(8).
342 '';
343 };
344 faxqclean.doneqMinutes = mkOption {
345 type = ints.positive;
346 default = 15;
347 example = literalExample "24*60";
348 description = ''
349 Set the job
350 age threshold (in minutes) that controls how long
351 jobs may reside in the doneq directory.
352 '';
353 };
354 faxqclean.docqMinutes = mkOption {
355 type = ints.positive;
356 default = 60;
357 example = literalExample "24*60";
358 description = ''
359 Set the document
360 age threshold (in minutes) that controls how long
361 unreferenced files may reside in the docq directory.
362 '';
363 };
364
365 };
366
367
368 config.services.hylafax =
369 mkIf
370 (config.services.hylafax.enable)
371 (mkMerge [ defaultConfig localConfig ])
372 ;
373
374}