summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
authorVladimír Čunát <vcunat@gmail.com>2017-08-29 10:51:54 +0200
committerVladimír Čunát <vcunat@gmail.com>2017-08-29 10:51:54 +0200
commit2858c41823db1654f123c455cca2b145b8d8737b (patch)
tree48518e1eb8916d1528d75823c2d1232822d059e9 /nixos/modules/services
parent34b6bbe021baa11a4dddf9532b331dac8d4162f2 (diff)
parente1f755e44faa5745ad3a8b18e18017e77dfbe67c (diff)
downloadnixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar.gz
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar.bz2
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar.lz
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar.xz
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.tar.zst
nixlib-2858c41823db1654f123c455cca2b145b8d8737b.zip
Merge branch 'master' into staging
There were some conflicts in python modules, commented at #28314.
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/misc/airsonic.nix117
-rw-r--r--nixos/modules/services/networking/i2pd.nix198
-rw-r--r--nixos/modules/services/networking/lldpd.nix9
-rw-r--r--nixos/modules/services/printing/cupsd.nix32
-rw-r--r--nixos/modules/services/security/usbguard.nix200
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix3
6 files changed, 444 insertions, 115 deletions
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
new file mode 100644
index 000000000000..b92104787a56
--- /dev/null
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.airsonic;
+in {
+  options = {
+
+    services.airsonic = {
+      enable = mkEnableOption "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)";
+
+      user = mkOption {
+        type = types.str;
+        default = "airsonic";
+        description = "User account under which airsonic runs.";
+      };
+
+      home = mkOption {
+        type = types.path;
+        default = "/var/lib/airsonic";
+        description = ''
+          The directory where Airsonic will create files.
+          Make sure it is writable.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.string;
+        default = "127.0.0.1";
+        description = ''
+          The host name or IP address on which to bind Airsonic.
+          Only relevant if you have multiple network interfaces and want
+          to make Airsonic available on only one of them. The default value
+          will bind Airsonic to all available network interfaces.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 4040;
+        description = ''
+          The port on which Airsonic will listen for
+          incoming HTTP traffic. Set to 0 to disable.
+        '';
+      };
+
+      contextPath = mkOption {
+        type = types.path;
+        default = "/";
+        description = ''
+          The context path, i.e., the last part of the Airsonic
+          URL. Typically '/' or '/airsonic'. Default '/'
+        '';
+      };
+
+      maxMemory = mkOption {
+        type = types.int;
+        default = 100;
+        description = ''
+          The memory limit (max Java heap size) in megabytes.
+          Default: 100
+        '';
+      };
+
+      transcoders = mkOption {
+        type = types.listOf types.path;
+        default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+        defaultText= [ "\${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+        description = ''
+          List of paths to transcoder executables that should be accessible
+          from Airsonic. Symlinks will be created to each executable inside
+          ${cfg.home}/transcoders.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.airsonic = {
+      description = "Airsonic Media Server";
+      after = [ "local-fs.target" "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        # Install transcoders.
+        rm -rf ${cfg.home}/transcode
+        mkdir -p ${cfg.home}/transcode
+        for exe in ${toString cfg.transcoders}; do
+          ln -sf "$exe" ${cfg.home}/transcode
+        done
+      '';
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
+          -Dairsonic.home=${cfg.home} \
+          -Dserver.address=${cfg.listenAddress} \
+          -Dserver.port=${toString cfg.port} \
+          -Dairsonic.contextPath=${cfg.contextPath} \
+          -Djava.awt.headless=true \
+          -verbose:gc \
+          -jar ${pkgs.airsonic}/webapps/airsonic.war
+        '';
+        Restart = "always";
+        User = "airsonic";
+        UMask = "0022";
+      };
+    };
+
+    users.extraUsers.airsonic = {
+      description = "Airsonic service user";
+      name = cfg.user;
+      home = cfg.home;
+      createHome = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 7622f030f832..5c4932075fed 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -28,15 +28,15 @@ let
   };
 
   mkKeyedEndpointOpt = name: addr: port: keyFile:
-  (mkEndpointOpt name addr port) // {
-    keys = mkOption {
-      type = types.str;
-      default = "";
-      description = ''
-        File to persist ${lib.toUpper name} keys.
-      '';
+    (mkEndpointOpt name addr port) // {
+      keys = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          File to persist ${lib.toUpper name} keys.
+        '';
+      };
     };
-  };
 
   commonTunOpts = let
     i2cpOpts = {
@@ -59,7 +59,7 @@ let
       description = "Number of ElGamal/AES tags to send.";
       default = 40;
     };
-   destination = mkOption {
+    destination = mkOption {
       type = types.str;
       description = "Remote endpoint, I2P hostname or b32.i2p address.";
     };
@@ -70,88 +70,91 @@ let
     };
   } // mkEndpointOpt name "127.0.0.1" 0;
 
-  i2pdConf = pkgs.writeText "i2pd.conf"
-  ''
-  ipv4 = ${boolToString cfg.enableIPv4}
-  ipv6 = ${boolToString cfg.enableIPv6}
-  notransit = ${boolToString cfg.notransit}
-  floodfill = ${boolToString cfg.floodfill}
-  netid = ${toString cfg.netid}
-  ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" }
-  ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
-
-  [limits]
-  transittunnels = ${toString cfg.limits.transittunnels}
-
-  [upnp]
-  enabled = ${boolToString cfg.upnp.enable}
-  name = ${cfg.upnp.name}
-
-  [precomputation]
-  elgamal = ${boolToString cfg.precomputation.elgamal}
-
-  [reseed]
-  verify = ${boolToString cfg.reseed.verify}
-  file = ${cfg.reseed.file}
-  urls = ${builtins.concatStringsSep "," cfg.reseed.urls}
-
-  [addressbook]
-  defaulturl = ${cfg.addressbook.defaulturl}
-  subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions}
-  ${flip concatMapStrings
+  i2pdConf = pkgs.writeText "i2pd.conf" ''
+    # DO NOT EDIT -- this file has been generated automatically.
+    loglevel = ${cfg.logLevel}
+
+    ipv4 = ${boolToString cfg.enableIPv4}
+    ipv6 = ${boolToString cfg.enableIPv6}
+    notransit = ${boolToString cfg.notransit}
+    floodfill = ${boolToString cfg.floodfill}
+    netid = ${toString cfg.netid}
+    ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" }
+    ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
+
+    [limits]
+    transittunnels = ${toString cfg.limits.transittunnels}
+
+    [upnp]
+    enabled = ${boolToString cfg.upnp.enable}
+    name = ${cfg.upnp.name}
+
+    [precomputation]
+    elgamal = ${boolToString cfg.precomputation.elgamal}
+
+    [reseed]
+    verify = ${boolToString cfg.reseed.verify}
+    file = ${cfg.reseed.file}
+    urls = ${builtins.concatStringsSep "," cfg.reseed.urls}
+
+    [addressbook]
+    defaulturl = ${cfg.addressbook.defaulturl}
+    subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions}
+
+    ${flip concatMapStrings
       (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
-      (proto: let portStr = toString proto.port; in
-        ''
-          [${proto.name}]
-          enabled = ${boolToString proto.enable}
-          address = ${proto.address}
-          port = ${toString proto.port}
-          ${if proto ? keys then "keys = ${proto.keys}" else ""}
-          ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""}
-          ${if proto ? user then "user = ${proto.user}" else ""}
-          ${if proto ? pass then "pass = ${proto.pass}" else ""}
-          ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""}
-          ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""}
-        '')
-      }
+      (proto: let portStr = toString proto.port; in ''
+        [${proto.name}]
+        enabled = ${boolToString proto.enable}
+        address = ${proto.address}
+        port = ${toString proto.port}
+        ${if proto ? keys then "keys = ${proto.keys}" else ""}
+        ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""}
+        ${if proto ? user then "user = ${proto.user}" else ""}
+        ${if proto ? pass then "pass = ${proto.pass}" else ""}
+        ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""}
+        ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""}
+      '')
+    }
   '';
 
   i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" ''
