summary refs log tree commit diff
path: root/nixos/modules/services/misc
diff options
context:
space:
mode:
authorVladimír Čunát <vcunat@gmail.com>2016-03-08 09:57:58 +0100
committerVladimír Čunát <vcunat@gmail.com>2016-03-08 09:58:19 +0100
commit09af15654f0c8091f1b9e0bbb2e523cdee194442 (patch)
treee648edef1ce4c64c533f2593aa22b8015cf0e506 /nixos/modules/services/misc
parentf306e67e15bdbe9a8358c9f81319fc4fcbadc2eb (diff)
parent0ee75214f336474e127c2e3546c0406a0c4d5fa7 (diff)
downloadnixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar.gz
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar.bz2
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar.lz
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar.xz
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.tar.zst
nixpkgs-09af15654f0c8091f1b9e0bbb2e523cdee194442.zip
Merge master into closure-size
The kde-5 stuff still didn't merge well.
I hand-fixed what I saw, but there may be more problems.
Diffstat (limited to 'nixos/modules/services/misc')
-rw-r--r--nixos/modules/services/misc/bepasty.nix8
-rw-r--r--nixos/modules/services/misc/cfdyndns.nix70
-rw-r--r--nixos/modules/services/misc/defaultUnicornConfig.rb1
-rw-r--r--nixos/modules/services/misc/gammu-smsd.nix253
-rw-r--r--nixos/modules/services/misc/gitlab.nix390
-rw-r--r--nixos/modules/services/misc/gitlab.xml103
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix1
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix2
-rw-r--r--nixos/modules/services/misc/nixos-manual.nix38
-rw-r--r--nixos/modules/services/misc/octoprint.nix120
-rw-r--r--nixos/modules/services/misc/plex.nix1
-rw-r--r--nixos/modules/services/misc/spice-vdagentd.nix30
-rw-r--r--nixos/modules/services/misc/subsonic.nix4
13 files changed, 878 insertions, 143 deletions
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
index 12671cb1b6c..5bda73ab64f 100644
--- a/nixos/modules/services/misc/bepasty.nix
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -103,9 +103,13 @@ in
           after = [ "network.target" ];
           restartIfChanged = true;
 
