···
1
+
{ config, lib, pkgs, ... }:
6
+
inherit (lib) mkIf mkMerge;
7
+
inherit (lib) concatStringsSep optionalString;
9
+
cfg = config.services.hylafax;
10
+
mapModems = lib.flip map (lib.attrValues cfg.modems);
12
+
mkConfigFile = name: conf:
13
+
# creates hylafax config file,
14
+
# makes sure "Include" is listed *first*
18
+
(lib.flip lib.mapAttrsToList conf
19
+
(k: map (v: ''${k}: ${v}'')
21
+
include = mkLines { Include = conf.Include or []; };
22
+
other = mkLines ( conf // { Include = []; } );
24
+
pkgs.writeText ''hylafax-config${name}''
25
+
(concatStringsSep "\n" (include ++ other));
27
+
globalConfigPath = mkConfigFile "" cfg.faxqConfig;
31
+
mkModemConfigFile = { config, name, ... }:
32
+
mkConfigFile ''.${name}''
33
+
(cfg.commonModemConfig // config);
34
+
mkLine = { name, type, ... }@modem: ''
35
+
# check if modem config file exists:
36
+
test -f "${pkgs.hylafaxplus}/spool/config/${type}"
39
+
--no-target-directory \
40
+
"${mkModemConfigFile modem}" \
41
+
"$out/config.${name}"
44
+
pkgs.runCommand "hylafax-config-modems" {}
45
+
''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
47
+
setupSpoolScript = pkgs.substituteAll {
48
+
name = "hylafax-setup-spool.sh";
50
+
isExecutable = true;
51
+
inherit (pkgs.stdenv) shell;
52
+
hylafax = pkgs.hylafaxplus;
55
+
lockPath = "/var/lock";
56
+
inherit globalConfigPath modemConfigPath;
57
+
inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
60
+
waitFaxqScript = pkgs.substituteAll {
61
+
# This script checks the modems status files
62
+
# and waits until all modems report readiness.
63
+
name = "hylafax-faxq-wait-start.sh";
64
+
src = ./faxq-wait.sh;
65
+
isExecutable = true;
66
+
timeoutSec = toString 10;
67
+
inherit (pkgs.stdenv) shell;
68
+
inherit (cfg) spoolAreaPath;
71
+
sockets."hylafax-hfaxd" = {
72
+
description = "HylaFAX server socket";
73
+
documentation = [ "man:hfaxd(8)" ];
74
+
wantedBy = [ "multi-user.target" ];
75
+
listenStreams = [ "127.0.0.1:4559" ];
76
+
socketConfig.FreeBind = true;
77
+
socketConfig.Accept = true;
80
+
paths."hylafax-faxq" = {
81
+
description = "HylaFAX queue manager sendq watch";
82
+
documentation = [ "man:faxq(8)" "man:sendq(5)" ];
83
+
wantedBy = [ "multi-user.target" ];
84
+
pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
89
+
mkIf (cfg.faxcron.enable.frequency!=null)
90
+
{ "hylafax-faxcron".timerConfig.Persistent = true; }
93
+
mkIf (cfg.faxqclean.enable.frequency!=null)
94
+
{ "hylafax-faxqclean".timerConfig.Persistent = true; }
99
+
# Add some common systemd service hardening settings,
100
+
# but allow each service (here) to override
101
+
# settings by explicitely setting those to `null`.
102
+
# More hardening would be nice but makes
103
+
# customizing hylafax setups very difficult.
104
+
# If at all, it should only be added along
105
+
# with some options to customize it.
108
+
PrivateDevices = true; # breaks /dev/tty...
109
+
PrivateNetwork = true;
111
+
ProtectControlGroups = true;
112
+
#ProtectHome = true; # breaks custom spool dirs
113
+
ProtectKernelModules = true;
114
+
ProtectKernelTunables = true;
115
+
#ProtectSystem = "strict"; # breaks custom spool dirs
116
+
RestrictNamespaces = true;
117
+
RestrictRealtime = true;
119
+
filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
120
+
apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
122
+
service: service // { serviceConfig = apply service; };
124
+
services."hylafax-spool" = {
125
+
description = "HylaFAX spool area preparation";
126
+
documentation = [ "man:hylafax-server(4)" ];
128
+
${setupSpoolScript}
129
+
cd "${cfg.spoolAreaPath}"
130
+
${cfg.spoolExtraInit}
131
+
if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
133
+
echo hosts.hfaxd is missing
137
+
serviceConfig.ExecStop = ''${setupSpoolScript}'';
138
+
serviceConfig.RemainAfterExit = true;
139
+
serviceConfig.Type = "oneshot";
140
+
unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
143
+
services."hylafax-faxq" = {
144
+
description = "HylaFAX queue manager";
145
+
documentation = [ "man:faxq(8)" ];
146
+
requires = [ "hylafax-spool.service" ];
147
+
after = [ "hylafax-spool.service" ];
148
+
wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
149
+
wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
150
+
serviceConfig.Type = "forking";
151
+
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
152
+
# This delays the "readiness" of this service until
153
+
# all modems are initialized (or a timeout is reached).
154
+
# Otherwise, sending a fax with the fax service
155
+
# stopped will always yield a failed send attempt:
156
+
# The fax service is started when the job is created with
157
+
# `sendfax`, but modems need some time to initialize.
158
+
serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
159
+
# faxquit fails if the pipe is already gone
160
+
# (e.g. the service is already stopping)
161
+
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
162
+
# disable some systemd hardening settings
163
+
serviceConfig.PrivateDevices = null;
164
+
serviceConfig.RestrictRealtime = null;
167
+
services."hylafax-hfaxd@" = {
168
+
description = "HylaFAX server";
169
+
documentation = [ "man:hfaxd(8)" ];
170
+
after = [ "hylafax-faxq.service" ];
171
+
requires = [ "hylafax-faxq.service" ];
172
+
serviceConfig.StandardInput = "socket";
173
+
serviceConfig.StandardOutput = "socket";
174
+
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
175
+
unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
176
+
# disable some systemd hardening settings
177
+
serviceConfig.PrivateDevices = null;
178
+
serviceConfig.PrivateNetwork = null;
181
+
services."hylafax-faxcron" = rec {
182
+
description = "HylaFAX spool area maintenance";
183
+
documentation = [ "man:faxcron(8)" ];
184
+
after = [ "hylafax-spool.service" ];
185
+
requires = [ "hylafax-spool.service" ];
186
+
wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
187
+
startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
188
+
serviceConfig.ExecStart = concatStringsSep " " [
189
+
''${pkgs.hylafaxplus}/spool/bin/faxcron''
190
+
''-q "${cfg.spoolAreaPath}"''
191
+
''-info ${toString cfg.faxcron.infoDays}''
192
+
''-log ${toString cfg.faxcron.logDays}''
193
+
''-rcv ${toString cfg.faxcron.rcvDays}''
197
+
services."hylafax-faxqclean" = rec {
198
+
description = "HylaFAX spool area queue cleaner";
199
+
documentation = [ "man:faxqclean(8)" ];
200
+
after = [ "hylafax-spool.service" ];
201
+
requires = [ "hylafax-spool.service" ];
202
+
wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
203
+
startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
204
+
serviceConfig.ExecStart = concatStringsSep " " [
205
+
''${pkgs.hylafaxplus}/spool/bin/faxqclean''
206
+
''-q "${cfg.spoolAreaPath}"''
208
+
(optionalString (cfg.faxqclean.archiving!="never") ''-a'')
209
+
(optionalString (cfg.faxqclean.archiving=="always") ''-A'')
210
+
''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
211
+
''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
215
+
mkFaxgettyService = { name, ... }:
216
+
lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
217
+
description = "HylaFAX faxgetty for %I";
218
+
documentation = [ "man:faxgetty(8)" ];
219
+
bindsTo = [ "dev-%i.device" ];
220
+
requires = [ "hylafax-spool.service" ];
221
+
after = bindsTo ++ requires;
222
+
before = [ "hylafax-faxq.service" "getty.target" ];
223
+
unitConfig.StopWhenUnneeded = true;
224
+
unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
225
+
serviceConfig.UtmpIdentifier = "%I";
226
+
serviceConfig.TTYPath = "/dev/%I";
227
+
serviceConfig.Restart = "always";
228
+
serviceConfig.KillMode = "process";
229
+
serviceConfig.IgnoreSIGPIPE = false;
230
+
serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
231
+
# faxquit fails if the pipe is already gone
232
+
# (e.g. the service is already stopping)
233
+
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
234
+
# disable some systemd hardening settings
235
+
serviceConfig.PrivateDevices = null;
236
+
serviceConfig.RestrictRealtime = null;
240
+
lib.listToAttrs (mapModems mkFaxgettyService);
245
+
config.systemd = mkIf cfg.enable {
246
+
inherit sockets timers paths;
247
+
services = lib.mapAttrs (lib.const hardenService) (services // modemServices);