-  ${flip concatMapStrings
-    (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
-    (tun: let portStr = toString tun.port; in ''
-  [${tun.name}]
-  type = client
-  destination = ${tun.destination}
-  keys = ${tun.keys}
-  address = ${tun.address}
-  port = ${toString tun.port}
-  inbound.length = ${toString tun.inbound.length}
-  outbound.length = ${toString tun.outbound.length}
-  inbound.quantity = ${toString tun.inbound.quantity}
-  outbound.quantity = ${toString tun.outbound.quantity}
-  crypto.tagsToSend = ${toString tun.crypto.tagsToSend}
-  '')
-  }
-  ${flip concatMapStrings
-    (collect (tun: tun ? port && tun ? host) cfg.inTunnels)
-    (tun: let portStr = toString tun.port; in ''
-  [${tun.name}]
-  type = server
-  destination = ${tun.destination}
-  keys = ${tun.keys}
-  host = ${tun.address}
-  port = ${tun.port}
-  inport = ${tun.inPort}
-  accesslist = ${builtins.concatStringsSep "," tun.accessList}
-  '')
-  }
+    # DO NOT EDIT -- this file has been generated automatically.
+    ${flip concatMapStrings
+      (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
+      (tun: let portStr = toString tun.port; in ''
+        [${tun.name}]
+        type = client
+        destination = ${tun.destination}
+        keys = ${tun.keys}
+        address = ${tun.address}
+        port = ${toString tun.port}
+        inbound.length = ${toString tun.inbound.length}
+        outbound.length = ${toString tun.outbound.length}
+        inbound.quantity = ${toString tun.inbound.quantity}
+        outbound.quantity = ${toString tun.outbound.quantity}
+        crypto.tagsToSend = ${toString tun.crypto.tagsToSend}
+      '')
+    }
+    ${flip concatMapStrings
+      (collect (tun: tun ? port && tun ? host) cfg.inTunnels)
+      (tun: let portStr = toString tun.port; in ''
+        [${tun.name}]
+        type = server
+        destination = ${tun.destination}
+        keys = ${tun.keys}
+        host = ${tun.address}
+        port = ${tun.port}
+        inport = ${tun.inPort}
+        accesslist = ${builtins.concatStringsSep "," tun.accessList}
+      '')
+    }
   '';
 
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
-    ${pkgs.i2pd}/bin/i2pd \
-      ${if isNull cfg.extIp then "" else "--host="+cfg.extIp} \
+    exec ${pkgs.i2pd}/bin/i2pd \
+      ${if isNull cfg.address then "" else "--host="+cfg.address} \
       --conf=${i2pdConf} \
       --tunconf=${i2pdTunnelConf}
   '';
@@ -176,11 +179,23 @@ in
         '';
       };
 
-      extIp = mkOption {
+      logLevel = mkOption {
+        type = types.enum ["debug" "info" "warn" "error"];
+        default = "error";
+        description = ''
+          The log level. <command>i2pd</command> defaults to "info"
+          but that generates copious amounts of log messages.
+
+          We default to "error" which is similar to the default log
+          level of <command>tor</command>.
+        '';
+      };
+
+      address = mkOption {
         type = with types; nullOr str;
         default = null;
         description = ''
-          Your external IP.
+          Your external IP or hostname.
         '';
       };
 
@@ -213,7 +228,7 @@ in
         default = null;
         description = ''
            Set a router bandwidth limit integer in KBps.
-           If not set, i2pd defaults to 32KBps.
+           If not set, <command>i2pd</command> defaults to 32KBps.
         '';
       };
 
@@ -261,9 +276,14 @@ in
 
       precomputation.elgamal = mkOption {
         type = types.bool;
-        default = false;
+        default = true;
         description = ''
-          Use ElGamal precomputated tables.
+          Whenever to use precomputated tables for ElGamal.
+          <command>i2pd</command> defaults to <literal>false</literal>
+          to save 64M of memory (and looses some performance).
+
+          We default to <literal>true</literal> as that is what most
+          users want anyway.
         '';
       };
 
@@ -353,7 +373,7 @@ in
         };
       };
 