-          environment = {
+          environment = let
+            penv = python.buildEnv.override {
+              extraLibs = [ bepasty gevent ];
+            };
+          in {
             BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf";
-            PYTHONPATH= "${bepasty}/lib/${python.libPrefix}/site-packages:${gevent}/lib/${python.libPrefix}/site-packages";
+            PYTHONPATH= "${penv}/${python.sitePackages}/";
           };
 
           serviceConfig = {
diff --git a/nixos/modules/services/misc/cfdyndns.nix b/nixos/modules/services/misc/cfdyndns.nix
new file mode 100644
index 00000000000..69a33d0b8c1
--- /dev/null
+++ b/nixos/modules/services/misc/cfdyndns.nix
@@ -0,0 +1,70 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cfdyndns;
+in
+{
+  options = {
+    services.cfdyndns = {
+      enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+
+      email = mkOption {
+        type = types.str;
+        description = ''
+          The email address to use to authenticate to CloudFlare.
+        '';
+      };
+
+      apikey = mkOption {
+        type = types.str;
+        description = ''
+          The API Key to use to authenticate to CloudFlare.
+        '';
+      };
+
+      records = mkOption {
+        default = [];
+        example = [ "host.tld" ];
+        type = types.listOf types.str;
+        description = ''
+          The records to update in CloudFlare.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.cfdyndns = {
+      description = "CloudFlare Dynamic DNS Client";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      startAt = "5 minutes";
+      serviceConfig = {
+        Type = "simple";
+        User = config.ids.uids.cfdyndns;
+        Group = config.ids.gids.cfdyndns;
+        ExecStart = "/bin/sh -c '${pkgs.cfdyndns}/bin/cfdyndns'";
+      };
+      environment = {
+        CLOUDFLARE_EMAIL="${cfg.email}";
+        CLOUDFLARE_APIKEY="${cfg.apikey}";
+        CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}";
+      };
+    };
+
+    users.extraUsers = {
+      cfdyndns = {
+        group = "cfdyndns";
+        uid = config.ids.uids.cfdyndns;
+      };
+    };
+
+    users.extraGroups = {
+      cfdyndns = {
+        gid = config.ids.gids.cfdyndns;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixos/modules/services/misc/defaultUnicornConfig.rb
index 81abaf336dc..84622622db7 100644
--- a/nixos/modules/services/misc/defaultUnicornConfig.rb
+++ b/nixos/modules/services/misc/defaultUnicornConfig.rb
@@ -187,7 +187,6 @@ working_directory ENV["GITLAB_PATH"]
 pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid"
 
 listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024
-listen "127.0.0.1:8080", :tcp_nopush => true
 
 timeout 60
 
diff --git a/nixos/modules/services/misc/gammu-smsd.nix b/nixos/modules/services/misc/gammu-smsd.nix
new file mode 100644
index 00000000000..91047ead436
--- /dev/null
+++ b/nixos/modules/services/misc/gammu-smsd.nix
@@ -0,0 +1,253 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+let
+  cfg = config.services.gammu-smsd;
+
+  configFile = pkgs.writeText "gammu-smsd.conf" ''
+    [gammu]
+    Device = ${cfg.device.path}
+    Connection = ${cfg.device.connection}
+    SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"}
+    LogFormat = ${cfg.log.format}
+    ${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""}
+    ${cfg.extraConfig.gammu}
+
+
+    [smsd]
+    LogFile = ${cfg.log.file}
+    Service = ${cfg.backend.service}
+
+    ${optionalString (cfg.backend.service == "files") ''
+      InboxPath = ${cfg.backend.files.inboxPath}
+      OutboxPath = ${cfg.backend.files.outboxPath}
+      SentSMSPath = ${cfg.backend.files.sentSMSPath}
+      ErrorSMSPath = ${cfg.backend.files.errorSMSPath}
+    ''}
+
+    ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "sqlite") ''
+      Driver = ${cfg.backend.sql.driver}
+      DBDir = ${cfg.backend.sql.database}
+    ''}
+
+    ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") (
+      with cfg.backend; ''
+        Driver = ${sql.driver}
+        ${if (sql.database!= null) then "Database = ${sql.database}" else ""}
+        ${if (sql.host != null) then "Host = ${sql.host}" else ""}
+        ${if (sql.user != null) then "User = ${sql.user}" else ""}
+        ${if (sql.password != null) then "Password = ${sql.password}" else ""}
+      '')}
+
+    ${cfg.extraConfig.smsd}
+  '';
+
+  initDBDir = "share/doc/gammu/examples/sql";
+
+  gammuPackage = with cfg.backend; (pkgs.gammu.override {
+    dbiSupport = (service == "sql" && sql.driver == "sqlite");
+    postgresSupport = (service == "sql" && sql.driver == "native_pgsql");
+  });
+
+in {
+  options = {
+    services.gammu-smsd = {
+
+      enable = mkEnableOption "gammu-smsd daemon";
+
+      user = mkOption {
+        type = types.str;
+        default = "smsd";
+        description = "User that has access to the device";
+      };
+
+      device = {
+        path = mkOption {
+          type = types.path;
+          description = "Device node or address of the phone";
+          example = "/dev/ttyUSB2";
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "root";
+          description = "Owner group of the device";
+          example = "dialout";
+        };
+
+        connection = mkOption {
+          type = types.str;
+          default = "at";
+          description = "Protocol which will be used to talk to the phone";
+        };
+
+        synchronizeTime = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to set time from computer to the phone during starting connection";
+        };
+
+        pin = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = "PIN code for the simcard";
+        };
+      };
+
+
+      log = {
+        file = mkOption {
+          type = types.str;
+          default = "syslog";
+          description = "Path to file where information about communication will be stored";
+        };
+
+        format = mkOption {
+          type = types.enum [ "nothing" "text" "textall" "textalldate" "errors" "errorsdate" "binary" ];
+          default = "errors";
+          description = "Determines what will be logged to the LogFile";
+        };
+      };
+
+
+      extraConfig = {
+        gammu = mkOption {
+          type = types.lines;
+          default = "";
+          description = "Extra config lines to be added into [gammu] section";
+        };
+
+
+        smsd = mkOption {
+          type = types.lines;
+          default = "";
+          description = "Extra config lines to be added into [smsd] section";
+        };
+      };
+
+
+      backend = {
+        service = mkOption {
+          type = types.enum [ "null" "files" "sql" ];
+          default = "null";
+          description = "Service to use to store sms data.";
+        };
+
+        files = {
+          inboxPath = mkOption {
+            type = types.path;
+            default = "/var/spool/sms/inbox/";
+            description = "Where the received SMSes are stored";
+          };
+
+          outboxPath = mkOption {
+            type = types.path;
+            default = "/var/spool/sms/outbox/";
+            description = "Where SMSes to be sent should be placed";
+          };
+
+          sentSMSPath = mkOption {
+            type = types.path;
+            default = "/var/spool/sms/sent/";
+            description = "Where the transmitted SMSes are placed";
+          };
+
+          errorSMSPath = mkOption {
+            type = types.path;
+            default = "/var/spool/sms/error/";
+            description = "Where SMSes with error in transmission is placed";
+          };
+        };
+
+        sql = {
+          driver = mkOption {
+            type = types.enum [ "native_mysql" "native_pgsql" "odbc" "dbi" ];
+            description = "DB driver to use";
+          };
+
+          sqlDialect = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = "SQL dialect to use (odbc driver only)";
+          };
+
+          database = mkOption {
+            type = types.str;
+            default = null;
+            description = "Database name to store sms data";
+          };
+
+          host = mkOption {
+            type = types.str;
+            default = "localhost";
+            description = "Database server address";
+          };
+
+          user = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = "User name used for connection to the database";
+          };
+
+          password = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = "User password used for connetion to the database";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.extraUsers.${cfg.user} = {
+      description = "gammu-smsd user";
+      uid = config.ids.uids.gammu-smsd;
+      extraGroups = [ "${cfg.device.group}" ];
+    };
+
+    environment.systemPackages = with cfg.backend; [ gammuPackage ]
+    ++ optionals (service == "sql" && sql.driver == "sqlite")  [ pkgs.sqlite ];
+
+    systemd.services.gammu-smsd = {
+      description = "gammu-smsd daemon";
+
+      wantedBy = [ "multi-user.target" ];
+
+      wants = with cfg.backend; [ ]
+      ++ optionals (service == "sql" && sql.driver == "native_pgsql") [ "postgresql.service" ];
+
+      preStart = with cfg.backend;
+
+        optionalString (service == "files") (with files; ''
+          mkdir -m 755 -p ${inboxPath} ${outboxPath} ${sentSMSPath} ${errorSMSPath}
+          chown ${cfg.user} -R ${inboxPath}
+          chown ${cfg.user} -R ${outboxPath}
+          chown ${cfg.user} -R ${sentSMSPath}
+          chown ${cfg.user} -R ${errorSMSPath}
+        '')
+      + optionalString (service == "sql" && sql.driver == "sqlite") ''
+         cat "${gammuPackage}/${initDBDir}/sqlite.sql" \
+         | ${pkgs.sqlite}/bin/sqlite3 ${sql.database}
+        ''
+      + (let execPsql = extraArgs: concatStringsSep " " [
+          (optionalString (sql.password != null) "PGPASSWORD=${sql.password}")
+          "${config.services.postgresql.package}/bin/psql"
+          (optionalString (sql.host != null) "-h ${sql.host}")
+          (optionalString (sql.user != null) "-U ${sql.user}")
+          "$extraArgs"
+          "${sql.database}"
+        ]; in optionalString (service == "sql" && sql.driver == "native_pgsql") ''
+         echo '\i '"${gammuPackage}/${initDBDir}/pgsql.sql" | ${execPsql ""}
+       '');
+
+      serviceConfig = {
+        User = "${cfg.user}";
+        Group = "${cfg.device.group}";
+        PermissionsStartOnly = true;
+        ExecStart = "${gammuPackage}/bin/gammu-smsd -c ${configFile}";
+      };
+
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 949357ab20f..cc50bfbea53 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -7,10 +7,13 @@ with lib;
 let
   cfg = config.services.gitlab;
 
-  ruby = pkgs.gitlab.ruby;
+  ruby = cfg.packages.gitlab.ruby;
   bundler = pkgs.bundler;
 
-  gemHome = "${pkgs.gitlab.env}/${ruby.gemPath}";
+  gemHome = "${cfg.packages.gitlab.env}/${ruby.gemPath}";
+
+  gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
+  pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
 
   databaseYml = ''
     production:
@@ -21,14 +24,15 @@ let
       username: ${cfg.databaseUsername}
       encoding: utf8
   '';
+
   gitlabShellYml = ''
-    user: gitlab
-    gitlab_url: "http://${cfg.host}:${toString cfg.port}/"
+    user: ${cfg.user}
+    gitlab_url: "http+unix://${pathUrlQuote gitlabSocket}"
     http_settings:
       self_signed_cert: false
-    repos_path: "${cfg.stateDir}/repositories"
-    secret_file: "${cfg.stateDir}/config/gitlab_shell_secret"
-    log_file: "${cfg.stateDir}/log/gitlab-shell.log"
+    repos_path: "${cfg.statePath}/repositories"
+    secret_file: "${cfg.statePath}/config/gitlab_shell_secret"
+    log_file: "${cfg.statePath}/log/gitlab-shell.log"
     redis:
       bin: ${pkgs.redis}/bin/redis-cli
       host: 127.0.0.1
@@ -37,33 +41,102 @@ let
       namespace: resque:gitlab
   '';
 
+  gitlabConfig = {
+    # These are the default settings from config/gitlab.example.yml
+    production = flip recursiveUpdate cfg.extraConfig {
+      gitlab = {
+        host = cfg.host;
+        port = cfg.port;
+        https = cfg.https;
+        user = cfg.user;
+        email_enabled = true;
+        email_display_name = "GitLab";
+        email_reply_to = "noreply@localhost";
+        default_theme = 2;
+        default_projects_features = {
+          issues = true;
+          merge_requests = true;
+          wiki = true;
+          snippets = false;
+          builds = true;
+        };
+      };
+      artifacts = {
+        enabled = true;
+      };
+      lfs = {
+        enabled = true;
+      };
+      gravatar = {
+        enabled = true;
+      };
+      cron_jobs = {
+        stuck_ci_builds_worker = {
+          cron = "0 0 * * *";
+        };
+      };
+      gitlab_ci = {
+        builds_path = "${cfg.statePath}/builds";
+      };
+      ldap = {
+        enabled = false;
+      };
+      omniauth = {
+        enabled = false;
+      };
+      shared = {
+        path = "${cfg.statePath}/shared";
+      };
+      backup = {
+        path = "${cfg.backupPath}";
+      };
+      gitlab_shell = {
+        path = "${cfg.packages.gitlab-shell}";
+        repos_path = "${cfg.statePath}/repositories";
+        hooks_path = "${cfg.statePath}/shell/hooks";
+        secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+        upload_pack = true;
+        receive_pack = true;
+      };
+      git = {
+        bin_path = "git";
+        max_size = 20971520; # 20MB
+        timeout = 10;
+      };
+      extra = {};
+    };
+  };
+
+  gitlabEnv = {
+    HOME = "${cfg.statePath}/home";
+    GEM_HOME = gemHome;
+    BUNDLE_GEMFILE = "${cfg.packages.gitlab}/share/gitlab/Gemfile";
+    UNICORN_PATH = "${cfg.statePath}/";
+    GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
+    GITLAB_STATE_PATH = "${cfg.statePath}";
+    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
+    GITLAB_LOG_PATH = "${cfg.statePath}/log";
+    GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}";
+    GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml";
+    GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret";
+    GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks";
+    RAILS_ENV = "production";
+  };
+
   unicornConfig = builtins.readFile ./defaultUnicornConfig.rb;
 
   gitlab-runner = pkgs.stdenv.mkDerivation rec {
     name = "gitlab-runner";
-    buildInputs = [ pkgs.gitlab pkgs.bundler pkgs.makeWrapper ];
+    buildInputs = [ cfg.packages.gitlab bundler pkgs.makeWrapper ];
     phases = "installPhase fixupPhase";
     buildPhase = "";
     installPhase = ''
       mkdir -p $out/bin
-      makeWrapper ${bundler}/bin/bundle $out/bin/gitlab-runner\
-          --set RAKEOPT '"-f ${pkgs.gitlab}/share/gitlab/Rakefile"'\
-          --set GEM_HOME '${gemHome}'\
-          --set UNICORN_PATH "${cfg.stateDir}/"\
-          --set GITLAB_PATH "${pkgs.gitlab}/share/gitlab/"\
-          --set GITLAB_APPLICATION_LOG_PATH "${cfg.stateDir}/log/application.log"\
-          --set GITLAB_SATELLITES_PATH "${cfg.stateDir}/satellites"\
-          --set GITLAB_SHELL_PATH "${pkgs.gitlab-shell}"\
-          --set GITLAB_REPOSITORIES_PATH "${cfg.stateDir}/repositories"\
-          --set GITLAB_SHELL_HOOKS_PATH "${cfg.stateDir}/shell/hooks"\
-          --set BUNDLE_GEMFILE "${pkgs.gitlab}/share/gitlab/Gemfile"\
-          --set GITLAB_EMAIL_FROM "${cfg.emailFrom}"\
-          --set GITLAB_SHELL_CONFIG_PATH "${cfg.stateDir}/shell/config.yml"\
-          --set GITLAB_SHELL_SECRET_PATH "${cfg.stateDir}/config/gitlab_shell_secret"\
-          --set GITLAB_HOST "${cfg.host}"\
-          --set GITLAB_PORT "${toString cfg.port}"\
-          --set GITLAB_BACKUP_PATH "${cfg.backupPath}"\
-          --set RAILS_ENV "production"
+      makeWrapper ${bundler}/bin/bundle $out/bin/gitlab-runner \
+          ${concatStrings (mapAttrsToList (name: value: "--set ${name} '\"${value}\"' ") gitlabEnv)} \
+          --set GITLAB_CONFIG_PATH '"${cfg.statePath}/config"' \
+          --set PATH '"${pkgs.nodejs}/bin:${pkgs.gzip}/bin:${config.services.postgresql.package}/bin:$PATH"' \
+          --set RAKEOPT '"-f ${cfg.packages.gitlab}/share/gitlab/Rakefile"'
     '';
   };
 
@@ -79,13 +152,25 @@ in {
         '';
       };
 
-      satelliteDir = mkOption {
-        type = types.str;
-        default = "/var/gitlab/git-satellites";
-        description = "Gitlab directory to store checked out git trees requires for operation.";
+      packages.gitlab = mkOption {
+        type = types.package;
+        default = pkgs.gitlab;
+        description = "Reference to the gitlab package";
+      };
+
+      packages.gitlab-shell = mkOption {
+        type = types.package;
+        default = pkgs.gitlab-shell;
+        description = "Reference to the gitlab-shell package";
+      };
+
+      packages.gitlab-workhorse = mkOption {
+        type = types.package;
+        default = pkgs.gitlab-workhorse;
+        description = "Reference to the gitlab-workhorse package";
       };
 
-      stateDir = mkOption {
+      statePath = mkOption {
         type = types.str;
         default = "/var/gitlab/state";
         description = "Gitlab state directory, logs are stored here.";
@@ -93,7 +178,7 @@ in {
 
       backupPath = mkOption {
         type = types.str;
-        default = cfg.stateDir + "/backup";
+        default = cfg.statePath + "/backup";
         description = "Gitlab path for backups.";
       };
 
@@ -136,14 +221,67 @@ in {
       port = mkOption {
         type = types.int;
         default = 8080;
-        description = "Gitlab server listening port.";
+        description = ''
+          Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're
+          service over https.
+        '';
+      };
+
+      https = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether gitlab prints URLs with https as scheme.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "gitlab";
+        description = "User to run gitlab and all related services.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "gitlab";
+        description = "Group to run gitlab and all related services.";
+      };
+
+      initialRootEmail = mkOption {
+        type = types.str;
+        default = "admin@local.host";
+        description = ''
+          Initial email address of the root account if this is a new install.
+        '';
+      };
+
+      initialRootPassword = mkOption {
+        type = types.str;
+        default = "UseNixOS!";
+        description = ''
+          Initial password of the root account if this is a new install.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = {
+          gitlab = {
+            default_projects_features = {
+              builds = false;
+            };
+          };
+        };
+        description = ''
+          Extra options to be merged into config/gitlab.yml as nix
+          attribute set.
+        '';
       };
     };
   };
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.git gitlab-runner pkgs.gitlab-shell ];
+    environment.systemPackages = [ pkgs.git gitlab-runner cfg.packages.gitlab-shell ];
 
     assertions = [
       { assertion = cfg.databasePassword != "";
@@ -159,39 +297,24 @@ in {
     services.postfix.enable = mkDefault true;
 
     users.extraUsers = [
-      { name = "gitlab";
-        group = "gitlab";
-        home = "${cfg.stateDir}/home";
+      { name = cfg.user;
+        group = cfg.group;
+        home = "${cfg.statePath}/home";
         shell = "${pkgs.bash}/bin/bash";
         uid = config.ids.uids.gitlab;
-      } ];
+      }
+    ];
 
     users.extraGroups = [
-      { name = "gitlab";
+      { name = cfg.group;
         gid = config.ids.gids.gitlab;
-      } ];
+      }
+    ];
 
     systemd.services.gitlab-sidekiq = {
       after = [ "network.target" "redis.service" ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = "${cfg.stateDir}/home";
-      environment.GEM_HOME = gemHome;
-      environment.UNICORN_PATH = "${cfg.stateDir}/";
-      environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/";
-      environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log";
-      environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites";
-      environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}";
-      environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories";
-      environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks";
-      environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile";
-      environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}";
-      environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml";
-      environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret";
-      environment.GITLAB_HOST = "${cfg.host}";
-      environment.GITLAB_PORT = "${toString cfg.port}";
-      environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}";
-      environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}";
-      environment.RAILS_ENV = "production";
+      environment = gitlabEnv;
       path = with pkgs; [
         config.services.postgresql.package
         gitAndTools.git
@@ -201,116 +324,131 @@ in {
       ];
       serviceConfig = {
         Type = "simple";
-        User = "gitlab";
-        Group = "gitlab";
+        User = cfg.user;
+        Group = cfg.group;
         TimeoutSec = "300";
-        WorkingDirectory = "${pkgs.gitlab}/share/gitlab";
-        ExecStart="${bundler}/bin/bundle exec \"sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.stateDir}/tmp/sidekiq.pid\"";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        ExecStart="${bundler}/bin/bundle exec \"sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.statePath}/tmp/sidekiq.pid\"";
       };
     };
 
-    systemd.services.gitlab-git-http-server = {
+    systemd.services.gitlab-workhorse = {
       after = [ "network.target" "gitlab.service" ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = "${cfg.stateDir}/home";
+      environment.HOME = gitlabEnv.HOME;
+      environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH;
       path = with pkgs; [
         gitAndTools.git
         openssh
       ];
+      preStart = ''
+        mkdir -p /run/gitlab
+        chown ${cfg.user}:${cfg.group} /run/gitlab
+      '';
       serviceConfig = {
+        PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
-        User = "gitlab";
-        Group = "gitlab";
+        User = cfg.user;
+        Group = cfg.group;
         TimeoutSec = "300";
-        ExecStart = "${pkgs.gitlab-git-http-server}/bin/gitlab-git-http-server -listenUmask 0 -listenNetwork unix -listenAddr ${cfg.stateDir}/tmp/sockets/gitlab-git-http-server.socket -authBackend http://localhost:8080 ${cfg.stateDir}/repositories";
+        ExecStart =
+          "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse "
+          + "-listenUmask 0 "
+          + "-listenNetwork unix "
+          + "-listenAddr /run/gitlab/gitlab-workhorse.socket "
+          + "-authSocket ${gitlabSocket} "
+          + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public";
       };
     };
 
     systemd.services.gitlab = {
       after = [ "network.target" "postgresql.service" "redis.service" ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = "${cfg.stateDir}/home";
-      environment.GEM_HOME = gemHome;
-      environment.UNICORN_PATH = "${cfg.stateDir}/";
-      environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/";
-      environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log";
-      environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites";
-      environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}";
-      environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml";
-      environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret";
-      environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories";
-      environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks";
-      environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile";
-      environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}";
-      environment.GITLAB_HOST = "${cfg.host}";
-      environment.GITLAB_PORT = "${toString cfg.port}";
-      environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}";
-      environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}";
-      environment.RAILS_ENV = "production";
+      environment = gitlabEnv;
       path = with pkgs; [
         config.services.postgresql.package
         gitAndTools.git
-        ruby
         openssh
         nodejs
       ];
       preStart = ''
-        # TODO: use env vars
-        mkdir -p ${cfg.stateDir}
-        mkdir -p ${cfg.stateDir}/log
-        mkdir -p ${cfg.stateDir}/satellites
-        mkdir -p ${cfg.stateDir}/repositories
-        mkdir -p ${cfg.stateDir}/shell/hooks
-        mkdir -p ${cfg.stateDir}/tmp/pids
-        mkdir -p ${cfg.stateDir}/tmp/sockets
-        rm -rf ${cfg.stateDir}/config
-        mkdir -p ${cfg.stateDir}/config
-        # TODO: What exactly is gitlab-shell doing with the secret?
-        tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 20 > ${cfg.stateDir}/config/gitlab_shell_secret
-        mkdir -p ${cfg.stateDir}/home/.ssh
-        touch ${cfg.stateDir}/home/.ssh/authorized_keys
-
-        cp -rf ${pkgs.gitlab}/share/gitlab/config ${cfg.stateDir}/
-        cp ${pkgs.gitlab}/share/gitlab/VERSION ${cfg.stateDir}/VERSION
+        mkdir -p ${cfg.backupPath}
+        mkdir -p ${cfg.statePath}/builds
+        mkdir -p ${cfg.statePath}/repositories
+        mkdir -p ${gitlabConfig.production.shared.path}/artifacts
+        mkdir -p ${gitlabConfig.production.shared.path}/lfs-objects
+        mkdir -p ${cfg.statePath}/log
+        mkdir -p ${cfg.statePath}/shell
+        mkdir -p ${cfg.statePath}/tmp/pids
+        mkdir -p ${cfg.statePath}/tmp/sockets
+
+        rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks
+        mkdir -p ${cfg.statePath}/config ${cfg.statePath}/shell
 
-        ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml
-        ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.stateDir}/config/unicorn.rb
-
-        chown -R gitlab:gitlab ${cfg.stateDir}/
-        chmod -R 755 ${cfg.stateDir}/
+        # TODO: What exactly is gitlab-shell doing with the secret?
+        tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 20 > ${cfg.statePath}/config/gitlab_shell_secret
+
+        # The uploads directory is hardcoded somewhere deep in rails. It is
+        # symlinked in the gitlab package to /run/gitlab/uploads to make it
+        # configurable
+        mkdir -p /run/gitlab
+        mkdir -p ${cfg.statePath}/uploads
+        ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
+        chown -R ${cfg.user}:${cfg.group} /run/gitlab
+
+        # Prepare home directory
+        mkdir -p ${gitlabEnv.HOME}/.ssh
+        touch ${gitlabEnv.HOME}/.ssh/authorized_keys
+        chown -R ${cfg.user}:${cfg.group} ${gitlabEnv.HOME}/
+        chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}/
+
+        cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+        ln -sf ${cfg.statePath}/config /run/gitlab/config
+        cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+
+        # JSON is a subset of YAML
+        ln -fs ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml
+        ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.statePath}/config/database.yml
+        ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.statePath}/config/unicorn.rb
+
+        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/
+        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/
+
+        # Install the shell required to push repositories
+        ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} "$GITLAB_SHELL_CONFIG_PATH"
+        ln -fs ${cfg.packages.gitlab-shell}/hooks "$GITLAB_SHELL_HOOKS_PATH"
+        ${cfg.packages.gitlab-shell}/bin/install
 
         if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
