summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/misc/ids.nix2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix37
-rw-r--r--nixos/modules/security/acme.nix64
-rw-r--r--nixos/modules/services/mail/spamassassin.nix164
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix5
-rw-r--r--nixos/modules/services/networking/dnschain.nix16
-rw-r--r--nixos/modules/services/networking/i2pd.nix3
-rw-r--r--nixos/modules/services/networking/networkmanager.nix21
-rw-r--r--nixos/modules/services/networking/resilio.nix268
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix2
11 files changed, 516 insertions, 67 deletions
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index d217b3452fb5..d7459e3fe91c 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -294,6 +294,7 @@
       jackett = 276;
       aria2 = 277;
       clickhouse = 278;
+      rslsync = 279;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -557,6 +558,7 @@
       jackett = 276;
       aria2 = 277;
       clickhouse = 278;
+      rslsync = 279;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 7afcb9051bd7..47adfa334eed 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -485,6 +485,7 @@
   ./services/networking/radvd.nix
   ./services/networking/rdnssd.nix
   ./services/networking/redsocks.nix
+  ./services/networking/resilio.nix
   ./services/networking/rpcbind.nix
   ./services/networking/sabnzbd.nix
   ./services/networking/searx.nix
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 39176b287843..9452489e2fb4 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -8,13 +8,7 @@ in
   {
     options = {
       programs.zsh.syntaxHighlighting = {
-        enable = mkOption {
-          default = false;
-          type = types.bool;
-          description = ''
-            Enable zsh-syntax-highlighting.
-          '';
-        };
+        enable = mkEnableOption "zsh-syntax-highlighting";
 
         highlighters = mkOption {
           default = [ "main" ];
@@ -38,13 +32,13 @@ in
         };
 
         patterns = mkOption {
-          default = [];
-          type = types.listOf(types.listOf(types.string));
+          default = {};
+          type = types.attrsOf types.string;
 
           example = literalExample ''
-            [
-              ["rm -rf *" "fg=white,bold,bg=red"]
-            ]
+            {
+              "rm -rf *" = "fg=white,bold,bg=red";
+            }
           '';
 
           description = ''
@@ -67,14 +61,17 @@ in
           "ZSH_HIGHLIGHT_HIGHLIGHTERS=(${concatStringsSep " " cfg.highlighters})"
         }
 
-        ${optionalString (length(cfg.patterns) > 0)
-          (assert(elem "pattern" cfg.highlighters); (foldl (
-            a: b:
-              assert(length(b) == 2); ''
-                ${a}
-                ZSH_HIGHLIGHT_PATTERNS+=('${elemAt b 0}' '${elemAt b 1}')
-              ''
-          ) "") cfg.patterns)
+        ${let
+            n = attrNames cfg.patterns;
+          in
+            optionalString (length(n) > 0)
+              (assert(elem "pattern" cfg.highlighters); (foldl (
+                a: b:
+                  ''
+                    ${a}
+                    ZSH_HIGHLIGHT_PATTERNS+=('${b}' '${attrByPath [b] "" cfg.patterns}')
+                  ''
+              ) "") n)
         }
       '';
     };
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 321b9f7f3753..a40c5ef9ebe1 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -13,7 +13,7 @@ let
         description = ''
           Where the webroot of the HTTP vhost is located.
           <filename>.well-known/acme-challenge/</filename> directory
-          will be created automatically if it doesn't exist.
+          will be created below the webroot if it doesn't exist.
           <literal>http://example.org/.well-known/acme-challenge/</literal> must also
           be available (notice unencrypted HTTP).
         '';
@@ -46,7 +46,10 @@ let
       allowKeysForGroup = mkOption {
         type = types.bool;
         default = false;
-        description = "Give read permissions to the specified group to read SSL private certificates.";
+        description = ''
+          Give read permissions to the specified group
+          (<option>security.acme.group</option>) to read SSL private certificates.
+        '';
       };
 
       postRun = mkOption {
@@ -65,21 +68,24 @@ let
           "cert.der" "cert.pem" "chain.pem" "external.sh"
           "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json"
         ]);
-        default = [ "fullchain.pem" "key.pem" "account_key.json" ];
+        default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" ];
         description = ''
           Plugins to enable. With default settings simp_le will
-          store public certificate bundle in <filename>fullchain.pem</filename>
-          and private key in <filename>key.pem</filename> in its state directory.
+          store public certificate bundle in <filename>fullchain.pem</filename>,
+          private key in <filename>key.pem</filename> and those two previous
+          files combined in <filename>full.pem</filename> in its state directory.
         '';
       };
 
       extraDomains = mkOption {
         type = types.attrsOf (types.nullOr types.str);
         default = {};
-        example = {
-          "example.org" = "/srv/http/nginx";
-          "mydomain.org" = null;
-        };
+        example = literalExample ''
+          {
+            "example.org" = "/srv/http/nginx";
+            "mydomain.org" = null;
+          }
+        '';
         description = ''
           Extra domain names for which certificates are to be issued, with their
           own server roots if needed.
@@ -139,17 +145,19 @@ in
         description = ''
           Attribute set of certificates to get signed and renewed.
         '';
-        example = {
-          "example.com" = {
-            webroot = "/var/www/challenges/";
-            email = "foo@example.com";
-            extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
-          };
-          "bar.example.com" = {
-            webroot = "/var/www/challenges/";
-            email = "bar@example.com";
-          };
-        };
+        example = literalExample ''
+          {
+            "example.com" = {
+              webroot = "/var/www/challenges/";
+              email = "foo@example.com";
+              extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
+            };
+            "bar.example.com" = {
+              webroot = "/var/www/challenges/";
+              email = "bar@example.com";
+            };
+          }
+        '';
       };
     };
   };