-      proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4446 "";
+      proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "";
       proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "")
       // {
         outproxy = mkOption {
diff --git a/nixos/modules/services/networking/lldpd.nix b/nixos/modules/services/networking/lldpd.nix
index 4f951d843e2c..ba4e1b1542fe 100644
--- a/nixos/modules/services/networking/lldpd.nix
+++ b/nixos/modules/services/networking/lldpd.nix
@@ -28,16 +28,11 @@ in
     users.extraGroups._lldpd = {};
 
     environment.systemPackages = [ pkgs.lldpd ];
+    systemd.packages = [ pkgs.lldpd ];
 
     systemd.services.lldpd = {
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      requires = [ "network.target" ];
-      serviceConfig = {
-        ExecStart = "${pkgs.lldpd}/bin/lldpd -d ${concatStringsSep " " cfg.extraArgs}";
-        PrivateTmp = true;
-        PrivateDevices = true;
-      };
+      environment.LLDPD_OPTIONS = concatStringsSep " " cfg.extraArgs;
     };
   };
 }
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 855c89303847..4c7f58d1d8bc 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  inherit (pkgs) cups cups-pk-helper cups-filters gutenprint;
+  inherit (pkgs) cups cups-pk-helper cups-filters;
 
   cfg = config.services.printing;
 
