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