modules: HylaFAX server configuration

This commit adds the following
* the uucp user
* options for HylaFAX server to control startup and modems
* systemd services for HylaFAX server processes
  including faxgettys for modems
* systemd services to maintain the HylaFAX spool area,
  including cleanup with faxcron and faxqclean
* default configuration for all server processes
  for a minimal working configuration

Some notes:

* HylaFAX configuration cannot be initialized with faxsetup
  (as it would be common on other Linux distributions).
  The hylafaxplus package contains a template spool area.
* Modems are controlled by faxgetty.
  Send-only configuration (modems controlled by faxq)
  is not supported by this configuration setup.
* To enable the service, one or more modems must be defined with
  config.services.hylafax.modems .
* Sending mail *should* work:
  HylaFAX will use whatever is in
  config.services.mail.sendmailSetuidWrapper.program
  unless overridden with the sendmailPath option.
* The admin has to create a hosts.hfaxd file somewhere
  (e.g. in /etc) before enabling HylaFAX.
  This file controls access to the server (see hosts.hfaxd(5) ).
  Sadly, HylaFAX does not permit account-based access
  control as is accepts connections via TCP only.
* Active fax polling should work; I can't test it.
* Passive fax polling is not supported by HylaFAX.
* Pager transmissions (with sendpage) are disabled by default.
  I have never tested or used these.
* Incoming data/voice/"extern"al calls
  won't be handled by default.
  I have never tested or used these.
This commit is contained in:
Yarny0 2018-08-04 17:08:54 +02:00
parent a08b633fe7
commit 12fa95f2d6
10 changed files with 839 additions and 1 deletions

View file

@ -53,7 +53,7 @@
tomcat = 16;
#audio = 17; # unused
#floppy = 18; # unused
#uucp = 19; # unused
uucp = 19;
#lp = 20; # unused
#proc = 21; # unused
pulseaudio = 22; # must match `pulseaudio' GID

View file

@ -517,6 +517,7 @@
./services/networking/heyefi.nix
./services/networking/hostapd.nix
./services/networking/htpdate.nix
./services/networking/hylafax/default.nix
./services/networking/i2pd.nix
./services/networking/i2p.nix
./services/networking/iodine.nix

View file

@ -0,0 +1,29 @@
{ config, lib, pkgs, ... }:
{
imports = [
./options.nix
./systemd.nix
];
config = lib.modules.mkIf config.services.hylafax.enable {
environment.systemPackages = [ pkgs.hylafaxplus ];
users.users.uucp = {
uid = config.ids.uids.uucp;
group = "uucp";
description = "Unix-to-Unix CoPy system";
isSystemUser = true;
inherit (config.users.users.nobody) home;
};
assertions = [{
assertion = config.services.hylafax.modems != {};
message = ''
HylaFAX cannot be used without modems.
Please define at least one modem with
<option>config.services.hylafax</option>.
'';
}];
};
}

View file

@ -0,0 +1,12 @@
{ ... }:
# see man:hylafax-config(5)
{
ModemGroup = [ ''"any:.*"'' ];
ServerTracing = "0x78701";
SessionTracing = "0x78701";
UUCPLockDir = "/var/lock";
}

View file