-          if ! test -e "${cfg.stateDir}/db-created"; then
+          if ! test -e "${cfg.statePath}/db-created"; then
             psql postgres -c "CREATE ROLE gitlab WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'"
             ${config.services.postgresql.package}/bin/createdb --owner gitlab gitlab || true
-            touch "${cfg.stateDir}/db-created"
+            touch "${cfg.statePath}/db-created"
 
-            # force=yes disables the manual-interaction yes/no prompt
-            # which breaks without an stdin.
-            force=yes ${bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile gitlab:setup RAILS_ENV=production
+            # The gitlab:setup task is horribly broken somehow, these two tasks will do the same for setting up the initial database
+            ${gitlab-runner}/bin/gitlab-runner exec rake db:migrate RAILS_ENV=production
+            ${gitlab-runner}/bin/gitlab-runner exec rake db:seed_fu RAILS_ENV=production \
+              GITLAB_ROOT_PASSWORD="${cfg.initialRootPassword}" GITLAB_ROOT_EMAIL="${cfg.initialRootEmail}";
           fi
         fi
 
-      ${bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile db:migrate RAILS_ENV=production
-      # Install the shell required to push repositories
-      ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} ${cfg.stateDir}/shell/config.yml
-      export GITLAB_SHELL_CONFIG_PATH=""${cfg.stateDir}/shell/config.yml
-      ${pkgs.gitlab-shell}/bin/install
+        # Always do the db migrations just to be sure the database is up-to-date
+        ${gitlab-runner}/bin/gitlab-runner exec rake db:migrate RAILS_ENV=production
 
-      # Change permissions in the last step because some of the
-      # intermediary scripts like to create directories as root.
-      chown -R gitlab:gitlab ${cfg.stateDir}/
-      chmod -R 755 ${cfg.stateDir}/
+        # Change permissions in the last step because some of the
+        # intermediary scripts like to create directories as root.
+        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}
+        chmod -R u+rwX,go-rwx+X ${cfg.statePath}
       '';
 
       serviceConfig = {
         PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
-        User = "gitlab";
-        Group = "gitlab";
+        User = cfg.user;
+        Group = cfg.group;
         TimeoutSec = "300";
-        WorkingDirectory = "${pkgs.gitlab}/share/gitlab";
-        ExecStart="${bundler}/bin/bundle exec \"unicorn -c ${cfg.stateDir}/config/unicorn.rb -E production\"";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        ExecStart="${bundler}/bin/bundle exec \"unicorn -c ${cfg.statePath}/config/unicorn.rb -E production\"";
       };
 
     };
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
new file mode 100644
index 00000000000..b630fe42113
--- /dev/null
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -0,0 +1,103 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-gitlab">
+
+<title>Gitlab</title>
+
+<para>Gitlab is a feature-rich git hosting service.</para>
+
+<section><title>Prerequisites</title>
+
+<para>The gitlab service exposes only an Unix socket at
+<literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to configure a
+webserver to proxy HTTP requests to the socket.</para>
+
+<para>For instance, this could be used for Nginx:
+
+<programlisting>
+services.nginx.httpConfig = ''
+  server {
+    server_name git.example.com;
+    listen 443 ssl spdy;
+    listen [::]:443 ssl spdy;
+
+    ssl_certificate /var/lib/acme/git.example.com/fullchain.pem;
+    ssl_certificate_key /var/lib/acme/git.example.com/key.pem;
+
+    location / {
+      proxy_http_version 1.1;
+      proxy_set_header    Host                $http_host;
+      proxy_set_header    X-Real-IP           $remote_addr;
+      proxy_set_header    X-Forwarded-Ssl     on;
+      proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
+      proxy_set_header    X-Forwarded-Proto   $scheme;
+
+      proxy_pass http://unix:/run/gitlab/gitlab-workhorse.socket;
+    }
+  }
+'';
+</programlisting>
+</para>
+
+</section>
+
+<section><title>Configuring</title>
+
+<para>Gitlab depends on both PostgreSQL and Redis and will automatically enable
+both services. In the case of PostgreSQL, a database and a role will be created.
+</para>
+
+<para>The default state dir is /var/gitlab/state. This is where all data like
+the repositories and uploads will be stored.</para>
+
+<para>A basic configuration could look like this:
+
+<programlisting>
+services.gitlab = {
+  enable = true;
+  databasePassword = "eXaMpl3";
+  initialRootPassword = "UseNixOS!";
+  https = true;
+  host = "git.example.com";
+  port = 443;
+  user = "git";
+  group = "git";
+  extraConfig = {
+    gitlab = {
+      default_projects_features = { builds = false; };
+    };
+  };
+};
+</programlisting>
+</para>
+
+<para>Refer to <xref linkend="ch-options" /> for all available configuration
+options for the <literal>services.gitlab</literal> module.</para>
+
+</section>
+
+<section><title>Maintenance</title>
+
+<para>You can run all Gitlab related commands like rake tasks with
+<literal>gitlab-runner</literal> which will be available on the system
+when gitlab is enabled. You will have to run the commands as the user that
+you configured to run gitlab.</para>
+
+<para>For instance, to backup a Gitlab instance:
+
+<programlisting>
+$ sudo -u git -H gitlab-runner exec rake gitlab:backup:create
+</programlisting>
+
+A list of all availabe rake tasks can be obtained by running:
+
+<programlisting>
+$ sudo -u git -H gitlab-runner exec rake -T
+</programlisting>
+</para>
+
+</section>
+
+</chapter>
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 27c5a38e6b8..0ae0516769c 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -61,6 +61,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.matrix-synapse;
+        defaultText = "pkgs.matrix-synapse";
         description = ''
           Overridable attribute of the matrix synapse server package to use.
         '';
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 24ae515a6b8..b0e4bf106d3 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -367,6 +367,8 @@ in
           // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
           // config.networking.proxy.envVars;
 