@@ -35,7 +35,6 @@ let
     name = "cups-progs";
     paths =
       [ cups.out additionalBackends cups-filters pkgs.ghostscript ]
-      ++ optional cfg.gutenprint gutenprint
       ++ cfg.drivers;
     pathsToLink = [ "/lib" "/share/cups" "/bin" ];
     postBuild = cfg.bindirCmds;
@@ -97,12 +96,15 @@ let
       (writeConf "client.conf" cfg.clientConf)
       (writeConf "snmp.conf" cfg.snmpConf)
     ] ++ optional avahiEnabled browsedFile
-      ++ optional cfg.gutenprint gutenprint
       ++ cfg.drivers;
     pathsToLink = [ "/etc/cups" ];
     ignoreCollisions = true;
   };
 
+  filterGutenprint = pkgs: filter (pkg: pkg.meta.isGutenprint or false == true) pkgs;
+  containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
+  getGutenprint = pkgs: head (filterGutenprint pkgs);
+
 in
 
 {
@@ -224,23 +226,17 @@ in
         '';
       };
 
-      gutenprint = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable Gutenprint drivers for CUPS. This includes auto-updating
-          Gutenprint PPD files.
-        '';
-      };
-
       drivers = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ pkgs.splix ]";
+        example = literalExample "[ pkgs.gutenprint pkgs.hplip pkgs.splix ]";
         description = ''
-          CUPS drivers to use. Drivers provided by CUPS, cups-filters, Ghostscript
-          and Samba are added unconditionally. For adding Gutenprint, see
-          <literal>gutenprint</literal>.
+          CUPS drivers to use. Drivers provided by CUPS, cups-filters,
+          Ghostscript and Samba are added unconditionally. If this list contains
+          Gutenprint (i.e. a derivation with
+          <literal>meta.isGutenprint = true</literal>) the PPD files in
+          <filename>/var/lib/cups/ppd</filename> will be updated automatically
+          to avoid errors due to incompatible versions.
         '';
       };
 
@@ -318,9 +314,9 @@ in
             [ ! -e /var/lib/cups/path ] && \
               ln -s ${bindir} /var/lib/cups/path
 