@ -0,0 +1,29 @@
#! @shell@ -e
# skip this if there are no modems at all
if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
then
exit 0
fi
echo "faxq started, waiting for modem(s) to initialize..."
for i in `seq @timeoutSec@0 -1 0` # gracefully timeout
do
sleep 0.1
# done if status files exist, but don't mention initialization
if \
stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
&& \
! grep --silent --ignore-case 'initializing server' \
"@spoolAreaPath@"/status/*
then
echo "modem(s) apparently ready"
exit 0
fi
# if i reached 0, modems probably failed to initialize
if test $i -eq 0
then
echo "warning: modem initialization timed out"
fi
done

View file

@ -0,0 +1,10 @@
{ ... }:
# see man:hfaxd(8)
{
ServerTracing = "0x91";
XferLogFile = "/clientlog";
}

View file

@ -0,0 +1,22 @@
{ pkgs, ... }:
# see man:hylafax-config(5)
{
TagLineFont = "etc/LiberationSans-25.pcf";
TagLineLocale = ''en_US.UTF-8'';
AdminGroup = "root"; # groups that can change server config
AnswerRotary = "fax"; # don't accept anything else but faxes
LogFileMode = "0640";
PriorityScheduling = true;
RecvFileMode = "0640";
ServerTracing = "0x78701";
SessionTracing = "0x78701";
UUCPLockDir = "/var/lock";
SendPageCmd = ''${pkgs.coreutils}/bin/false''; # prevent pager transmit
SendUUCPCmd = ''${pkgs.coreutils}/bin/false''; # prevent UUCP transmit
}

View file

@ -0,0 +1,375 @@
{ config, lib, pkgs, ... }:
let
inherit (lib.options) literalExample mkEnableOption mkOption;
inherit (lib.types) bool enum int lines loaOf nullOr path str submodule;
inherit (lib.modules) mkDefault mkIf mkMerge;
commonDescr = ''
Values can be either strings or integers
(which will be added to the config file verbatimly)
or lists thereof
(which will be translated to multiple
lines with the same configuration key).
Boolean values are translated to "Yes" or "No".
The default contains some reasonable
configuration to yield an operational system.
'';
str1 = lib.types.addCheck str (s: s!=""); # non-empty string
int1 = lib.types.addCheck int (i: i>0); # positive integer
configAttrType =
# Options in HylaFAX configuration files can be
# booleans, strings, integers, or list thereof
# representing multiple config directives with the same key.
# This type definition resolves all
# those types into a list of strings.
let
inherit (lib.types) attrsOf coercedTo listOf;
innerType = coercedTo bool (x: if x then "Yes" else "No")
(coercedTo int (toString) str);
in
attrsOf (coercedTo innerType lib.singleton (listOf innerType));
cfg = config.services.hylafax;
modemConfigOptions = { name, config, ... }: {
options = {
name = mkOption {
type = str1;
example = "ttyS1";
description = ''
Name of modem device,
will be searched for in <filename>/dev</filename>.
'';
};
type = mkOption {
type = str1;
example = "cirrus";
description = ''
Name of modem configuration file,
will be searched for in <filename>config</filename>
in the spooling area directory.
'';
};
config = mkOption {
type = configAttrType;
example = {
AreaCode = "49";
LocalCode = "30";
FAXNumber = "123456";
LocalIdentifier = "LostInBerlin";
};
description = ''
Attribute set of values for the given modem.
${commonDescr}
Options defined here override options in
<option>commonModemConfig</option> for this modem.
'';
};
};
config.name = mkDefault name;
config.config.Include = [ "config/${config.type}" ];
};
defaultConfig =
let
inherit (config.security) wrapperDir;
inherit (config.services.mail.sendmailSetuidWrapper) program;
mkIfDefault = cond: value: mkIf cond (mkDefault value);
noWrapper = config.services.mail.sendmailSetuidWrapper==null;
# If a sendmail setuid wrapper exists,
# we add the path to the default configuration file.
# Otherwise, we use `false` to provoke
# an error if hylafax tries to use it.
c.sendmailPath = mkMerge [
(mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'')
(mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'')
];
importDefaultConfig = file:
lib.attrsets.mapAttrs
(lib.trivial.const mkDefault)
(import file { inherit pkgs; });
c.commonModemConfig = importDefaultConfig ./modem-default.nix;
c.faxqConfig = importDefaultConfig ./faxq-default.nix;
c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
in
c;
localConfig =
let
c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
c.faxqConfig = lib.attrsets.mapAttrs
(lib.trivial.const (v: mkIf (v!=null) v))
{
AreaCode = cfg.areaCode;
CountryCode = cfg.countryCode;
LongDistancePrefix = cfg.longDistancePrefix;
InternationalPrefix = cfg.internationalPrefix;
};
c.commonModemConfig = c.faxqConfig;
in
c;
in
{
options.services.hylafax = {
enable = mkEnableOption ''HylaFAX server'';
autostart = mkOption {
type = bool;
default = true;
example = false;
description = ''
Autostart the HylaFAX queue manager at system start.
If this is <literal>false</literal>, the queue manager
will still be started if there are pending
jobs or if a user tries to connect to it.
'';
};
countryCode = mkOption {
type = nullOr str1;
default = null;
example = "49";
description = ''Country code for server and all modems.'';
};
areaCode = mkOption {
type = nullOr str1;
default = null;
example = "30";
description = ''Area code for server and all modems.'';
};
longDistancePrefix = mkOption {
type = nullOr str;
default = null;
example = "0";
description = ''Long distance prefix for server and all modems.'';
};
internationalPrefix = mkOption {
type = nullOr str;
default = null;
example = "00";
description = ''International prefix for server and all modems.'';
};
spoolAreaPath = mkOption {
type = path;
default = "/var/spool/fax";
description = ''
The spooling area will be created/maintained
at the location given here.
'';
};
userAccessFile = mkOption {
type = path;
default = "/etc/hosts.hfaxd";
description = ''
The <filename>hosts.hfaxd</filename>
file entry in the spooling area
will be symlinked to the location given here.
This file must exist and be
readable only by the <literal>uucp</literal> user.
See hosts.hfaxd(5) for details.
This configuration permits access for all users:
<literal>
environment.etc."hosts.hfaxd" = {
mode = "0600";
user = "uucp";
text = ".*";
};
</literal>
Note that host-based access can be controlled with
<option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
by default, only 127.0.0.1 is permitted to connect.
'';
};
sendmailPath = mkOption {
type = path;
example = literalExample "''${pkgs.postfix}/bin/sendmail";
# '' ; # fix vim
description = ''
Path to <filename>sendmail</filename> program.
The default uses the local sendmail wrapper
(see <option>config.services.mail.sendmailSetuidWrapper</option>),
otherwise the <filename>false</filename>
binary to cause an error if used.
'';
};
hfaxdConfig = mkOption {
type = configAttrType;
example.RecvqProtection = "0400";
description = ''
Attribute set of lines for the global
hfaxd config file <filename>etc/hfaxd.conf</filename>.
${commonDescr}
'';
};
faxqConfig = mkOption {
type = configAttrType;
example = {
InternationalPrefix = "00";
LongDistancePrefix = "0";
};
description = ''
Attribute set of lines for the global
faxq config file <filename>etc/config</filename>.
${commonDescr}
'';
};
commonModemConfig = mkOption {
type = configAttrType;
example = {
InternationalPrefix = "00";
LongDistancePrefix = "0";
};
description = ''
Attribute set of default values for
modem config files <filename>etc/config.*</filename>.
${commonDescr}
Think twice before changing
paths of fax-processing scripts.
'';
};
modems = mkOption {
type = loaOf (submodule [ modemConfigOptions ]);
default = {};
example.ttyS1 = {
type = "cirrus";
config = {
FAXNumber = "123456";
LocalIdentifier = "Smith";
};
};
description = ''
Description of installed modems.
At least on modem must be defined
to enable the HylaFAX server.
'';
};
spoolExtraInit = mkOption {
type = lines;
default = "";
example = ''chmod 0755 . # everyone may read my faxes'';
description = ''
Additional shell code that is executed within the
spooling area directory right after its setup.
'';
};
faxcron.enable.spoolInit = mkEnableOption ''
Purge old files from the spooling area with
<filename>faxcron</filename>
each time the spooling area is initialized.
'';
faxcron.enable.frequency = mkOption {
type = nullOr str1;
default = null;
example = "daily";
description = ''
Purge old files from the spooling area with
<filename>faxcron</filename> with the given frequency
(see systemd.time(7)).
'';
};
faxcron.infoDays = mkOption {
type = int1;
default = 30;
description = ''
Set the expiration time for data in the
remote machine information directory in days.
'';
};
faxcron.logDays = mkOption {
type = int1;
default = 30;
description = ''
Set the expiration time for
session trace log files in days.
'';
};
faxcron.rcvDays = mkOption {
type = int1;
default = 7;
description = ''
Set the expiration time for files in
the received facsimile queue in days.
'';
};
faxqclean.enable.spoolInit = mkEnableOption ''
Purge old files from the spooling area with
<filename>faxqclean</filename>
each time the spooling area is initialized.
'';
faxqclean.enable.frequency = mkOption {
type = nullOr str1;
default = null;
example = "daily";
description = ''
Purge old files from the spooling area with
<filename>faxcron</filename> with the given frequency
(see systemd.time(7)).
'';
};
faxqclean.archiving = mkOption {
type = enum [ "never" "as-flagged" "always" ];
default = "as-flagged";
example = "always";
description = ''
Enable or suppress job archiving:
<literal>never</literal> disables job archiving,
<literal>as-flagged</literal> archives jobs that
have been flagged for archiving by sendfax,
<literal>always</literal> forces archiving of all jobs.
See also sendfax(1) and faxqclean(8).
'';
};
faxqclean.doneqMinutes = mkOption {
type = int1;
default = 15;
example = literalExample ''24*60'';
description = ''
Set the job
age threshold (in minutes) that controls how long
jobs may reside in the doneq directory.
'';
};
faxqclean.docqMinutes = mkOption {
type = int1;
default = 60;
example = literalExample ''24*60'';
description = ''
Set the document
age threshold (in minutes) that controls how long
unreferenced files may reside in the docq directory.
'';
};
};
config.services.hylafax =
mkIf
(config.services.hylafax.enable)
(mkMerge [ defaultConfig localConfig ])
;
}

View file

@ -0,0 +1,111 @@
#! @shell@ -e
# The following lines create/update the HylaFAX spool directory:
# Subdirectories/files with persistent data are kept,
# other directories/files are removed/recreated,
# mostly from the template spool
# directory in the HylaFAX package.
# This block explains how the spool area is
# derived from the spool template in the HylaFAX package:
#
# + capital letter: directory; file otherwise
# + P/p: persistent directory
# + F/f: directory with symlinks per entry
# + T/t: temporary data
# + S/s: single symlink into package
# |
# | + u: change ownership to uucp:uucp
# | + U: ..also change access mode to user-only
# | |
# archive P U
# bin S
# client T u (client connection info)
# config S
# COPYRIGHT s
# dev T u (maybe some FIFOs)
# docq P U
# doneq P U
# etc F contains customized config files!
# etc/hosts.hfaxd f
# etc/xferfaxlog f
# info P u (database of called devices)
# log P u (communication logs)
# pollq P U
# recvq P u
# sendq P U
# status T u (modem status info files)
# tmp T U
shopt -s dotglob # if bash sees "*", it also includes dot files
lnsym () { ln --symbol "$@" ; }
lnsymfrc () { ln --symbolic --force "$@" ; }
cprd () { cp --remove-destination "$@" ; }
update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
## create/update spooling area
update --mode=0750 -d "@spoolAreaPath@"
cd "@spoolAreaPath@"
persist=(archive docq doneq info log pollq recvq sendq)
# remove entries that don't belong here
touch dummy # ensure "*" resolves to something
for k in *
do
keep=0
for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
do
if test "$k" == "$j"
then
keep=1
break
fi
done
if test "$keep" == "0"
then
rm --recursive "$k"
fi
done
# create persistent data directories (unless they exist already)
update --mode=0700 -d "${persist[@]}"
chmod 0755 info log recvq
# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
touch clientlog faxcron.lastrun xferfaxlog
chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
# create symlinks for frozen directories/files
lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config}
# create empty temporary directories
update --mode=0700 -d client dev status
update -d tmp
## create and fill etc
install -d "@spoolAreaPath@/etc"
cd "@spoolAreaPath@/etc"
# create symlinks to all files in template's etc
lnsym --target-directory=. "@hylafax@/spool/etc"/*
# set LOCKDIR in setup.cache
sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
# etc/{xferfaxlog,lastrun} are stored in the spool root
lnsymfrc --target-directory=. ../xferfaxlog
lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
# etc/hosts.hfaxd is provided by the NixOS configuration
lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
# etc/config and etc/config.${DEVID} must be copied:
# hfaxd reads these file after locking itself up in a chroot
cprd --no-target-directory "@globalConfigPath@" config
cprd --target-directory=. "@modemConfigPath@"/*

View file

@ -0,0 +1,249 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) mkIf mkMerge;
inherit (lib) concatStringsSep optionalString;
cfg = config.services.hylafax;
mapModems = lib.flip map (lib.attrValues cfg.modems);
mkConfigFile = name: conf:
# creates hylafax config file,
# makes sure "Include" is listed *first*
let
mkLines = conf:
(lib.concatLists
(lib.flip lib.mapAttrsToList conf
(k: map (v: ''${k}: ${v}'')
)));
include = mkLines { Include = conf.Include or []; };
other = mkLines ( conf // { Include = []; } );
in
pkgs.writeText ''hylafax-config${name}''
(concatStringsSep "\n" (include ++ other));
globalConfigPath = mkConfigFile "" cfg.faxqConfig;
modemConfigPath =
let
mkModemConfigFile = { config, name, ... }:
mkConfigFile ''.${name}''
(cfg.commonModemConfig // config);
mkLine = { name, type, ... }@modem: ''
# check if modem config file exists:
test -f "${pkgs.hylafaxplus}/spool/config/${type}"
ln \
--symbolic \
--no-target-directory \
"${mkModemConfigFile modem}" \
"$out/config.${name}"
'';
in
pkgs.runCommand "hylafax-config-modems" {}
''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
setupSpoolScript = pkgs.substituteAll {
name = "hylafax-setup-spool.sh";
src = ./spool.sh;
isExecutable = true;
inherit (pkgs.stdenv) shell;
hylafax = pkgs.hylafaxplus;
faxuser = "uucp";
faxgroup = "uucp";
lockPath = "/var/lock";
inherit globalConfigPath modemConfigPath;
inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
};
waitFaxqScript = pkgs.substituteAll {
# This script checks the modems status files
# and waits until all modems report readiness.
name = "hylafax-faxq-wait-start.sh";
src = ./faxq-wait.sh;
isExecutable = true;
timeoutSec = toString 10;
inherit (pkgs.stdenv) shell;
inherit (cfg) spoolAreaPath;
};
sockets."hylafax-hfaxd" = {
description = "HylaFAX server socket";
documentation = [ "man:hfaxd(8)" ];
wantedBy = [ "multi-user.target" ];
listenStreams = [ "127.0.0.1:4559" ];
socketConfig.FreeBind = true;
socketConfig.Accept = true;
};
paths."hylafax-faxq" = {
description = "HylaFAX queue manager sendq watch";
documentation = [ "man:faxq(8)" "man:sendq(5)" ];
wantedBy = [ "multi-user.target" ];
pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
};
timers = mkMerge [
(
mkIf (cfg.faxcron.enable.frequency!=null)
{ "hylafax-faxcron".timerConfig.Persistent = true; }
)
(
mkIf (cfg.faxqclean.enable.frequency!=null)
{ "hylafax-faxqclean".timerConfig.Persistent = true; }
)
];
hardenService =
# Add some common systemd service hardening settings,
# but allow each service (here) to override
# settings by explicitely setting those to `null`.
# More hardening would be nice but makes
# customizing hylafax setups very difficult.
# If at all, it should only be added along
# with some options to customize it.
let
hardening = {
PrivateDevices = true; # breaks /dev/tty...
PrivateNetwork = true;
PrivateTmp = true;
ProtectControlGroups = true;
#ProtectHome = true; # breaks custom spool dirs
ProtectKernelModules = true;
ProtectKernelTunables = true;
#ProtectSystem = "strict"; # breaks custom spool dirs
RestrictNamespaces = true;
RestrictRealtime = true;
};
filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
in
service: service // { serviceConfig = apply service; };
services."hylafax-spool" = {
description = "HylaFAX spool area preparation";
documentation = [ "man:hylafax-server(4)" ];
script = ''
${setupSpoolScript}
cd "${cfg.spoolAreaPath}"
${cfg.spoolExtraInit}
if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
then
echo hosts.hfaxd is missing
exit 1
fi
'';
serviceConfig.ExecStop = ''${setupSpoolScript}'';
serviceConfig.RemainAfterExit = true;
serviceConfig.Type = "oneshot";
unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
};
services."hylafax-faxq" = {
description = "HylaFAX queue manager";
documentation = [ "man:faxq(8)" ];
requires = [ "hylafax-spool.service" ];
after = [ "hylafax-spool.service" ];
wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
serviceConfig.Type = "forking";
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
# This delays the "readiness" of this service until
# all modems are initialized (or a timeout is reached).
# Otherwise, sending a fax with the fax service
# stopped will always yield a failed send attempt:
# The fax service is started when the job is created with
# `sendfax`, but modems need some time to initialize.
serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
# faxquit fails if the pipe is already gone
# (e.g. the service is already stopping)
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
# disable some systemd hardening settings
serviceConfig.PrivateDevices = null;
serviceConfig.RestrictRealtime = null;
};
services."hylafax-hfaxd@" = {
description = "HylaFAX server";
documentation = [ "man:hfaxd(8)" ];
after = [ "hylafax-faxq.service" ];
requires = [ "hylafax-faxq.service" ];
serviceConfig.StandardInput = "socket";
serviceConfig.StandardOutput = "socket";
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
# disable some systemd hardening settings
serviceConfig.PrivateDevices = null;
serviceConfig.PrivateNetwork = null;
};
services."hylafax-faxcron" = rec {
description = "HylaFAX spool area maintenance";
documentation = [ "man:faxcron(8)" ];
after = [ "hylafax-spool.service" ];
requires = [ "hylafax-spool.service" ];
wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
serviceConfig.ExecStart = concatStringsSep " " [
''${pkgs.hylafaxplus}/spool/bin/faxcron''
''-q "${cfg.spoolAreaPath}"''
''-info ${toString cfg.faxcron.infoDays}''
''-log ${toString cfg.faxcron.logDays}''
''-rcv ${toString cfg.faxcron.rcvDays}''
];
};
services."hylafax-faxqclean" = rec {
description = "HylaFAX spool area queue cleaner";
documentation = [ "man:faxqclean(8)" ];
after = [ "hylafax-spool.service" ];
requires = [ "hylafax-spool.service" ];
wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
serviceConfig.ExecStart = concatStringsSep " " [
''${pkgs.hylafaxplus}/spool/bin/faxqclean''
''-q "${cfg.spoolAreaPath}"''
''-v''
(optionalString (cfg.faxqclean.archiving!="never") ''-a'')
(optionalString (cfg.faxqclean.archiving=="always") ''-A'')
''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
];
};
mkFaxgettyService = { name, ... }:
lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
description = "HylaFAX faxgetty for %I";
documentation = [ "man:faxgetty(8)" ];
bindsTo = [ "dev-%i.device" ];
requires = [ "hylafax-spool.service" ];
after = bindsTo ++ requires;
before = [ "hylafax-faxq.service" "getty.target" ];
unitConfig.StopWhenUnneeded = true;
unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
serviceConfig.UtmpIdentifier = "%I";
serviceConfig.TTYPath = "/dev/%I";
serviceConfig.Restart = "always";
serviceConfig.KillMode = "process";
serviceConfig.IgnoreSIGPIPE = false;
serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
# faxquit fails if the pipe is already gone
# (e.g. the service is already stopping)
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
# disable some systemd hardening settings
serviceConfig.PrivateDevices = null;
serviceConfig.RestrictRealtime = null;
};
modemServices =
lib.listToAttrs (mapModems mkFaxgettyService);
in
{
config.systemd = mkIf cfg.enable {
inherit sockets timers paths;
services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
};
}