+        unitConfig.RequiresMountsFor = "/nix/store";
+
         serviceConfig =
           { Nice = cfg.daemonNiceLevel;
             IOSchedulingPriority = cfg.daemonIONiceLevel;
diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix
index 3e1f53e79f3..37ea339300d 100644
--- a/nixos/modules/services/misc/nixos-manual.nix
+++ b/nixos/modules/services/misc/nixos-manual.nix
@@ -17,16 +17,32 @@ let
       nixpkgs.system = config.nixpkgs.system;
     };
 
-  eval = evalModules {
-    modules = [ versionModule ] ++ baseModules;
-    args = (config._module.args) // { modules = [ ]; };
-  };
-
+  /* For the purpose of generating docs, evaluate options with each derivation
+    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
+    It isn't perfect, but it seems to cover a vast majority of use cases.
+    Caveat: even if the package is reached by a different means,
+    the path above will be shown and not e.g. `${config.services.foo.package}`. */
   manual = import ../../../doc/manual {
     inherit pkgs;
     version = config.system.nixosVersion;
     revision = config.system.nixosRevision;
-    options = eval.options;
+    options =
+      let
+        scrubbedEval = evalModules {
+          modules = [ versionModule ] ++ baseModules;
+          args = (config._module.args) // { modules = [ ]; };
+          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
+        };
+        scrubDerivations = namePrefix: pkgSet: mapAttrs
+          (name: value:
+            let wholeName = "${namePrefix}.${name}"; in
+            if isAttrs value then
+              scrubDerivations wholeName value
+              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
+            else value
+          )
+          pkgSet;
+      in scrubbedEval.options;
   };
 
   entry = "${manual.manual}/share/doc/nixos/index.html";