@@ -238,6 +246,9 @@ in
                       mv $workdir/server.key ${cpath}/key.pem
                       mv $workdir/server.crt ${cpath}/fullchain.pem
 
+                      # Create full.pem for e.g. lighttpd (same format as "simp_le ... -f full.pem" creates)
+                      cat "${cpath}/key.pem" "${cpath}/fullchain.pem" > "${cpath}/full.pem"
+
                       # Clean up working directory
                       rm $workdir/server.csr
                       rm $workdir/server.pass.key
@@ -247,6 +258,8 @@ in
                       chown '${data.user}:${data.group}' '${cpath}/key.pem'
                       chmod ${rights} '${cpath}/fullchain.pem'
                       chown '${data.user}:${data.group}' '${cpath}/fullchain.pem'
+                      chmod ${rights} '${cpath}/full.pem'
+                      chown '${data.user}:${data.group}' '${cpath}/full.pem'
                     '';
                   serviceConfig = {
                     Type = "oneshot";
@@ -275,15 +288,14 @@ in
                 )
               );
           servicesAttr = listToAttrs services;
-          nginxAttr = {
-            nginx = {
-              after = [ "acme-selfsigned-certificates.target" ];
-              wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
-            };
+          injectServiceDep = {
+            after = [ "acme-selfsigned-certificates.target" ];
+            wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
           };
         in
           servicesAttr //
-          (if config.services.nginx.enable then nginxAttr else {});
+          (if config.services.nginx.enable then { nginx = injectServiceDep; } else {}) //
+          (if config.services.lighttpd.enable then { lighttpd = injectServiceDep; } else {});
 
       systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
         ("acme-${cert}")
diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix
index 08953134b3b3..fc8396cd85e9 100644
--- a/nixos/modules/services/mail/spamassassin.nix
+++ b/nixos/modules/services/mail/spamassassin.nix
@@ -3,43 +3,129 @@
 with lib;
 
 let
-
   cfg = config.services.spamassassin;
+  spamassassin-local-cf = pkgs.writeText "local.cf" cfg.config;
+  spamassassin-init-pre = pkgs.writeText "init.pre" cfg.initPreConf;
+
+  spamdEnv = pkgs.buildEnv {
+    name = "spamd-env";
+    paths = [];
+    postBuild = ''
+      ln -sf ${spamassassin-init-pre} $out/init.pre
+      ln -sf ${spamassassin-local-cf} $out/local.cf
+    '';
+  };
 
 in
 
 {
-
-  ###### interface
-
   options = {
 
     services.spamassassin = {
-
       enable = mkOption {
         default = false;
-        description = "Whether to run the SpamAssassin daemon.";
+        description = "Whether to run the SpamAssassin daemon";
       };
 
       debug = mkOption {
         default = false;
-        description = "Whether to run the SpamAssassin daemon in debug mode.";
+        description = "Whether to run the SpamAssassin daemon in debug mode";
       };
 
-    };
+      config = mkOption {
+        type = types.lines;
+        description = ''
+          The SpamAssassin local.cf config
+
+          If you are using this configuration:
+            add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
+
+          Then you can Use this sieve filter:
+            require ["fileinto", "reject", "envelope"];
+            
+            if header :contains "X-Spam-Flag" "YES" {
+              fileinto "spam";
+            }
+
+          Or this procmail filter:
+            :0:
+            * ^X-Spam-Flag: YES
+            /var/vpopmail/domains/lastlog.de/js/.maildir/.spam/new
+
+          To filter your messages based on the additional mail headers added by spamassassin.
+        '';
+        example = ''
+          #rewrite_header Subject [***** SPAM _SCORE_ *****]
+          required_score          5.0
+          use_bayes               1
+          bayes_auto_learn        1
+          add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
+        '';
+        default = "";
+      };
 
+      initPreConf = mkOption {
+        type = types.str;
+        description = "The SpamAssassin init.pre config.";
+        default = 
+        ''          
+          #
+          # to update this list, run this command in the rules directory:
+          # grep 'loadplugin.*Mail::SpamAssassin::Plugin::.*' -o -h * | sort | uniq     
+          #
+
+          #loadplugin Mail::SpamAssassin::Plugin::AccessDB
+          #loadplugin Mail::SpamAssassin::Plugin::AntiVirus
+          loadplugin Mail::SpamAssassin::Plugin::AskDNS
+          # loadplugin Mail::SpamAssassin::Plugin::ASN
+          loadplugin Mail::SpamAssassin::Plugin::AutoLearnThreshold
+          #loadplugin Mail::SpamAssassin::Plugin::AWL
+          loadplugin Mail::SpamAssassin::Plugin::Bayes
+          loadplugin Mail::SpamAssassin::Plugin::BodyEval
+          loadplugin Mail::SpamAssassin::Plugin::Check
+          #loadplugin Mail::SpamAssassin::Plugin::DCC
+          loadplugin Mail::SpamAssassin::Plugin::DKIM
+          loadplugin Mail::SpamAssassin::Plugin::DNSEval
+          loadplugin Mail::SpamAssassin::Plugin::FreeMail
+          loadplugin Mail::SpamAssassin::Plugin::Hashcash
+          loadplugin Mail::SpamAssassin::Plugin::HeaderEval
+          loadplugin Mail::SpamAssassin::Plugin::HTMLEval
+          loadplugin Mail::SpamAssassin::Plugin::HTTPSMismatch
+          loadplugin Mail::SpamAssassin::Plugin::ImageInfo
+          loadplugin Mail::SpamAssassin::Plugin::MIMEEval
+          loadplugin Mail::SpamAssassin::Plugin::MIMEHeader
+          # loadplugin Mail::SpamAssassin::Plugin::PDFInfo
+          #loadplugin Mail::SpamAssassin::Plugin::PhishTag
+          loadplugin Mail::SpamAssassin::Plugin::Pyzor
+          loadplugin Mail::SpamAssassin::Plugin::Razor2
+          # loadplugin Mail::SpamAssassin::Plugin::RelayCountry
+          loadplugin Mail::SpamAssassin::Plugin::RelayEval
+          loadplugin Mail::SpamAssassin::Plugin::ReplaceTags
+          # loadplugin Mail::SpamAssassin::Plugin::Rule2XSBody
+          # loadplugin Mail::SpamAssassin::Plugin::Shortcircuit
+          loadplugin Mail::SpamAssassin::Plugin::SpamCop
+          loadplugin Mail::SpamAssassin::Plugin::SPF
+          #loadplugin Mail::SpamAssassin::Plugin::TextCat
+          # loadplugin Mail::SpamAssassin::Plugin::TxRep
+          loadplugin Mail::SpamAssassin::Plugin::URIDetail
+          loadplugin Mail::SpamAssassin::Plugin::URIDNSBL
+          loadplugin Mail::SpamAssassin::Plugin::URIEval
+          # loadplugin Mail::SpamAssassin::Plugin::URILocalBL
+          loadplugin Mail::SpamAssassin::Plugin::VBounce
+          loadplugin Mail::SpamAssassin::Plugin::WhiteListSubject
+          loadplugin Mail::SpamAssassin::Plugin::WLBLEval
+        '';
+      };
+    };
   };
 
-
-  ###### implementation
-
   config = mkIf cfg.enable {
 
     # Allow users to run 'spamc'.
     environment.systemPackages = [ pkgs.spamassassin ];
 
     users.extraUsers = singleton {
-    name = "spamd";
+      name = "spamd";
       description = "Spam Assassin Daemon";
       uid = config.ids.uids.spamd;
       group = "spamd";
@@ -50,13 +136,65 @@ in
       gid = config.ids.gids.spamd;
     };
 
+    systemd.services.sa-update = {
+      script = ''
+        set +e 
+        ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd
+
+        v=$?
+        set -e
+        if [ $v -gt 1 ]; then
+          echo "sa-update execution error"
+          exit $v
+        fi
+        if [ $v -eq 0 ]; then
+          systemctl reload spamd.service
+        fi
+      '';
+    };
+
+    systemd.timers.sa-update = { 
+      description = "sa-update-service";
+      partOf      = [ "sa-update.service" ];
+      wantedBy    = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "1:*";
+        Persistent = true;
+      };
+    };
+
     systemd.services.spamd = {
       description = "Spam Assassin Server";
 
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
 
-      script = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --nouser-config --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/var/run/spamd.pid";
+      serviceConfig = {
+        ExecStart = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --siteconfigpath=${spamdEnv} --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/var/run/spamd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+
+      # 0 and 1 no error, exitcode > 1 means error:
+      # https://spamassassin.apache.org/full/3.1.x/doc/sa-update.html#exit_codes
+      preStart = ''
+        # this abstraction requires no centralized config at all
+        if [ -d /etc/spamassassin ]; then
+          echo "This spamassassin does not support global '/etc/spamassassin' folder for configuration as this would be impure. Merge your configs into 'services.spamassassin' and remove the '/etc/spamassassin' folder to make this service work. Also see 'https://github.com/NixOS/nixpkgs/pull/26470'."; 
+          exit 1
+        fi
+        echo "Recreating '/var/lib/spamasassin' with creating '3.004001' (or similar) and 'sa-update-keys'"
+        mkdir -p /var/lib/spamassassin
+        chown spamd:spamd /var/lib/spamassassin -R
+        set +e 
+        ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd
+        v=$?
+        set -e
+        if [ $v -gt 1 ]; then
+          echo "sa-update execution error"
+          exit $v
+        fi
+        chown spamd:spamd /var/lib/spamassassin -R
+      '';
     };
   };
 }
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index a2f2c0339515..f7fed793066a 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  inherit (pkgs) glusterfs;
+  inherit (pkgs) glusterfs rsync;
 
   cfg = config.services.glusterfs;
 