-            ${optionalString cfg.gutenprint ''
+            ${optionalString (containsGutenprint cfg.drivers) ''
               if [ -d /var/lib/cups/ppd ]; then
-                ${gutenprint}/bin/cups-genppdupdate -p /var/lib/cups/ppd
+                ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
               fi
             ''}
           '';
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
new file mode 100644
index 000000000000..1f2c56a9efa1
--- /dev/null
+++ b/nixos/modules/services/security/usbguard.nix
@@ -0,0 +1,200 @@
+{config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.usbguard;
+
+  # valid policy options
+  policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
+
+  # decide what file to use for rules
+  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
+
+  daemonConf = ''
+      # generated by nixos/modules/services/security/usbguard.nix
+      RuleFile=${ruleFile}
+      ImplicitPolicyTarget=${cfg.implictPolicyTarget}
+      PresentDevicePolicy=${cfg.presentDevicePolicy}
+      PresentControllerPolicy=${cfg.presentControllerPolicy}
+      InsertedDevicePolicy=${cfg.insertedDevicePolicy}
+      RestoreControllerDeviceState=${if cfg.restoreControllerDeviceState then "true" else "false"}
+      # this does not seem useful for endusers to change
+      DeviceManagerBackend=uevent
+      IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
+      IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
+      IPCAccessControlFiles=${cfg.IPCAccessControlFiles}
+      DeviceRulesWithPort=${if cfg.deviceRulesWithPort then "true" else "false"}
+      AuditFilePath=${cfg.auditFilePath}
+    '';
+
+    daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
+
+in {
+
+  ###### interface
+
+  options = {
+    services.usbguard = {
+      enable = mkEnableOption "USBGuard daemon";
+
+      ruleFile = mkOption {
+        type = types.path;
+        default = "/var/lib/usbguard/rules.conf";
+        description = ''
+          The USBGuard daemon will use this file to load the policy rule set
+          from it and to write new rules received via the IPC interface.
+
+          Running the command <literal>usbguard generate-policy</literal> as
+          root will generate a config for your currently plugged in devices.
+          For a in depth guide consult the official documentation.
+
+          Setting the <literal>rules</literal> option will ignore the
+          <literal>ruleFile</literal> option.
+        '';
+      };
+
+      rules = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = ''
+          allow with-interface equals { 08:*:* }
+        '';
+        description = ''
+          The USBGuard daemon will load this policy rule set. Modifying it via
+          the IPC interface won't work if you use this option, since the
+          contents of this option will be written into the nix-store it will be
+          read-only.
+
+          You can still use <literal> usbguard generate-policy</literal> to
+          generate rules, but you would have to insert them here.
+
+          Setting the <literal>rules</literal> option will ignore the
+          <literal>ruleFile</literal> option.
+        '';
+      };
+
+      implictPolicyTarget = mkOption {
+        type = policy;
+        default = "block";
+        description = ''
+          How to treat USB devices that don't match any rule in the policy.
+          Target should be one of allow, block or reject (logically remove the
+          device node from the system).
+        '';
+      };
+
+      presentDevicePolicy = mkOption {
+        type = policy;
+        default = "apply-policy";
+        description = ''
+          How to treat USB devices that are already connected when the daemon
+          starts. Policy should be one of allow, block, reject, keep (keep
+          whatever state the device is currently in) or apply-policy (evaluate
+          the rule set for every present device).
+        '';
+      };
+
+      presentControllerPolicy = mkOption {
+        type = policy;
+        default = "keep";
+        description = ''
+          How to treat USB controller devices that are already connected when
+          the daemon starts. One of allow, block, reject, keep or apply-policy.
+        '';
+      };
+
+      insertedDevicePolicy = mkOption {
+        type = policy;
+        default = "apply-policy";
+        description = ''
+          How to treat USB devices that are already connected after the daemon
+          starts. One of block, reject, apply-policy.
+        '';
+      };
+
+      restoreControllerDeviceState = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          The  USBGuard  daemon  modifies  some attributes of controller
+          devices like the default authorization state of new child device
+          instances. Using this setting, you can controll whether the daemon
+          will try to restore the attribute values to the state before
+          modificaton on shutdown.
+        '';
+      };
+
+      IPCAllowedUsers = mkOption {
+        type = types.listOf types.str;
+        default = [ "root" ];
+        example = [ "root" "yourusername" ];
+        description = ''
+          A list of usernames that the daemon will accept IPC connections from.
+        '';
+      };
+
+      IPCAllowedGroups = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "wheel" ];
+        description = ''
+          A list of groupnames that the daemon will accept IPC connections
+          from.
+        '';
+      };
+
+      IPCAccessControlFiles = mkOption {
+        type = types.path;
+        default = "/var/lib/usbguard/IPCAccessControl.d/";
+        description = ''
+          The files at this location will be interpreted by the daemon as IPC
+          access control definition files. See the IPC ACCESS CONTROL section
+          in <citerefentry><refentrytitle>usbguard-daemon.conf</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for more details.
+        '';
+      };
+
+      deviceRulesWithPort = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Generate device specific rules including the "via-port" attribute.
+        '';
+      };
+
+      auditFilePath = mkOption {
+        type = types.path;
+        default = "/var/log/usbguard/usbguard-audit.log";
+        description = ''
+          USBGuard audit events log file path.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.usbguard ];
+
+    systemd.services.usbguard = {
+      description = "USBGuard daemon";
+
+      wantedBy = [ "basic.target" ];
+      wants = [ "systemd-udevd.service" "local-fs.target" ];
+
+      # make sure an empty rule file and required directories exist
+      preStart = ''mkdir -p $(dirname "${cfg.ruleFile}") "${cfg.IPCAccessControlFiles}" && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})'';
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -d -k -c ${daemonConfFile}'';
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index f9febbfbacdf..e1f4ff5db7f2 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -150,7 +150,8 @@ in {
           PrivateDevices = true;
           ProtectSystem = "full";
           ProtectHome = true;
-          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+          # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
           Type = "notify";
           ExecStart = "${cfg.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${phpIni}";
           ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";