diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
index 5397845d728a..010c032cc097 100644
--- a/maintainers/maintainer-list.nix
+++ b/maintainers/maintainer-list.nix
@@ -4523,6 +4523,11 @@
github = "y0no";
name = "Yoann Ono";
};
+ yarny = {
+ email = "41838844+Yarny0@users.noreply.github.com";
+ github = "Yarny0";
+ name = "Yarny";
+ };
yarr = {
email = "savraz@gmail.com";
github = "Eternity-Yarr";
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 8292cdc995e0..aafeb997c326 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -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
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3f3123798f59..f51a30aec2e9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -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
diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix
new file mode 100644
index 000000000000..4c63b822d165
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/default.nix
@@ -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
+ .
+ '';
+ }];
+ };
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix
new file mode 100644
index 000000000000..a2630ce66b71
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-default.nix
@@ -0,0 +1,12 @@
+{ ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+ ModemGroup = [ ''"any:.*"'' ];
+ ServerTracing = "0x78701";
+ SessionTracing = "0x78701";
+ UUCPLockDir = "/var/lock";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh
new file mode 100755
index 000000000000..8c39e9d20c18
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh
@@ -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
diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
new file mode 100644
index 000000000000..8999dae57f41
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix
@@ -0,0 +1,10 @@
+{ ... }:
+
+# see man:hfaxd(8)
+
+{
+
+ ServerTracing = "0x91";
+ XferLogFile = "/clientlog";
+
+}
diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix
new file mode 100644
index 000000000000..7529b5b0aafd
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/modem-default.nix
@@ -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
+
+}
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
new file mode 100644
index 000000000000..4ac6d3fa8432
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -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 /dev.
+ '';
+ };
+ type = mkOption {
+ type = str1;
+ example = "cirrus";
+ description = ''
+ Name of modem configuration file,
+ will be searched for in config
+ 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
+ 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 false, 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 hosts.hfaxd
+ file entry in the spooling area
+ will be symlinked to the location given here.
+ This file must exist and be
+ readable only by the uucp user.
+ See hosts.hfaxd(5) for details.
+ This configuration permits access for all users:
+
+ environment.etc."hosts.hfaxd" = {
+ mode = "0600";
+ user = "uucp";
+ text = ".*";
+ };
+
+ Note that host-based access can be controlled with
+ ;
+ 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 sendmail program.
+ The default uses the local sendmail wrapper
+ (see ),
+ otherwise the false
+ 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 etc/hfaxd.conf.
+ ${commonDescr}
+ '';
+ };
+
+ faxqConfig = mkOption {
+ type = configAttrType;
+ example = {
+ InternationalPrefix = "00";
+ LongDistancePrefix = "0";
+ };
+ description = ''
+ Attribute set of lines for the global
+ faxq config file etc/config.
+ ${commonDescr}
+ '';
+ };
+
+ commonModemConfig = mkOption {
+ type = configAttrType;
+ example = {
+ InternationalPrefix = "00";
+ LongDistancePrefix = "0";
+ };
+ description = ''
+ Attribute set of default values for
+ modem config files etc/config.*.
+ ${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
+ faxcron
+ 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
+ faxcron 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
+ faxqclean
+ 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
+ faxcron 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:
+ never disables job archiving,
+ as-flagged archives jobs that
+ have been flagged for archiving by sendfax,
+ always 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 ])
+ ;
+
+}
diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh
new file mode 100755
index 000000000000..31e930e8c597
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/spool.sh
@@ -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@"/*
diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix
new file mode 100644
index 000000000000..91d9c1a37da6
--- /dev/null
+++ b/nixos/modules/services/networking/hylafax/systemd.nix
@@ -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);
+ };
+}
diff --git a/pkgs/servers/hylafaxplus/config.site b/pkgs/servers/hylafaxplus/config.site
new file mode 100644
index 000000000000..7c8014449216
--- /dev/null
+++ b/pkgs/servers/hylafaxplus/config.site
@@ -0,0 +1,20 @@
+@config_maxgid@
+DIR_BIN="@out_@/bin"
+DIR_FONTMAP="@out_@/share/ghostscript/@ghostscript_version@"
+DIR_LIB="@out_@/lib"
+DIR_LIBDATA="@out_@/spool/etc"
+DIR_LIBEXEC="@out_@/spool/bin"
+DIR_LOCKS=/var/lock
+DIR_MAN="@out_@/share/man"
+DIR_SBIN="@out_@/spool/bin"
+DIR_SPOOL="@out_@/spool"
+FONTMAP="@ghostscript@/share/ghostscript/@ghostscript_version@"
+PATH_AFM="@ghostscript@/share/ghostscript/fonts"
+PATH_DPSRIP="@out_@/spool/bin/ps2fax"
+PATH_EGETTY="@coreutils@/bin/false"
+PATH_GSRIP="@ghostscript@/bin/gs"
+PATH_IMPRIP="@coreutils@/bin/false"
+PATH_SENDMAIL="@coreutils@/bin/false"
+PATH_VGETTY="@coreutils@/bin/false"
+SYSVINIT=no
+TIFFBIN="@libtiff@/bin"
diff --git a/pkgs/servers/hylafaxplus/default.nix b/pkgs/servers/hylafaxplus/default.nix
new file mode 100644
index 000000000000..410d24974268
--- /dev/null
+++ b/pkgs/servers/hylafaxplus/default.nix
@@ -0,0 +1,95 @@
+{ stdenv
+, lib
+, fakeroot
+, fetchurl
+, libfaketime
+, substituteAll
+## runtime dependencies
+, coreutils
+, file
+, findutils
+, gawk
+, ghostscript
+, gnugrep
+, gnused
+, libtiff
+, psmisc
+, sharutils
+, utillinux
+, zlib
+## optional packages (using `null` disables some functionality)
+, jbigkit ? null
+, lcms2 ? null # for colored faxes
+, openldap ? null
+, pam ? null
+## system-dependent settings that have to be hardcoded
+, maxgid ? 65534 # null -> try to auto-detect (bad on linux)
+, maxuid ? 65534 # null -> hardcoded value 60002
+}:
+
+let
+
+ name = "hylafaxplus-${version}";
+ version = "5.6.0";
+ sha256 = "128514kw9kb5cvznm87z7gis1mpyx4bcqrxx4xa7cbfj1v3v81fr";
+
+ configSite = substituteAll {
+ name = "hylafaxplus-config.site";
+ src = ./config.site;
+ config_maxgid = lib.optionalString (maxgid!=null) ''CONFIG_MAXGID=${builtins.toString maxgid}'';
+ ghostscript_version = ghostscript.version;
+ out_ = "@out@"; # "out" will be resolved in post-install.sh
+ inherit coreutils ghostscript libtiff;
+ };
+
+ postPatch = substituteAll {
+ name = "hylafaxplus-post-patch.sh";
+ src = ./post-patch.sh;
+ inherit configSite;
+ maxuid = lib.optionalString (maxuid!=null) (builtins.toString maxuid);
+ faxcover_binpath = lib.makeBinPath
+ [stdenv.shellPackage coreutils];
+ faxsetup_binpath = lib.makeBinPath
+ [stdenv.shellPackage coreutils findutils gnused gnugrep gawk];
+ };
+
+ postInstall = substituteAll {
+ name = "hylafaxplus-post-install.sh";
+ src = ./post-install.sh;
+ inherit fakeroot libfaketime;
+ };
+
+in
+
+stdenv.mkDerivation {
+ inherit name version;
+ src = fetchurl {
+ url = "mirror://sourceforge/hylafax/hylafax-${version}.tar.gz";
+ inherit sha256;
+ };
+ # Note that `configure` (and maybe `faxsetup`) are looking
+ # for a couple of standard binaries in the `PATH` and
+ # hardcode their absolute paths in the new package.
+ buildInputs = [
+ file # for `file` command
+ ghostscript
+ libtiff
+ psmisc # for `fuser` command
+ sharutils # for `uuencode` command
+ utillinux # for `agetty` command
+ zlib
+ jbigkit # optional
+ lcms2 # optional
+ openldap # optional
+ pam # optional
+ ];
+ postPatch = ''. ${postPatch}'';
+ dontAddPrefix = true;
+ postInstall = ''. ${postInstall}'';
+ postInstallCheck = ''. ${./post-install-check.sh}'';
+ meta.description = "enterprise-class system for sending and receiving facsimiles";
+ meta.homepage = http://hylafax.sourceforge.net;
+ meta.license = lib.licenses.bsd3;
+ meta.maintainers = [ lib.maintainers.yarny ];
+ meta.platforms = lib.platforms.linux;
+}
diff --git a/pkgs/servers/hylafaxplus/post-install-check.sh b/pkgs/servers/hylafaxplus/post-install-check.sh
new file mode 100644
index 000000000000..2850738edccc
--- /dev/null
+++ b/pkgs/servers/hylafaxplus/post-install-check.sh
@@ -0,0 +1,7 @@
+# check if the package contains all the files needed
+for x in faxq faxquit hfaxd faxcron faxqclean faxgetty
+do
+ test -x "$out/spool/bin/$x"
+done
+test -d "$out/spool/config"
+test -f "$out/spool/etc/setup.cache"
diff --git a/pkgs/servers/hylafaxplus/post-install.sh b/pkgs/servers/hylafaxplus/post-install.sh
new file mode 100644
index 000000000000..ddc7c3f85eda
--- /dev/null
+++ b/pkgs/servers/hylafaxplus/post-install.sh
@@ -0,0 +1,24 @@
+# Parts of the `install` make target don't
+# dare to set file modes (or owners), but put the
+# needed commands in a new file called `root.sh`.
+# We execute the `chmod` commands of
+# this script to set execute bits.
+sed '/chown/d;/chgrp/d' --in-place root.sh
+. root.sh
+
+# We run `faxsetup` to prepare some config files
+# that the admin would have to create otherwise.
+# Since `faxsetup` is quite picky about its environment,
+# we have to prepare some dummy files.
+# `faxsetup` stores today's date in the output files,
+# so we employ faketime to simulate a deterministic date.
+echo "uucp:x:0" >> "$TMPDIR/passwd.dummy" # dummy uucp user
+touch "$out/spool/etc/config.dummy" # dummy modem config
+mkdir "$TMPDIR/lock.dummy" # dummy lock dir
+"@libfaketime@/bin/faketime" -f "$(date --utc --date=@$SOURCE_DATE_EPOCH '+%F %T')" \
+ "@fakeroot@/bin/fakeroot" -- \
+ "$out/spool/bin/faxsetup" -with-DIR_LOCKS="$TMPDIR/lock.dummy" -with-PASSWD="$TMPDIR/passwd.dummy"
+rm "$out/spool/etc/config.dummy"
+
+# Ensure all binaries are reachable within the spooling area.
+ln --symbolic --target-directory="$out/spool/bin/" "$out/bin/"*
diff --git a/pkgs/servers/hylafaxplus/post-patch.sh b/pkgs/servers/hylafaxplus/post-patch.sh
new file mode 100644
index 000000000000..6ec5937147e2
--- /dev/null
+++ b/pkgs/servers/hylafaxplus/post-patch.sh
@@ -0,0 +1,25 @@
+# `configure` (maybe others) set `POSIXLY_CORRECT`, which
+# breaks the gcc wrapper script of nixpkgs (maybe others).
+# We simply un-export `POSIXLY_CORRECT` after each export so
+# its effects don't apply within nixpkgs wrapper scripts.
+grep -rlF POSIXLY_CORRECT | xargs \
+ sed '/export *POSIXLY_CORRECT/a export -n POSIXLY_CORRECT' -i
+
+# Replace strange default value for the nobody account.
+if test -n "@maxuid@"
+then
+ for f in util/faxadduser.c hfaxd/manifest.h
+ do
+ substituteInPlace "$f" --replace 60002 "@maxuid@"
+ done
+fi
+
+# Replace hardcoded `PATH` variables with proper paths.
+# Note: `findutils` is needed for `faxcron`.
+substituteInPlace faxcover/edit-faxcover.sh.in \
+ --replace 'PATH=/bin' 'PATH="@faxcover_binpath@"'
+substituteInPlace etc/faxsetup.sh.in \
+ --replace 'PATH=/bin' 'PATH="@faxsetup_binpath@"'
+
+# Create `config.site`
+substitute "@configSite@" config.site --subst-var out
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 487fcccb1928..80721d63914e 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -3200,6 +3200,8 @@ with pkgs;
hwinfo = callPackage ../tools/system/hwinfo { };
+ hylafaxplus = callPackage ../servers/hylafaxplus { };
+
i2c-tools = callPackage ../os-specific/linux/i2c-tools { };
i2p = callPackage ../tools/networking/i2p {};