@@ -50,8 +50,11 @@ in
       after = [ "rpcbind.service" "network.target" "local-fs.target" ];
       before = [ "network-online.target" ];
 
+      # The copying of hooks is due to upstream bug https://bugzilla.redhat.com/show_bug.cgi?id=1452761
       preStart = ''
         install -m 0755 -d /var/log/glusterfs
+        mkdir -p /var/lib/glusterd/hooks/
+        ${rsync}/bin/rsync -a ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/networking/dnschain.nix b/nixos/modules/services/networking/dnschain.nix
index b64929960576..ab7bbb15ad4f 100644
--- a/nixos/modules/services/networking/dnschain.nix
+++ b/nixos/modules/services/networking/dnschain.nix
@@ -17,7 +17,7 @@ let
     host = ${cfg.dns.address}
     port = ${toString cfg.dns.port}
     oldDNSMethod = NO_OLD_DNS
-    externalIP = ${cfg.dns.address}
+    externalIP = ${cfg.dns.externalAddress}
 
     [http]
     host = ${cfg.api.hostname}
@@ -47,8 +47,18 @@ in
         type = types.str;
         default = "127.0.0.1";
         description = ''
-          The IP address that will be used to reach this machine.
-          Leave this unchanged if you do not wish to directly expose the DNSChain resolver.
+          The IP address the DNSChain resolver will bind to.
+          Leave this unchanged if you do not wish to directly expose the resolver.
+        '';
+      };
+
+      dns.externalAddress = mkOption {
+        type = types.str;
+        default = cfg.dns.address;
+        description = ''
+           The IP address used by clients to reach the resolver and the value of
+           the <literal>namecoin.dns</literal> record. Set this in case the bind address
+           is not the actual IP address (e.g. the machine is behind a NAT).
         '';
       };
 
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 4e176353fc28..7622f030f832 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -212,7 +212,8 @@ in
         type = with types; nullOr int;
         default = null;
         description = ''
