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