@@ -72,7 +88,8 @@ in
     };
 
     services.nixosManual.ttyNumber = mkOption {
-      default = "8";
+      type = types.int;
+      default = 8;
       description = ''
         Virtual console on which to show the manual.
       '';
@@ -80,6 +97,7 @@ in
 
     services.nixosManual.browser = mkOption {
       type = types.path;
+      default = "${pkgs.w3m-nox}/bin/w3m";
       description = ''
         Browser used to show the manual.
       '';
@@ -96,7 +114,7 @@ in
       [ manual.manual help ]
       ++ optional config.programs.man.enable manual.manpages;
 
-    boot.extraTTYs = mkIf cfg.showManual ["tty${cfg.ttyNumber}"];
+    boot.extraTTYs = mkIf cfg.showManual ["tty${toString cfg.ttyNumber}"];
 
     systemd.services = optionalAttrs cfg.showManual
       { "nixos-manual" =
@@ -106,7 +124,7 @@ in
             { ExecStart = "${cfg.browser} ${entry}";
               StandardInput = "tty";
               StandardOutput = "tty";
-              TTYPath = "/dev/tty${cfg.ttyNumber}";
+              TTYPath = "/dev/tty${toString cfg.ttyNumber}";
               TTYReset = true;
               TTYVTDisallocate = true;
               Restart = "always";
@@ -117,8 +135,6 @@ in
     services.mingetty.helpLine = mkIf cfg.showManual
       "\nPress <Alt-F${toString cfg.ttyNumber}> for the NixOS manual.";
 
-    services.nixosManual.browser = mkDefault "${pkgs.w3m-nox}/bin/w3m";
-
   };
 
 }
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
new file mode 100644
index 00000000000..9cf46345c22
--- /dev/null
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.octoprint;
+
+  cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON {
+    plugins.cura.cura_engine = "${pkgs.curaengine}/bin/CuraEngine";
+    server.host = cfg.host;
+    server.port = cfg.port;
+    webcam.ffmpeg = "${pkgs.ffmpeg}/bin/ffmpeg";
+  });
+
+  pluginsEnv = pkgs.python.buildEnv.override {
+    extraLibs = cfg.plugins pkgs.octoprint-plugins;
+  };
+
+in
+{
+  ##### interface
+
+  options = {
+
+    services.octoprint = {
+
+      enable = mkEnableOption "OctoPrint, web interface for 3D printers";
+
+      host = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          Host to bind OctoPrint to.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Port to bind OctoPrint to.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "octoprint";
+        description = "User for the daemon.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "octoprint";
+        description = "Group for the daemon.";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/lib/octoprint";
+        description = "State directory of the daemon.";
+      };
+
+      plugins = mkOption {
+        #type = types.functionTo (types.listOf types.package);
+        default = plugins: [];
+        defaultText = "plugins: []";
+        example = literalExample "plugins: [ m3d-fio ]";
+        description = "Additional plugins.";
+      };
+
+    };
+
+  };
+
+  ##### implementation
+
+  config = mkIf cfg.enable {
+
+    users.extraUsers = optionalAttrs (cfg.user == "octoprint") (singleton
+      { name = "octoprint";
+        group = cfg.group;
+        uid = config.ids.uids.octoprint;
+      });
+
+    users.extraGroups = optionalAttrs (cfg.group == "octoprint") (singleton
+      { name = "octoprint";
+        gid = config.ids.gids.octoprint;
+      });
+
+    systemd.services.octoprint = {
+      description = "OctoPrint, web interface for 3D printers";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pluginsEnv ];
+      environment.PYTHONPATH = makeSearchPath pkgs.python.sitePackages [ pluginsEnv ];
+
+      preStart = ''
+        mkdir -p "${cfg.stateDir}"
+        if [ -e "${cfg.stateDir}/config.yaml" ]; then
+          ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
+          mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
+        else
+          cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
+          chmod 600 "${cfg.stateDir}/config.yaml"
+        fi
+        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}"
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkgs.octoprint}/bin/octoprint -b ${cfg.stateDir}";
+        User = cfg.user;
+        Group = cfg.group;
+        PermissionsStartOnly = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index fb62351365e..875771dfa37 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -58,6 +58,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.plex;
+        defaultText = "pkgs.plex";
         description = ''
           The Plex package to use. Plex subscribers may wish to use their own
           package here, pointing to subscriber-only server versions.