-           Set a router bandwidth limit integer in kbps or letters: L (32), O (256), P (2048), X (>9000)
+           Set a router bandwidth limit integer in KBps.
+           If not set, i2pd defaults to 32KBps.
         '';
       };
 
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 876dbe1524e4..f8b7f26f5f2f 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -12,6 +12,7 @@ let
   configFile = writeText "NetworkManager.conf" ''
     [main]
     plugins=keyfile
+    dhcp=${cfg.dhcp}
     dns=${if cfg.useDnsmasq then "dnsmasq" else "default"}
 
     [keyfile]
@@ -21,7 +22,7 @@ let
       ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
 
     [logging]
-    level=WARN
+    level=${cfg.logLevel}
 
     [connection]
     ipv6.ip6-privacy=2
@@ -138,6 +139,22 @@ in {
         apply = list: (attrValues cfg.basePackages) ++ list;
       };
 
+      dhcp = mkOption {
+        type = types.enum [ "dhclient" "dhcpcd" "internal" ];
+        default = "dhclient";
+        description = ''
+          Which program (or internal library) should be used for DHCP.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
+        default = "WARN";
+        description = ''
+          Set the default logging verbosity level.
+        '';
+      };
+
       appendNameservers = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -181,7 +198,7 @@ in {
             };
 
             type = mkOption {
-              type = types.enum (attrNames dispatcherTypesSubdirMap); 
+              type = types.enum (attrNames dispatcherTypesSubdirMap);
               default = "basic";
               description = ''
                 Dispatcher hook type. Only basic hooks are currently available.
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
new file mode 100644
index 000000000000..6d2b7bdbca1b
--- /dev/null
+++ b/nixos/modules/services/networking/resilio.nix
@@ -0,0 +1,268 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.resilio;
+
+  resilioSync = pkgs.resilio-sync;
+
+  sharedFoldersRecord = map (entry: {
+    secret = entry.secret;
+    dir = entry.directory;
+
+    use_relay_server = entry.useRelayServer;
+    use_tracker = entry.useTracker;
+    use_dht = entry.useDHT;
+
+    search_lan = entry.searchLAN;
+    use_sync_trash = entry.useSyncTrash;
+    known_hosts = knownHosts;
+  }) cfg.sharedFolders;
+
+  configFile = pkgs.writeText "config.json" (builtins.toJSON ({
+    device_name = cfg.deviceName;
+    storage_path = cfg.storagePath;
+    listening_port = cfg.listeningPort;
+    use_gui = false;
+    check_for_updates = cfg.checkForUpdates;
+    use_upnp = cfg.useUpnp;
+    download_limit = cfg.downloadLimit;
+    upload_limit = cfg.uploadLimit;
+    lan_encrypt_data = cfg.encryptLAN;
+  } // optionalAttrs cfg.enableWebUI {
+    webui = { listen = "${cfg.httpListenAddr}:${toString cfg.httpListenPort}"; } //
+      (optionalAttrs (cfg.httpLogin != "") { login = cfg.httpLogin; }) //
+      (optionalAttrs (cfg.httpPass != "") { password = cfg.httpPass; }) //
+      (optionalAttrs (cfg.apiKey != "") { api_key = cfg.apiKey; }) //
+      (optionalAttrs (cfg.directoryRoot != "") { directory_root = cfg.directoryRoot; });
+  } // optionalAttrs (sharedFoldersRecord != []) {
+    shared_folders = sharedFoldersRecord;
+  }));
+
+in
+{
+  options = {
+    services.resilio = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, start the Resilio Sync daemon. Once enabled, you can
+          interact with the service through the Web UI, or configure it in your
+          NixOS configuration. Enabling the <literal>resilio</literal> service
+          also installs a systemd user unit which can be used to start
+          user-specific copies of the daemon. Once installed, you can use
+          <literal>systemctl --user start resilio</literal> as your user to start
+          the daemon using the configuration file located at
+          <literal>$HOME/.config/resilio-sync/config.json</literal>.
+        '';
+      };
+
+      deviceName = mkOption {
+        type = types.str;
+        example = "Voltron";
+        default = config.networking.hostName;
+        description = ''
+          Name of the Resilio Sync device.
+        '';
+      };
+
+      listeningPort = mkOption {
+        type = types.int;
+        default = 0;
+        example = 44444;
+        description = ''
+          Listening port. Defaults to 0 which randomizes the port.
+        '';
+      };
+
+      checkForUpdates = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Determines whether to check for updates and alert the user
+          about them in the UI.
+        '';
+      };
+
+      useUpnp = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Use Universal Plug-n-Play (UPnP)
+        '';
+      };
+
+      downloadLimit = mkOption {
+        type = types.int;
+        default = 0;
+        example = 1024;
+        description = ''
+          Download speed limit. 0 is unlimited (default).
+        '';
+      };
+
+      uploadLimit = mkOption {
+        type = types.int;
+        default = 0;
+        example = 1024;
+        description = ''
+          Upload speed limit. 0 is unlimited (default).
+        '';
+      };
+
+      httpListenAddr = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        example = "1.2.3.4";
+        description = ''
+          HTTP address to bind to.
+        '';
+      };
+
+      httpListenPort = mkOption {
+        type = types.int;
+        default = 9000;
+        description = ''
+          HTTP port to bind on.
+        '';
+      };
+
+      httpLogin = mkOption {
+        type = types.str;
+        example = "allyourbase";
+        default = "";
+        description = ''
+          HTTP web login username.
+        '';
+      };
+
+      httpPass = mkOption {
+        type = types.str;
+        example = "arebelongtous";
+        default = "";
+        description = ''
+          HTTP web login password.
+        '';
+      };
+
+      encryptLAN = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Encrypt LAN data.";
+      };
+
+      enableWebUI = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Web UI for administration. Bound to the specified
+          <literal>httpListenAddress</literal> and
+          <literal>httpListenPort</literal>.
+          '';
+      };
+
+      storagePath = mkOption {
+        type = types.path;
+        default = "/var/lib/resilio-sync/";
+        description = ''
+          Where BitTorrent Sync will store it's database files (containing
+          things like username info and licenses). Generally, you should not
+          need to ever change this.
+        '';
+      };
+
+      apiKey = mkOption {
+        type = types.str;
+        default = "";
+        description = "API key, which enables the developer API.";
+      };
+
+      directoryRoot = mkOption {
+        type = types.str;
+        default = "";
+        example = "/media";
+        description = "Default directory to add folders in the web UI.";
+      };
+
+      sharedFolders = mkOption {
+        default = [];
+        example =
+          [ { secret         = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
+              directory      = "/home/user/sync_test";
+              useRelayServer = true;
+              useTracker     = true;
+              useDHT         = false;
+              searchLAN      = true;
+              useSyncTrash   = true;
+              knownHosts     = [
+                "192.168.1.2:4444"
+                "192.168.1.3:4444"
+              ];
+            }
+          ];
+        description = ''
+          Shared folder list. If enabled, web UI must be
+          disabled. Secrets can be generated using <literal>rslsync
+          --generate-secret</literal>. Note that this secret will be
+          put inside the Nix store, so it is realistically not very
+          secret.
+
+          If you would like to be able to modify the contents of this
+          directories, it is recommended that you make your user a
+          member of the <literal>resilio</literal> group.
+
+          Directories in this list should be in the
+          <literal>resilio</literal> group, and that group must have
+          write access to the directory. It is also recommended that
+          <literal>chmod g+s</literal> is applied to the directory
+          so that any sub directories created will also belong to
+          the <literal>resilio</literal> group. Also,
+          <literal>setfacl -d -m group:resilio:rwx</literal> and
+          <literal>setfacl -m group:resilio:rwx</literal> should also
+          be applied so that the sub directories are writable by
+          the group.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions =
+      [ { assertion = cfg.deviceName != "";
+          message   = "Device name cannot be empty.";
+        }
+        { assertion = cfg.enableWebUI -> cfg.sharedFolders == [];
+          message   = "If using shared folders, the web UI cannot be enabled.";
+        }
+        { assertion = cfg.apiKey != "" -> cfg.enableWebUI;
+          message   = "If you're using an API key, you must enable the web server.";
+        }
+      ];
+
+    users.extraUsers.rslsync = {
+      description     = "Resilio Sync Service user";
+      home            = cfg.storagePath;
+      createHome      = true;
+      uid             = config.ids.uids.rslsync;
+      group           = "rslsync";
+    };
+
+    users.extraGroups = [ { name = "rslsync"; } ];
+
+    systemd.services.resilio = with pkgs; {
+      description = "Resilio Sync Service";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network.target" "local-fs.target" ];
+      serviceConfig = {
+        Restart   = "on-abort";
+        UMask     = "0002";
+        User      = "rslsync";
+        ExecStart = ''
+          ${resilioSync}/bin/rslsync --nodaemon --config ${configFile}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index 37f84f0903c3..ad3b65150b01 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -15,7 +15,7 @@ in
     services.xserver.windowManager.session = [{
       name = "qtile";
       start = ''
-        ${pkgs.qtile}/bin/qtile
+        ${pkgs.qtile}/bin/qtile &
         waitPID=$!
       '';
     }];