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 ''"''${pkgs.postfix}/bin/sendmail"'';
225 # '' ; # fix vim
226 description = ''
227 Path to {file}`sendmail` program.
228 The default uses the local sendmail wrapper
229 (see {option}`config.services.mail.sendmailSetuidWrapper`),
230 otherwise the {file}`false`
231 binary to cause an error if used.
232 '';
233 };
234
235 hfaxdConfig = mkOption {
236 type = configAttrType;
237 example.RecvqProtection = "0400";
238 description = ''
239 Attribute set of lines for the global
240 hfaxd config file {file}`etc/hfaxd.conf`.
241 ${commonDescr}
242 '';
243 };
244
245 faxqConfig = mkOption {
246 type = configAttrType;
247 example = {
248 InternationalPrefix = "00";
249 LongDistancePrefix = "0";
250 };
251 description = ''
252 Attribute set of lines for the global
253 faxq config file {file}`etc/config`.
254 ${commonDescr}
255 '';
256 };
257
258 commonModemConfig = mkOption {
259 type = configAttrType;
260 example = {
261 InternationalPrefix = "00";
262 LongDistancePrefix = "0";
263 };
264 description = ''
265 Attribute set of default values for
266 modem config files {file}`etc/config.*`.
267 ${commonDescr}
268 Think twice before changing
269 paths of fax-processing scripts.
270 '';
271 };
272
273 modems = mkOption {
274 type = attrsOf (submodule [ modemConfigOptions ]);
275 default = { };
276 example.ttyS1 = {
277 type = "cirrus";
278 config = {
279 FAXNumber = "123456";
280 LocalIdentifier = "Smith";
281 };
282 };
283 description = ''
284 Description of installed modems.
285 At least on modem must be defined
286 to enable the HylaFAX server.
287 '';
288 };
289
290 spoolExtraInit = mkOption {
291 type = lines;
292 default = "";
293 example = "chmod 0755 . # everyone may read my faxes";
294 description = ''
295 Additional shell code that is executed within the
296 spooling area directory right after its setup.
297 '';
298 };
299
300 faxcron.enable.spoolInit = mkEnableOption ''
301 purging old files from the spooling area with
302 {file}`faxcron`
303 each time the spooling area is initialized
304 '';
305 faxcron.enable.frequency = mkOption {
306 type = nullOr nonEmptyStr;
307 default = null;
308 example = "daily";
309 description = ''
310 purging old files from the spooling area with
311 {file}`faxcron` with the given frequency
312 (see {manpage}`systemd.time(7)`)
313 '';
314 };
315 faxcron.infoDays = mkOption {
316 type = ints.positive;
317 default = 30;
318 description = ''
319 Set the expiration time for data in the
320 remote machine information directory in days.
321 '';
322 };
323 faxcron.logDays = mkOption {
324 type = ints.positive;
325 default = 30;
326 description = ''
327 Set the expiration time for
328 session trace log files in days.
329 '';
330 };
331 faxcron.rcvDays = mkOption {
332 type = ints.positive;
333 default = 7;
334 description = ''
335 Set the expiration time for files in
336 the received facsimile queue in days.
337 '';
338 };
339
340 faxqclean.enable.spoolInit = mkEnableOption ''
341 purging old files from the spooling area with
342 {file}`faxqclean`
343 each time the spooling area is initialized
344 '';
345 faxqclean.enable.frequency = mkOption {
346 type = nullOr nonEmptyStr;
347 default = null;
348 example = "daily";
349 description = ''
350 Purge old files from the spooling area with
351 {file}`faxcron` with the given frequency
352 (see {manpage}`systemd.time(7)`).
353 '';
354 };
355 faxqclean.archiving = mkOption {
356 type = enum [
357 "never"
358 "as-flagged"
359 "always"
360 ];
361 default = "as-flagged";
362 example = "always";
363 description = ''
364 Enable or suppress job archiving:
365 `never` disables job archiving,
366 `as-flagged` archives jobs that
367 have been flagged for archiving by sendfax,
368 `always` forces archiving of all jobs.
369 See also {manpage}`sendfax(1)` and {manpage}`faxqclean(8)`.
370 '';
371 };
372 faxqclean.doneqMinutes = mkOption {
373 type = ints.positive;
374 default = 15;
375 example = literalExpression "24*60";
376 description = ''
377 Set the job
378 age threshold (in minutes) that controls how long
379 jobs may reside in the doneq directory.
380 '';
381 };
382 faxqclean.docqMinutes = mkOption {
383 type = ints.positive;
384 default = 60;
385 example = literalExpression "24*60";
386 description = ''
387 Set the document
388 age threshold (in minutes) that controls how long
389 unreferenced files may reside in the docq directory.
390 '';
391 };
392
393 };
394
395 config.services.hylafax = mkIf (config.services.hylafax.enable) (mkMerge [
396 defaultConfig
397 localConfig
398 ]);
399
400}