diff --git a/nixos/modules/services/misc/spice-vdagentd.nix b/nixos/modules/services/misc/spice-vdagentd.nix
new file mode 100644
index 00000000000..f8133394ffd
--- /dev/null
+++ b/nixos/modules/services/misc/spice-vdagentd.nix
@@ -0,0 +1,30 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.spice-vdagentd;
+in
+{
+  options = {
+    services.spice-vdagentd = {
+      enable = mkEnableOption "Spice guest vdagent daemon";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.spice-vdagent ];
+
+    systemd.services.spice-vdagentd = {
+      description = "spice-vdagent daemon";
+      wantedBy = [ "graphical.target" ];
+      preStart = ''
+        mkdir -p "/var/run/spice-vdagentd/"
+      '';
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "/bin/sh -c '${pkgs.spice-vdagent}/bin/spice-vdagentd'";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/subsonic.nix b/nixos/modules/services/misc/subsonic.nix
index 5a33aa33b26..c1ebe418f72 100644
--- a/nixos/modules/services/misc/subsonic.nix
+++ b/nixos/modules/services/misc/subsonic.nix
@@ -97,6 +97,7 @@ in
 
       transcoders = mkOption {
         type = types.listOf types.path;
+        default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
         description = ''
           List of paths to transcoder executables that should be accessible
           from Subsonic. Symlinks will be created to each executable inside
@@ -152,8 +153,5 @@ in
     };
 
     users.extraGroups.subsonic.gid = config.ids.gids.subsonic;
-
-    services.subsonic.transcoders = mkDefault [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
-
   };
 }